@anonympins/fingerprint 0.4.4 → 0.4.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,256 +1,258 @@
1
- <?php
2
-
3
- declare(strict_types=1);
4
-
5
- namespace Anonympins\Fingerprint\Optimization;
6
-
7
- /**
8
- * Bibliothèque d'algorithmes d'optimisation.
9
- */
10
- class Optimization
11
- {
12
- /**
13
- * Génère un nombre flottant aléatoire cryptographiquement sûr entre 0 (inclus) et 1 (exclus).
14
- */
15
- private static function secureRandom(): float
16
- {
17
- try {
18
- return random_int(0, PHP_INT_MAX - 1) / PHP_INT_MAX;
19
- } catch (\Exception $e) {
20
- return (float)mt_rand() / (float)mt_getrandmax(); // Fallback
21
- }
22
- }
23
-
24
- /**
25
- * Algorithme génétique multi-objectifs (inspiré de NSGA-II).
26
- * @param callable $createIndividual
27
- * @param callable $fitnessFunction
28
- * @param callable $crossover
29
- * @param callable $mutate
30
- * @param array $options
31
- * @return array<int, array{solution: mixed, objectives: array<float>}>
32
- */
33
- public static function geneticAlgorithmMultiObjective(
34
- callable $createIndividual,
35
- callable $fitnessFunction,
36
- callable $crossover,
37
- callable $mutate,
38
- array $options = []
39
- ): array {
40
- $generations = $options['generations'] ?? 150;
41
- $populationSize = $options['populationSize'] ?? 60;
42
- $mutationRate = $options['mutationRate'] ?? 0.1;
43
-
44
- $population = [];
45
- for ($i = 0; $i < $populationSize; $i++) {
46
- $individual = $createIndividual();
47
- $population[] = [
48
- 'individual' => $individual,
49
- 'objectives' => $fitnessFunction($individual)
50
- ];
51
- }
52
-
53
- for ($gen = 0; $gen < $generations; $gen++) {
54
- // 1. Créer une population d'enfants
55
- $offspring = [];
56
- for ($i = 0; $i < $populationSize; $i++) {
57
- $parent1 = $population[random_int(0, count($population) - 1)];
58
- $parent2 = $population[random_int(0, count($population) - 1)];
59
- $childIndividual = $crossover($parent1['individual'], $parent2['individual']);
60
- if (self::secureRandom() < $mutationRate) {
61
- $childIndividual = $mutate($childIndividual);
62
- }
63
- $offspring[] = [
64
- 'individual' => $childIndividual,
65
- 'objectives' => $fitnessFunction($childIndividual)
66
- ];
67
- }
68
-
69
- // 2. Combiner parents et enfants
70
- $combinedPopulation = array_merge($population, $offspring);
71
-
72
- // 3. Trier la population combinée en fronts
73
- $fronts = self::nonDominatedSort($combinedPopulation);
74
-
75
- // 4. Construire la nouvelle population
76
- $newPopulation = [];
77
- foreach ($fronts as $front) {
78
- if (count($newPopulation) + count($front) <= $populationSize) {
79
- $newPopulation = array_merge($newPopulation, $front);
80
- } else {
81
- self::calculateCrowdingDistance($front);
82
- // Trier par distance décroissante
83
- usort($front, fn ($a, $b) => $b['crowdingDistance'] <=> $a['crowdingDistance']);
84
- $remaining = $populationSize - count($newPopulation);
85
- $newPopulation = array_merge($newPopulation, array_slice($front, 0, $remaining));
86
- break;
87
- }
88
- }
89
- $population = $newPopulation;
90
- }
91
-
92
- // Retourner le premier front de la population finale
93
- $finalFronts = self::nonDominatedSort($population);
94
- $bestFront = $finalFronts[0] ?? [];
95
-
96
- // Filtrer pour ne garder que les solutions avec des objectifs uniques
97
- $uniqueSolutionsMap = [];
98
- foreach ($bestFront as $p) {
99
- $key = json_encode($p['objectives']);
100
- if (!isset($uniqueSolutionsMap[$key])) {
101
- $uniqueSolutionsMap[$key] = [
102
- 'solution' => $p['individual'],
103
- 'objectives' => $p['objectives'],
104
- ];
105
- }
106
- }
107
- return array_values($uniqueSolutionsMap);
108
- }
109
-
110
- /**
111
- * Détermine si la solution A domine la solution B.
112
- * @param array<float> $objectivesA
113
- * @param array<float> $objectivesB
114
- */
115
- private static function paretoDominates(array $objectivesA, array $objectivesB): bool
116
- {
117
- $aIsBetterInOne = false;
118
- for ($i = 0; $i < count($objectivesA); $i++) {
119
- if ($objectivesA[$i] > $objectivesB[$i]) {
120
- return false; // A est pire sur au moins un objectif
121
- }
122
- if ($objectivesA[$i] < $objectivesB[$i]) {
123
- $aIsBetterInOne = true; // A est strictement meilleur sur au moins un
124
- }
125
- }
126
- return $aIsBetterInOne;
127
- }
128
-
129
- /**
130
- * Trie une population en fronts de Pareto non-dominés.
131
- * @param array<int, array> &$populationWithObjectives
132
- * @return array<int, array>
133
- */
134
- private static function nonDominatedSort(array &$populationWithObjectives): array
135
- {
136
- $fronts = [[]];
137
- $n = count($populationWithObjectives);
138
-
139
- for ($i = 0; $i < $n; $i++) {
140
- $p1 = &$populationWithObjectives[$i];
141
- $p1['dominationCount'] = 0;
142
- $p1['dominatedSolutions'] = [];
143
-
144
- for ($j = 0; $j < $n; $j++) {
145
- if ($i === $j) continue;
146
- $p2 = &$populationWithObjectives[$j];
147
-
148
- if (self::paretoDominates($p1['objectives'], $p2['objectives'])) {
149
- $p1['dominatedSolutions'][] = $j;
150
- } elseif (self::paretoDominates($p2['objectives'], $p1['objectives'])) {
151
- $p1['dominationCount']++;
152
- }
153
- }
154
-
155
- if ($p1['dominationCount'] === 0) {
156
- $p1['rank'] = 0;
157
- $fronts[0][] = $p1;
158
- }
159
- }
160
-
161
- $i = 0;
162
- while (!empty($fronts[$i])) {
163
- $nextFront = [];
164
- foreach ($fronts[$i] as $p1) {
165
- foreach ($p1['dominatedSolutions'] as $p2_idx) {
166
- $p2 = &$populationWithObjectives[$p2_idx];
167
- $p2['dominationCount']--;
168
- if ($p2['dominationCount'] === 0) {
169
- $p2['rank'] = $i + 1;
170
- $nextFront[] = $p2;
171
- }
172
- }
173
- }
174
- $i++;
175
- if (!empty($nextFront)) {
176
- $fronts[$i] = $nextFront;
177
- }
178
- }
179
- return $fronts;
180
- }
181
-
182
- /**
183
- * Calcule la distance de promiscuité (crowding distance) pour un front.
184
- * @param array<int, array> &$front
185
- */
186
- private static function calculateCrowdingDistance(array &$front): void
187
- {
188
- if (empty($front)) return;
189
-
190
- $numObjectives = count($front[0]['objectives']);
191
- $l = count($front);
192
-
193
- foreach ($front as &$p) {
194
- $p['crowdingDistance'] = 0;
195
- }
196
-
197
- for ($i = 0; $i < $numObjectives; $i++) {
198
- // Trier le front par l'objectif courant
199
- usort($front, fn ($a, $b) => $a['objectives'][$i] <=> $b['objectives'][$i]);
200
-
201
- $minObj = $front[0]['objectives'][$i];
202
- $maxObj = $front[$l - 1]['objectives'][$i];
203
-
204
- // Les solutions aux extrémités ont une distance infinie
205
- $front[0]['crowdingDistance'] = INF;
206
- $front[$l - 1]['crowdingDistance'] = INF;
207
-
208
- if ($maxObj === $minObj) continue;
209
-
210
- for ($j = 1; $j < $l - 1; $j++) {
211
- $front[$j]['crowdingDistance'] +=
212
- ($front[$j + 1]['objectives'][$i] - $front[$j - 1]['objectives'][$i]) /
213
- ($maxObj - $minObj);
214
- }
215
- }
216
- }
217
-
218
- /**
219
- * Calcule la déviation d'une série de chiffres par rapport à la loi de Benford.
220
- * @public
221
- * @param array<int|float> $numbers
222
- */
223
- public static function benfordTest(array $numbers): float // Rendre la méthode publique et statique
224
- {
225
- if (count($numbers) < 10) {
226
- return 0.0; // Pas assez de données
227
- }
228
-
229
- $leadingDigits = array_map(function ($n) {
230
- $str = ltrim((string)$n, '0.');
231
- return $str[0] ?? '';
232
- }, $numbers);
233
-
234
- $leadingDigits = array_filter($leadingDigits, fn ($d) => $d >= '1' && $d <= '9');
235
-
236
- if (count($leadingDigits) < 10) {
237
- return 0.0;
238
- }
239
-
240
- $counts = array_fill(1, 9, 0);
241
- foreach ($leadingDigits as $digit) {
242
- $counts[(int)$digit]++;
243
- }
244
-
245
- $benfordDistribution = [1 => 30.1, 2 => 17.6, 3 => 12.5, 4 => 9.7, 5 => 7.9, 6 => 6.7, 7 => 5.8, 8 => 5.1, 9 => 4.6];
246
-
247
- $totalDeviation = 0.0;
248
- for ($i = 1; $i <= 9; $i++) {
249
- $observedFrequency = ($counts[$i] / count($leadingDigits)) * 100;
250
- $expectedFrequency = $benfordDistribution[$i];
251
- $totalDeviation += pow($observedFrequency - $expectedFrequency, 2);
252
- }
253
-
254
- return sqrt($totalDeviation) / 50.0;
255
- }
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace Anonympins\Fingerprint\Optimization;
6
+
7
+ /**
8
+ * Bibliothèque d'algorithmes d'optimisation.
9
+ */
10
+ class Optimization
11
+ {
12
+ /**
13
+ * Génère un nombre flottant aléatoire cryptographiquement sûr entre 0 (inclus) et 1 (exclus).
14
+ */
15
+ private static function secureRandom(): float
16
+ {
17
+ try {
18
+ return random_int(0, PHP_INT_MAX - 1) / PHP_INT_MAX;
19
+ } catch (\Exception $e) {
20
+ return (float)mt_rand() / (float)mt_getrandmax(); // Fallback
21
+ }
22
+ }
23
+
24
+ /**
25
+ * Algorithme génétique multi-objectifs (inspiré de NSGA-II).
26
+ * @param callable $createIndividual
27
+ * @param callable $fitnessFunction
28
+ * @param callable $crossover
29
+ * @param callable $mutate
30
+ * @param array|null $currentConfig La configuration actuelle pour guider l'initialisation et la mutation.
31
+ * @param array $options
32
+ * @return array<int, array{solution: mixed, objectives: array<float>}>
33
+ */
34
+ public static function geneticAlgorithmMultiObjective(
35
+ callable $createIndividual,
36
+ callable $fitnessFunction,
37
+ callable $crossover,
38
+ callable $mutate, // La fonction mutate doit maintenant accepter $currentConfig
39
+ ?array $currentConfig = null, // NOUVEAU: La configuration actuelle
40
+ array $options = []
41
+ ): array {
42
+ $generations = $options['generations'] ?? 150;
43
+ $populationSize = $options['populationSize'] ?? 60;
44
+ $mutationRate = $options['mutationRate'] ?? 0.1;
45
+
46
+ $population = [];
47
+ for ($i = 0; $i < $populationSize; $i++) {
48
+ $individual = $createIndividual();
49
+ $population[] = [
50
+ 'individual' => $individual,
51
+ 'objectives' => $fitnessFunction($individual)
52
+ ];
53
+ }
54
+
55
+ for ($gen = 0; $gen < $generations; $gen++) {
56
+ // 1. Créer une population d'enfants
57
+ $offspring = [];
58
+ for ($i = 0; $i < $populationSize; $i++) {
59
+ $parent1 = $population[random_int(0, count($population) - 1)];
60
+ $parent2 = $population[random_int(0, count($population) - 1)];
61
+ $childIndividual = $crossover($parent1['individual'], $parent2['individual']);
62
+ if (self::secureRandom() < $mutationRate) {
63
+ $childIndividual = $mutate($childIndividual, $currentConfig); // Passer $currentConfig à mutate
64
+ }
65
+ $offspring[] = [
66
+ 'individual' => $childIndividual,
67
+ 'objectives' => $fitnessFunction($childIndividual)
68
+ ];
69
+ }
70
+
71
+ // 2. Combiner parents et enfants
72
+ $combinedPopulation = array_merge($population, $offspring);
73
+
74
+ // 3. Trier la population combinée en fronts
75
+ $fronts = self::nonDominatedSort($combinedPopulation);
76
+
77
+ // 4. Construire la nouvelle population
78
+ $newPopulation = [];
79
+ foreach ($fronts as $front) {
80
+ if (count($newPopulation) + count($front) <= $populationSize) {
81
+ $newPopulation = array_merge($newPopulation, $front);
82
+ } else {
83
+ self::calculateCrowdingDistance($front);
84
+ // Trier par distance décroissante
85
+ usort($front, fn ($a, $b) => $b['crowdingDistance'] <=> $a['crowdingDistance']);
86
+ $remaining = $populationSize - count($newPopulation);
87
+ $newPopulation = array_merge($newPopulation, array_slice($front, 0, $remaining));
88
+ break;
89
+ }
90
+ }
91
+ $population = $newPopulation;
92
+ }
93
+
94
+ // Retourner le premier front de la population finale
95
+ $finalFronts = self::nonDominatedSort($population);
96
+ $bestFront = $finalFronts[0] ?? [];
97
+
98
+ // Filtrer pour ne garder que les solutions avec des objectifs uniques
99
+ $uniqueSolutionsMap = [];
100
+ foreach ($bestFront as $p) {
101
+ $key = json_encode($p['objectives']);
102
+ if (!isset($uniqueSolutionsMap[$key])) {
103
+ $uniqueSolutionsMap[$key] = [
104
+ 'solution' => $p['individual'],
105
+ 'objectives' => $p['objectives'],
106
+ ];
107
+ }
108
+ }
109
+ return array_values($uniqueSolutionsMap);
110
+ }
111
+
112
+ /**
113
+ * Détermine si la solution A domine la solution B.
114
+ * @param array<float> $objectivesA
115
+ * @param array<float> $objectivesB
116
+ */
117
+ private static function paretoDominates(array $objectivesA, array $objectivesB): bool
118
+ {
119
+ $aIsBetterInOne = false;
120
+ for ($i = 0; $i < count($objectivesA); $i++) {
121
+ if ($objectivesA[$i] > $objectivesB[$i]) {
122
+ return false; // A est pire sur au moins un objectif
123
+ }
124
+ if ($objectivesA[$i] < $objectivesB[$i]) {
125
+ $aIsBetterInOne = true; // A est strictement meilleur sur au moins un
126
+ }
127
+ }
128
+ return $aIsBetterInOne;
129
+ }
130
+
131
+ /**
132
+ * Trie une population en fronts de Pareto non-dominés.
133
+ * @param array<int, array> &$populationWithObjectives
134
+ * @return array<int, array>
135
+ */
136
+ private static function nonDominatedSort(array &$populationWithObjectives): array
137
+ {
138
+ $fronts = [[]];
139
+ $n = count($populationWithObjectives);
140
+
141
+ for ($i = 0; $i < $n; $i++) {
142
+ $p1 = &$populationWithObjectives[$i];
143
+ $p1['dominationCount'] = 0;
144
+ $p1['dominatedSolutions'] = [];
145
+
146
+ for ($j = 0; $j < $n; $j++) {
147
+ if ($i === $j) continue;
148
+ $p2 = &$populationWithObjectives[$j];
149
+
150
+ if (self::paretoDominates($p1['objectives'], $p2['objectives'])) {
151
+ $p1['dominatedSolutions'][] = $j;
152
+ } elseif (self::paretoDominates($p2['objectives'], $p1['objectives'])) {
153
+ $p1['dominationCount']++;
154
+ }
155
+ }
156
+
157
+ if ($p1['dominationCount'] === 0) {
158
+ $p1['rank'] = 0;
159
+ $fronts[0][] = $p1;
160
+ }
161
+ }
162
+
163
+ $i = 0;
164
+ while (!empty($fronts[$i])) {
165
+ $nextFront = [];
166
+ foreach ($fronts[$i] as $p1) {
167
+ foreach ($p1['dominatedSolutions'] as $p2_idx) {
168
+ $p2 = &$populationWithObjectives[$p2_idx];
169
+ $p2['dominationCount']--;
170
+ if ($p2['dominationCount'] === 0) {
171
+ $p2['rank'] = $i + 1;
172
+ $nextFront[] = $p2;
173
+ }
174
+ }
175
+ }
176
+ $i++;
177
+ if (!empty($nextFront)) {
178
+ $fronts[$i] = $nextFront;
179
+ }
180
+ }
181
+ return $fronts;
182
+ }
183
+
184
+ /**
185
+ * Calcule la distance de promiscuité (crowding distance) pour un front.
186
+ * @param array<int, array> &$front
187
+ */
188
+ private static function calculateCrowdingDistance(array &$front): void
189
+ {
190
+ if (empty($front)) return;
191
+
192
+ $numObjectives = count($front[0]['objectives']);
193
+ $l = count($front);
194
+
195
+ foreach ($front as &$p) {
196
+ $p['crowdingDistance'] = 0;
197
+ }
198
+
199
+ for ($i = 0; $i < $numObjectives; $i++) {
200
+ // Trier le front par l'objectif courant
201
+ usort($front, fn ($a, $b) => $a['objectives'][$i] <=> $b['objectives'][$i]);
202
+
203
+ $minObj = $front[0]['objectives'][$i];
204
+ $maxObj = $front[$l - 1]['objectives'][$i];
205
+
206
+ // Les solutions aux extrémités ont une distance infinie
207
+ $front[0]['crowdingDistance'] = INF;
208
+ $front[$l - 1]['crowdingDistance'] = INF;
209
+
210
+ if ($maxObj === $minObj) continue;
211
+
212
+ for ($j = 1; $j < $l - 1; $j++) {
213
+ $front[$j]['crowdingDistance'] +=
214
+ ($front[$j + 1]['objectives'][$i] - $front[$j - 1]['objectives'][$i]) /
215
+ ($maxObj - $minObj);
216
+ }
217
+ }
218
+ }
219
+
220
+ /**
221
+ * Calcule la déviation d'une série de chiffres par rapport à la loi de Benford.
222
+ * @public
223
+ * @param array<int|float> $numbers
224
+ */
225
+ public static function benfordTest(array $numbers): float // Rendre la méthode publique et statique
226
+ {
227
+ if (count($numbers) < 10) {
228
+ return 0.0; // Pas assez de données
229
+ }
230
+
231
+ $leadingDigits = array_map(function ($n) {
232
+ $str = ltrim((string)$n, '0.');
233
+ return $str[0] ?? '';
234
+ }, $numbers);
235
+
236
+ $leadingDigits = array_filter($leadingDigits, fn ($d) => $d >= '1' && $d <= '9');
237
+
238
+ if (count($leadingDigits) < 10) {
239
+ return 0.0;
240
+ }
241
+
242
+ $counts = array_fill(1, 9, 0);
243
+ foreach ($leadingDigits as $digit) {
244
+ $counts[(int)$digit]++;
245
+ }
246
+
247
+ $benfordDistribution = [1 => 30.1, 2 => 17.6, 3 => 12.5, 4 => 9.7, 5 => 7.9, 6 => 6.7, 7 => 5.8, 8 => 5.1, 9 => 4.6];
248
+
249
+ $totalDeviation = 0.0;
250
+ for ($i = 1; $i <= 9; $i++) {
251
+ $observedFrequency = ($counts[$i] / count($leadingDigits)) * 100;
252
+ $expectedFrequency = $benfordDistribution[$i];
253
+ $totalDeviation += pow($observedFrequency - $expectedFrequency, 2);
254
+ }
255
+
256
+ return sqrt($totalDeviation) / 50.0;
257
+ }
256
258
  }
@@ -104,39 +104,6 @@ class OptimizationOperators
104
104
  {
105
105
  $fitnessFunction = self::createFullSecurityConfigEvaluator($context);
106
106
 
107
- $createIndividual = function (): array {
108
- return [
109
- 'thresholds' => [
110
- 'low' => 15 + self::secureRandom() * 20,
111
- 'medium' => 40 + self::secureRandom() * 25,
112
- 'high' => 70 + self::secureRandom() * 20,
113
- ],
114
- 'weights' => [
115
- 'historyScore' => self::secureRandom(),
116
- 'rotationScore' => self::secureRandom(),
117
- 'headerAnomalyScore' => self::secureRandom(),
118
- 'requestPatternScore' => 0.5 + self::secureRandom(),
119
- 'inconsistencyScore' => self::secureRandom(),
120
- 'honeypotScore' => 1.0,
121
- 'behaviorScore' => self::secureRandom(),
122
- 'crossLayerInconsistencyScore' => self::secureRandom(),
123
- 'timeInconsistencyScore' => self::secureRandom(),
124
- 'tlsSpoofingScore' => self::secureRandom(),
125
- 'botScore' => self::secureRandom(),
126
- 'cookieDroppingScore' => self::secureRandom(),
127
- 'threatIntelScore' => self::secureRandom(),
128
- ],
129
- 'patterns' => [
130
- 'velocityThreshold' => 100 + self::secureRandom() * 400,
131
- 'burstThreshold' => 300 + self::secureRandom() * 700,
132
- 'scrapeThreshold' => 500 + self::secureRandom() * 1000,
133
- 'regularityThreshold' => 50 + self::secureRandom() * 200,
134
- 'decayFactor' => 0.85 + self::secureRandom() * 0.14,
135
- 'inactivityReset' => 15000 + self::secureRandom() * 45000,
136
- ]
137
- ];
138
- };
139
-
140
107
  $crossover = function (array $c1, array $c2): array {
141
108
  $child = $c1;
142
109
  foreach (['thresholds', 'weights', 'patterns'] as $section) {
@@ -148,8 +115,35 @@ class OptimizationOperators
148
115
  }
149
116
  return $child;
150
117
  };
118
+ $createIndividual = function () use ($currentConfig): array {
119
+ if ($currentConfig) {
120
+ $ind = [
121
+ 'thresholds' => [],
122
+ 'weights' => [],
123
+ 'patterns' => []
124
+ ];
125
+ foreach (['thresholds', 'weights', 'patterns'] as $section) {
126
+ if (isset($currentConfig[$section]) && is_array($currentConfig[$section])) {
127
+ foreach ($currentConfig[$section] as $k => $v) {
128
+ if (is_numeric($v) && $k !== 'honeypotScore') {
129
+ $randomVariation = 1.0 + (self::secureRandom() - 0.5) * 0.5; // Variation de +/- 25%
130
+ $ind[$section][$k] = $v * $randomVariation;
131
+ } else {
132
+ $ind[$section][$k] = $v;
133
+ }
134
+ }
135
+ }
136
+ }
137
+ if (isset($ind['thresholds']['low'], $ind['thresholds']['medium'], $ind['thresholds']['high'])) {
138
+ $ind['thresholds']['low'] = max(10.0, min(35.0, (float)$ind['thresholds']['low']));
139
+ $ind['thresholds']['medium'] = max($ind['thresholds']['low'] + 5.0, min(70.0, (float)$ind['thresholds']['medium']));
140
+ $ind['thresholds']['high'] = max($ind['thresholds']['medium'] + 5.0, min(90.0, (float)$ind['thresholds']['high']));
141
+ }
142
+ return $ind;
143
+ }
144
+ };
151
145
 
152
- $mutate = function (array $c): array {
146
+ $mutate = function (array $c, ?array $currentConfigRef = null) use ($currentConfig): array {
153
147
  $newConfig = $c;
154
148
  $sections = [
155
149
  ['name' => 'patterns', 'weight' => 0.5],
@@ -179,15 +173,27 @@ class OptimizationOperators
179
173
  $newConfig[$sectionToMutate][$keyToMutate] = max(0, min(1.5, $newConfig[$sectionToMutate][$keyToMutate]));
180
174
  }
181
175
 
176
+ $refConfig = $currentConfigRef ?? $currentConfig;
177
+ if ($refConfig && isset($refConfig[$sectionToMutate][$keyToMutate])) {
178
+ $originalValue = $refConfig[$sectionToMutate][$keyToMutate];
179
+ if (is_numeric($originalValue) && $originalValue != 0) {
180
+ $minAllowed = $originalValue * 0.7; // -30%
181
+ $maxAllowed = $originalValue * 1.3; // +30%
182
+ $newConfig[$sectionToMutate][$keyToMutate] = max($minAllowed, min($maxAllowed, $newConfig[$sectionToMutate][$keyToMutate]));
183
+ }
184
+ }
185
+
182
186
  return $newConfig;
183
187
  };
188
+ $gaOptions = array_merge(['generations' => 50, 'populationSize' => 50], $options);
184
189
 
185
190
  return Optimization::geneticAlgorithmMultiObjective(
186
191
  $createIndividual,
187
192
  $fitnessFunction,
188
193
  $crossover,
189
194
  $mutate,
190
- array_merge(['generations' => 50, 'populationSize' => 50], $options)
195
+ $currentConfig,
196
+ $gaOptions
191
197
  );
192
198
  }
193
199
 
@@ -24,6 +24,7 @@ class RequestContext
24
24
  public array $cookies = [];
25
25
  public ?string $httpVersion = null;
26
26
  public int $requestTimestamp = 0;
27
+ public ?string $tlsSessionId = null;
27
28
 
28
29
  /** @var ?array{type: string, name: string} */
29
30
  public ?array $graphqlOperation = null;
@@ -76,6 +77,7 @@ class RequestContext
76
77
  $this->ja4h = $this->headers['x-ja4h-hash'] ?? null;
77
78
  $this->http2Fingerprint = $this->headers['x-http2-fingerprint'] ?? null;
78
79
  $this->tcpFingerprint = $this->headers['x-tcp-fingerprint'] ?? null;
80
+ $this->tlsSessionId = $this->headers['x-tls-session-id'] ?? $this->headers['x-ssl-session-id'] ?? null;
79
81
  }
80
82
  /**
81
83
  * Récupère la valeur d'un en-tête HTTP de manière insensible à la casse.