- package conn
-
- import (
- "bufio"
- "encoding/hex"
- "flag"
- "fmt"
- "io"
- "log"
- mrand "math/rand"
- "os"
- "path/filepath"
- "strconv"
- "strings"
- "sync"
- "testing"
-
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
-
- "github.com/tendermint/tendermint/crypto"
- "github.com/tendermint/tendermint/crypto/ed25519"
- "github.com/tendermint/tendermint/crypto/sr25519"
- "github.com/tendermint/tendermint/internal/libs/async"
- tmrand "github.com/tendermint/tendermint/libs/rand"
- )
-
- // Run go test -update from within this module
- // to update the golden test vector file
- var update = flag.Bool("update", false, "update .golden files")
-
- type kvstoreConn struct {
- *io.PipeReader
- *io.PipeWriter
- }
-
- func (drw kvstoreConn) Close() (err error) {
- err2 := drw.PipeWriter.CloseWithError(io.EOF)
- err1 := drw.PipeReader.Close()
- if err2 != nil {
- return err
- }
- return err1
- }
-
- type privKeyWithNilPubKey struct {
- orig crypto.PrivKey
- }
-
- func (pk privKeyWithNilPubKey) Bytes() []byte { return pk.orig.Bytes() }
- func (pk privKeyWithNilPubKey) Sign(msg []byte) ([]byte, error) { return pk.orig.Sign(msg) }
- func (pk privKeyWithNilPubKey) PubKey() crypto.PubKey { return nil }
- func (pk privKeyWithNilPubKey) Equals(pk2 crypto.PrivKey) bool { return pk.orig.Equals(pk2) }
- func (pk privKeyWithNilPubKey) Type() string { return "privKeyWithNilPubKey" }
- func (privKeyWithNilPubKey) TypeTag() string { return "test/privKeyWithNilPubKey" }
-
- func TestSecretConnectionHandshake(t *testing.T) {
- fooSecConn, barSecConn := makeSecretConnPair(t)
- if err := fooSecConn.Close(); err != nil {
- t.Error(err)
- }
- if err := barSecConn.Close(); err != nil {
- t.Error(err)
- }
- }
-
- func TestConcurrentWrite(t *testing.T) {
- fooSecConn, barSecConn := makeSecretConnPair(t)
- fooWriteText := tmrand.Str(dataMaxSize)
-
- // write from two routines.
- // should be safe from race according to net.Conn:
- // https://golang.org/pkg/net/#Conn
- n := 100
- wg := new(sync.WaitGroup)
- wg.Add(3)
- go writeLots(t, wg, fooSecConn, fooWriteText, n)
- go writeLots(t, wg, fooSecConn, fooWriteText, n)
-
- // Consume reads from bar's reader
- readLots(t, wg, barSecConn, n*2)
- wg.Wait()
-
- if err := fooSecConn.Close(); err != nil {
- t.Error(err)
- }
- }
-
- func TestConcurrentRead(t *testing.T) {
- fooSecConn, barSecConn := makeSecretConnPair(t)
- fooWriteText := tmrand.Str(dataMaxSize)
- n := 100
-
- // read from two routines.
- // should be safe from race according to net.Conn:
- // https://golang.org/pkg/net/#Conn
- wg := new(sync.WaitGroup)
- wg.Add(3)
- go readLots(t, wg, fooSecConn, n/2)
- go readLots(t, wg, fooSecConn, n/2)
-
- // write to bar
- writeLots(t, wg, barSecConn, fooWriteText, n)
- wg.Wait()
-
- if err := fooSecConn.Close(); err != nil {
- t.Error(err)
- }
- }
-
- func TestSecretConnectionReadWrite(t *testing.T) {
- fooConn, barConn := makeKVStoreConnPair()
- fooWrites, barWrites := []string{}, []string{}
- fooReads, barReads := []string{}, []string{}
-
- // Pre-generate the things to write (for foo & bar)
- for i := 0; i < 100; i++ {
- fooWrites = append(fooWrites, tmrand.Str((mrand.Int()%(dataMaxSize*5))+1))
- barWrites = append(barWrites, tmrand.Str((mrand.Int()%(dataMaxSize*5))+1))
- }
-
- // A helper that will run with (fooConn, fooWrites, fooReads) and vice versa
- genNodeRunner := func(id string, nodeConn kvstoreConn, nodeWrites []string, nodeReads *[]string) async.Task {
- return func(_ int) (interface{}, bool, error) {
- // Initiate cryptographic private key and secret connection trhough nodeConn.
- nodePrvKey := ed25519.GenPrivKey()
- nodeSecretConn, err := MakeSecretConnection(nodeConn, nodePrvKey)
- if err != nil {
- t.Errorf("failed to establish SecretConnection for node: %w", err)
- return nil, true, err
- }
- // In parallel, handle some reads and writes.
- var trs, ok = async.Parallel(
- func(_ int) (interface{}, bool, error) {
- // Node writes:
- for _, nodeWrite := range nodeWrites {
- n, err := nodeSecretConn.Write([]byte(nodeWrite))
- if err != nil {
- t.Errorf("failed to write to nodeSecretConn: %w", err)
- return nil, true, err
- }
- if n != len(nodeWrite) {
- err = fmt.Errorf("failed to write all bytes. Expected %v, wrote %v", len(nodeWrite), n)
- t.Error(err)
- return nil, true, err
- }
- }
- if err := nodeConn.PipeWriter.Close(); err != nil {
- t.Error(err)
- return nil, true, err
- }
- return nil, false, nil
- },
- func(_ int) (interface{}, bool, error) {
- // Node reads:
- readBuffer := make([]byte, dataMaxSize)
- for {
- n, err := nodeSecretConn.Read(readBuffer)
- if err == io.EOF {
- if err := nodeConn.PipeReader.Close(); err != nil {
- t.Error(err)
- return nil, true, err
- }
- return nil, false, nil
- } else if err != nil {
- t.Errorf("failed to read from nodeSecretConn: %w", err)
- return nil, true, err
- }
- *nodeReads = append(*nodeReads, string(readBuffer[:n]))
- }
- },
- )
- assert.True(t, ok, "Unexpected task abortion")
-
- // If error:
- if trs.FirstError() != nil {
- return nil, true, trs.FirstError()
- }
-
- // Otherwise:
- return nil, false, nil
- }
- }
-
- // Run foo & bar in parallel
- var trs, ok = async.Parallel(
- genNodeRunner("foo", fooConn, fooWrites, &fooReads),
- genNodeRunner("bar", barConn, barWrites, &barReads),
- )
- require.Nil(t, trs.FirstError())
- require.True(t, ok, "unexpected task abortion")
-
- // A helper to ensure that the writes and reads match.
- // Additionally, small writes (<= dataMaxSize) must be atomically read.
- compareWritesReads := func(writes []string, reads []string) {
- for {
- // Pop next write & corresponding reads
- var read, write = "", writes[0]
- var readCount = 0
- for _, readChunk := range reads {
- read += readChunk
- readCount++
- if len(write) <= len(read) {
- break
- }
- if len(write) <= dataMaxSize {
- break // atomicity of small writes
- }
- }
- // Compare
- if write != read {
- t.Errorf("expected to read %X, got %X", write, read)
- }
- // Iterate
- writes = writes[1:]
- reads = reads[readCount:]
- if len(writes) == 0 {
- break
- }
- }
- }
-
- compareWritesReads(fooWrites, barReads)
- compareWritesReads(barWrites, fooReads)
- }
-
- func TestDeriveSecretsAndChallengeGolden(t *testing.T) {
- goldenFilepath := filepath.Join("testdata", t.Name()+".golden")
- if *update {
- t.Logf("Updating golden test vector file %s", goldenFilepath)
- data := createGoldenTestVectors(t)
- require.NoError(t, os.WriteFile(goldenFilepath, []byte(data), 0644))
- }
- f, err := os.Open(goldenFilepath)
- if err != nil {
- log.Fatal(err)
- }
- t.Cleanup(closeAll(t, f))
- scanner := bufio.NewScanner(f)
- for scanner.Scan() {
- line := scanner.Text()
- params := strings.Split(line, ",")
- randSecretVector, err := hex.DecodeString(params[0])
- require.NoError(t, err)
- randSecret := new([32]byte)
- copy((*randSecret)[:], randSecretVector)
- locIsLeast, err := strconv.ParseBool(params[1])
- require.NoError(t, err)
- expectedRecvSecret, err := hex.DecodeString(params[2])
- require.NoError(t, err)
- expectedSendSecret, err := hex.DecodeString(params[3])
- require.NoError(t, err)
-
- recvSecret, sendSecret := deriveSecrets(randSecret, locIsLeast)
- require.Equal(t, expectedRecvSecret, (*recvSecret)[:], "Recv Secrets aren't equal")
- require.Equal(t, expectedSendSecret, (*sendSecret)[:], "Send Secrets aren't equal")
- }
- }
-
- func TestNilPubkey(t *testing.T) {
- var fooConn, barConn = makeKVStoreConnPair()
- t.Cleanup(closeAll(t, fooConn, barConn))
- var fooPrvKey = ed25519.GenPrivKey()
- var barPrvKey = privKeyWithNilPubKey{ed25519.GenPrivKey()}
-
- go MakeSecretConnection(fooConn, fooPrvKey) //nolint:errcheck // ignore for tests
-
- _, err := MakeSecretConnection(barConn, barPrvKey)
- require.Error(t, err)
- assert.Equal(t, "toproto: key type <nil> is not supported", err.Error())
- }
-
- func TestNonEd25519Pubkey(t *testing.T) {
- var fooConn, barConn = makeKVStoreConnPair()
- t.Cleanup(closeAll(t, fooConn, barConn))
-
- var fooPrvKey = ed25519.GenPrivKey()
- var barPrvKey = sr25519.GenPrivKey()
-
- go MakeSecretConnection(barConn, barPrvKey) //nolint:errcheck // ignore for tests
-
- _, err := MakeSecretConnection(fooConn, fooPrvKey)
- require.Error(t, err)
- }
-
- func writeLots(t *testing.T, wg *sync.WaitGroup, conn io.Writer, txt string, n int) {
- defer wg.Done()
- for i := 0; i < n; i++ {
- _, err := conn.Write([]byte(txt))
- if err != nil {
- t.Errorf("failed to write to fooSecConn: %w", err)
- return
- }
- }
- }
-
- func readLots(t *testing.T, wg *sync.WaitGroup, conn io.Reader, n int) {
- readBuffer := make([]byte, dataMaxSize)
- for i := 0; i < n; i++ {
- _, err := conn.Read(readBuffer)
- assert.NoError(t, err)
- }
- wg.Done()
- }
-
- // Creates the data for a test vector file.
- // The file format is:
- // Hex(diffie_hellman_secret), loc_is_least, Hex(recvSecret), Hex(sendSecret), Hex(challenge)
- func createGoldenTestVectors(t *testing.T) string {
- data := ""
- for i := 0; i < 32; i++ {
- randSecretVector := tmrand.Bytes(32)
- randSecret := new([32]byte)
- copy((*randSecret)[:], randSecretVector)
- data += hex.EncodeToString((*randSecret)[:]) + ","
- locIsLeast := mrand.Int63()%2 == 0
- data += strconv.FormatBool(locIsLeast) + ","
- recvSecret, sendSecret := deriveSecrets(randSecret, locIsLeast)
- data += hex.EncodeToString((*recvSecret)[:]) + ","
- data += hex.EncodeToString((*sendSecret)[:]) + ","
- }
- return data
- }
-
- // Each returned ReadWriteCloser is akin to a net.Connection
- func makeKVStoreConnPair() (fooConn, barConn kvstoreConn) {
- barReader, fooWriter := io.Pipe()
- fooReader, barWriter := io.Pipe()
- return kvstoreConn{fooReader, fooWriter}, kvstoreConn{barReader, barWriter}
- }
-
- func makeSecretConnPair(tb testing.TB) (fooSecConn, barSecConn *SecretConnection) {
- var (
- fooConn, barConn = makeKVStoreConnPair()
- fooPrvKey = ed25519.GenPrivKey()
- fooPubKey = fooPrvKey.PubKey()
- barPrvKey = ed25519.GenPrivKey()
- barPubKey = barPrvKey.PubKey()
- )
-
- // Make connections from both sides in parallel.
- var trs, ok = async.Parallel(
- func(_ int) (val interface{}, abort bool, err error) {
- fooSecConn, err = MakeSecretConnection(fooConn, fooPrvKey)
- if err != nil {
- tb.Errorf("failed to establish SecretConnection for foo: %w", err)
- return nil, true, err
- }
- remotePubBytes := fooSecConn.RemotePubKey()
- if !remotePubBytes.Equals(barPubKey) {
- err = fmt.Errorf("unexpected fooSecConn.RemotePubKey. Expected %v, got %v",
- barPubKey, fooSecConn.RemotePubKey())
- tb.Error(err)
- return nil, true, err
- }
- return nil, false, nil
- },
- func(_ int) (val interface{}, abort bool, err error) {
- barSecConn, err = MakeSecretConnection(barConn, barPrvKey)
- if barSecConn == nil {
- tb.Errorf("failed to establish SecretConnection for bar: %w", err)
- return nil, true, err
- }
- remotePubBytes := barSecConn.RemotePubKey()
- if !remotePubBytes.Equals(fooPubKey) {
- err = fmt.Errorf("unexpected barSecConn.RemotePubKey. Expected %v, got %v",
- fooPubKey, barSecConn.RemotePubKey())
- tb.Error(err)
- return nil, true, err
- }
- return nil, false, nil
- },
- )
-
- require.Nil(tb, trs.FirstError())
- require.True(tb, ok, "Unexpected task abortion")
-
- return fooSecConn, barSecConn
- }
-
- // Benchmarks
-
- func BenchmarkWriteSecretConnection(b *testing.B) {
- b.StopTimer()
- b.ReportAllocs()
- fooSecConn, barSecConn := makeSecretConnPair(b)
- randomMsgSizes := []int{
- dataMaxSize / 10,
- dataMaxSize / 3,
- dataMaxSize / 2,
- dataMaxSize,
- dataMaxSize * 3 / 2,
- dataMaxSize * 2,
- dataMaxSize * 7 / 2,
- }
- fooWriteBytes := make([][]byte, 0, len(randomMsgSizes))
- for _, size := range randomMsgSizes {
- fooWriteBytes = append(fooWriteBytes, tmrand.Bytes(size))
- }
- // Consume reads from bar's reader
- go func() {
- readBuffer := make([]byte, dataMaxSize)
- for {
- _, err := barSecConn.Read(readBuffer)
- if err == io.EOF {
- return
- } else if err != nil {
- b.Errorf("failed to read from barSecConn: %w", err)
- return
- }
- }
- }()
-
- b.StartTimer()
- for i := 0; i < b.N; i++ {
- idx := mrand.Intn(len(fooWriteBytes))
- _, err := fooSecConn.Write(fooWriteBytes[idx])
- if err != nil {
- b.Errorf("failed to write to fooSecConn: %w", err)
- return
- }
- }
- b.StopTimer()
-
- if err := fooSecConn.Close(); err != nil {
- b.Error(err)
- }
- // barSecConn.Close() race condition
- }
-
- func BenchmarkReadSecretConnection(b *testing.B) {
- b.StopTimer()
- b.ReportAllocs()
- fooSecConn, barSecConn := makeSecretConnPair(b)
- randomMsgSizes := []int{
- dataMaxSize / 10,
- dataMaxSize / 3,
- dataMaxSize / 2,
- dataMaxSize,
- dataMaxSize * 3 / 2,
- dataMaxSize * 2,
- dataMaxSize * 7 / 2,
- }
- fooWriteBytes := make([][]byte, 0, len(randomMsgSizes))
- for _, size := range randomMsgSizes {
- fooWriteBytes = append(fooWriteBytes, tmrand.Bytes(size))
- }
- go func() {
- for i := 0; i < b.N; i++ {
- idx := mrand.Intn(len(fooWriteBytes))
- _, err := fooSecConn.Write(fooWriteBytes[idx])
- if err != nil {
- b.Errorf("failed to write to fooSecConn: %v, %v,%v", err, i, b.N)
- return
- }
- }
- }()
-
- b.StartTimer()
- for i := 0; i < b.N; i++ {
- readBuffer := make([]byte, dataMaxSize)
- _, err := barSecConn.Read(readBuffer)
-
- if err == io.EOF {
- return
- } else if err != nil {
- b.Fatalf("Failed to read from barSecConn: %v", err)
- }
- }
- b.StopTimer()
- }
|