0, 'off' => 0, 'batch' => self::START, 'tries' => [], 'count' => 0, 'bytes' => 0, 'gone' => 0, 'done' => false]; } /** * After a batch: the sender finished $sent of $want and stopped $off bytes into the next; $result is what * the receiving side wrote (Unpacker::unpack()); $call is the request's size in files (raw), its size on the * wire, and how long it took (Client::last_call()). Returns the new state. * * @param int[] $want * @param array{raw?: int, wire?: int, seconds?: float} $call */ public static function advance(array $c, array $want, int $sent, int $off, array $result, FileList $list, int $limit, array $call = []): array { $c['count'] += count($result['done']); $c['bytes'] += (int) $result['bytes']; $c['gone'] += count($result['gone']); $positions = array_flip($want); $redo = null; foreach ($result['failed'] as $i) { $c['tries'][$i] = ($c['tries'][$i] ?? 0) + 1; if ($c['tries'][$i] >= self::TRIES) { $e = $list->get((int) $i); throw new RelayError( 'files.checksum', /* translators: %s: file path */ esc_html(sprintf(__('%s kept arriving damaged (checksum mismatch), so nothing was changed.', 'siterelay'), $e !== null ? $e['g'] . '/' . $e['r'] : '#' . $i)), esc_html__('The file may be changing while it is copied. Exclude it, or try again later.', 'siterelay') ); } if (isset($positions[$i]) && ($redo === null || $positions[$i] < $redo[0])) { $redo = [$positions[$i], 0]; } } $partial = $result['partial']; if (is_array($partial) && isset($positions[$partial['i']]) && ($redo === null || $positions[$partial['i']] < $redo[0])) { $redo = [$positions[$partial['i']], (int) $partial['have']]; } if ($redo !== null) { $c['pos'] += $redo[0]; $c['off'] = $redo[1]; return $c; } $c['pos'] += $sent; $c['off'] = $off; $c['batch'] = self::next_batch($c, $limit, $call); return $c; } /** @param array{raw?: int, wire?: int, seconds?: float} $call */ private static function next_batch(array $c, int $limit, array $call): int { $seconds = (float) ($call['seconds'] ?? 0); $batch = (int) ((int) $c['batch'] * ($seconds > 0 ? min(self::GROW, self::SECONDS / $seconds) : self::GROW)); $raw = (int) ($call['raw'] ?? 0); $wire = (int) ($call['wire'] ?? 0); if (isset($c['refused']) && $raw > 0 && $wire > 0) { // As much of the files as the last batch's compression fits under the refused size. $batch = min($batch, intdiv((int) (self::UNDER * (int) $c['refused']) * $raw, $wire)); } return max(isset($c['refused']) ? Limits::MIN_REQUEST : Limits::MIN_BATCH, min($limit, $batch)); } /** * The other site's server refused a batch of $raw bytes of files, $wire on the wire, before SiteRelay saw it (a * size limit in the web server or a firewall): three quarters of it next, and from then on batches stay under * the smallest size refused (see next_batch()). Returns null when the batch is already as small as it gets. */ public static function refused(array $c, int $raw, int $wire): ?array { if ($raw <= Limits::MIN_REQUEST) { return null; } $c['refused'] = min((int) ($c['refused'] ?? PHP_INT_MAX), $wire); $c['batch'] = max(Limits::MIN_REQUEST, (int) ($raw * self::AFTER_REFUSAL)); return $c; } /** A request failed (timeout, server error): smaller batches from now on. */ public static function shrink(array $c): array { $c['batch'] = max(Limits::MIN_BATCH, intdiv((int) $c['batch'], 2)); return $c; } }