aboutsummaryrefslogtreecommitdiff
path: root/chunk.go
diff options
context:
space:
mode:
authorroot <sina@snix.ir>2022-07-24 04:38:03 +0000
committerroot <sina@snix.ir>2022-07-24 04:38:03 +0000
commit1e8c539dba28f730ba01458bc4c8475a1cfc642f (patch)
tree5bc0d40b3f1a4be3a3fcbf524e4fa235f8691a7c /chunk.go
git add files
Diffstat (limited to 'chunk.go')
-rw-r--r--chunk.go214
1 files changed, 214 insertions, 0 deletions
diff --git a/chunk.go b/chunk.go
new file mode 100644
index 0000000..3b56d62
--- /dev/null
+++ b/chunk.go
@@ -0,0 +1,214 @@
+package rabaead
+
+import (
+ "bytes"
+ "crypto/cipher"
+ "encoding/binary"
+ "io"
+
+ "snix.ir/rabbitio"
+)
+
+const cmrk = 0x08 // chunk size indicator,
+// without this reader cannot calculate actual size of plaintext
+
+// additional data func, return value is used as AD in Seal and Open
+// nil AdFunc is harmless and equal to func()[]byte{return nil}
+type AdditionalFunc func() []byte
+
+type chunkReader struct {
+ aead cipher.AEAD
+ csize int
+ rader io.Reader
+ buff []byte
+ nonce []byte
+ adexe AdditionalFunc
+}
+
+type chunkWriter struct {
+ aead cipher.AEAD
+ csize int
+ writer io.Writer
+ buff []byte
+ nonce []byte
+ adexe AdditionalFunc
+}
+
+// NewChunkReader returns a chunkReader data type, this reader reads and open() aead
+// ciphertext, each chunk has its own tag and cmrk value.
+// this reader has a chunk size in-memory buffer, large chunk size can make application to runs
+// out of memory, thus is most suitable for sliced data, like network data transmit and so..
+func NewChunkReader(r io.Reader, chnk int, a cipher.AEAD, nonce []byte, f AdditionalFunc) (*chunkReader, error) {
+
+ if len(nonce) != rabbitio.IVXLen && len(nonce) != 0 {
+ return nil, rabbitio.ErrInvalidIVX
+ }
+
+ s := &chunkReader{
+ aead: a,
+ buff: []byte{},
+ nonce: make([]byte, len(nonce)),
+ csize: chnk,
+ rader: r,
+ adexe: f,
+ }
+
+ if s.adexe == nil {
+ s.adexe = func() []byte { return nil }
+ }
+ copy(s.nonce, nonce)
+ return s, nil
+}
+
+// NewChunkWriter returns a chunkWriter data type, this writer sale() and write aead
+// plaintext, each chunk has its own tag and cmrk value.
+// this writer has a chunk size in-memory buffer, large chunk size can make application to
+// runs out of memory, thus is most suitable for sliced data, like network data transmit and so..
+func NewChunkWriter(w io.Writer, chnk int, a cipher.AEAD, nonce []byte, f AdditionalFunc) (*chunkWriter, error) {
+
+ if len(nonce) != rabbitio.IVXLen && len(nonce) != 0 {
+ return nil, rabbitio.ErrInvalidIVX
+ }
+ s := &chunkWriter{
+ aead: a,
+ buff: []byte{},
+ nonce: make([]byte, len(nonce)),
+ csize: chnk,
+ writer: w,
+ adexe: f,
+ }
+
+ if s.adexe == nil {
+ s.adexe = func() []byte { return nil }
+ }
+
+ copy(s.nonce, nonce)
+ return s, nil
+}
+
+// Close method, if there is any
+func (w *chunkWriter) Close() error {
+ if c, ok := w.writer.(io.Closer); ok {
+ return c.Close()
+ }
+ return nil
+}
+
+// Write writes plaintext chunk into the sale() and underlying writer
+// write would not report overhead data (chunk size marker and poly1305 tag) in
+// written return value. for each chunk there is 8+16 byte overhead data.
+// AdFunc will be triggered for each chunk of data
+func (w *chunkWriter) Write(b []byte) (n int, err error) {
+ w.buff = b
+ for len(w.buff) > 0 {
+ s, err := w.write()
+ if err != nil {
+ return n, err
+ }
+ n += s
+ }
+ return
+}
+
+func (w *chunkWriter) write() (int, error) {
+ size := cmrk + w.csize + w.aead.Overhead()
+ chnk := make([]byte, size)
+ var n int
+ var err error
+
+ if len(w.buff) > 0 {
+ s := copy(chnk[cmrk:len(chnk)-w.aead.Overhead()], w.buff)
+ w.buff = w.buff[s:]
+ copy(chnk[0:cmrk], uint64Little(uint64(s)))
+
+ w.aead.Seal(chnk[:0], w.nonce, chnk[:cmrk+w.csize], w.adexe())
+ _, err = w.writer.Write(chnk)
+ if err != nil {
+ return n, err
+ }
+ n += s
+ }
+
+ return n, err
+}
+
+// Read reads and open() ciphertext chunk from underlying reader
+// read would not report overhead data (chunk size marker and poly1305 tag) in its
+// return value. if the read data from underlying reader is corrupted, ErrAuthMsg
+// error will be returned. for each chunk there is 8+16 byte overhead data.
+// AdFunc will be triggered for each chunk of data
+
+func (r *chunkReader) Read(b []byte) (int, error) {
+ if len(b) <= r.csize {
+ return r.readTo(b)
+ }
+ n := 0
+ for {
+ if n+r.csize > len(b) {
+ sr, err := r.readTo(b[n:])
+ n += sr
+ if err != nil {
+ return n, err
+ }
+ break
+ }
+ sr, err := r.readTo(b[n : n+r.csize])
+ n += sr
+ if err != nil {
+ return n, err
+ }
+ }
+
+ return n, nil
+}
+
+func (r *chunkReader) readTo(b []byte) (int, error) {
+ var n int
+ if len(r.buff) > 0 {
+ n = copy(b, r.buff)
+ r.buff = r.buff[n:]
+ return n, nil
+ }
+
+ sr, err := r.read()
+ n = copy(b, r.buff[:sr])
+ r.buff = r.buff[n:]
+ return n, err
+}
+
+func (r *chunkReader) read() (int, error) {
+
+ var n int
+ size := cmrk + r.csize + r.aead.Overhead()
+ chnk := make([]byte, size)
+ chLE := uint64Little(uint64(r.csize))
+
+ si, err := io.ReadFull(r.rader, chnk)
+ if err != nil {
+ return n, err
+ }
+
+ if si > 0 {
+ _, err = r.aead.Open(chnk[:0], r.nonce, chnk, r.adexe())
+ if err != nil {
+ return n, err
+ }
+
+ if bytes.Equal(chnk[0:cmrk], chLE) {
+ n += r.csize
+ r.buff = append(r.buff, chnk[cmrk:cmrk+r.csize]...)
+ } else {
+ f := binary.LittleEndian.Uint64(chnk[0:cmrk])
+ n += int(f)
+ r.buff = append(r.buff, chnk[cmrk:cmrk+f]...)
+ }
+ }
+
+ return n, err
+}
+
+func uint64Little(n uint64) []byte {
+ b := make([]byte, cmrk)
+ binary.LittleEndian.PutUint64(b, n)
+ return b
+}

Snix LLC Git Repository Holder Copyright(C) 2022 All Rights Reserved Email To Snix.IR