Skip to content

Commit 6b11e7e

Browse files
authored
Merge pull request #2329 from hashtopolis/2297-enhancement-add-crackingtime-on-agentassignment
Add aggregate field crackingTime to AgentAssignment
2 parents 180cb5d + 6b358c5 commit 6b11e7e

7 files changed

Lines changed: 151 additions & 28 deletions

File tree

ci/apiv2/test_agentassignment.py

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
from hashtopolis import AgentAssignment
1+
import time
2+
from hashtopolis import AgentAssignment, Chunk
23

3-
from utils import BaseTest, do_create_dummy_agent
4+
from utils import BaseTest, do_create_dummy_agent, do_create_agentassignent
45

56

67
class AgentStatTest(BaseTest):
@@ -50,3 +51,33 @@ def test_agent_assign_task(self):
5051
def test_acl(self):
5152
model_obj = self.create_test_object()
5253
self._test_acl_list(model_obj, {'permAgentAssignmentRead': True})
54+
55+
def test_cracking_time_aggregation(self):
56+
dummy_agent, agent, _, task = self.create_agent_with_task().values()
57+
time.sleep(1) # Simulate cracking time
58+
dummy_agent.send_process(progress=100)
59+
dummy_agent.get_chunk()
60+
time.sleep(1) # Simulate cracking time
61+
dummy_agent.send_process(progress=100)
62+
dummy_agent.get_chunk()
63+
time.sleep(1) # Simulate cracking time
64+
dummy_agent.send_process(progress=100)
65+
dummy_agent.get_chunk() # Leave the last chunk unfinished for a more meaningful test
66+
67+
# Calculate a reference value for the cracking time by applying an interval merge algorithm
68+
chunks = Chunk.objects.filter(agentId=agent.id, taskId=task.id)
69+
totalEnd = totalSum = 0
70+
for chunk in sorted(chunks, key=lambda c: c.dispatchTime): # Expects list to be sorted by time
71+
if chunk.dispatchTime > 0 and chunk.solveTime > 0:
72+
totalSum = (
73+
totalSum + (chunk.solveTime - chunk.dispatchTime)
74+
- max(0, totalEnd - chunk.dispatchTime)
75+
+ max(0, totalEnd - chunk.solveTime)
76+
)
77+
totalEnd = max(totalEnd, chunk.solveTime)
78+
79+
# Get the aggregate cracking time from the application
80+
aggregate_attrs = ['crackingTime']
81+
assignment = AgentAssignment.objects.params(**{"aggregate[assignment]": ','.join(aggregate_attrs)}).get(agentId=agent.id, taskId=task.id)
82+
83+
self.assertEqual(totalSum, assignment.crackingTime)

ci/phpunit/TestBase.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,23 @@ protected function createAgent(string $prefix, int $isTrusted = 1): Agent {
316316
return $agent;
317317
}
318318

319+
public function createTaskHelper(): array {
320+
$user = $this->createUser("phpunit");
321+
$accessGroup = $this->createAccessGroup("phpunit");
322+
$this->createAccessGroupUser($user, $accessGroup);
323+
324+
$hashType = $this->createHashType();
325+
$hashlist = $this->createHashlist($accessGroup, $hashType);
326+
327+
$taskWrapper = $this->createTaskWrapper($accessGroup, $hashlist);
328+
329+
$crackerBinaryType = $this->createCrackerBinaryType();
330+
$crackerBinary = $this->createCrackerBinary($crackerBinaryType);
331+
$task = $this->createTask($taskWrapper, $crackerBinary, $crackerBinaryType);
332+
333+
return array("user"=> $user, "accessGroup"=>$accessGroup, "hashType"=>$hashType, "hashlist"=>$hashlist, "taskWrapper"=>$taskWrapper, "crackerBinaryType"=>$crackerBinaryType, "crackerBinary"=>$crackerBinary, "task"=>$task);
334+
}
335+
319336
/**
320337
* used to create an object in the database and then register it directly for deletion to be cleaned up after the test
321338
*
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<?php
2+
3+
namespace Hashtopolis\inc\utils;
4+
5+
use Exception;
6+
use Hashtopolis\dba\Factory;
7+
use Hashtopolis\TestBase;
8+
9+
require_once(dirname(__FILE__) . '/../../TestBase.php');
10+
11+
final class AgentUtilsTest extends TestBase {
12+
13+
protected function setUp(): void {
14+
parent::setUp();
15+
}
16+
17+
/**
18+
* Test cracking time aggregation for an agent on a task.
19+
*
20+
* @return void
21+
* @throws Exception
22+
*/
23+
public function testCrackingTimeAggregation(): void {
24+
$task = $this->createTaskHelper()["task"];
25+
$agent1 = $this->createAgent("test");
26+
$agent2 = $this->createAgent("test");
27+
$timeSpans = (array) [
28+
[1000, 2000, $agent1],
29+
[3000, 5000, $agent2],
30+
[3000, 8000, $agent1],
31+
[8000, 10000, $agent1],
32+
[15000, 20000, $agent1],
33+
];
34+
35+
foreach ($timeSpans as [$start, $end, $agent]) {
36+
$chunk = $this->createChunk($task, $agent, 4);
37+
$chunk->setDispatchTime($start);
38+
$chunk->setSolveTime($end);
39+
Factory::getChunkFactory()->update($chunk);
40+
}
41+
42+
// Calculate reference value via an interval merge algorithm
43+
$totalEnd = $referenceSum = 0;
44+
usort($timeSpans, fn($a, $b) => $a[0] <=> $b[0]); // Make sure time spans are sorted
45+
foreach ($timeSpans as [$currentStart, $currentEnd, $agent]) { // Expects list to be sorted by time
46+
if ($agent == $agent1) {
47+
$referenceSum = $referenceSum + ($currentEnd - $currentStart) // Add current time span to running total
48+
- max(0, $totalEnd - $currentStart) // Correct for potential overlapping when current start before previous end
49+
+ max(0, $totalEnd - $currentEnd); // Correct for potential overcorrection when current end before previous end
50+
$totalEnd = max($totalEnd, $currentEnd); // Extend window if current end exceeds previous end
51+
}
52+
}
53+
54+
// Calculate aggregate cracking time via AgentUtils
55+
$crackingTime = AgentUtils::getAggregateCrackingTime($agent1->getId(), $task->getId());
56+
57+
$this->assertEquals($referenceSum, $crackingTime);
58+
}
59+
}

ci/phpunit/inc/utils/TaskUtilsTest.php

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -250,21 +250,4 @@ public function testSetCpuTask(): void {
250250
$taskUpdated = Factory::getTaskFactory()->get($taskObjects["task"]->getId());
251251
$this->assertEquals(0, $taskUpdated->getIsCpuTask());
252252
}
253-
254-
public function createTaskHelper(): array {
255-
$user = $this->createUser("phpunit");
256-
$accessGroup = $this->createAccessGroup("phpunit");
257-
$this->createAccessGroupUser($user, $accessGroup);
258-
259-
$hashType = $this->createHashType();
260-
$hashlist = $this->createHashlist($accessGroup, $hashType);
261-
262-
$taskWrapper = $this->createTaskWrapper($accessGroup, $hashlist);
263-
264-
$crackerBinaryType = $this->createCrackerBinaryType();
265-
$crackerBinary = $this->createCrackerBinary($crackerBinaryType);
266-
$task = $this->createTask($taskWrapper, $crackerBinary, $crackerBinaryType);
267-
268-
return array("user"=> $user, "accessGroup"=>$accessGroup, "hashType"=>$hashType, "hashlist"=>$hashlist, "taskWrapper"=>$taskWrapper, "crackerBinaryType"=>$crackerBinaryType, "crackerBinary"=>$crackerBinary, "task"=>$task);
269-
}
270253
}

src/inc/apiv2/model/AgentAPI.php

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
use Exception;
66
use Hashtopolis\dba\AbstractModel;
7-
use Hashtopolis\dba\Aggregation;
87
use Hashtopolis\inc\utils\AccessUtils;
98
use Hashtopolis\inc\utils\AgentUtils;
109
use Hashtopolis\inc\defines\DHashcatStatus;
@@ -65,14 +64,7 @@ public function getAggregateFieldsets(): array {
6564
* @throws Exception
6665
*/
6766
protected function getAggregateCrackingTime(AbstractModel $object): int {
68-
// in order to make sense of the diff, we need to make sure that both values solve time and dispatch time are set (i.e. >0).
69-
$qF1 = new QueryFilter(Chunk::AGENT_ID, $object->getId(), "=");
70-
$qF2 = new QueryFilter(Chunk::SOLVE_TIME, 0, ">");
71-
$qF3 = new QueryFilter(Chunk::DISPATCH_TIME, 0, ">");
72-
$agg1 = new Aggregation(Chunk::SOLVE_TIME, Aggregation::SUM);
73-
$agg2 = new Aggregation(Chunk::DISPATCH_TIME, Aggregation::SUM);
74-
$results = Factory::getChunkFactory()->multicolAggregationFilter([Factory::FILTER => [$qF1, $qF2, $qF3]], [$agg1, $agg2]);
75-
return $results[$agg1->getName()] - $results[$agg2->getName()];
67+
return AgentUtils::getAggregateCrackingTime($object->getId());
7668
}
7769

7870
/**

src/inc/apiv2/model/AgentAssignmentAPI.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,23 @@ protected function getUpdateHandlers($id, $current_user): array {
107107
];
108108
}
109109

110+
public function getAggregateFieldsets(): array {
111+
return [
112+
'assignment' => [
113+
'crackingTime' => [$this, 'getAggregateCrackingTime'],
114+
]
115+
];
116+
}
117+
118+
/**
119+
* @param Assignment $object
120+
* @return int
121+
* @throws Exception
122+
*/
123+
protected function getAggregateCrackingTime(AbstractModel $object): int {
124+
return AgentUtils::getAggregateCrackingTime($object->getAgentId(), $object->getTaskId());
125+
}
126+
110127
/**
111128
* @param Assignment $object
112129
* @throws HTException

src/inc/utils/AgentUtils.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace Hashtopolis\inc\utils;
44

55
use Exception;
6+
use Hashtopolis\dba\Aggregation;
67
use Hashtopolis\inc\DataSet;
78
use Hashtopolis\dba\models\Agent;
89
use Hashtopolis\dba\QueryFilter;
@@ -603,4 +604,27 @@ public static function deleteVoucher(int|string $voucher): void {
603604
}
604605
Factory::getRegVoucherFactory()->delete($voucher);
605606
}
607+
608+
/**
609+
* Get the aggregate cracking time of an agent or more specifically
610+
* (if task ID is specified) of an agent on a specific task.
611+
*
612+
* @param int $agentId
613+
* @param int|null $taskId
614+
* @return int
615+
* @throws Exception
616+
*/
617+
public static function getAggregateCrackingTime(int $agentId, ?int $taskId = null): int {
618+
// In order to make sense of the diff, we need to make sure that both values solve time and dispatch time are set (i.e. > 0).
619+
// Since an agent can only work on one chunk at a time, there inherently are no overlapping time spans to consider.
620+
// We can therefore simply sum up the corresponding time spans in the database already.
621+
$qF1 = new QueryFilter(Chunk::AGENT_ID, $agentId, "=");
622+
$qF2 = $taskId !== null ? new QueryFilter(Chunk::TASK_ID, $taskId, "=") : null;
623+
$qF3 = new QueryFilter(Chunk::SOLVE_TIME, 0, ">");
624+
$qF4 = new QueryFilter(Chunk::DISPATCH_TIME, 0, ">");
625+
$agg1 = new Aggregation(Chunk::SOLVE_TIME, Aggregation::SUM);
626+
$agg2 = new Aggregation(Chunk::DISPATCH_TIME, Aggregation::SUM);
627+
$results = Factory::getChunkFactory()->multicolAggregationFilter([Factory::FILTER => array_filter([$qF1, $qF2, $qF3, $qF4])], [$agg1, $agg2]);
628+
return $results[$agg1->getName()] - $results[$agg2->getName()];
629+
}
606630
}

0 commit comments

Comments
 (0)