public function hydrate($data) { $index = new Index(); $reader = new StringReader($data); $signature = $reader->readString8(4); $version = $reader->readUInt32BE(); if ($signature != Index::SIGNATURE || $version != Index::VERSION) { throw new IndexVersionException("IndexHydrator only supports indexes with signature " . Index::SIGNATURE . " and version " . Index::VERSION); } $entries = $reader->readUInt32BE(); $extended_contents_start = $reader->getOffset(); for ($i = 0; $i < $entries; $i++) { $entry = new IndexEntry(); $start = $reader->getOffset(); $entry->setCtime($this->readEntryTime($reader)); $entry->setMtime($this->readEntryTime($reader)); $entry->setDev($reader->readUInt32BE()); $entry->setInode($reader->readUInt32BE()); $entry->setMode($reader->readUInt32BE()); $entry->setUid($reader->readUInt32BE()); $entry->setGid($reader->readUInt32BE()); $entry->setFileSize($reader->readUInt32BE()); $entry->setBlob(new BlobProxy($this->repo, $reader->readHHex(20))); $flags = $reader->readUInt16BE(); $entry->setName(rtrim($reader->readString16(($flags & 0xfff) + 1, $foo, true), "")); //+1 is to capture mandatory NUL $entry->setStage(($flags & 0x3000) >> 12); $stop = $reader->getOffset(); $length = $stop - $start; $padded_length = ceil($length / 8) * 8; $reader->setOffset($start + $padded_length); $index->addEntry($entry); } while ($reader->getOffset() < $reader->getSize() - 20) { $name = $reader->readString8(4); if (ord($name[0]) >= 0x41 && ord($name[0]) <= 0x5a) { // Optional extension, just skip it $data_size = $reader->readUInt32BE(); $reader->skip($data_size); } else { throw new IndexVersionException("IndexHydrator doesn't support the mandatory " . $name . " extension"); } } $extended_contents_stop = $reader->getOffset(); $reader->setOffset(0); $calculated_sha = sha1($reader->read($extended_contents_stop)); $checksum = $reader->readHHex(20); if ($checksum != $calculated_sha) { throw new ChecksumException("Index checksum does not match"); } return $index; }
public function desiccate(Index $index) { $writer = new StringWriter(); $entries = $index->getEntries(); $writer->writeString8(Index::SIGNATURE); $writer->writeUInt32BE(Index::VERSION); $writer->writeUInt32BE(count($entries)); $contents_offset = $writer->getOffset(); usort($entries, function ($a, $b) { $cmp = strcmp($a->getName(), $b->getName()); if ($cmp == 0) { returnĀ ($a->getStage() - $b->getStage()); } return $cmp; }); foreach ($entries as $entry) { $start = $writer->getOffset(); $this->writeEntryTime($writer, $entry->getCtime()); $this->writeEntryTime($writer, $entry->getMtime()); $writer->writeUInt32BE($entry->getDev()); $writer->writeUInt32BE($entry->getInode()); $writer->writeUInt32BE($entry->getMode()); $writer->writeUInt32BE($entry->getUid()); $writer->writeUInt32BE($entry->getGid()); $writer->writeUInt32BE($entry->getFileSize()); $writer->writeHHex($entry->getBlob()->getSha()); //FIXME: Stage $writer->writeUInt16BE(strlen($entry->getName())); $stop = $writer->getOffset(); $length = $stop - $start + strlen($entry->getName()) + 1; //We need at least 1 NUL $padded_length = ceil($length / 8) * 8; $rest_length = $padded_length - $length; $writer->writeString8($entry->getName(), strlen($entry->getName()) + 1 + $rest_length); } $writer->writeHHex(sha1($writer->toString())); return $writer->toString(); }