github.com/quay/claircore@v1.5.28/rpm/ndb/package.go (about)

     1  package ndb
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"hash/adler32"
     8  	"io"
     9  )
    10  
    11  // Pages are hard-coded to 4096 bytes for the Package database; this
    12  // is different from the Index database, which could have variable page
    13  // sizes.
    14  
    15  // PackageDB is the "pkgdb" a.k.a. "Packages.db", the raw package data.
    16  type PackageDB struct {
    17  	r      io.ReaderAt
    18  	lookup map[uint32]*pkgSlot
    19  	slot   []pkgSlot
    20  	pkgHeader
    21  }
    22  
    23  // Parse closes over the provided [io.ReaderAt] and populates the provided PackageDB.
    24  func (db *PackageDB) Parse(r io.ReaderAt) error {
    25  	const (
    26  		headerSz = 4 * 8
    27  		pageSz   = 4096
    28  	)
    29  
    30  	// Read and verify the header.
    31  	b := make([]byte, headerSz)
    32  	if _, err := r.ReadAt(b, 0); err != nil {
    33  		return fmt.Errorf("ndb: package: unable to read header: %w", err)
    34  	}
    35  	if err := db.pkgHeader.UnmarshalBinary(b); err != nil {
    36  		return fmt.Errorf("ndb: package: unable to unmarshal header: %w", err)
    37  	}
    38  
    39  	// Package count should be contiguous.
    40  	lastIndex := db.NextPkgIdx - 1
    41  	db.lookup = make(map[uint32]*pkgSlot, lastIndex)
    42  	db.slot = make([]pkgSlot, 0, lastIndex)
    43  	b = b[:slotSize]
    44  	// Read every populated slot (these should be contiguous) and populate the lookup table.
    45  	for off := int64(slotStart * slotSize); off < int64(db.NPages*pageSz); off += slotSize {
    46  		if _, err := r.ReadAt(b, off); err != nil {
    47  			return fmt.Errorf("ndb: package: unable to read slot @%d: %w", off, err)
    48  		}
    49  		var s pkgSlot
    50  		err := s.UnmarshalBinary(b)
    51  		switch {
    52  		case errors.Is(err, nil):
    53  		case errors.Is(err, errSkipSlot):
    54  			continue
    55  		default:
    56  			return fmt.Errorf("ndb: package: slot @%d: unexpected error: %w", off, err)
    57  		}
    58  		i := len(db.slot)
    59  		db.slot = append(db.slot, s)
    60  		db.lookup[s.Index] = &db.slot[i]
    61  		if s.Index == lastIndex {
    62  			break
    63  		}
    64  	}
    65  	db.r = r
    66  
    67  	return nil
    68  }
    69  
    70  // AllHeaders returns ReaderAts for all RPM headers in the PackageDB.
    71  func (db *PackageDB) AllHeaders(_ context.Context) ([]io.ReaderAt, error) {
    72  	r := make([]io.ReaderAt, len(db.slot))
    73  	var err error
    74  	for i, s := range db.slot {
    75  		r[i], err = db.GetHeader(s.Index)
    76  		if err != nil {
    77  			return nil, err
    78  		}
    79  	}
    80  	return r, nil
    81  }
    82  
    83  // Validate currently here to fulfil an interface.
    84  func (db *PackageDB) Validate(_ context.Context) error {
    85  	return nil
    86  }
    87  
    88  // GetHeader returns an [io.ReaderAt] populated with [rpm.Header] data or
    89  // reports an error.
    90  func (db *PackageDB) GetHeader(pkgID uint32) (io.ReaderAt, error) {
    91  	const (
    92  		headerSize  = 4 * 4
    93  		trailerSize = 4 * 3
    94  	)
    95  	// Lookup offset and count.
    96  	blob, ok := db.lookup[pkgID]
    97  	if !ok {
    98  		return nil, fmt.Errorf("ndb: package: package id %d does not exist", pkgID)
    99  	}
   100  
   101  	// Read and verify header.
   102  	b := make([]byte, headerSize)
   103  	if _, err := db.r.ReadAt(b, blob.Offset()); err != nil {
   104  		return nil, fmt.Errorf("ndb: package: error reading header: %w", err)
   105  	}
   106  	var bh blobHeader
   107  	if err := bh.UnmarshalBinary(b); err != nil {
   108  		return nil, fmt.Errorf("ndb: package: bad header: %w", err)
   109  	}
   110  	if bh.Package != pkgID {
   111  		return nil, fmt.Errorf("ndb: package: martian blob")
   112  	}
   113  
   114  	// Read and verify trailer.
   115  	if _, err := db.r.ReadAt(b[:trailerSize], blob.Offset()+blob.Count()-trailerSize); err != nil {
   116  		return nil, fmt.Errorf("ndb: package: error reading trailer: %w", err)
   117  	}
   118  	var bt blobTrailer
   119  	if err := bt.UnmarshalBinary(b); err != nil {
   120  		return nil, fmt.Errorf("ndb: package: bad trailer: %w", err)
   121  	}
   122  	if bt.Len != bh.Len {
   123  		return nil, fmt.Errorf("ndb: package: header/trailer length mismatch")
   124  	}
   125  	// This is slightly different from the ultimate reader -- the checksum includes any padding.
   126  	h := adler32.New()
   127  	rd := io.NewSectionReader(db.r, blob.Offset(), blob.Count()-trailerSize)
   128  	if _, err := io.Copy(h, rd); err != nil {
   129  		panic(err)
   130  	}
   131  	if got, want := h.Sum32(), bt.Checksum; got != want {
   132  		return nil, fmt.Errorf("ndb: package: checksum mismatch; got: 0x%08x, want: 0x%08x", got, want)
   133  	}
   134  
   135  	return io.NewSectionReader(db.r, blob.Offset()+headerSize, int64(bh.Len)), nil
   136  }
   137  
   138  // PkgHeader is the header for the PackageDB. It's meant to be embedded.
   139  type pkgHeader struct {
   140  	Generation uint32
   141  	NPages     uint32
   142  	NextPkgIdx uint32
   143  }
   144  
   145  // UnmarshalBinary implements encoding.BinaryUnmarshaler for a PackageDB header.
   146  func (h *pkgHeader) UnmarshalBinary(b []byte) error {
   147  	const (
   148  		magic   = 'R' | 'p'<<8 | 'm'<<16 | 'P'<<24
   149  		version = 0
   150  
   151  		magicOffset        = 0
   152  		versionOffset      = 4
   153  		generationOffset   = 8
   154  		nPagesOffset       = 12
   155  		nextPkgIndexOffset = 16
   156  	)
   157  	if len(b) < 32 {
   158  		return io.ErrShortBuffer
   159  	}
   160  	if le.Uint32(b[magicOffset:]) != magic {
   161  		return fmt.Errorf("ndb: package: bad header: bad magic")
   162  	}
   163  	if le.Uint32(b[versionOffset:]) != version {
   164  		return fmt.Errorf("ndb: package: bad header: bad version")
   165  	}
   166  
   167  	h.Generation = le.Uint32(b[generationOffset:])
   168  	h.NPages = le.Uint32(b[nPagesOffset:])
   169  	h.NextPkgIdx = le.Uint32(b[nextPkgIndexOffset:])
   170  
   171  	return nil
   172  }
   173  
   174  // PkgSlot is a decoded package slot.
   175  type pkgSlot struct {
   176  	Index     uint32
   177  	blkOffset uint32
   178  	blkCount  uint32
   179  }
   180  
   181  // BlockSize is the size of a blob block.
   182  //
   183  // Blobs are denominated and allocated in blocks.
   184  const blockSize = 16
   185  
   186  // ErrSkipSlot is returned by pkgSlot.UnmarshalBinary when the slot is empty.
   187  var errSkipSlot = errors.New("skip slot")
   188  
   189  func (s *pkgSlot) GoString() string {
   190  	return fmt.Sprintf("blob@%08x[%08x]", s.blkOffset*blockSize, s.blkCount*blockSize)
   191  }
   192  
   193  // Offset reports the byte offset indicated by the slot.
   194  func (s *pkgSlot) Offset() int64 { return int64(s.blkOffset) * blockSize }
   195  
   196  // Count reports the length in bytes of the data in the slot.
   197  func (s *pkgSlot) Count() int64 { return int64(s.blkCount) * blockSize }
   198  
   199  // UnmarshalBinary implements encoding.BinaryUnmarshaler.
   200  func (s *pkgSlot) UnmarshalBinary(b []byte) error {
   201  	const (
   202  		magic = ('S' | 'l'<<8 | 'o'<<16 | 't'<<24)
   203  
   204  		magicOffset      = 0
   205  		slotIdxOffset    = 4
   206  		slotOffsetOffset = 8
   207  		slotCountOffset  = 12
   208  
   209  		headerSize  = 4 * 4
   210  		trailerSize = 3 * 4
   211  	)
   212  	if len(b) < slotSize {
   213  		return io.ErrShortBuffer
   214  	}
   215  	if le.Uint32(b[magicOffset:]) != magic {
   216  		return fmt.Errorf("slot: bad magic")
   217  	}
   218  	s.blkOffset = le.Uint32(b[slotOffsetOffset:])
   219  	if s.blkOffset == 0 {
   220  		return errSkipSlot
   221  	}
   222  	s.Index = le.Uint32(b[slotIdxOffset:])
   223  	s.blkCount = le.Uint32(b[slotCountOffset:])
   224  	// Double-check the blob size.
   225  	if s.blkCount < ((headerSize + trailerSize + blockSize - 1) / blockSize) {
   226  		return fmt.Errorf("slot: nonsense block count (%d)", s.blkCount)
   227  	}
   228  
   229  	return nil
   230  }
   231  
   232  // BlobHeader is the header for a blob.
   233  type blobHeader struct {
   234  	Package    uint32
   235  	Generation uint32
   236  	Len        uint32
   237  }
   238  
   239  // UnmarshalBinary implements encoding.BinaryUnmarshaler.
   240  func (h *blobHeader) UnmarshalBinary(b []byte) error {
   241  	const (
   242  		magic   = ('B' | 'l'<<8 | 'b'<<16 | 'S'<<24)
   243  		minSize = 4 * 4
   244  
   245  		offsetMagic      = 0
   246  		offsetPackage    = 4
   247  		offsetGeneration = 8
   248  		offsetLength     = 12
   249  	)
   250  
   251  	if len(b) < minSize {
   252  		return io.ErrShortBuffer
   253  	}
   254  	if le.Uint32(b[offsetMagic:]) != magic {
   255  		return errors.New("blob: header: bad magic")
   256  	}
   257  	h.Package = le.Uint32(b[offsetPackage:])
   258  	h.Generation = le.Uint32(b[offsetGeneration:])
   259  	h.Len = le.Uint32(b[offsetLength:])
   260  
   261  	return nil
   262  }
   263  
   264  // BlockCount reports the number of 16-byte blocks this blob occupies.
   265  func (h *blobHeader) BlockCount() uint32 {
   266  	const (
   267  		headerSize  = 4 * 4
   268  		trailerSize = 3 * 4
   269  	)
   270  	return ((headerSize + h.Len + trailerSize + blockSize) - 1) / blockSize
   271  }
   272  
   273  // BlobTrailer is the trailer (a.k.a. "tail") of a blob.
   274  type blobTrailer struct {
   275  	Checksum uint32
   276  	Len      uint32
   277  }
   278  
   279  // UnmarshalBinary implements encoding.BinaryUnmarshaler.
   280  func (t *blobTrailer) UnmarshalBinary(b []byte) error {
   281  	const (
   282  		magic   = ('B' | 'l'<<8 | 'b'<<16 | 'E'<<24)
   283  		minSize = 3 * 4
   284  
   285  		offsetChecksum = 0
   286  		offsetLength   = 4
   287  		offsetMagic    = 8
   288  	)
   289  
   290  	if len(b) < minSize {
   291  		return io.ErrShortBuffer
   292  	}
   293  	if le.Uint32(b[offsetMagic:]) != magic {
   294  		return errors.New("blob: trailer: bad magic")
   295  	}
   296  	t.Checksum = le.Uint32(b[offsetChecksum:])
   297  	t.Len = le.Uint32(b[offsetLength:])
   298  	return nil
   299  }