container: implement header based serialization, also add SPEC test vectors
This commit is contained in:
118
pkg/container/packaging.go
Normal file
118
pkg/container/packaging.go
Normal file
@@ -0,0 +1,118 @@
|
||||
package container
|
||||
|
||||
import (
|
||||
"compress/gzip"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
const containerVersionTag = "ctn-v1"
|
||||
|
||||
type header byte
|
||||
|
||||
const (
|
||||
headerRawBytes = header(0x40)
|
||||
headerBase64StdPadding = header(0x42)
|
||||
headerBase64URL = header(0x43)
|
||||
headerRawBytesGzip = header(0x4D)
|
||||
headerBase64StdPaddingGzip = header(0x4F)
|
||||
headerBase64URLGzip = header(0x50)
|
||||
)
|
||||
|
||||
func (h header) encoder(w io.Writer) *payloadWriter {
|
||||
res := &payloadWriter{rawWriter: w, writer: w, header: h}
|
||||
|
||||
switch h {
|
||||
case headerBase64StdPadding, headerBase64StdPaddingGzip:
|
||||
b64Writer := base64.NewEncoder(base64.StdEncoding, res.writer)
|
||||
res.writer = b64Writer
|
||||
res.closers = append([]io.Closer{b64Writer}, res.closers...)
|
||||
case headerBase64URL, headerBase64URLGzip:
|
||||
b64Writer := base64.NewEncoder(base64.RawURLEncoding, res.writer)
|
||||
res.writer = b64Writer
|
||||
res.closers = append([]io.Closer{b64Writer}, res.closers...)
|
||||
}
|
||||
|
||||
switch h {
|
||||
case headerRawBytesGzip, headerBase64StdPaddingGzip, headerBase64URLGzip:
|
||||
gzipWriter := gzip.NewWriter(res.writer)
|
||||
res.writer = gzipWriter
|
||||
res.closers = append([]io.Closer{gzipWriter}, res.closers...)
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
func decodePayload(r io.Reader) (io.Reader, error) {
|
||||
headerBuf := make([]byte, 1)
|
||||
_, err := r.Read(headerBuf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
h := header(headerBuf[0])
|
||||
|
||||
switch h {
|
||||
case headerRawBytes,
|
||||
headerBase64StdPadding,
|
||||
headerBase64URL,
|
||||
headerRawBytesGzip,
|
||||
headerBase64StdPaddingGzip,
|
||||
headerBase64URLGzip:
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown container header")
|
||||
}
|
||||
|
||||
switch h {
|
||||
case headerBase64StdPadding, headerBase64StdPaddingGzip:
|
||||
r = base64.NewDecoder(base64.StdEncoding, r)
|
||||
case headerBase64URL, headerBase64URLGzip:
|
||||
r = base64.NewDecoder(base64.RawURLEncoding, r)
|
||||
}
|
||||
|
||||
switch h {
|
||||
case headerRawBytesGzip, headerBase64StdPaddingGzip, headerBase64URLGzip:
|
||||
gzipReader, err := gzip.NewReader(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r = gzipReader
|
||||
}
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
var _ io.WriteCloser = &payloadWriter{}
|
||||
|
||||
// payloadWriter is tasked with two things:
|
||||
// - prepend the header byte
|
||||
// - call Close() on all the underlying io.Writer
|
||||
type payloadWriter struct {
|
||||
rawWriter io.Writer
|
||||
writer io.Writer
|
||||
header header
|
||||
headerWrote bool
|
||||
closers []io.Closer
|
||||
}
|
||||
|
||||
func (w *payloadWriter) Write(p []byte) (n int, err error) {
|
||||
if !w.headerWrote {
|
||||
_, err := w.rawWriter.Write([]byte{byte(w.header)})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
w.headerWrote = true
|
||||
}
|
||||
return w.writer.Write(p)
|
||||
}
|
||||
|
||||
func (w *payloadWriter) Close() error {
|
||||
var errs error
|
||||
for _, closer := range w.closers {
|
||||
if err := closer.Close(); err != nil {
|
||||
errs = errors.Join(errs, err)
|
||||
}
|
||||
}
|
||||
return errs
|
||||
}
|
||||
Reference in New Issue
Block a user