github.com/grafana/pyroscope@v1.18.0/pkg/phlaredb/symdb/format.go (about)

     1  //nolint:unused
     2  package symdb
     3  
     4  import (
     5  	"bytes"
     6  	"encoding/binary"
     7  	"fmt"
     8  	"hash/crc32"
     9  	"io"
    10  	"unsafe"
    11  
    12  	"github.com/parquet-go/parquet-go/encoding/delta"
    13  
    14  	"github.com/grafana/pyroscope/pkg/slices"
    15  )
    16  
    17  // V1 and V2:
    18  //
    19  // The database is a collection of files. The only file that is guaranteed
    20  // to be present is the index file: it indicates the version of the format,
    21  // and the structure of the database contents. The file is supposed to be
    22  // read into memory entirely and opened with an OpenIndex call.
    23  
    24  // V3:
    25  //
    26  // The database is a single file. The file consists of the following sections:
    27  //  [Data  ]
    28  //  [Index ]
    29  //  [Footer]
    30  //
    31  // The file is supposed to be open with Open call: it reads the footer, locates
    32  // index section, and fetches it into memory.
    33  //
    34  // Data section is version specific.
    35  //   v3: Partitions.
    36  //
    37  // Index section is structured in the following way:
    38  //
    39  // [IndexHeader] Header defines the format version and denotes the content type.
    40  // [TOC        ] Table of contents. Its entries refer to the Data section.
    41  //               It is of a fixed size for a given version (number of entries).
    42  // [Data       ] Data is an arbitrary structured section. The exact structure is
    43  //               defined by the TOC and Header (version, flags, etc).
    44  //                 v1: StacktraceChunkHeaders.
    45  //                 v2: PartitionHeadersV2.
    46  //                 v3: PartitionHeadersV3.
    47  // [CRC32      ] Checksum.
    48  //
    49  // Footer section is version agnostic and is only needed to locate
    50  // the index offset within the file.
    51  
    52  // In all version big endian order is used unless otherwise noted.
    53  
    54  const (
    55  	DefaultFileName = "symbols.symdb" // Added in v3.
    56  
    57  	// Pre-v3 assets. Left for compatibility reasons.
    58  
    59  	DefaultDirName      = "symbols"
    60  	IndexFileName       = "index.symdb"
    61  	StacktracesFileName = "stacktraces.symdb"
    62  )
    63  
    64  type FormatVersion uint32
    65  
    66  const (
    67  	// Within a database, the same format version
    68  	// must be used in all places.
    69  	_ FormatVersion = iota
    70  
    71  	FormatV1
    72  	FormatV2
    73  	FormatV3
    74  
    75  	unknownVersion
    76  )
    77  
    78  const (
    79  	// TOC entries are version-specific.
    80  	// The constants point to the entry index in the TOC.
    81  	tocEntryStacktraceChunkHeaders = 0
    82  	tocEntryPartitionHeaders       = 0
    83  
    84  	// Total number of entries in the current version.
    85  	// TODO(kolesnikovae): TOC size is version specific,
    86  	//   but at the moment, all versions have the same size: 1.
    87  	tocEntriesTotal = 1
    88  )
    89  
    90  // https://en.wikipedia.org/wiki/List_of_file_signatures
    91  var symdbMagic = [4]byte{'s', 'y', 'm', '1'}
    92  
    93  var castagnoli = crc32.MakeTable(crc32.Castagnoli)
    94  
    95  const (
    96  	checksumSize        = 4
    97  	indexChecksumOffset = -checksumSize
    98  )
    99  
   100  var (
   101  	ErrInvalidSize    = &FormatError{fmt.Errorf("invalid size")}
   102  	ErrInvalidCRC     = &FormatError{fmt.Errorf("invalid CRC")}
   103  	ErrInvalidMagic   = &FormatError{fmt.Errorf("invalid magic number")}
   104  	ErrUnknownVersion = &FormatError{fmt.Errorf("unknown version")}
   105  )
   106  
   107  type FormatError struct{ err error }
   108  
   109  func (e *FormatError) Error() string {
   110  	return e.err.Error()
   111  }
   112  
   113  type IndexFile struct {
   114  	Header IndexHeader
   115  	TOC    TOC
   116  
   117  	// Version-specific.
   118  	PartitionHeaders PartitionHeaders
   119  
   120  	CRC uint32 // Checksum of the index.
   121  }
   122  
   123  // NOTE(kolesnikovae): IndexHeader is rudimentary and is left for compatibility.
   124  
   125  type IndexHeader struct {
   126  	Magic   [4]byte
   127  	Version FormatVersion
   128  	_       [4]byte // Reserved for future use.
   129  	_       [4]byte // Reserved for future use.
   130  }
   131  
   132  const IndexHeaderSize = int(unsafe.Sizeof(IndexHeader{}))
   133  
   134  func (h *IndexHeader) MarshalBinary() []byte {
   135  	b := make([]byte, IndexHeaderSize)
   136  	copy(b[0:4], h.Magic[:])
   137  	binary.BigEndian.PutUint32(b[4:8], uint32(h.Version))
   138  	return b
   139  }
   140  
   141  func (h *IndexHeader) UnmarshalBinary(b []byte) error {
   142  	if len(b) != IndexHeaderSize {
   143  		return ErrInvalidSize
   144  	}
   145  	if copy(h.Magic[:], b[0:4]); !bytes.Equal(h.Magic[:], symdbMagic[:]) {
   146  		return ErrInvalidMagic
   147  	}
   148  	h.Version = FormatVersion(binary.BigEndian.Uint32(b[4:8]))
   149  	if h.Version >= unknownVersion {
   150  		return ErrUnknownVersion
   151  	}
   152  	return nil
   153  }
   154  
   155  type Footer struct {
   156  	Magic       [4]byte
   157  	Version     FormatVersion
   158  	IndexOffset uint64  // Index header offset in the file.
   159  	_           [4]byte // Reserved for future use.
   160  	CRC         uint32  // CRC of the footer.
   161  }
   162  
   163  const FooterSize = int(unsafe.Sizeof(Footer{}))
   164  
   165  func (f *Footer) MarshalBinary() []byte {
   166  	b := make([]byte, FooterSize)
   167  	copy(b[0:4], f.Magic[:])
   168  	binary.BigEndian.PutUint32(b[4:8], uint32(f.Version))
   169  	binary.BigEndian.PutUint64(b[8:16], f.IndexOffset)
   170  	binary.BigEndian.PutUint32(b[16:20], 0)
   171  	binary.BigEndian.PutUint32(b[20:24], crc32.Checksum(b[0:20], castagnoli))
   172  	return b
   173  }
   174  
   175  func (f *Footer) UnmarshalBinary(b []byte) error {
   176  	if len(b) != FooterSize {
   177  		return ErrInvalidSize
   178  	}
   179  	if copy(f.Magic[:], b[0:4]); !bytes.Equal(f.Magic[:], symdbMagic[:]) {
   180  		return ErrInvalidMagic
   181  	}
   182  	f.Version = FormatVersion(binary.BigEndian.Uint32(b[4:8]))
   183  	if f.Version >= unknownVersion {
   184  		return ErrUnknownVersion
   185  	}
   186  	f.IndexOffset = binary.BigEndian.Uint64(b[8:16])
   187  	f.CRC = binary.BigEndian.Uint32(b[20:24])
   188  	if crc32.Checksum(b[0:20], castagnoli) != f.CRC {
   189  		return ErrInvalidCRC
   190  	}
   191  	return nil
   192  }
   193  
   194  // Table of contents.
   195  
   196  const tocEntrySize = int(unsafe.Sizeof(TOCEntry{}))
   197  
   198  type TOC struct {
   199  	Entries []TOCEntry
   200  }
   201  
   202  // TOCEntry refers to a section within the index.
   203  // Offset is relative to the header offset.
   204  type TOCEntry struct {
   205  	Offset int64
   206  	Size   int64
   207  }
   208  
   209  func (toc *TOC) Size() int {
   210  	return tocEntrySize * tocEntriesTotal
   211  }
   212  
   213  func (toc *TOC) MarshalBinary() ([]byte, error) {
   214  	b := make([]byte, len(toc.Entries)*tocEntrySize)
   215  	for i := range toc.Entries {
   216  		toc.Entries[i].marshal(b[i*tocEntrySize:])
   217  	}
   218  	return b, nil
   219  }
   220  
   221  func (toc *TOC) UnmarshalBinary(b []byte) error {
   222  	s := len(b)
   223  	if s < tocEntrySize || s%tocEntrySize > 0 {
   224  		return ErrInvalidSize
   225  	}
   226  	toc.Entries = make([]TOCEntry, s/tocEntrySize)
   227  	for i := range toc.Entries {
   228  		off := i * tocEntrySize
   229  		toc.Entries[i].unmarshal(b[off : off+tocEntrySize])
   230  	}
   231  	return nil
   232  }
   233  
   234  func (h *TOCEntry) marshal(b []byte) {
   235  	binary.BigEndian.PutUint64(b[0:8], uint64(h.Size))
   236  	binary.BigEndian.PutUint64(b[8:16], uint64(h.Offset))
   237  }
   238  
   239  func (h *TOCEntry) unmarshal(b []byte) {
   240  	h.Size = int64(binary.BigEndian.Uint64(b[0:8]))
   241  	h.Offset = int64(binary.BigEndian.Uint64(b[8:16]))
   242  }
   243  
   244  type PartitionHeaders []*PartitionHeader
   245  
   246  type PartitionHeader struct {
   247  	Partition uint64
   248  	// TODO(kolesnikovae): Switch to SymbolsBlock encoding.
   249  	Stacktraces []StacktraceBlockHeader
   250  	V2          *PartitionHeaderV2
   251  	V3          *PartitionHeaderV3
   252  }
   253  
   254  func (h *PartitionHeaders) Size() int64 {
   255  	s := int64(4)
   256  	for _, p := range *h {
   257  		s += p.Size()
   258  	}
   259  	return s
   260  }
   261  
   262  func (h *PartitionHeaders) MarshalV3To(dst io.Writer) (_ int64, err error) {
   263  	w := withWriterOffset(dst)
   264  	buf := make([]byte, 4, 128)
   265  	binary.BigEndian.PutUint32(buf, uint32(len(*h)))
   266  	w.write(buf)
   267  	for _, p := range *h {
   268  		buf = slices.GrowLen(buf, int(p.Size()))
   269  		p.marshalV3(buf)
   270  		w.write(buf)
   271  	}
   272  	return w.offset, w.err
   273  }
   274  
   275  func (h *PartitionHeaders) MarshalV2To(dst io.Writer) (_ int64, err error) {
   276  	w := withWriterOffset(dst)
   277  	buf := make([]byte, 4, 128)
   278  	binary.BigEndian.PutUint32(buf, uint32(len(*h)))
   279  	w.write(buf)
   280  	for _, p := range *h {
   281  		s := p.Size()
   282  		if int(s) > cap(buf) {
   283  			buf = make([]byte, s)
   284  		}
   285  		buf = buf[:s]
   286  		p.marshalV2(buf)
   287  		w.write(buf)
   288  	}
   289  	return w.offset, w.err
   290  }
   291  
   292  func (h *PartitionHeaders) UnmarshalV1(b []byte) error {
   293  	s := len(b)
   294  	if s%stacktraceBlockHeaderSize > 0 {
   295  		return ErrInvalidSize
   296  	}
   297  	chunks := make([]StacktraceBlockHeader, s/stacktraceBlockHeaderSize)
   298  	for i := range chunks {
   299  		off := i * stacktraceBlockHeaderSize
   300  		chunks[i].unmarshal(b[off : off+stacktraceBlockHeaderSize])
   301  	}
   302  	var p *PartitionHeader
   303  	for _, c := range chunks {
   304  		if p == nil || p.Partition != c.Partition {
   305  			p = &PartitionHeader{Partition: c.Partition}
   306  			*h = append(*h, p)
   307  		}
   308  		p.Stacktraces = append(p.Stacktraces, c)
   309  	}
   310  	return nil
   311  }
   312  
   313  func (h *PartitionHeaders) UnmarshalV2(b []byte) error { return h.unmarshal(b, FormatV2) }
   314  
   315  func (h *PartitionHeaders) UnmarshalV3(b []byte) error { return h.unmarshal(b, FormatV3) }
   316  
   317  func (h *PartitionHeaders) unmarshal(b []byte, version FormatVersion) error {
   318  	partitions := binary.BigEndian.Uint32(b[0:4])
   319  	b = b[4:]
   320  	*h = make(PartitionHeaders, partitions)
   321  	for i := range *h {
   322  		var p PartitionHeader
   323  		if err := p.unmarshal(b, version); err != nil {
   324  			return err
   325  		}
   326  		b = b[p.Size():]
   327  		(*h)[i] = &p
   328  	}
   329  	return nil
   330  }
   331  
   332  func (h *PartitionHeader) marshalV2(buf []byte) {
   333  	binary.BigEndian.PutUint64(buf[0:8], h.Partition)
   334  	binary.BigEndian.PutUint32(buf[8:12], uint32(len(h.Stacktraces)))
   335  	binary.BigEndian.PutUint32(buf[12:16], uint32(len(h.V2.Locations)))
   336  	binary.BigEndian.PutUint32(buf[16:20], uint32(len(h.V2.Mappings)))
   337  	binary.BigEndian.PutUint32(buf[20:24], uint32(len(h.V2.Functions)))
   338  	binary.BigEndian.PutUint32(buf[24:28], uint32(len(h.V2.Strings)))
   339  	n := 28
   340  	for i := range h.Stacktraces {
   341  		h.Stacktraces[i].marshal(buf[n:])
   342  		n += stacktraceBlockHeaderSize
   343  	}
   344  	n += marshalRowRangeReferences(buf[n:], h.V2.Locations)
   345  	n += marshalRowRangeReferences(buf[n:], h.V2.Mappings)
   346  	n += marshalRowRangeReferences(buf[n:], h.V2.Functions)
   347  	marshalRowRangeReferences(buf[n:], h.V2.Strings)
   348  }
   349  
   350  func (h *PartitionHeader) marshalV3(buf []byte) {
   351  	binary.BigEndian.PutUint64(buf[0:8], h.Partition)
   352  	binary.BigEndian.PutUint32(buf[8:12], uint32(len(h.Stacktraces)))
   353  	n := 12
   354  	for i := range h.Stacktraces {
   355  		h.Stacktraces[i].marshal(buf[n:])
   356  		n += stacktraceBlockHeaderSize
   357  	}
   358  	n += marshalSymbolsBlockReferences(buf[n:], h.V3.Locations)
   359  	n += marshalSymbolsBlockReferences(buf[n:], h.V3.Mappings)
   360  	n += marshalSymbolsBlockReferences(buf[n:], h.V3.Functions)
   361  	marshalSymbolsBlockReferences(buf[n:], h.V3.Strings)
   362  }
   363  
   364  func (h *PartitionHeader) unmarshal(buf []byte, version FormatVersion) (err error) {
   365  	h.Partition = binary.BigEndian.Uint64(buf[0:8])
   366  	h.Stacktraces = make([]StacktraceBlockHeader, int(binary.BigEndian.Uint32(buf[8:12])))
   367  	switch version {
   368  	case FormatV2:
   369  		h.V2 = new(PartitionHeaderV2)
   370  		h.V2.Locations = make([]RowRangeReference, int(binary.BigEndian.Uint32(buf[12:16])))
   371  		h.V2.Mappings = make([]RowRangeReference, int(binary.BigEndian.Uint32(buf[16:20])))
   372  		h.V2.Functions = make([]RowRangeReference, int(binary.BigEndian.Uint32(buf[20:24])))
   373  		h.V2.Strings = make([]RowRangeReference, int(binary.BigEndian.Uint32(buf[24:28])))
   374  		buf = buf[28:]
   375  		stacktracesSize := len(h.Stacktraces) * stacktraceBlockHeaderSize
   376  		if err = h.unmarshalStacktraceBlockHeaders(buf[:stacktracesSize]); err != nil {
   377  			return err
   378  		}
   379  		err = h.V2.unmarshal(buf[stacktracesSize:])
   380  	case FormatV3:
   381  		buf = buf[12:]
   382  		stacktracesSize := len(h.Stacktraces) * stacktraceBlockHeaderSize
   383  		if err = h.unmarshalStacktraceBlockHeaders(buf[:stacktracesSize]); err != nil {
   384  			return err
   385  		}
   386  		h.V3 = new(PartitionHeaderV3)
   387  		err = h.V3.unmarshal(buf[stacktracesSize:])
   388  	default:
   389  		return fmt.Errorf("bug: unsupported version: %d", version)
   390  	}
   391  	// TODO(kolesnikovae): Validate headers.
   392  	return err
   393  }
   394  
   395  func (h *PartitionHeader) Size() int64 {
   396  	s := 12 // Partition 8b + number of stacktrace blocks.
   397  	s += len(h.Stacktraces) * stacktraceBlockHeaderSize
   398  	if h.V3 != nil {
   399  		s += h.V3.size()
   400  	}
   401  	if h.V2 != nil {
   402  		s += h.V2.size()
   403  	}
   404  	return int64(s)
   405  }
   406  
   407  type PartitionHeaderV3 struct {
   408  	Locations SymbolsBlockHeader
   409  	Mappings  SymbolsBlockHeader
   410  	Functions SymbolsBlockHeader
   411  	Strings   SymbolsBlockHeader
   412  }
   413  
   414  const partitionHeaderV3Size = int(unsafe.Sizeof(PartitionHeaderV3{}))
   415  
   416  func (h *PartitionHeaderV3) size() int { return partitionHeaderV3Size }
   417  
   418  func (h *PartitionHeaderV3) unmarshal(buf []byte) (err error) {
   419  	if len(buf) < symbolsBlockReferenceSize {
   420  		return ErrInvalidSize
   421  	}
   422  	h.Locations.unmarshal(buf[:symbolsBlockReferenceSize])
   423  	buf = buf[symbolsBlockReferenceSize:]
   424  	h.Mappings.unmarshal(buf[:symbolsBlockReferenceSize])
   425  	buf = buf[symbolsBlockReferenceSize:]
   426  	h.Functions.unmarshal(buf[:symbolsBlockReferenceSize])
   427  	buf = buf[symbolsBlockReferenceSize:]
   428  	h.Strings.unmarshal(buf[:symbolsBlockReferenceSize])
   429  	return nil
   430  }
   431  
   432  func (h *PartitionHeader) unmarshalStacktraceBlockHeaders(b []byte) error {
   433  	s := len(b)
   434  	if s%stacktraceBlockHeaderSize > 0 {
   435  		return ErrInvalidSize
   436  	}
   437  	for i := range h.Stacktraces {
   438  		off := i * stacktraceBlockHeaderSize
   439  		h.Stacktraces[i].unmarshal(b[off : off+stacktraceBlockHeaderSize])
   440  	}
   441  	return nil
   442  }
   443  
   444  // SymbolsBlockHeader describes a collection of elements encoded in a
   445  // content-specific way: symbolic information such as locations, functions,
   446  // mappings, and strings is represented as Array of Structures in memory,
   447  // and is encoded as Structure of Arrays when written on disk.
   448  type SymbolsBlockHeader struct {
   449  	// Offset in the data file.
   450  	Offset uint64
   451  	// Size of the section.
   452  	Size uint32
   453  	// Checksum of the section.
   454  	CRC uint32
   455  	// Length denotes the total number of items encoded.
   456  	Length uint32
   457  	// BlockSize denotes the number of items per block.
   458  	BlockSize uint32
   459  	// BlockHeaderSize denotes the encoder block header size in bytes.
   460  	// This enables forward compatibility within the same format version:
   461  	// as long as fields are not removed or reordered, and the encoding
   462  	// scheme does not change, the format can be extended without updating
   463  	// the format version. Decoder is able to read the whole header and
   464  	// skip unknown fields.
   465  	BlockHeaderSize uint16
   466  	// Format of the encoded data.
   467  	// Change of the format _version_ may break forward compatibility.
   468  	Format SymbolsBlockFormat
   469  }
   470  
   471  type SymbolsBlockFormat uint16
   472  
   473  const (
   474  	_ SymbolsBlockFormat = iota
   475  	BlockLocationsV1
   476  	BlockFunctionsV1
   477  	BlockMappingsV1
   478  	BlockStringsV1
   479  )
   480  
   481  type headerUnmarshaler interface {
   482  	unmarshal([]byte)
   483  	checksum() uint32
   484  }
   485  
   486  func readSymbolsBlockHeader(buf []byte, r io.Reader, v headerUnmarshaler) error {
   487  	if _, err := io.ReadFull(r, buf); err != nil {
   488  		return err
   489  	}
   490  	v.unmarshal(buf)
   491  	if crc32.Checksum(buf[:len(buf)-checksumSize], castagnoli) != v.checksum() {
   492  		return ErrInvalidCRC
   493  	}
   494  	return nil
   495  }
   496  
   497  const symbolsBlockReferenceSize = int(unsafe.Sizeof(SymbolsBlockHeader{}))
   498  
   499  func (h *SymbolsBlockHeader) marshal(b []byte) {
   500  	binary.BigEndian.PutUint64(b[0:8], h.Offset)
   501  	binary.BigEndian.PutUint32(b[8:12], h.Size)
   502  	binary.BigEndian.PutUint32(b[12:16], h.CRC)
   503  	binary.BigEndian.PutUint32(b[16:20], h.Length)
   504  	binary.BigEndian.PutUint32(b[20:24], h.BlockSize)
   505  	binary.BigEndian.PutUint16(b[24:26], h.BlockHeaderSize)
   506  	binary.BigEndian.PutUint16(b[26:28], uint16(h.Format))
   507  }
   508  
   509  func (h *SymbolsBlockHeader) unmarshal(b []byte) {
   510  	h.Offset = binary.BigEndian.Uint64(b[0:8])
   511  	h.Size = binary.BigEndian.Uint32(b[8:12])
   512  	h.CRC = binary.BigEndian.Uint32(b[12:16])
   513  	h.Length = binary.BigEndian.Uint32(b[16:20])
   514  	h.BlockSize = binary.BigEndian.Uint32(b[20:24])
   515  	h.BlockHeaderSize = binary.BigEndian.Uint16(b[24:26])
   516  	h.Format = SymbolsBlockFormat(binary.BigEndian.Uint16(b[26:28]))
   517  }
   518  
   519  func marshalSymbolsBlockReferences(b []byte, refs ...SymbolsBlockHeader) int {
   520  	var off int
   521  	for i := range refs {
   522  		refs[i].marshal(b[off : off+symbolsBlockReferenceSize])
   523  		off += symbolsBlockReferenceSize
   524  	}
   525  	return off
   526  }
   527  
   528  type PartitionHeaderV2 struct {
   529  	Locations []RowRangeReference
   530  	Mappings  []RowRangeReference
   531  	Functions []RowRangeReference
   532  	Strings   []RowRangeReference
   533  }
   534  
   535  func (h *PartitionHeaderV2) size() int {
   536  	s := 16 // Length of row ranges per type.
   537  	r := len(h.Locations) + len(h.Mappings) + len(h.Functions) + len(h.Strings)
   538  	return s + rowRangeReferenceSize*r
   539  }
   540  
   541  func (h *PartitionHeaderV2) unmarshal(buf []byte) (err error) {
   542  	locationsSize := len(h.Locations) * rowRangeReferenceSize
   543  	if err = h.unmarshalRowRangeReferences(h.Locations, buf[:locationsSize]); err != nil {
   544  		return err
   545  	}
   546  	buf = buf[locationsSize:]
   547  	mappingsSize := len(h.Mappings) * rowRangeReferenceSize
   548  	if err = h.unmarshalRowRangeReferences(h.Mappings, buf[:mappingsSize]); err != nil {
   549  		return err
   550  	}
   551  	buf = buf[mappingsSize:]
   552  	functionsSize := len(h.Functions) * rowRangeReferenceSize
   553  	if err = h.unmarshalRowRangeReferences(h.Functions, buf[:functionsSize]); err != nil {
   554  		return err
   555  	}
   556  	buf = buf[functionsSize:]
   557  	stringsSize := len(h.Strings) * rowRangeReferenceSize
   558  	if err = h.unmarshalRowRangeReferences(h.Strings, buf[:stringsSize]); err != nil {
   559  		return err
   560  	}
   561  	return nil
   562  }
   563  
   564  func (h *PartitionHeaderV2) unmarshalRowRangeReferences(refs []RowRangeReference, b []byte) error {
   565  	s := len(b)
   566  	if s%rowRangeReferenceSize > 0 {
   567  		return ErrInvalidSize
   568  	}
   569  	for i := range refs {
   570  		off := i * rowRangeReferenceSize
   571  		refs[i].unmarshal(b[off : off+rowRangeReferenceSize])
   572  	}
   573  	return nil
   574  }
   575  
   576  func marshalRowRangeReferences(b []byte, refs []RowRangeReference) int {
   577  	var off int
   578  	for i := range refs {
   579  		refs[i].marshal(b[off : off+rowRangeReferenceSize])
   580  		off += rowRangeReferenceSize
   581  	}
   582  	return off
   583  }
   584  
   585  const rowRangeReferenceSize = int(unsafe.Sizeof(RowRangeReference{}))
   586  
   587  type RowRangeReference struct {
   588  	RowGroup uint32
   589  	Index    uint32
   590  	Rows     uint32
   591  }
   592  
   593  func (r *RowRangeReference) marshal(b []byte) {
   594  	binary.BigEndian.PutUint32(b[0:4], r.RowGroup)
   595  	binary.BigEndian.PutUint32(b[4:8], r.Index)
   596  	binary.BigEndian.PutUint32(b[8:12], r.Rows)
   597  }
   598  
   599  func (r *RowRangeReference) unmarshal(b []byte) {
   600  	r.RowGroup = binary.BigEndian.Uint32(b[0:4])
   601  	r.Index = binary.BigEndian.Uint32(b[4:8])
   602  	r.Rows = binary.BigEndian.Uint32(b[8:12])
   603  }
   604  
   605  func OpenIndex(b []byte) (f IndexFile, err error) {
   606  	s := len(b)
   607  	if !f.assertSizeIsValid(b) {
   608  		return f, ErrInvalidSize
   609  	}
   610  	f.CRC = binary.BigEndian.Uint32(b[s+indexChecksumOffset:])
   611  	if f.CRC != crc32.Checksum(b[:s+indexChecksumOffset], castagnoli) {
   612  		return f, ErrInvalidCRC
   613  	}
   614  	if err = f.Header.UnmarshalBinary(b[:IndexHeaderSize]); err != nil {
   615  		return f, fmt.Errorf("unmarshal header: %w", err)
   616  	}
   617  	if err = f.TOC.UnmarshalBinary(b[IndexHeaderSize:f.dataOffset()]); err != nil {
   618  		return f, fmt.Errorf("unmarshal table of contents: %w", err)
   619  	}
   620  
   621  	// TODO: validate TOC
   622  
   623  	// Version-specific data section.
   624  	switch f.Header.Version {
   625  	default:
   626  		return f, fmt.Errorf("bug: unsupported version: %d", f.Header.Version)
   627  
   628  	case FormatV1:
   629  		sch := f.TOC.Entries[tocEntryStacktraceChunkHeaders]
   630  		if err = f.PartitionHeaders.UnmarshalV1(b[sch.Offset : sch.Offset+sch.Size]); err != nil {
   631  			return f, fmt.Errorf("unmarshal stacktraces: %w", err)
   632  		}
   633  
   634  	case FormatV2:
   635  		ph := f.TOC.Entries[tocEntryPartitionHeaders]
   636  		if err = f.PartitionHeaders.UnmarshalV2(b[ph.Offset : ph.Offset+ph.Size]); err != nil {
   637  			return f, fmt.Errorf("reading partition headers: %w", err)
   638  		}
   639  
   640  	case FormatV3:
   641  		ph := f.TOC.Entries[tocEntryPartitionHeaders]
   642  		if err = f.PartitionHeaders.UnmarshalV3(b[ph.Offset : ph.Offset+ph.Size]); err != nil {
   643  			return f, fmt.Errorf("reading partition headers: %w", err)
   644  		}
   645  	}
   646  
   647  	return f, nil
   648  }
   649  
   650  func (f *IndexFile) assertSizeIsValid(b []byte) bool {
   651  	return len(b) >= IndexHeaderSize+f.TOC.Size()+checksumSize
   652  }
   653  
   654  func (f *IndexFile) dataOffset() int {
   655  	return IndexHeaderSize + f.TOC.Size()
   656  }
   657  
   658  func (f *IndexFile) WriteTo(dst io.Writer) (n int64, err error) {
   659  	checksum := crc32.New(castagnoli)
   660  	w := withWriterOffset(io.MultiWriter(dst, checksum))
   661  	if _, err = w.Write(f.Header.MarshalBinary()); err != nil {
   662  		return w.offset, fmt.Errorf("header write: %w", err)
   663  	}
   664  
   665  	toc := TOC{Entries: make([]TOCEntry, tocEntriesTotal)}
   666  	toc.Entries[tocEntryPartitionHeaders] = TOCEntry{
   667  		Offset: int64(f.dataOffset()),
   668  		Size:   f.PartitionHeaders.Size(),
   669  	}
   670  	tocBytes, _ := toc.MarshalBinary()
   671  	if _, err = w.Write(tocBytes); err != nil {
   672  		return w.offset, fmt.Errorf("toc write: %w", err)
   673  	}
   674  
   675  	switch f.Header.Version {
   676  	case FormatV3:
   677  		_, err = f.PartitionHeaders.MarshalV3To(w)
   678  	default:
   679  		_, err = f.PartitionHeaders.MarshalV2To(w)
   680  	}
   681  	if err != nil {
   682  		return w.offset, fmt.Errorf("partitions headers: %w", err)
   683  	}
   684  
   685  	f.CRC = checksum.Sum32()
   686  	if err = binary.Write(dst, binary.BigEndian, f.CRC); err != nil {
   687  		return w.offset, fmt.Errorf("checksum write: %w", err)
   688  	}
   689  
   690  	return w.offset, nil
   691  }
   692  
   693  type StacktraceBlockHeader struct {
   694  	Offset int64
   695  	Size   int64
   696  
   697  	Partition  uint64 // Used in v1.
   698  	BlockIndex uint16 // Used in v1.
   699  
   700  	Encoding ChunkEncoding
   701  	_        [5]byte // Reserved.
   702  
   703  	Stacktraces        uint32 // Number of unique stack traces in the chunk.
   704  	StacktraceNodes    uint32 // Number of nodes in the stacktrace tree.
   705  	StacktraceMaxDepth uint32 // Max stack trace depth in the tree.
   706  	StacktraceMaxNodes uint32 // Max number of nodes at the time of the chunk creation.
   707  
   708  	_   [12]byte // Padding. 64 bytes per chunk header.
   709  	CRC uint32   // Checksum of the chunk data [Offset:Size).
   710  }
   711  
   712  const stacktraceBlockHeaderSize = int(unsafe.Sizeof(StacktraceBlockHeader{}))
   713  
   714  type ChunkEncoding byte
   715  
   716  const (
   717  	_ ChunkEncoding = iota
   718  	StacktraceEncodingGroupVarint
   719  )
   720  
   721  func (h *StacktraceBlockHeader) marshal(b []byte) {
   722  	binary.BigEndian.PutUint64(b[0:8], uint64(h.Offset))
   723  	binary.BigEndian.PutUint64(b[8:16], uint64(h.Size))
   724  	binary.BigEndian.PutUint64(b[16:24], h.Partition)
   725  	binary.BigEndian.PutUint16(b[24:26], h.BlockIndex)
   726  	b[27] = byte(h.Encoding)
   727  	// 5 bytes reserved.
   728  	binary.BigEndian.PutUint32(b[32:36], h.Stacktraces)
   729  	binary.BigEndian.PutUint32(b[36:40], h.StacktraceNodes)
   730  	binary.BigEndian.PutUint32(b[40:44], h.StacktraceMaxDepth)
   731  	binary.BigEndian.PutUint32(b[44:48], h.StacktraceMaxNodes)
   732  	// 12 bytes reserved.
   733  	binary.BigEndian.PutUint32(b[60:64], h.CRC)
   734  }
   735  
   736  func (h *StacktraceBlockHeader) unmarshal(b []byte) {
   737  	h.Offset = int64(binary.BigEndian.Uint64(b[0:8]))
   738  	h.Size = int64(binary.BigEndian.Uint64(b[8:16]))
   739  	h.Partition = binary.BigEndian.Uint64(b[16:24])
   740  	h.BlockIndex = binary.BigEndian.Uint16(b[24:26])
   741  	h.Encoding = ChunkEncoding(b[27])
   742  	// 5 bytes reserved.
   743  	h.Stacktraces = binary.BigEndian.Uint32(b[32:36])
   744  	h.StacktraceNodes = binary.BigEndian.Uint32(b[36:40])
   745  	h.StacktraceMaxDepth = binary.BigEndian.Uint32(b[40:44])
   746  	h.StacktraceMaxNodes = binary.BigEndian.Uint32(b[44:48])
   747  	// 12 bytes reserved.
   748  	h.CRC = binary.BigEndian.Uint32(b[60:64])
   749  }
   750  
   751  type symbolsBlockEncoder[T any] interface {
   752  	encode(w io.Writer, block []T) error
   753  	format() SymbolsBlockFormat
   754  	headerSize() uintptr
   755  }
   756  
   757  type symbolsEncoder[T any] struct {
   758  	blockEncoder symbolsBlockEncoder[T]
   759  	blockSize    int
   760  }
   761  
   762  const defaultSymbolsBlockSize = 1 << 10
   763  
   764  func newSymbolsEncoder[T any](e symbolsBlockEncoder[T]) *symbolsEncoder[T] {
   765  	return &symbolsEncoder[T]{blockEncoder: e, blockSize: defaultSymbolsBlockSize}
   766  }
   767  
   768  func (e *symbolsEncoder[T]) encode(w io.Writer, items []T) (err error) {
   769  	l := len(items)
   770  	for i := 0; i < l; i += e.blockSize {
   771  		block := items[i:min(i+e.blockSize, l)]
   772  		if err = e.blockEncoder.encode(w, block); err != nil {
   773  			return err
   774  		}
   775  	}
   776  	return nil
   777  }
   778  
   779  type symbolsBlockDecoder[T any] interface {
   780  	decode(r io.Reader, dst []T) error
   781  }
   782  
   783  type symbolsDecoder[T any] struct {
   784  	h SymbolsBlockHeader
   785  	d symbolsBlockDecoder[T]
   786  }
   787  
   788  func newSymbolsDecoder[T any](h SymbolsBlockHeader, d symbolsBlockDecoder[T]) *symbolsDecoder[T] {
   789  	return &symbolsDecoder[T]{h: h, d: d}
   790  }
   791  
   792  func (d *symbolsDecoder[T]) decode(dst []T, r io.Reader) error {
   793  	if d.h.BlockSize == 0 || d.h.Length == 0 {
   794  		return nil
   795  	}
   796  	if len(dst) < int(d.h.Length) {
   797  		return fmt.Errorf("decoder buffer too short (format %d)", d.h.Format)
   798  	}
   799  	blocks := int((d.h.Length + d.h.BlockSize - 1) / d.h.BlockSize)
   800  	for i := 0; i < blocks; i++ {
   801  		lo := i * int(d.h.BlockSize)
   802  		hi := min(lo+int(d.h.BlockSize), int(d.h.Length))
   803  		block := dst[lo:hi]
   804  		if err := d.d.decode(r, block); err != nil {
   805  			return fmt.Errorf("malformed block (format %d): %w", d.h.Format, err)
   806  		}
   807  	}
   808  	return nil
   809  }
   810  
   811  // NOTE(kolesnikovae): delta.BinaryPackedEncoding may
   812  // silently fail on malformed data, producing empty slice.
   813  
   814  func decodeBinaryPackedInt32(dst []int32, data []byte, length int) ([]int32, error) {
   815  	var enc delta.BinaryPackedEncoding
   816  	var err error
   817  	dst, err = enc.DecodeInt32(dst, data)
   818  	if err != nil {
   819  		return dst, err
   820  	}
   821  	if len(dst) != length {
   822  		return dst, fmt.Errorf("%w: binary packed: expected %d, got %d", ErrInvalidSize, length, len(dst))
   823  	}
   824  	return dst, nil
   825  }
   826  
   827  func decodeBinaryPackedInt64(dst []int64, data []byte, length int) ([]int64, error) {
   828  	var enc delta.BinaryPackedEncoding
   829  	var err error
   830  	dst, err = enc.DecodeInt64(dst, data)
   831  	if err != nil {
   832  		return dst, err
   833  	}
   834  	if len(dst) != length {
   835  		return dst, fmt.Errorf("%w: binary packed: expected %d, got %d", ErrInvalidSize, length, len(dst))
   836  	}
   837  	return dst, nil
   838  }