Merge branch 'otp' into 'master'
otp task See merge request slon/shad-go-private!12
This commit is contained in:
commit
0fb5390d43
3 changed files with 183 additions and 0 deletions
25
otp/README.md
Normal file
25
otp/README.md
Normal file
|
@ -0,0 +1,25 @@
|
|||
# otp
|
||||
|
||||
Напишите код, который реализует схему шифрования [stream cipher](https://en.wikipedia.org/wiki/Stream_cipher).
|
||||
|
||||
Потоковый шифр обрабатывает поток по одному байту за раз. Каждый байт входного потока xor-ится с байтом из prng и записывается в выходной поток.
|
||||
|
||||
Вам нужно реализовать две версии api.
|
||||
|
||||
```golang
|
||||
func NewReader(r io.Reader, prng io.Reader) io.Reader
|
||||
func NewWriter(w io.Writer, prng io.Reader) io.Writer
|
||||
```
|
||||
|
||||
* `NewReader` принимает входной поток `r` и генератор случайных чисел `prng`. `NewReader` возвращает
|
||||
`io.Reader`, который читает поток из `r` и расшифровывает его с помощью `prng`.
|
||||
* `NewWriter` принимает выходной поток `w` и генератор случайных чисел `prng`. `NewWriter` возвращает
|
||||
`io.Writer`, который шифрует поток с помощью `prng` и пишет его в `w`.
|
||||
|
||||
Вы можете считать, что prng никогда не может вернуть ошибку.
|
||||
|
||||
## Замечания
|
||||
- Прочитайте контракт [io.Reader](https://golang.org/pkg/io/#Reader) и [io.Writer](https://golang.org/pkg/io/#Writer) в документации.
|
||||
- То что шифр работает с одним байтом, не значит что нужно передавать в Read() слайс размера 1.
|
||||
- Подумайте, почему потоковый шифр в стандартной библиотеке имеет интерфейс [cipher.Stream](https://golang.org/pkg/crypto/cipher/#Stream), а не `io.Reader` как у нас.
|
||||
- Для отладки вы можете использовать `iotest.NewReadLogger` и `iotest.NewWriteLogger` из пакета [iotest](https://golang.org/pkg/testing/iotest/).
|
15
otp/cipher.go
Normal file
15
otp/cipher.go
Normal file
|
@ -0,0 +1,15 @@
|
|||
// +build !solution
|
||||
|
||||
package otp
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
func NewReader(r io.Reader, prng io.Reader) io.Reader {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func NewWriter(w io.Writer, prng io.Reader) io.Writer {
|
||||
panic("implement me")
|
||||
}
|
143
otp/cipher_test.go
Normal file
143
otp/cipher_test.go
Normal file
|
@ -0,0 +1,143 @@
|
|||
package otp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"testing"
|
||||
"testing/iotest"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type zeroSometimesReader struct {
|
||||
io.Reader
|
||||
i int
|
||||
}
|
||||
|
||||
func (r *zeroSometimesReader) Read(p []byte) (n int, err error) {
|
||||
r.i++
|
||||
if r.i&1 == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
return r.Reader.Read(p)
|
||||
}
|
||||
|
||||
const testSize = 1234
|
||||
|
||||
var (
|
||||
plaintext = make([]byte, testSize)
|
||||
randomBytes = make([]byte, testSize)
|
||||
ciphertext = make([]byte, testSize)
|
||||
|
||||
plaintextBackup = make([]byte, testSize)
|
||||
)
|
||||
|
||||
func init() {
|
||||
rand.Read(plaintext)
|
||||
rand.Read(randomBytes)
|
||||
|
||||
copy(plaintextBackup, plaintext)
|
||||
|
||||
for i := range plaintext {
|
||||
ciphertext[i] = plaintext[i] ^ randomBytes[i]
|
||||
}
|
||||
}
|
||||
|
||||
func TestReader(t *testing.T) {
|
||||
for _, testCase := range []struct {
|
||||
name string
|
||||
r io.Reader
|
||||
prng io.Reader
|
||||
|
||||
err error
|
||||
result []byte
|
||||
}{
|
||||
{
|
||||
name: "simple",
|
||||
r: bytes.NewBuffer(plaintext),
|
||||
prng: bytes.NewBuffer(randomBytes),
|
||||
result: ciphertext,
|
||||
},
|
||||
{
|
||||
name: "eof",
|
||||
r: iotest.DataErrReader(bytes.NewBuffer(plaintext)),
|
||||
prng: bytes.NewBuffer(randomBytes),
|
||||
result: ciphertext,
|
||||
},
|
||||
{
|
||||
name: "halfreader",
|
||||
r: iotest.HalfReader(bytes.NewBuffer(plaintext)),
|
||||
prng: bytes.NewBuffer(randomBytes),
|
||||
result: ciphertext,
|
||||
},
|
||||
{
|
||||
name: "zerosometimes",
|
||||
r: &zeroSometimesReader{Reader: iotest.HalfReader(iotest.HalfReader(bytes.NewBuffer(plaintext)))},
|
||||
prng: bytes.NewBuffer(randomBytes),
|
||||
result: ciphertext,
|
||||
},
|
||||
{
|
||||
name: "timeout",
|
||||
r: iotest.TimeoutReader(bytes.NewBuffer(plaintext)),
|
||||
prng: bytes.NewBuffer(randomBytes),
|
||||
result: ciphertext[:bytes.MinRead],
|
||||
err: iotest.ErrTimeout,
|
||||
},
|
||||
} {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
r := NewReader(testCase.r, testCase.prng)
|
||||
|
||||
buf, err := ioutil.ReadAll(r)
|
||||
require.Equal(t, testCase.err, err)
|
||||
require.Equal(t, testCase.result, buf)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriterSimple(t *testing.T) {
|
||||
out := &bytes.Buffer{}
|
||||
prng := bytes.NewBuffer(randomBytes)
|
||||
|
||||
w := NewWriter(out, prng)
|
||||
n, err := w.Write(plaintext)
|
||||
|
||||
require.Equalf(t, plaintextBackup, plaintext, "Write must not modify the slice data, even temporarily.")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, len(plaintext), n)
|
||||
require.Equal(t, out.Bytes(), ciphertext)
|
||||
}
|
||||
|
||||
type errWriter struct {
|
||||
buf bytes.Buffer
|
||||
n int
|
||||
}
|
||||
|
||||
func (w *errWriter) Write(p []byte) (n int, err error) {
|
||||
if len(p) > w.n {
|
||||
p = p[:w.n]
|
||||
}
|
||||
|
||||
n = len(p)
|
||||
if w.n == 0 {
|
||||
err = iotest.ErrTimeout
|
||||
}
|
||||
|
||||
w.buf.Write(p)
|
||||
return
|
||||
}
|
||||
|
||||
func TestWriterError(t *testing.T) {
|
||||
out := &errWriter{n: 512}
|
||||
prng := bytes.NewBuffer(randomBytes)
|
||||
|
||||
w := NewWriter(out, prng)
|
||||
n, err := w.Write(plaintext)
|
||||
|
||||
require.Equalf(t, plaintextBackup, plaintext, "Write must not modify the slice data, even temporarily.")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 512, n)
|
||||
require.Equal(t, out.buf.Bytes(), ciphertext[:512])
|
||||
}
|
Loading…
Reference in a new issue