Dave Jarvis' Repositories

git clone https://repo.autonoma.ca/repo/treetrek.git
<?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;
  }
}