aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--format/pack/entry.go64
-rw-r--r--objectstore/packed/delta_base.go3
-rw-r--r--objectstore/packed/delta_plan.go5
-rw-r--r--objectstore/packed/entry_parse.go88
-rw-r--r--objectstore/packed/pack.go7
-rw-r--r--objectstore/packed/read_reader.go5
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)