<?php require_once __DIR__ . '/CompressionStream.php'; require_once __DIR__ . '/StreamReader.php'; class DeltaDecoder { private const CHUNK_SIZE = 65536; private const array COPY_INSTRUCTION_SIZES = [ 0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4, 1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5, 1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5, 2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6, 1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5, 2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6, 2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6, 3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7, ]; public function apply( string $base, string $delta, int $cap ): string { $pos = 0; $this->readDeltaSize( $delta, $pos ); $this->readDeltaSize( $delta, $pos ); $chunks = []; $len = \strlen( $delta ); $outLen = 0; while( $pos < $len && ( $cap === 0 || $outLen < $cap ) ) { $op = \ord( $delta[$pos++] ); if( $op & 128 ) { $off = 0; $ln = 0; $this->parseCopyInstruction( $op, $delta, $pos, $off, $ln ); $chunks[] = \substr( $base, $off, $ln ); $outLen += $ln; } else { $ln = $op & 127; $chunks[] = \substr( $delta, $pos, $ln ); $outLen += $ln; $pos += $ln; } } $result = \implode( '', $chunks ); return $cap > 0 && \strlen( $result ) > $cap ? \substr( $result, 0, $cap ) : $result; } public function applyStreamGenerator( StreamReader $handle, mixed $base ): Generator { $stream = new ZlibInflaterStream(); $state = 0; $buffer = ''; $offset = 0; $yieldBuffer = ''; $yieldBufLen = 0; $isStream = $base instanceof StreamReader; foreach( $stream->stream( $handle ) as $data ) { $buffer .= $data; $bufLen = \strlen( $buffer ); while( $offset < $bufLen ) { $len = $bufLen - $offset; if( $state < 2 ) { $pos = $offset; $found = false; while( $pos < $bufLen ) { if( !(\ord( $buffer[$pos] ) & 128) ) { $found = true; $pos++; break; } $pos++; } if( $found ) { $offset = $pos; $state++; } else { break; } } else { $op = \ord( $buffer[$offset] ); if( $op & 128 ) { $need = self::COPY_INSTRUCTION_SIZES[$op & 0x7F]; if( $len < 1 + $need ) { break; } $off = 0; $ln = 0; $ptr = $offset + 1; $this->parseCopyInstruction( $op, $buffer, $ptr, $off, $ln ); if( $isStream ) { $base->seek( $off ); $rem = $ln; while( $rem > 0 ) { $slc = $base->read( \min( self::CHUNK_SIZE, $rem ) ); if( $slc === '' ) { $rem = 0; } else { $slcLen = \strlen( $slc ); $yieldBuffer .= $slc; $yieldBufLen += $slcLen; $rem -= $slcLen; if( $yieldBufLen >= self::CHUNK_SIZE ) { yield $yieldBuffer; $yieldBuffer = ''; $yieldBufLen = 0; } } } } else { $slc = \substr( $base, $off, $ln ); $yieldBuffer .= $slc; $yieldBufLen += \strlen( $slc ); if( $yieldBufLen >= self::CHUNK_SIZE ) { yield $yieldBuffer; $yieldBuffer = ''; $yieldBufLen = 0; } } $offset = $ptr; } else { $ln = $op & 127; if( $len < 1 + $ln ) { break; } $yieldBuffer .= \substr( $buffer, $offset + 1, $ln ); $yieldBufLen += $ln; $offset += 1 + $ln; if( $yieldBufLen >= self::CHUNK_SIZE ) { yield $yieldBuffer; $yieldBuffer = ''; $yieldBufLen = 0; } } } } if( $offset >= self::CHUNK_SIZE ) { $buffer = \substr( $buffer, $offset ); $offset = 0; } } if( $yieldBuffer !== '' ) { yield $yieldBuffer; } } public function readDeltaTargetSize( StreamReader $handle, int $type ): int { if( $type === 6 ) { $byte = \ord( $handle->read( 1 ) ); while( $byte & 128 ) { $byte = \ord( $handle->read( 1 ) ); } } else { $handle->seek( 20, SEEK_CUR ); } $head = $this->readInflatedHead( $handle ); $pos = 0; if( \strlen( $head ) > 0 ) { $this->readDeltaSize( $head, $pos ); } return \strlen( $head ) > 0 ? $this->readDeltaSize( $head, $pos ) : 0; } public function readDeltaBaseSize( StreamReader $handle ): int { $head = $this->readInflatedHead( $handle ); $pos = 0; return \strlen( $head ) > 0 ? $this->readDeltaSize( $head, $pos ) : 0; } private function readInflatedHead( StreamReader $handle ): string { $stream = new ZlibInflaterStream(); $head = ''; $try = 0; foreach( $stream->stream( $handle, 512 ) as $out ) { $head .= $out; $try++; if( \strlen( $head ) >= 32 || $try >= 64 ) { break; } } return $head; } private function parseCopyInstruction( int $op, string $data, int &$pos, int &$off, int &$len ): void { $off = 0; $len = 0; $off |= ($op & 0x01) ? \ord( $data[$pos++] ) : 0; $off |= ($op & 0x02) ? \ord( $data[$pos++] ) << 8 : 0; $off |= ($op & 0x04) ? \ord( $data[$pos++] ) << 16 : 0; $off |= ($op & 0x08) ? \ord( $data[$pos++] ) << 24 : 0; $len |= ($op & 0x10) ? \ord( $data[$pos++] ) : 0; $len |= ($op & 0x20) ? \ord( $data[$pos++] ) << 8 : 0; $len |= ($op & 0x40) ? \ord( $data[$pos++] ) << 16 : 0; $len = $len === 0 ? 0x10000 : $len; } private function readDeltaSize( string $data, int &$pos ): int { $len = \strlen( $data ); $val = 0; $shift = 0; $done = false; while( !$done && $pos < $len ) { $byte = \ord( $data[$pos++] ); $val |= ($byte & 0x7F) << $shift; $done = !($byte & 0x80); $shift += 7; } return $val; } }