aboutsummaryrefslogtreecommitdiff
path: root/chunk.go
blob: df25f1cd1f84714dee17ec4d222b8fbdd5c147c7 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
package rabaead

import (
	"bytes"
	"crypto/cipher"
	"encoding/binary"
	"errors"
	"io"

	"snix.ir/rabbitio"
)

const cmrs = 0x02 // 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
	}

	if chnk > int(^uint16(0)) || chnk <= 0 {
		return nil, errors.New("rabaead: bad chunk size")
	}

	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
	}

	if chnk > int(^uint16(0)) || chnk <= 0 {
		return nil, errors.New("rabaead: bad chunk size")
	}

	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 2+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 := cmrs + w.csize + w.aead.Overhead()
	chnk := make([]byte, size)
	var n int
	var err error

	if len(w.buff) > 0 {
		s := copy(chnk[cmrs:len(chnk)-w.aead.Overhead()], w.buff)
		w.buff = w.buff[s:]
		copy(chnk[0:cmrs], uint16Little(uint16(s)))

		w.aead.Seal(chnk[:0], w.nonce, chnk[:cmrs+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 2+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 := cmrs + r.csize + r.aead.Overhead()
	chnk := make([]byte, size)
	chLE := uint16Little(uint16(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:cmrs], chLE) {
			n += r.csize
			r.buff = append(r.buff, chnk[cmrs:cmrs+r.csize]...)
		} else {
			f := binary.LittleEndian.Uint16(chnk[0:cmrs])
			n += int(f)
			r.buff = append(r.buff, chnk[cmrs:cmrs+f]...)
		}
	}

	return n, err
}

func uint16Little(n uint16) []byte {
	b := make([]byte, cmrs)
	binary.LittleEndian.PutUint16(b, n)
	return b
}

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