119 lines
2.7 KiB
Go
119 lines
2.7 KiB
Go
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 payloadDecoder(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
|
|
}
|