diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9332b31 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/cmd/htpacker/htpacker diff --git a/cmd/htpacker/packer.go b/cmd/htpacker/packer.go new file mode 100644 index 0000000..5a6ae74 --- /dev/null +++ b/cmd/htpacker/packer.go @@ -0,0 +1,306 @@ +package main + +import ( + "bufio" + "crypto/sha512" + "fmt" + "io/ioutil" + "net/http" + "os" + "os/exec" + + "golang.org/x/sys/unix" + + "github.com/foobaz/go-zopfli/zopfli" + "github.com/lwithers/htpack/internal/packed" + "github.com/lwithers/pkg/writefile" +) + +// TODO: abandon packed version if no size saving + +var BrotliPath string = "brotli" + +type FilesToPack map[string]FileToPack + +type FileToPack struct { + Filename string `yaml:"filename"` + ContentType string `yaml:"content_type"` + DisableCompression bool `yaml:"disable_compression"` + DisableGzip bool `yaml:"disable_gzip"` + DisableBrotli bool `yaml:"disable_brotli"` + + uncompressed, gzip, brotli packInfo +} + +type packInfo struct { + present bool + offset, len uint64 +} + +func Pack(filesToPack FilesToPack, outputFilename string) error { + finalFname, outputFile, err := writefile.New(outputFilename) + if err != nil { + return err + } + defer writefile.Abort(outputFile) + packer := &packWriter{f: outputFile} + + // write initial header (will rewrite offset/length when known) + hdr := &packed.Header{ + Magic: 123, + Version: 1, + DirectoryOffset: 1, + DirectoryLength: 1, + } + m, _ := hdr.Marshal() + packer.Write(m) + + dir := packed.Directory{ + Files: make(map[string]*packed.File), + } + + for path, fileToPack := range filesToPack { + info, err := packOne(packer, fileToPack) + if err != nil { + return err + } + dir.Files[path] = &info + } + + // write the directory + if m, err = dir.Marshal(); err != nil { + // TODO: decorate + return err + } + + packer.Pad() + hdr.DirectoryOffset = packer.Pos() + hdr.DirectoryLength = uint64(len(m)) + if _, err := packer.Write(m); err != nil { + // TODO: decorate + return err + } + + // write header at start of file + m, _ = hdr.Marshal() + if _, err = outputFile.WriteAt(m, 0); err != nil { + return err + } + + // all done! + return writefile.Commit(finalFname, outputFile) +} + +func packOne(packer *packWriter, fileToPack FileToPack) (info packed.File, err error) { + // implementation detail: write files at a page boundary + if err = packer.Pad(); err != nil { + return + } + + // open and mmap input file + f, err := os.Open(fileToPack.Filename) + if err != nil { + return + } + defer f.Close() + + fi, err := f.Stat() + if err != nil { + return + } + + data, err := unix.Mmap(int(f.Fd()), 0, int(fi.Size()), + unix.PROT_READ, unix.MAP_SHARED) + if err != nil { + // TODO: decorate + return + } + defer unix.Munmap(data) + + // TODO: content-type, etag + info.Etag = etag(data) + info.ContentType = fileToPack.ContentType + if info.ContentType == "" { + info.ContentType = http.DetectContentType(data) + } + + // copy the uncompressed version + fileData := &packed.FileData{ + Offset: packer.Pos(), + Length: uint64(len(data)), + } + if _, err = packer.CopyFrom(f); err != nil { + // TODO: decorate + return + } + info.Uncompressed = fileData + + if fileToPack.DisableCompression { + return + } + + // gzip compression + if !fileToPack.DisableGzip { + if err = packer.Pad(); err != nil { + return + } + fileData = &packed.FileData{ + Offset: packer.Pos(), + } + fileData.Length, err = packOneGzip(packer, data) + if err != nil { + return + } + info.Gzip = fileData + } + + // brotli compression + if BrotliPath != "" && !fileToPack.DisableBrotli { + if err = packer.Pad(); err != nil { + return + } + fileData = &packed.FileData{ + Offset: packer.Pos(), + } + fileData.Length, err = packOneBrotli(packer, fileToPack.Filename) + if err != nil { + return + } + info.Brotli = fileData + } + + return +} + +func etag(in []byte) string { + h := sha512.New384() + h.Write(in) + return fmt.Sprintf(`"1--%x"`, h.Sum(nil)) +} + +func packOneGzip(packer *packWriter, data []byte) (uint64, error) { + // write via temporary file + tmpfile, err := ioutil.TempFile("", "") + if err != nil { + return 0, err + } + defer os.Remove(tmpfile.Name()) + defer tmpfile.Close() + + // compress + opts := zopfli.DefaultOptions() + if len(data) > (10 << 20) { // 10MiB + opts.NumIterations = 5 + } + + buf := bufio.NewWriter(tmpfile) + if err = zopfli.GzipCompress(&opts, data, buf); err != nil { + return 0, err + } + if err = buf.Flush(); err != nil { + return 0, err + } + + // copy into packfile + return packer.CopyFrom(tmpfile) +} + +func packOneBrotli(packer *packWriter, filename string) (uint64, error) { + // write via temporary file + tmpfile, err := ioutil.TempFile("", "") + if err != nil { + return 0, err + } + defer os.Remove(tmpfile.Name()) + defer tmpfile.Close() + + // compress via commandline + cmd := exec.Command(BrotliPath, "--input", filename, + "--output", tmpfile.Name()) + out, err := cmd.CombinedOutput() + if err != nil { + // TODO: decorate + _ = out + return 0, err + } + + // copy into packfile + return packer.CopyFrom(tmpfile) +} + +type packWriter struct { + f *os.File + err error +} + +func (pw *packWriter) Write(buf []byte) (int, error) { + if pw.err != nil { + return 0, pw.err + } + n, err := pw.f.Write(buf) + pw.err = err + return n, err +} + +func (pw *packWriter) Pos() uint64 { + pos, err := pw.f.Seek(0, os.SEEK_CUR) + if err != nil { + pw.err = err + } + return uint64(pos) +} + +func (pw *packWriter) Pad() error { + if pw.err != nil { + return pw.err + } + + pos, err := pw.f.Seek(0, os.SEEK_CUR) + if err != nil { + pw.err = err + return pw.err + } + + pos &= 0xFFF + if pos == 0 { + return pw.err + } + + if _, err = pw.f.Seek(4096-pos, os.SEEK_CUR); err != nil { + pw.err = err + } + return pw.err +} + +func (pw *packWriter) CopyFrom(in *os.File) (uint64, error) { + if pw.err != nil { + return 0, pw.err + } + + fi, err := in.Stat() + if err != nil { + pw.err = err + return 0, pw.err + } + + var off int64 + remain := fi.Size() + for remain > 0 { + var amt int + if remain > (1 << 30) { + amt = (1 << 30) + } else { + amt = int(remain) + } + + amt, err = unix.Sendfile(int(pw.f.Fd()), int(in.Fd()), &off, amt) + remain -= int64(amt) + off += int64(amt) + if err != nil { + pw.err = err + return uint64(off), pw.err + } + } + + return uint64(off), nil +} diff --git a/cmd/htpacker/quick_pack.go b/cmd/htpacker/quick_pack.go new file mode 100644 index 0000000..7d33323 --- /dev/null +++ b/cmd/htpacker/quick_pack.go @@ -0,0 +1,30 @@ +package main + +import ( + "fmt" + "io/ioutil" + "os" + + yaml "gopkg.in/yaml.v2" +) + +func main() { + if err := run(); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } +} + +func run() error { + raw, err := ioutil.ReadFile("in.yaml") + if err != nil { + return err + } + + var ftp FilesToPack + if err := yaml.UnmarshalStrict(raw, &ftp); err != nil { + return err + } + + return Pack(ftp, "out.htpack") +} diff --git a/go.mod b/go.mod index f5d7be8..8505667 100644 --- a/go.mod +++ b/go.mod @@ -1 +1,9 @@ module github.com/lwithers/htpack + +require ( + github.com/foobaz/go-zopfli v0.0.0-20140122214029-7432051485e2 + github.com/gogo/protobuf v1.2.1 + github.com/lwithers/pkg v1.2.1 + golang.org/x/sys v0.0.0-20180924175946-90868a75fefd + gopkg.in/yaml.v2 v2.2.2 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..d4334f1 --- /dev/null +++ b/go.sum @@ -0,0 +1,14 @@ +github.com/foobaz/go-zopfli v0.0.0-20140122214029-7432051485e2 h1:VA6jElpcJ+wkwEBufbnVkSBCA2TEnxdRppjRT5Kvh0A= +github.com/foobaz/go-zopfli v0.0.0-20140122214029-7432051485e2/go.mod h1:Yi95+RbwKz7uGndSuUhoq7LJKh8qH8DT9fnL4ewU30k= +github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/lwithers/pkg v1.2.1 h1:KNnZFGv0iyduc+uUF5UB8vDyr2ofRq930cVKqrpQulY= +github.com/lwithers/pkg v1.2.1/go.mod h1:0CRdDnVCqIa5uaIs1u8Gmwl3M7sm181QmSmVVaPTZUo= +golang.org/x/sys v0.0.0-20180924175946-90868a75fefd h1:ELJRxcWg6//yYBDjuf/SnMg1+X0jj5+BP5xXF31wl4w= +golang.org/x/sys v0.0.0-20180924175946-90868a75fefd/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/internal/packed/packed.pb.go b/internal/packed/packed.pb.go new file mode 100644 index 0000000..b551cd2 --- /dev/null +++ b/internal/packed/packed.pb.go @@ -0,0 +1,1193 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: packed.proto + +/* + Package packed is a generated protocol buffer package. + + It is generated from these files: + packed.proto + + It has these top-level messages: + Header + Directory + File + FileData +*/ +package packed + +import proto "github.com/gogo/protobuf/proto" +import fmt "fmt" +import math "math" + +import io "io" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion2 // please upgrade the proto package + +// Header at start of file. This must be a fixed, known size. +type Header struct { + // Magic number, used to quickly detect misconfigured systems or + // corrupted files. + Magic uint64 `protobuf:"fixed64,1,opt,name=magic,proto3" json:"magic,omitempty"` + // Version of file. + Version uint64 `protobuf:"fixed64,2,opt,name=version,proto3" json:"version,omitempty"` + // DirectoryOffset is the byte offset from the start of the file at + // which the Directory object may be found. + DirectoryOffset uint64 `protobuf:"fixed64,3,opt,name=directory_offset,json=directoryOffset,proto3" json:"directory_offset,omitempty"` + // DirectoryLength is the byte length of the serialised Directory + // object. + DirectoryLength uint64 `protobuf:"fixed64,4,opt,name=directory_length,json=directoryLength,proto3" json:"directory_length,omitempty"` +} + +func (m *Header) Reset() { *m = Header{} } +func (m *Header) String() string { return proto.CompactTextString(m) } +func (*Header) ProtoMessage() {} +func (*Header) Descriptor() ([]byte, []int) { return fileDescriptorPacked, []int{0} } + +func (m *Header) GetMagic() uint64 { + if m != nil { + return m.Magic + } + return 0 +} + +func (m *Header) GetVersion() uint64 { + if m != nil { + return m.Version + } + return 0 +} + +func (m *Header) GetDirectoryOffset() uint64 { + if m != nil { + return m.DirectoryOffset + } + return 0 +} + +func (m *Header) GetDirectoryLength() uint64 { + if m != nil { + return m.DirectoryLength + } + return 0 +} + +// Directory of available files. +type Directory struct { + // Files available within this pack. The key is the path of the URL to + // serve, and the value describes the file associated with that path. + Files map[string]*File `protobuf:"bytes,1,rep,name=files" json:"files,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value"` +} + +func (m *Directory) Reset() { *m = Directory{} } +func (m *Directory) String() string { return proto.CompactTextString(m) } +func (*Directory) ProtoMessage() {} +func (*Directory) Descriptor() ([]byte, []int) { return fileDescriptorPacked, []int{1} } + +func (m *Directory) GetFiles() map[string]*File { + if m != nil { + return m.Files + } + return nil +} + +// File that can be served. +type File struct { + // ContentType of the file, copied directly into the "Content-Type" header. + ContentType string `protobuf:"bytes,1,opt,name=content_type,json=contentType,proto3" json:"content_type,omitempty"` + // Etag of the file (includes double quotes). Remembered by the browser + // and used to preempt responses if it is unmodified between resource get + // requests. + Etag string `protobuf:"bytes,2,opt,name=etag,proto3" json:"etag,omitempty"` + // Uncompressed version of the file. + Uncompressed *FileData `protobuf:"bytes,3,opt,name=uncompressed" json:"uncompressed,omitempty"` + // Gzip compressed version of the file. + Gzip *FileData `protobuf:"bytes,4,opt,name=gzip" json:"gzip,omitempty"` + // Brotli compressed version of the file. + Brotli *FileData `protobuf:"bytes,5,opt,name=brotli" json:"brotli,omitempty"` +} + +func (m *File) Reset() { *m = File{} } +func (m *File) String() string { return proto.CompactTextString(m) } +func (*File) ProtoMessage() {} +func (*File) Descriptor() ([]byte, []int) { return fileDescriptorPacked, []int{2} } + +func (m *File) GetContentType() string { + if m != nil { + return m.ContentType + } + return "" +} + +func (m *File) GetEtag() string { + if m != nil { + return m.Etag + } + return "" +} + +func (m *File) GetUncompressed() *FileData { + if m != nil { + return m.Uncompressed + } + return nil +} + +func (m *File) GetGzip() *FileData { + if m != nil { + return m.Gzip + } + return nil +} + +func (m *File) GetBrotli() *FileData { + if m != nil { + return m.Brotli + } + return nil +} + +// FileData records the position of the file data within the pack. +type FileData struct { + // Offset is the start of the file, in bytes relative to the start of + // the pack. + Offset uint64 `protobuf:"fixed64,1,opt,name=offset,proto3" json:"offset,omitempty"` + // Length is the + Length uint64 `protobuf:"fixed64,2,opt,name=length,proto3" json:"length,omitempty"` +} + +func (m *FileData) Reset() { *m = FileData{} } +func (m *FileData) String() string { return proto.CompactTextString(m) } +func (*FileData) ProtoMessage() {} +func (*FileData) Descriptor() ([]byte, []int) { return fileDescriptorPacked, []int{3} } + +func (m *FileData) GetOffset() uint64 { + if m != nil { + return m.Offset + } + return 0 +} + +func (m *FileData) GetLength() uint64 { + if m != nil { + return m.Length + } + return 0 +} + +func init() { + proto.RegisterType((*Header)(nil), "packed.Header") + proto.RegisterType((*Directory)(nil), "packed.Directory") + proto.RegisterType((*File)(nil), "packed.File") + proto.RegisterType((*FileData)(nil), "packed.FileData") +} +func (m *Header) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Header) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.Magic != 0 { + dAtA[i] = 0x9 + i++ + i = encodeFixed64Packed(dAtA, i, uint64(m.Magic)) + } + if m.Version != 0 { + dAtA[i] = 0x11 + i++ + i = encodeFixed64Packed(dAtA, i, uint64(m.Version)) + } + if m.DirectoryOffset != 0 { + dAtA[i] = 0x19 + i++ + i = encodeFixed64Packed(dAtA, i, uint64(m.DirectoryOffset)) + } + if m.DirectoryLength != 0 { + dAtA[i] = 0x21 + i++ + i = encodeFixed64Packed(dAtA, i, uint64(m.DirectoryLength)) + } + return i, nil +} + +func (m *Directory) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Directory) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.Files) > 0 { + for k, _ := range m.Files { + dAtA[i] = 0xa + i++ + v := m.Files[k] + msgSize := 0 + if v != nil { + msgSize = v.Size() + msgSize += 1 + sovPacked(uint64(msgSize)) + } + mapSize := 1 + len(k) + sovPacked(uint64(len(k))) + msgSize + i = encodeVarintPacked(dAtA, i, uint64(mapSize)) + dAtA[i] = 0xa + i++ + i = encodeVarintPacked(dAtA, i, uint64(len(k))) + i += copy(dAtA[i:], k) + if v != nil { + dAtA[i] = 0x12 + i++ + i = encodeVarintPacked(dAtA, i, uint64(v.Size())) + n1, err := v.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n1 + } + } + } + return i, nil +} + +func (m *File) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *File) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.ContentType) > 0 { + dAtA[i] = 0xa + i++ + i = encodeVarintPacked(dAtA, i, uint64(len(m.ContentType))) + i += copy(dAtA[i:], m.ContentType) + } + if len(m.Etag) > 0 { + dAtA[i] = 0x12 + i++ + i = encodeVarintPacked(dAtA, i, uint64(len(m.Etag))) + i += copy(dAtA[i:], m.Etag) + } + if m.Uncompressed != nil { + dAtA[i] = 0x1a + i++ + i = encodeVarintPacked(dAtA, i, uint64(m.Uncompressed.Size())) + n2, err := m.Uncompressed.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n2 + } + if m.Gzip != nil { + dAtA[i] = 0x22 + i++ + i = encodeVarintPacked(dAtA, i, uint64(m.Gzip.Size())) + n3, err := m.Gzip.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n3 + } + if m.Brotli != nil { + dAtA[i] = 0x2a + i++ + i = encodeVarintPacked(dAtA, i, uint64(m.Brotli.Size())) + n4, err := m.Brotli.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n4 + } + return i, nil +} + +func (m *FileData) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *FileData) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.Offset != 0 { + dAtA[i] = 0x9 + i++ + i = encodeFixed64Packed(dAtA, i, uint64(m.Offset)) + } + if m.Length != 0 { + dAtA[i] = 0x11 + i++ + i = encodeFixed64Packed(dAtA, i, uint64(m.Length)) + } + return i, nil +} + +func encodeFixed64Packed(dAtA []byte, offset int, v uint64) int { + dAtA[offset] = uint8(v) + dAtA[offset+1] = uint8(v >> 8) + dAtA[offset+2] = uint8(v >> 16) + dAtA[offset+3] = uint8(v >> 24) + dAtA[offset+4] = uint8(v >> 32) + dAtA[offset+5] = uint8(v >> 40) + dAtA[offset+6] = uint8(v >> 48) + dAtA[offset+7] = uint8(v >> 56) + return offset + 8 +} +func encodeFixed32Packed(dAtA []byte, offset int, v uint32) int { + dAtA[offset] = uint8(v) + dAtA[offset+1] = uint8(v >> 8) + dAtA[offset+2] = uint8(v >> 16) + dAtA[offset+3] = uint8(v >> 24) + return offset + 4 +} +func encodeVarintPacked(dAtA []byte, offset int, v uint64) int { + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return offset + 1 +} +func (m *Header) Size() (n int) { + var l int + _ = l + if m.Magic != 0 { + n += 9 + } + if m.Version != 0 { + n += 9 + } + if m.DirectoryOffset != 0 { + n += 9 + } + if m.DirectoryLength != 0 { + n += 9 + } + return n +} + +func (m *Directory) Size() (n int) { + var l int + _ = l + if len(m.Files) > 0 { + for k, v := range m.Files { + _ = k + _ = v + l = 0 + if v != nil { + l = v.Size() + l += 1 + sovPacked(uint64(l)) + } + mapEntrySize := 1 + len(k) + sovPacked(uint64(len(k))) + l + n += mapEntrySize + 1 + sovPacked(uint64(mapEntrySize)) + } + } + return n +} + +func (m *File) Size() (n int) { + var l int + _ = l + l = len(m.ContentType) + if l > 0 { + n += 1 + l + sovPacked(uint64(l)) + } + l = len(m.Etag) + if l > 0 { + n += 1 + l + sovPacked(uint64(l)) + } + if m.Uncompressed != nil { + l = m.Uncompressed.Size() + n += 1 + l + sovPacked(uint64(l)) + } + if m.Gzip != nil { + l = m.Gzip.Size() + n += 1 + l + sovPacked(uint64(l)) + } + if m.Brotli != nil { + l = m.Brotli.Size() + n += 1 + l + sovPacked(uint64(l)) + } + return n +} + +func (m *FileData) Size() (n int) { + var l int + _ = l + if m.Offset != 0 { + n += 9 + } + if m.Length != 0 { + n += 9 + } + return n +} + +func sovPacked(x uint64) (n int) { + for { + n++ + x >>= 7 + if x == 0 { + break + } + } + return n +} +func sozPacked(x uint64) (n int) { + return sovPacked(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *Header) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowPacked + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Header: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Header: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 1 { + return fmt.Errorf("proto: wrong wireType = %d for field Magic", wireType) + } + m.Magic = 0 + if (iNdEx + 8) > l { + return io.ErrUnexpectedEOF + } + iNdEx += 8 + m.Magic = uint64(dAtA[iNdEx-8]) + m.Magic |= uint64(dAtA[iNdEx-7]) << 8 + m.Magic |= uint64(dAtA[iNdEx-6]) << 16 + m.Magic |= uint64(dAtA[iNdEx-5]) << 24 + m.Magic |= uint64(dAtA[iNdEx-4]) << 32 + m.Magic |= uint64(dAtA[iNdEx-3]) << 40 + m.Magic |= uint64(dAtA[iNdEx-2]) << 48 + m.Magic |= uint64(dAtA[iNdEx-1]) << 56 + case 2: + if wireType != 1 { + return fmt.Errorf("proto: wrong wireType = %d for field Version", wireType) + } + m.Version = 0 + if (iNdEx + 8) > l { + return io.ErrUnexpectedEOF + } + iNdEx += 8 + m.Version = uint64(dAtA[iNdEx-8]) + m.Version |= uint64(dAtA[iNdEx-7]) << 8 + m.Version |= uint64(dAtA[iNdEx-6]) << 16 + m.Version |= uint64(dAtA[iNdEx-5]) << 24 + m.Version |= uint64(dAtA[iNdEx-4]) << 32 + m.Version |= uint64(dAtA[iNdEx-3]) << 40 + m.Version |= uint64(dAtA[iNdEx-2]) << 48 + m.Version |= uint64(dAtA[iNdEx-1]) << 56 + case 3: + if wireType != 1 { + return fmt.Errorf("proto: wrong wireType = %d for field DirectoryOffset", wireType) + } + m.DirectoryOffset = 0 + if (iNdEx + 8) > l { + return io.ErrUnexpectedEOF + } + iNdEx += 8 + m.DirectoryOffset = uint64(dAtA[iNdEx-8]) + m.DirectoryOffset |= uint64(dAtA[iNdEx-7]) << 8 + m.DirectoryOffset |= uint64(dAtA[iNdEx-6]) << 16 + m.DirectoryOffset |= uint64(dAtA[iNdEx-5]) << 24 + m.DirectoryOffset |= uint64(dAtA[iNdEx-4]) << 32 + m.DirectoryOffset |= uint64(dAtA[iNdEx-3]) << 40 + m.DirectoryOffset |= uint64(dAtA[iNdEx-2]) << 48 + m.DirectoryOffset |= uint64(dAtA[iNdEx-1]) << 56 + case 4: + if wireType != 1 { + return fmt.Errorf("proto: wrong wireType = %d for field DirectoryLength", wireType) + } + m.DirectoryLength = 0 + if (iNdEx + 8) > l { + return io.ErrUnexpectedEOF + } + iNdEx += 8 + m.DirectoryLength = uint64(dAtA[iNdEx-8]) + m.DirectoryLength |= uint64(dAtA[iNdEx-7]) << 8 + m.DirectoryLength |= uint64(dAtA[iNdEx-6]) << 16 + m.DirectoryLength |= uint64(dAtA[iNdEx-5]) << 24 + m.DirectoryLength |= uint64(dAtA[iNdEx-4]) << 32 + m.DirectoryLength |= uint64(dAtA[iNdEx-3]) << 40 + m.DirectoryLength |= uint64(dAtA[iNdEx-2]) << 48 + m.DirectoryLength |= uint64(dAtA[iNdEx-1]) << 56 + default: + iNdEx = preIndex + skippy, err := skipPacked(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthPacked + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Directory) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowPacked + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Directory: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Directory: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Files", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowPacked + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthPacked + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Files == nil { + m.Files = make(map[string]*File) + } + var mapkey string + var mapvalue *File + for iNdEx < postIndex { + entryPreIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowPacked + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + if fieldNum == 1 { + var stringLenmapkey uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowPacked + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLenmapkey |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLenmapkey := int(stringLenmapkey) + if intStringLenmapkey < 0 { + return ErrInvalidLengthPacked + } + postStringIndexmapkey := iNdEx + intStringLenmapkey + if postStringIndexmapkey > l { + return io.ErrUnexpectedEOF + } + mapkey = string(dAtA[iNdEx:postStringIndexmapkey]) + iNdEx = postStringIndexmapkey + } else if fieldNum == 2 { + var mapmsglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowPacked + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + mapmsglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if mapmsglen < 0 { + return ErrInvalidLengthPacked + } + postmsgIndex := iNdEx + mapmsglen + if mapmsglen < 0 { + return ErrInvalidLengthPacked + } + if postmsgIndex > l { + return io.ErrUnexpectedEOF + } + mapvalue = &File{} + if err := mapvalue.Unmarshal(dAtA[iNdEx:postmsgIndex]); err != nil { + return err + } + iNdEx = postmsgIndex + } else { + iNdEx = entryPreIndex + skippy, err := skipPacked(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthPacked + } + if (iNdEx + skippy) > postIndex { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + m.Files[mapkey] = mapvalue + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipPacked(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthPacked + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *File) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowPacked + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: File: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: File: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ContentType", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowPacked + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthPacked + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ContentType = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Etag", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowPacked + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthPacked + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Etag = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Uncompressed", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowPacked + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthPacked + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Uncompressed == nil { + m.Uncompressed = &FileData{} + } + if err := m.Uncompressed.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Gzip", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowPacked + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthPacked + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Gzip == nil { + m.Gzip = &FileData{} + } + if err := m.Gzip.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Brotli", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowPacked + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthPacked + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Brotli == nil { + m.Brotli = &FileData{} + } + if err := m.Brotli.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipPacked(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthPacked + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *FileData) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowPacked + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: FileData: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: FileData: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 1 { + return fmt.Errorf("proto: wrong wireType = %d for field Offset", wireType) + } + m.Offset = 0 + if (iNdEx + 8) > l { + return io.ErrUnexpectedEOF + } + iNdEx += 8 + m.Offset = uint64(dAtA[iNdEx-8]) + m.Offset |= uint64(dAtA[iNdEx-7]) << 8 + m.Offset |= uint64(dAtA[iNdEx-6]) << 16 + m.Offset |= uint64(dAtA[iNdEx-5]) << 24 + m.Offset |= uint64(dAtA[iNdEx-4]) << 32 + m.Offset |= uint64(dAtA[iNdEx-3]) << 40 + m.Offset |= uint64(dAtA[iNdEx-2]) << 48 + m.Offset |= uint64(dAtA[iNdEx-1]) << 56 + case 2: + if wireType != 1 { + return fmt.Errorf("proto: wrong wireType = %d for field Length", wireType) + } + m.Length = 0 + if (iNdEx + 8) > l { + return io.ErrUnexpectedEOF + } + iNdEx += 8 + m.Length = uint64(dAtA[iNdEx-8]) + m.Length |= uint64(dAtA[iNdEx-7]) << 8 + m.Length |= uint64(dAtA[iNdEx-6]) << 16 + m.Length |= uint64(dAtA[iNdEx-5]) << 24 + m.Length |= uint64(dAtA[iNdEx-4]) << 32 + m.Length |= uint64(dAtA[iNdEx-3]) << 40 + m.Length |= uint64(dAtA[iNdEx-2]) << 48 + m.Length |= uint64(dAtA[iNdEx-1]) << 56 + default: + iNdEx = preIndex + skippy, err := skipPacked(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthPacked + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipPacked(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowPacked + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowPacked + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + return iNdEx, nil + case 1: + iNdEx += 8 + return iNdEx, nil + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowPacked + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + iNdEx += length + if length < 0 { + return 0, ErrInvalidLengthPacked + } + return iNdEx, nil + case 3: + for { + var innerWire uint64 + var start int = iNdEx + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowPacked + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + innerWire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + innerWireType := int(innerWire & 0x7) + if innerWireType == 4 { + break + } + next, err := skipPacked(dAtA[start:]) + if err != nil { + return 0, err + } + iNdEx = start + next + } + return iNdEx, nil + case 4: + return iNdEx, nil + case 5: + iNdEx += 4 + return iNdEx, nil + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + } + panic("unreachable") +} + +var ( + ErrInvalidLengthPacked = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowPacked = fmt.Errorf("proto: integer overflow") +) + +func init() { proto.RegisterFile("packed.proto", fileDescriptorPacked) } + +var fileDescriptorPacked = []byte{ + // 359 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x74, 0x92, 0xc1, 0x4e, 0xab, 0x50, + 0x10, 0x86, 0x2f, 0x2d, 0x70, 0x6f, 0x07, 0x92, 0x4b, 0x26, 0xc6, 0x10, 0x63, 0x1a, 0x25, 0x2e, + 0xea, 0xa6, 0x0b, 0x74, 0x61, 0xba, 0x34, 0xb5, 0x71, 0x61, 0x62, 0x42, 0xdc, 0x37, 0x14, 0xa6, + 0x48, 0x4a, 0x81, 0x1c, 0x4e, 0x9b, 0xe0, 0x0b, 0xb8, 0x33, 0xbe, 0x93, 0x1b, 0x97, 0x3e, 0x82, + 0xd1, 0x17, 0x11, 0x0e, 0x87, 0x6a, 0x8d, 0x2e, 0x4e, 0x72, 0xfe, 0x6f, 0x7e, 0x06, 0x66, 0x7e, + 0xc0, 0xcc, 0xfd, 0x60, 0x41, 0xe1, 0x30, 0x67, 0x19, 0xcf, 0x50, 0x6f, 0x94, 0xf3, 0xa0, 0x80, + 0x7e, 0x49, 0x7e, 0x48, 0x0c, 0x77, 0x40, 0x5b, 0xfa, 0x51, 0x1c, 0xd8, 0xca, 0x81, 0x32, 0xd0, + 0xbd, 0x46, 0xa0, 0x0d, 0x7f, 0xd7, 0xc4, 0x8a, 0x38, 0x4b, 0xed, 0x8e, 0xe0, 0xad, 0xc4, 0x63, + 0xb0, 0xc2, 0x98, 0x51, 0xc0, 0x33, 0x56, 0x4e, 0xb3, 0xf9, 0xbc, 0x20, 0x6e, 0x77, 0x85, 0xe5, + 0xff, 0x86, 0x5f, 0x0b, 0xbc, 0x6d, 0x4d, 0x28, 0x8d, 0xf8, 0xad, 0xad, 0x7e, 0xb3, 0x5e, 0x09, + 0xec, 0xdc, 0x2b, 0xd0, 0x1b, 0xb7, 0x0c, 0x5d, 0xd0, 0xe6, 0x71, 0x42, 0x45, 0xf5, 0x4d, 0xdd, + 0x81, 0xe1, 0xee, 0x0f, 0xe5, 0x10, 0x1b, 0xc7, 0x70, 0x52, 0x97, 0x2f, 0x52, 0xce, 0x4a, 0xaf, + 0xb1, 0xee, 0x4d, 0x00, 0x3e, 0x21, 0x5a, 0xd0, 0x5d, 0x50, 0x29, 0x66, 0xea, 0x79, 0xf5, 0x15, + 0x1d, 0xd0, 0xd6, 0x7e, 0xb2, 0x22, 0x31, 0x8f, 0xe1, 0x9a, 0x6d, 0xcf, 0xfa, 0x21, 0xaf, 0x29, + 0x8d, 0x3a, 0x67, 0x8a, 0xf3, 0xa4, 0x80, 0x5a, 0x33, 0x3c, 0x04, 0x33, 0xc8, 0x52, 0x4e, 0x29, + 0x9f, 0xf2, 0x32, 0x27, 0xd9, 0xcb, 0x90, 0xec, 0xa6, 0x42, 0x88, 0xa0, 0x12, 0xf7, 0x23, 0xd1, + 0xb2, 0xe7, 0x89, 0x3b, 0x9e, 0x82, 0xb9, 0x4a, 0x83, 0x6c, 0x99, 0x33, 0x2a, 0x0a, 0x0a, 0xc5, + 0x6e, 0x0c, 0xd7, 0xfa, 0xfa, 0xba, 0xb1, 0xcf, 0x7d, 0x6f, 0xcb, 0x85, 0x47, 0xa0, 0x46, 0x77, + 0x71, 0x2e, 0xd6, 0xf3, 0x93, 0x5b, 0x54, 0x71, 0x00, 0xfa, 0xac, 0xca, 0x31, 0x89, 0x6d, 0xed, + 0x17, 0x9f, 0xac, 0x3b, 0x23, 0xf8, 0xd7, 0x32, 0xdc, 0x05, 0x5d, 0xe6, 0xd4, 0x44, 0x2c, 0x55, + 0xcd, 0x65, 0x28, 0x4d, 0xc4, 0x52, 0x9d, 0x5b, 0xcf, 0x6f, 0x7d, 0xe5, 0xa5, 0x3a, 0xaf, 0xd5, + 0x79, 0x7c, 0xef, 0xff, 0x99, 0xe9, 0xe2, 0xef, 0x39, 0xf9, 0x08, 0x00, 0x00, 0xff, 0xff, 0x07, + 0x4a, 0x55, 0x53, 0x4d, 0x02, 0x00, 0x00, +} diff --git a/internal/packed/packed.proto b/internal/packed/packed.proto new file mode 100644 index 0000000..e797e59 --- /dev/null +++ b/internal/packed/packed.proto @@ -0,0 +1,59 @@ +syntax = "proto3"; + +package packed; + +// Header at start of file. This must be a fixed, known size. Fields cannot +// be zero. +message Header { + // Magic number, used to quickly detect misconfigured systems or + // corrupted files. + fixed64 magic = 1; + + // Version of file. + fixed64 version = 2; + + // DirectoryOffset is the byte offset from the start of the file at + // which the Directory object may be found. + fixed64 directory_offset = 3; + + // DirectoryLength is the byte length of the serialised Directory + // object. + fixed64 directory_length = 4; +} + +// Directory of available files. +message Directory { + // Files available within this pack. The key is the path of the URL to + // serve, and the value describes the file associated with that path. + map files = 1; +} + +// File that can be served. +message File { + // ContentType of the file, copied directly into the "Content-Type" header. + string content_type = 1; + + // Etag of the file (includes double quotes). Remembered by the browser + // and used to preempt responses if it is unmodified between resource get + // requests. + string etag = 2; + + // Uncompressed version of the file. + FileData uncompressed = 3; + + // Gzip compressed version of the file. + FileData gzip = 4; + + // Brotli compressed version of the file. + FileData brotli = 5; +} + +// FileData records the position of the file data within the pack. +message FileData { + // Offset is the start of the file, in bytes relative to the start of + // the pack. + fixed64 offset = 1; + + // Length is the + fixed64 length = 2; +}