added fs encryption overlay
This commit is contained in:
parent
2039086696
commit
fd1e8118a4
28 changed files with 3994 additions and 30 deletions
92
pkg/provider/crypto/crypto.go
Normal file
92
pkg/provider/crypto/crypto.go
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
package crypto
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/sha256"
|
||||
"io"
|
||||
|
||||
"golang.org/x/crypto/chacha20poly1305"
|
||||
"golang.org/x/crypto/hkdf"
|
||||
)
|
||||
|
||||
const (
|
||||
KDFNonceSize = 16
|
||||
NonceSize = chacha20poly1305.NonceSize
|
||||
Overhead = chacha20poly1305.Overhead
|
||||
ChunkSize = 64 * 1024
|
||||
FullSize = ChunkSize + Overhead
|
||||
)
|
||||
|
||||
const (
|
||||
infoCBC = "AES-CBC filename encryption key"
|
||||
infoCBCIV = "AES-CBC filename encryption initialization vector"
|
||||
infoChaPloy = "ChaCha20-Poly1305 file content encryption"
|
||||
)
|
||||
|
||||
func cbcKey(key []byte) []byte {
|
||||
return deriveKey(key, nil, infoCBC, 32)
|
||||
}
|
||||
|
||||
func cbcIV(key []byte) []byte {
|
||||
return deriveKey(key, nil, infoCBCIV, 16)
|
||||
}
|
||||
|
||||
func streamKey(key, nonce []byte) []byte {
|
||||
return deriveKey(key, nonce, infoChaPloy, chacha20poly1305.KeySize)
|
||||
}
|
||||
|
||||
func deriveKey(key, nonce []byte, info string, length int) []byte {
|
||||
h := hkdf.New(sha256.New, key, nonce, []byte(info))
|
||||
k := make([]byte, length)
|
||||
if _, err := io.ReadFull(h, k); err != nil {
|
||||
panic("crypto: internal error: failed to read from HKDF: " + err.Error())
|
||||
}
|
||||
return k
|
||||
}
|
||||
|
||||
type aesCbc struct {
|
||||
key []byte
|
||||
iv []byte
|
||||
}
|
||||
|
||||
func newAesCbc(key []byte) (*aesCbc, error) {
|
||||
cbc := &aesCbc{
|
||||
key: cbcKey(key),
|
||||
iv: cbcIV(key),
|
||||
}
|
||||
_, err := aes.NewCipher(cbc.key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cbc, nil
|
||||
}
|
||||
|
||||
func (cbc *aesCbc) encrypt(p []byte) (c []byte) {
|
||||
block, _ := aes.NewCipher(cbc.key)
|
||||
mode := cipher.NewCBCEncrypter(block, cbc.iv)
|
||||
padded := padPKCS5(p, block.BlockSize())
|
||||
c = make([]byte, len(padded))
|
||||
mode.CryptBlocks(c, padded)
|
||||
return
|
||||
}
|
||||
|
||||
func (cbc *aesCbc) decrypt(c []byte) []byte {
|
||||
block, _ := aes.NewCipher(cbc.key)
|
||||
mode := cipher.NewCBCDecrypter(block, cbc.iv)
|
||||
p := make([]byte, len(c))
|
||||
mode.CryptBlocks(p, c)
|
||||
return trimPKCS5(p)
|
||||
}
|
||||
|
||||
func padPKCS5(b []byte, bs int) []byte {
|
||||
n := bs - len(b)%bs
|
||||
pad := bytes.Repeat([]byte{byte(n)}, n)
|
||||
return append(b, pad...)
|
||||
}
|
||||
|
||||
func trimPKCS5(b []byte) []byte {
|
||||
pad := b[len(b)-1]
|
||||
return b[:len(b)-int(pad)]
|
||||
}
|
||||
186
pkg/provider/crypto/file.go
Normal file
186
pkg/provider/crypto/file.go
Normal file
|
|
@ -0,0 +1,186 @@
|
|||
package crypto
|
||||
|
||||
import (
|
||||
"cachefs/pkg/provider"
|
||||
"io"
|
||||
"io/fs"
|
||||
)
|
||||
|
||||
var _ provider.File = (*file)(nil)
|
||||
|
||||
type file struct {
|
||||
provider.File
|
||||
fs *FS
|
||||
r *reader
|
||||
w *writer
|
||||
nonce [NonceSize]byte
|
||||
offset int64
|
||||
name string
|
||||
}
|
||||
|
||||
func newFile(key []byte, name string, fs *FS, f provider.File) (ef provider.File, err error) {
|
||||
nonce, err := initNonce(f)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
skey := streamKey(key, nonce)
|
||||
w, err := newWriter(skey, f)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
r, err := newReader(skey, f)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return &file{
|
||||
File: f,
|
||||
fs: fs,
|
||||
w: w,
|
||||
r: r,
|
||||
name: name,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (f *file) Stat() (fs.FileInfo, error) {
|
||||
fi, err := f.File.Stat()
|
||||
return stat{FileInfo: fi, name: f.name}, err
|
||||
}
|
||||
|
||||
func (f *file) Name() string {
|
||||
return f.name
|
||||
}
|
||||
|
||||
func (file) Fd() uintptr {
|
||||
return 0
|
||||
}
|
||||
|
||||
type stat struct {
|
||||
fs.FileInfo
|
||||
name string
|
||||
}
|
||||
|
||||
func (s stat) Size() int64 {
|
||||
return RealSize(s.FileInfo.Size())
|
||||
}
|
||||
|
||||
func (s stat) Name() string {
|
||||
return s.name
|
||||
}
|
||||
|
||||
func (f *file) Close() error {
|
||||
e1 := f.w.Close()
|
||||
e2 := f.File.Close()
|
||||
if e1 != nil {
|
||||
return e1
|
||||
}
|
||||
return e2
|
||||
}
|
||||
|
||||
func (f *file) Seek(offset int64, whence int) (n int64, err error) {
|
||||
var cn, off, roff int64
|
||||
switch whence {
|
||||
case io.SeekStart:
|
||||
cn, off, roff = align(offset)
|
||||
f.offset = KDFNonceSize + off
|
||||
n, err = f.File.Seek(f.offset, whence)
|
||||
case io.SeekCurrent:
|
||||
if f.offset == 0 {
|
||||
f.offset = KDFNonceSize
|
||||
}
|
||||
cn, off, roff = align(f.offset + f.r.off + offset)
|
||||
f.offset = off
|
||||
_, err = f.File.Seek(f.offset, io.SeekStart)
|
||||
n = offset
|
||||
}
|
||||
f.nonce = [NonceSize]byte{}
|
||||
inc(&f.nonce, cn)
|
||||
f.r.nonce = f.nonce
|
||||
f.r.off = roff
|
||||
f.r.err = nil
|
||||
f.w.nonce = f.nonce
|
||||
f.r.unread = f.r.unread[:0]
|
||||
return
|
||||
}
|
||||
|
||||
func (f *file) Read(p []byte) (n int, err error) {
|
||||
n, err = f.r.Read(p)
|
||||
return
|
||||
}
|
||||
|
||||
func (f *file) Write(p []byte) (n int, err error) {
|
||||
n, err = f.w.Write(p)
|
||||
return
|
||||
}
|
||||
|
||||
func (f *file) ReadAt(p []byte, pos int64) (n int, err error) {
|
||||
_, _, roff := align(pos)
|
||||
//cn, off, roff := align(pos)
|
||||
f.Seek(pos, io.SeekStart)
|
||||
//fmt.Println("readAt:", cn, off+KDFNonceSize, roff, pos)
|
||||
_, err = f.r.readChunk()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
n = copy(p, f.r.unread[roff:])
|
||||
return
|
||||
}
|
||||
|
||||
func (f *file) WriteAt(data []byte, pos int64) (n int, err error) {
|
||||
cn, off, woff := align(pos)
|
||||
//fmt.Println("writeAt:", cn, off+KDFNonceSize, woff, pos)
|
||||
c, ok := f.w.cm[cn]
|
||||
if !ok {
|
||||
c = &chunkWriter{offset: off + KDFNonceSize, w: f.w, cn: cn}
|
||||
inc(&c.nonce, cn)
|
||||
f.w.cm[cn] = c
|
||||
if f.w.last == nil {
|
||||
f.w.last = c
|
||||
} else if cn > f.w.last.cn {
|
||||
f.w.last = c
|
||||
}
|
||||
}
|
||||
n, err = c.writeAt(data, woff)
|
||||
if c.full() {
|
||||
_, err = c.flush(notLastChunk)
|
||||
//fmt.Println("flush:", cn)
|
||||
delete(f.w.cm, cn)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (f *file) Readdir(n int) ([]fs.FileInfo, error) {
|
||||
fis, err := f.File.Readdir(n)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fio := make([]fs.FileInfo, len(fis))
|
||||
for i, fi := range fis {
|
||||
p, err := f.fs.decPath(fi.Name())
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
fio[i] = dir{FileInfo: fi, name: p}
|
||||
}
|
||||
return fio, nil
|
||||
}
|
||||
|
||||
func (f *file) ReadDir(n int) ([]fs.DirEntry, error) {
|
||||
fis, err := f.Readdir(n)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fio := make([]fs.DirEntry, len(fis))
|
||||
for i, fi := range fis {
|
||||
fio[i] = fs.FileInfoToDirEntry(fi)
|
||||
}
|
||||
return fio, nil
|
||||
}
|
||||
|
||||
type dir struct {
|
||||
fs.FileInfo
|
||||
name string
|
||||
}
|
||||
|
||||
func (d dir) Name() string {
|
||||
return d.name
|
||||
}
|
||||
129
pkg/provider/crypto/fs.go
Normal file
129
pkg/provider/crypto/fs.go
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
package crypto
|
||||
|
||||
import (
|
||||
"cachefs/pkg/provider"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var _ provider.FS = (*FS)(nil)
|
||||
|
||||
type FS struct {
|
||||
fs provider.FS
|
||||
cbc *aesCbc
|
||||
key []byte
|
||||
}
|
||||
|
||||
func NewFS(fs provider.FS, key []byte) (*FS, error) {
|
||||
if len(key) != 32 {
|
||||
return nil, errors.New("encryption key length must be 32 bytes")
|
||||
}
|
||||
cbc, err := newAesCbc(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &FS{
|
||||
fs: fs,
|
||||
cbc: cbc,
|
||||
key: key,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (fs *FS) encPath(path string) (n string, err error) {
|
||||
parts := strings.Split(path, "/")
|
||||
cparts := make([]string, len(parts))
|
||||
for i, p := range parts {
|
||||
if p == "" {
|
||||
continue
|
||||
}
|
||||
cparts[i] = base64.RawURLEncoding.EncodeToString(
|
||||
fs.cbc.encrypt([]byte(p)),
|
||||
)
|
||||
}
|
||||
n = filepath.Join(cparts...)
|
||||
return
|
||||
}
|
||||
|
||||
func (fs *FS) decPath(path string) (n string, err error) {
|
||||
parts := strings.Split(path, "/")
|
||||
pparts := make([]string, len(parts))
|
||||
for i, p := range parts {
|
||||
pparts[i] = base64.RawURLEncoding.EncodeToString(
|
||||
fs.cbc.decrypt([]byte(p)),
|
||||
)
|
||||
}
|
||||
n = filepath.Join(pparts...)
|
||||
return
|
||||
}
|
||||
|
||||
func (fs *FS) name(p string) string {
|
||||
return filepath.Join(fs.Root(), p)
|
||||
}
|
||||
|
||||
func (fs *FS) Stat(p string) (fs.FileInfo, error) {
|
||||
c, err := fs.encPath(p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fi, err := fs.fs.Stat(c)
|
||||
return stat{FileInfo: fi, name: fs.name(p)}, err
|
||||
}
|
||||
|
||||
func (fs *FS) Remove(p string) error {
|
||||
c, err := fs.encPath(p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return fs.fs.Remove(c)
|
||||
}
|
||||
|
||||
func (fs *FS) Open(p string) (provider.File, error) {
|
||||
c, err := fs.encPath(p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f, err := fs.fs.Open(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newFile(fs.key, p, fs, f)
|
||||
}
|
||||
|
||||
func (fs *FS) OpenFile(p string, flags int, mode os.FileMode) (provider.File, error) {
|
||||
c, err := fs.encPath(p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f, err := fs.fs.OpenFile(c, flags, mode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newFile(fs.key, p, fs, f)
|
||||
}
|
||||
|
||||
func (fs *FS) MkdirAll(p string, mode os.FileMode) error {
|
||||
c, err := fs.encPath(p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return fs.fs.MkdirAll(c, mode)
|
||||
}
|
||||
|
||||
func (fs *FS) Root() string {
|
||||
return fs.fs.Root()
|
||||
}
|
||||
|
||||
func (fs *FS) Fstat(_ uintptr) (int64, error) {
|
||||
return 0, provider.ErrFstat
|
||||
}
|
||||
|
||||
func (fs *FS) Close() error {
|
||||
fs.key = nil
|
||||
runtime.GC()
|
||||
return nil
|
||||
}
|
||||
78
pkg/provider/crypto/helper.go
Normal file
78
pkg/provider/crypto/helper.go
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
package crypto
|
||||
|
||||
import (
|
||||
"cachefs/pkg/provider"
|
||||
"crypto/rand"
|
||||
"io"
|
||||
"math"
|
||||
)
|
||||
|
||||
func isZero(nonce []byte) bool {
|
||||
for i := 0; i < len(nonce); i++ {
|
||||
if nonce[i] != 0 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func initNonce(f provider.File) (nonce []byte, err error) {
|
||||
nonce = make([]byte, KDFNonceSize)
|
||||
n, err := io.ReadFull(f, nonce)
|
||||
if err != nil || n != len(nonce) || isZero(nonce) {
|
||||
_, err = io.ReadFull(rand.Reader, nonce)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
_, err = f.Seek(0, io.SeekStart)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
_, err = f.Write(nonce)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func incNonce(nonce *[NonceSize]byte) {
|
||||
for i := len(nonce) - 2; i >= 0; i-- {
|
||||
nonce[i]++
|
||||
if nonce[i] != 0 {
|
||||
break
|
||||
} else if i == 0 {
|
||||
// The counter is 88 bits, this is unreachable.
|
||||
panic("stream: chunk counter wrapped around")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Size(s int64) int64 {
|
||||
return KDFNonceSize + s + (int64(math.Ceil(float64(s)/ChunkSize)) * Overhead)
|
||||
}
|
||||
|
||||
func RealSize(s int64) int64 {
|
||||
s -= KDFNonceSize
|
||||
return s - (((s / FullSize) + 1) * Overhead)
|
||||
}
|
||||
|
||||
func chunkNum(o int64) int64 {
|
||||
return int64(math.Ceil(float64(o) / ChunkSize))
|
||||
}
|
||||
|
||||
func align(o int64) (cn, off, roff int64) {
|
||||
cn = chunkNum(o)
|
||||
off = cn * FullSize
|
||||
roff = o - (cn * ChunkSize)
|
||||
if roff < 0 {
|
||||
cn--
|
||||
off = cn * FullSize
|
||||
roff = o - (cn * ChunkSize)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func inc(nonce *[NonceSize]byte, n int64) {
|
||||
for i := int64(0); i < n; i++ {
|
||||
incNonce(nonce)
|
||||
}
|
||||
}
|
||||
107
pkg/provider/crypto/reader.go
Normal file
107
pkg/provider/crypto/reader.go
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
package crypto
|
||||
|
||||
import (
|
||||
"crypto/cipher"
|
||||
"errors"
|
||||
"io"
|
||||
|
||||
"golang.org/x/crypto/chacha20poly1305"
|
||||
)
|
||||
|
||||
type cryptoReader interface {
|
||||
io.Reader
|
||||
io.ReaderAt
|
||||
}
|
||||
|
||||
type reader struct {
|
||||
a cipher.AEAD
|
||||
src cryptoReader
|
||||
|
||||
unread []byte // decrypted but unread data, backed by buf
|
||||
buf [FullSize]byte
|
||||
off int64
|
||||
|
||||
err error
|
||||
nonce [NonceSize]byte
|
||||
}
|
||||
|
||||
func newReader(key []byte, src cryptoReader) (*reader, error) {
|
||||
aead, err := chacha20poly1305.New(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &reader{
|
||||
a: aead,
|
||||
src: src,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *reader) Read(p []byte) (int, error) {
|
||||
if len(r.unread) > 0 {
|
||||
n := copy(p, r.unread)
|
||||
r.unread = r.unread[n:]
|
||||
return n, nil
|
||||
}
|
||||
if r.err != nil {
|
||||
return 0, r.err
|
||||
}
|
||||
if len(p) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
last, err := r.readChunk()
|
||||
if err != nil {
|
||||
r.err = err
|
||||
return 0, err
|
||||
}
|
||||
if r.off > 0 && len(r.unread) >= int(r.off) {
|
||||
r.unread = r.unread[r.off:]
|
||||
r.off = 0
|
||||
}
|
||||
|
||||
n := copy(p, r.unread)
|
||||
r.unread = r.unread[n:]
|
||||
|
||||
if last {
|
||||
r.err = io.EOF
|
||||
}
|
||||
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (r *reader) readChunk() (last bool, err error) {
|
||||
if len(r.unread) != 0 {
|
||||
panic("stream: internal error: readChunk called with dirty buffer")
|
||||
}
|
||||
|
||||
in := r.buf[:]
|
||||
n, err := io.ReadFull(r.src, in)
|
||||
switch {
|
||||
case err == io.EOF:
|
||||
// A message can't end without a marked chunk. This message is truncated.
|
||||
return false, io.ErrUnexpectedEOF
|
||||
case err == io.ErrUnexpectedEOF:
|
||||
// The last chunk can be short.
|
||||
in = in[:n]
|
||||
last = true
|
||||
setLastChunkFlag(&r.nonce)
|
||||
case err != nil:
|
||||
return false, err
|
||||
}
|
||||
|
||||
outBuf := make([]byte, 0, ChunkSize)
|
||||
out, err := r.a.Open(outBuf, r.nonce[:], in, nil)
|
||||
if err != nil && !last {
|
||||
// Check if this was a full-length final chunk.
|
||||
last = true
|
||||
setLastChunkFlag(&r.nonce)
|
||||
out, err = r.a.Open(outBuf, r.nonce[:], in, nil)
|
||||
}
|
||||
if err != nil {
|
||||
return false, errors.New("failed to decrypt and authenticate payload chunk")
|
||||
}
|
||||
|
||||
incNonce(&r.nonce)
|
||||
r.unread = r.buf[:copy(r.buf[:], out)]
|
||||
return last, nil
|
||||
}
|
||||
155
pkg/provider/crypto/writer.go
Normal file
155
pkg/provider/crypto/writer.go
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
package crypto
|
||||
|
||||
import (
|
||||
"cachefs/pkg/chunk"
|
||||
"crypto/cipher"
|
||||
"errors"
|
||||
"io"
|
||||
|
||||
"golang.org/x/crypto/chacha20poly1305"
|
||||
)
|
||||
|
||||
type cryptoWriter interface {
|
||||
io.Writer
|
||||
io.WriterAt
|
||||
}
|
||||
|
||||
const lastChunkFlag = 0x01
|
||||
|
||||
func setLastChunkFlag(nonce *[NonceSize]byte) {
|
||||
nonce[len(nonce)-1] = lastChunkFlag
|
||||
}
|
||||
|
||||
type writer struct {
|
||||
a cipher.AEAD
|
||||
dst cryptoWriter
|
||||
unwritten []byte // backed by buf
|
||||
buf [FullSize]byte
|
||||
nonce [NonceSize]byte
|
||||
err error
|
||||
cm map[int64]*chunkWriter
|
||||
last *chunkWriter
|
||||
flush bool
|
||||
}
|
||||
|
||||
func newWriter(key []byte, dst cryptoWriter) (*writer, error) {
|
||||
aead, err := chacha20poly1305.New(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
w := &writer{
|
||||
a: aead,
|
||||
dst: dst,
|
||||
cm: make(map[int64]*chunkWriter),
|
||||
}
|
||||
w.unwritten = w.buf[:0]
|
||||
return w, nil
|
||||
}
|
||||
|
||||
type chunkWriter struct {
|
||||
offset int64
|
||||
cn int64
|
||||
w *writer
|
||||
buf [ChunkSize]byte
|
||||
nonce [NonceSize]byte
|
||||
chunks chunk.Chunks
|
||||
}
|
||||
|
||||
func (c *chunkWriter) writeAt(p []byte, offset int64) (n int, err error) {
|
||||
n = copy(c.buf[offset:], p)
|
||||
c.chunks.Add(offset, n)
|
||||
return
|
||||
}
|
||||
|
||||
func (c *chunkWriter) full() bool {
|
||||
if len(c.chunks) == 1 {
|
||||
return c.chunks[0][1] == ChunkSize
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (c *chunkWriter) flush(last bool) (n int, err error) {
|
||||
wb := c.buf[:0]
|
||||
if last {
|
||||
setLastChunkFlag(&c.nonce)
|
||||
s := c.chunks.Size()
|
||||
wb = c.buf[:s]
|
||||
n = int(s)
|
||||
} else {
|
||||
wb = c.buf[:]
|
||||
n = len(c.buf)
|
||||
}
|
||||
buf := c.w.a.Seal(wb[:0], c.nonce[:], wb, nil)
|
||||
_, err = c.w.dst.WriteAt(buf, c.offset)
|
||||
return
|
||||
}
|
||||
|
||||
func (w *writer) Write(p []byte) (n int, err error) {
|
||||
// TODO: consider refactoring with a bytes.Buffer.
|
||||
if w.err != nil {
|
||||
return 0, w.err
|
||||
}
|
||||
if len(p) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
total := len(p)
|
||||
for len(p) > 0 {
|
||||
freeBuf := w.buf[len(w.unwritten):ChunkSize]
|
||||
n := copy(freeBuf, p)
|
||||
p = p[n:]
|
||||
w.unwritten = w.unwritten[:len(w.unwritten)+n]
|
||||
w.flush = true
|
||||
|
||||
if len(w.unwritten) == ChunkSize && len(p) > 0 {
|
||||
if err := w.flushChunk(notLastChunk); err != nil {
|
||||
w.err = err
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
}
|
||||
return total, nil
|
||||
}
|
||||
|
||||
func (w *writer) Close() error {
|
||||
if w.err != nil {
|
||||
return w.err
|
||||
}
|
||||
|
||||
if w.last != nil {
|
||||
_, w.err = w.last.flush(lastChunk)
|
||||
if w.err != nil {
|
||||
return w.err
|
||||
}
|
||||
}
|
||||
|
||||
if w.flush {
|
||||
w.err = w.flushChunk(lastChunk)
|
||||
if w.err != nil {
|
||||
return w.err
|
||||
}
|
||||
}
|
||||
|
||||
w.err = errors.New("stream.Writer is already closed")
|
||||
return nil
|
||||
}
|
||||
|
||||
const (
|
||||
lastChunk = true
|
||||
notLastChunk = false
|
||||
)
|
||||
|
||||
func (w *writer) flushChunk(last bool) error {
|
||||
if !last && len(w.unwritten) != ChunkSize {
|
||||
panic("stream: internal error: flush called with partial chunk")
|
||||
}
|
||||
|
||||
if last {
|
||||
setLastChunkFlag(&w.nonce)
|
||||
}
|
||||
buf := w.a.Seal(w.buf[:0], w.nonce[:], w.unwritten, nil)
|
||||
_, err := w.dst.Write(buf)
|
||||
w.unwritten = w.buf[:0]
|
||||
incNonce(&w.nonce)
|
||||
return err
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue