diff options
| -rw-r--r-- | format/pack/entry.go | 64 | ||||
| -rw-r--r-- | objectstore/packed/delta_base.go | 3 | ||||
| -rw-r--r-- | objectstore/packed/delta_plan.go | 5 | ||||
| -rw-r--r-- | objectstore/packed/entry_parse.go | 88 | ||||
| -rw-r--r-- | objectstore/packed/pack.go | 7 | ||||
| -rw-r--r-- | objectstore/packed/read_reader.go | 5 |
6 files changed, 87 insertions, 85 deletions
diff --git a/format/pack/entry.go b/format/pack/entry.go index 512b45f8..b95ad0ac 100644 --- a/format/pack/entry.go +++ b/format/pack/entry.go @@ -46,3 +46,67 @@ func ParseEntryHeader(data []byte) (EntryHeader, error) { } return header, nil } + +// Entry is one parsed pack entry prefix, including any delta base reference +// data that appears before the compressed payload. +type Entry struct { + // Type is the pack entry type. + Type objecttype.Type + // Size is the declared resulting object size. + Size int64 + // DataOffset is the byte offset from the start of the entry to the zlib + // payload bytes. + DataOffset int + // RefBaseID is the referenced base object ID bytes for ref-delta entries. + RefBaseID []byte + // OfsBaseDistance is the backward distance for ofs-delta entries. + OfsBaseDistance uint64 +} + +// ParseEntry parses one full pack entry prefix from data. +// +// hashSize must match the hash algorithm size used by the pack/index. +func ParseEntry(data []byte, hashSize int) (Entry, error) { + var zero Entry + + header, err := ParseEntryHeader(data) + if err != nil { + return zero, err + } + entry := Entry{ + Type: header.Type, + Size: header.Size, + DataOffset: header.HeaderSize, + } + + switch entry.Type { + case objecttype.TypeCommit, objecttype.TypeTree, objecttype.TypeBlob, objecttype.TypeTag: + // Base object entries have no extra prefix fields. + case objecttype.TypeRefDelta: + if hashSize <= 0 { + return zero, fmt.Errorf("format/pack: invalid hash size %d", hashSize) + } + end := entry.DataOffset + hashSize + if end > len(data) { + return zero, fmt.Errorf("format/pack: truncated ref-delta base id") + } + entry.RefBaseID = data[entry.DataOffset:end] + entry.DataOffset = end + case objecttype.TypeOfsDelta: + dist, consumed, err := ParseOfsDeltaDistance(data[entry.DataOffset:]) + if err != nil { + return zero, err + } + entry.OfsBaseDistance = dist + entry.DataOffset += consumed + case objecttype.TypeInvalid, objecttype.TypeFuture: + return zero, fmt.Errorf("format/pack: unsupported object type %d", entry.Type) + default: + return zero, fmt.Errorf("format/pack: unsupported object type %d", entry.Type) + } + + if entry.DataOffset > len(data) { + return zero, fmt.Errorf("format/pack: entry data offset out of bounds") + } + return entry, nil +} diff --git a/objectstore/packed/delta_base.go b/objectstore/packed/delta_base.go index fd9b96d5..b1113be7 100644 --- a/objectstore/packed/delta_base.go +++ b/objectstore/packed/delta_base.go @@ -3,6 +3,7 @@ package packed import ( "fmt" + packfmt "codeberg.org/lindenii/furgit/format/pack" "codeberg.org/lindenii/furgit/objecttype" ) @@ -24,7 +25,7 @@ func (store *Store) deltaResolveBase(plan deltaPlan) (objecttype.Type, []byte, e if err != nil { return objecttype.TypeInvalid, nil, err } - if !isBaseObjectType(meta.ty) { + if !packfmt.IsBaseObjectType(meta.ty) { return objecttype.TypeInvalid, nil, fmt.Errorf("objectstore/packed: delta plan base is not a base object") } base, err := inflateAt(pack, meta.dataOffset, meta.size) diff --git a/objectstore/packed/delta_plan.go b/objectstore/packed/delta_plan.go index 58e0fc0c..07dd5c77 100644 --- a/objectstore/packed/delta_plan.go +++ b/objectstore/packed/delta_plan.go @@ -4,6 +4,7 @@ import ( "fmt" deltaapply "codeberg.org/lindenii/furgit/format/delta/apply" + packfmt "codeberg.org/lindenii/furgit/format/pack" "codeberg.org/lindenii/furgit/objecttype" ) @@ -46,7 +47,7 @@ func (store *Store) deltaPlanFor(start location) (deltaPlan, error) { return deltaPlan{}, err } if plan.declaredSize < 0 { - if isBaseObjectType(meta.ty) { + if packfmt.IsBaseObjectType(meta.ty) { plan.declaredSize = meta.size } else { declaredSize, err := deltaDeclaredSizeAt(pack, meta.dataOffset) @@ -57,7 +58,7 @@ func (store *Store) deltaPlanFor(start location) (deltaPlan, error) { } } - if isBaseObjectType(meta.ty) { + if packfmt.IsBaseObjectType(meta.ty) { plan.baseLoc = current plan.baseType = meta.ty return plan, nil diff --git a/objectstore/packed/entry_parse.go b/objectstore/packed/entry_parse.go index 76fcb754..69ef80e6 100644 --- a/objectstore/packed/entry_parse.go +++ b/objectstore/packed/entry_parse.go @@ -3,6 +3,7 @@ package packed import ( "fmt" + packfmt "codeberg.org/lindenii/furgit/format/pack" "codeberg.org/lindenii/furgit/internal/intconv" "codeberg.org/lindenii/furgit/objectid" "codeberg.org/lindenii/furgit/objecttype" @@ -33,93 +34,28 @@ func parseEntryMeta(pack *packFile, algo objectid.Algorithm, offset uint64) (ent if err != nil { return zero, fmt.Errorf("objectstore/packed: pack %q offset conversion: %w", pack.name, err) } - first := pack.data[pos] - pos++ - - meta := entryMeta{ - ty: objecttype.Type((first >> 4) & 0x07), - size: int64(first & 0x0f), + entry, err := packfmt.ParseEntry(pack.data[pos:], algo.Size()) + if err != nil { + return zero, fmt.Errorf("objectstore/packed: pack %q: %w", pack.name, err) } - shift := uint(4) - b := first - for b&0x80 != 0 { - if pos >= len(pack.data) { - return zero, fmt.Errorf("objectstore/packed: pack %q truncated entry header", pack.name) - } - b = pack.data[pos] - pos++ - meta.size |= int64(b&0x7f) << shift - shift += 7 - } - if meta.size < 0 { - return zero, fmt.Errorf("objectstore/packed: pack %q entry has negative size", pack.name) + meta := entryMeta{ + ty: entry.Type, + size: entry.Size, + dataOffset: pos + entry.DataOffset, } - switch meta.ty { - case objecttype.TypeCommit, objecttype.TypeTree, objecttype.TypeBlob, objecttype.TypeTag: - // Base object entries have no extra header fields. case objecttype.TypeRefDelta: - hashSize := algo.Size() - if pos+hashSize > len(pack.data) { - return zero, fmt.Errorf("objectstore/packed: pack %q truncated ref-delta base id", pack.name) - } - baseID, err := objectid.FromBytes(algo, pack.data[pos:pos+hashSize]) + baseID, err := objectid.FromBytes(algo, entry.RefBaseID) if err != nil { - return zero, err + return zero, fmt.Errorf("objectstore/packed: pack %q invalid ref-delta base id: %w", pack.name, err) } meta.baseRefID = baseID - pos += hashSize case objecttype.TypeOfsDelta: - dist, consumed, err := parseOfsDeltaDistance(pack.data[pos:]) - if err != nil { - return zero, err - } - pos += consumed - if offset <= dist { + if offset <= entry.OfsBaseDistance { return zero, fmt.Errorf("objectstore/packed: pack %q has invalid ofs-delta base", pack.name) } - meta.baseOfs = offset - dist - case objecttype.TypeInvalid, objecttype.TypeFuture: - return zero, fmt.Errorf("objectstore/packed: pack %q has unsupported object type %d", pack.name, meta.ty) - default: - return zero, fmt.Errorf("objectstore/packed: pack %q has unsupported object type %d", pack.name, meta.ty) - } - - meta.dataOffset = pos - if meta.dataOffset > len(pack.data) { - return zero, fmt.Errorf("objectstore/packed: pack %q entry data offset out of bounds", pack.name) + meta.baseOfs = offset - entry.OfsBaseDistance } return meta, nil } - -// parseOfsDeltaDistance parses one ofs-delta backward distance. -func parseOfsDeltaDistance(buf []byte) (uint64, int, error) { - if len(buf) == 0 { - return 0, 0, fmt.Errorf("objectstore/packed: malformed ofs-delta distance") - } - b := buf[0] - dist := uint64(b & 0x7f) - consumed := 1 - for b&0x80 != 0 { - if consumed >= len(buf) { - return 0, 0, fmt.Errorf("objectstore/packed: malformed ofs-delta distance") - } - b = buf[consumed] - consumed++ - dist = ((dist + 1) << 7) + uint64(b&0x7f) - } - return dist, consumed, nil -} - -// isBaseObjectType reports whether ty is one of the four canonical object types. -func isBaseObjectType(ty objecttype.Type) bool { - switch ty { - case objecttype.TypeCommit, objecttype.TypeTree, objecttype.TypeBlob, objecttype.TypeTag: - return true - case objecttype.TypeInvalid, objecttype.TypeFuture, objecttype.TypeOfsDelta, objecttype.TypeRefDelta: - return false - default: - return false - } -} diff --git a/objectstore/packed/pack.go b/objectstore/packed/pack.go index 00950159..9af4c860 100644 --- a/objectstore/packed/pack.go +++ b/objectstore/packed/pack.go @@ -6,11 +6,10 @@ import ( "os" "syscall" + packfmt "codeberg.org/lindenii/furgit/format/pack" "codeberg.org/lindenii/furgit/internal/intconv" ) -const packSignature = 0x5041434b - // packFile stores one mapped and validated .pack file. type packFile struct { // name is the .pack basename. @@ -37,12 +36,12 @@ func openPackFile(name string, file *os.File, size int64) (*packFile, error) { if err != nil { return nil, err } - if binary.BigEndian.Uint32(data[:4]) != packSignature { + if binary.BigEndian.Uint32(data[:4]) != packfmt.Signature { _ = syscall.Munmap(data) return nil, fmt.Errorf("objectstore/packed: pack %q invalid signature", name) } version := binary.BigEndian.Uint32(data[4:8]) - if version != 2 && version != 3 { + if !packfmt.VersionSupported(version) { _ = syscall.Munmap(data) return nil, fmt.Errorf("objectstore/packed: pack %q unsupported version %d", name, version) } diff --git a/objectstore/packed/read_reader.go b/objectstore/packed/read_reader.go index bcafe975..a1f24799 100644 --- a/objectstore/packed/read_reader.go +++ b/objectstore/packed/read_reader.go @@ -5,6 +5,7 @@ import ( "fmt" "io" + packfmt "codeberg.org/lindenii/furgit/format/pack" "codeberg.org/lindenii/furgit/internal/iolimit" "codeberg.org/lindenii/furgit/objectheader" "codeberg.org/lindenii/furgit/objectid" @@ -40,7 +41,7 @@ func (store *Store) ReadReaderContent(id objectid.ObjectID) (objecttype.Type, in if err != nil { return objecttype.TypeInvalid, 0, nil, err } - if isBaseObjectType(meta.ty) { + if packfmt.IsBaseObjectType(meta.ty) { zr, err := zlibReaderAt(pack, meta.dataOffset) if err != nil { return objecttype.TypeInvalid, 0, nil, err @@ -71,7 +72,7 @@ func (store *Store) ReadReaderFull(id objectid.ObjectID) (io.ReadCloser, error) if err != nil { return nil, err } - if isBaseObjectType(meta.ty) { + if packfmt.IsBaseObjectType(meta.ty) { header, ok := objectheader.Encode(meta.ty, meta.size) if !ok { return nil, fmt.Errorf("objectstore/packed: failed to encode object header for type %d", meta.ty) |
