package conn import ( "bytes" "net" "testing" "time" "github.com/fortytw2/leaktest" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" amino "github.com/tendermint/go-amino" "github.com/tendermint/tmlibs/log" ) const maxPingPongPacketSize = 1024 // bytes func createTestMConnection(conn net.Conn) *MConnection { onReceive := func(chID byte, msgBytes []byte) { } onError := func(r interface{}) { } c := createMConnectionWithCallbacks(conn, onReceive, onError) c.SetLogger(log.TestingLogger()) return c } func createMConnectionWithCallbacks(conn net.Conn, onReceive func(chID byte, msgBytes []byte), onError func(r interface{})) *MConnection { cfg := DefaultMConnConfig() cfg.PingInterval = 90 * time.Millisecond cfg.PongTimeout = 45 * time.Millisecond chDescs := []*ChannelDescriptor{&ChannelDescriptor{ID: 0x01, Priority: 1, SendQueueCapacity: 1}} c := NewMConnectionWithConfig(conn, chDescs, onReceive, onError, cfg) c.SetLogger(log.TestingLogger()) return c } func TestMConnectionSend(t *testing.T) { server, client := NetPipe() defer server.Close() // nolint: errcheck defer client.Close() // nolint: errcheck mconn := createTestMConnection(client) err := mconn.Start() require.Nil(t, err) defer mconn.Stop() msg := []byte("Ant-Man") assert.True(t, mconn.Send(0x01, msg)) // Note: subsequent Send/TrySend calls could pass because we are reading from // the send queue in a separate goroutine. _, err = server.Read(make([]byte, len(msg))) if err != nil { t.Error(err) } assert.True(t, mconn.CanSend(0x01)) msg = []byte("Spider-Man") assert.True(t, mconn.TrySend(0x01, msg)) _, err = server.Read(make([]byte, len(msg))) if err != nil { t.Error(err) } assert.False(t, mconn.CanSend(0x05), "CanSend should return false because channel is unknown") assert.False(t, mconn.Send(0x05, []byte("Absorbing Man")), "Send should return false because channel is unknown") } func TestMConnectionReceive(t *testing.T) { server, client := NetPipe() defer server.Close() // nolint: errcheck defer client.Close() // nolint: errcheck receivedCh := make(chan []byte) errorsCh := make(chan interface{}) onReceive := func(chID byte, msgBytes []byte) { receivedCh <- msgBytes } onError := func(r interface{}) { errorsCh <- r } mconn1 := createMConnectionWithCallbacks(client, onReceive, onError) err := mconn1.Start() require.Nil(t, err) defer mconn1.Stop() mconn2 := createTestMConnection(server) err = mconn2.Start() require.Nil(t, err) defer mconn2.Stop() msg := []byte("Cyclops") assert.True(t, mconn2.Send(0x01, msg)) select { case receivedBytes := <-receivedCh: assert.Equal(t, []byte(msg), receivedBytes) case err := <-errorsCh: t.Fatalf("Expected %s, got %+v", msg, err) case <-time.After(500 * time.Millisecond): t.Fatalf("Did not receive %s message in 500ms", msg) } } func TestMConnectionStatus(t *testing.T) { server, client := NetPipe() defer server.Close() // nolint: errcheck defer client.Close() // nolint: errcheck mconn := createTestMConnection(client) err := mconn.Start() require.Nil(t, err) defer mconn.Stop() status := mconn.Status() assert.NotNil(t, status) assert.Zero(t, status.Channels[0].SendQueueSize) } func TestMConnectionPongTimeoutResultsInError(t *testing.T) { server, client := net.Pipe() defer server.Close() defer client.Close() receivedCh := make(chan []byte) errorsCh := make(chan interface{}) onReceive := func(chID byte, msgBytes []byte) { receivedCh <- msgBytes } onError := func(r interface{}) { errorsCh <- r } mconn := createMConnectionWithCallbacks(client, onReceive, onError) err := mconn.Start() require.Nil(t, err) defer mconn.Stop() serverGotPing := make(chan struct{}) go func() { // read ping var pkt PacketPing _, err = cdc.UnmarshalBinaryReader(server, &pkt, maxPingPongPacketSize) assert.Nil(t, err) serverGotPing <- struct{}{} }() <-serverGotPing pongTimerExpired := mconn.config.PongTimeout + 20*time.Millisecond select { case msgBytes := <-receivedCh: t.Fatalf("Expected error, but got %v", msgBytes) case err := <-errorsCh: assert.NotNil(t, err) case <-time.After(pongTimerExpired): t.Fatalf("Expected to receive error after %v", pongTimerExpired) } } func TestMConnectionMultiplePongsInTheBeginning(t *testing.T) { server, client := net.Pipe() defer server.Close() defer client.Close() receivedCh := make(chan []byte) errorsCh := make(chan interface{}) onReceive := func(chID byte, msgBytes []byte) { receivedCh <- msgBytes } onError := func(r interface{}) { errorsCh <- r } mconn := createMConnectionWithCallbacks(client, onReceive, onError) err := mconn.Start() require.Nil(t, err) defer mconn.Stop() // sending 3 pongs in a row (abuse) _, err = server.Write(cdc.MustMarshalBinary(PacketPong{})) require.Nil(t, err) _, err = server.Write(cdc.MustMarshalBinary(PacketPong{})) require.Nil(t, err) _, err = server.Write(cdc.MustMarshalBinary(PacketPong{})) require.Nil(t, err) serverGotPing := make(chan struct{}) go func() { // read ping (one byte) var packet, err = Packet(nil), error(nil) _, err = cdc.UnmarshalBinaryReader(server, &packet, maxPingPongPacketSize) require.Nil(t, err) serverGotPing <- struct{}{} // respond with pong _, err = server.Write(cdc.MustMarshalBinary(PacketPong{})) require.Nil(t, err) }() <-serverGotPing pongTimerExpired := mconn.config.PongTimeout + 20*time.Millisecond select { case msgBytes := <-receivedCh: t.Fatalf("Expected no data, but got %v", msgBytes) case err := <-errorsCh: t.Fatalf("Expected no error, but got %v", err) case <-time.After(pongTimerExpired): assert.True(t, mconn.IsRunning()) } } func TestMConnectionMultiplePings(t *testing.T) { server, client := net.Pipe() defer server.Close() defer client.Close() receivedCh := make(chan []byte) errorsCh := make(chan interface{}) onReceive := func(chID byte, msgBytes []byte) { receivedCh <- msgBytes } onError := func(r interface{}) { errorsCh <- r } mconn := createMConnectionWithCallbacks(client, onReceive, onError) err := mconn.Start() require.Nil(t, err) defer mconn.Stop() // sending 3 pings in a row (abuse) // see https://github.com/tendermint/tendermint/issues/1190 _, err = server.Write(cdc.MustMarshalBinary(PacketPing{})) require.Nil(t, err) var pkt PacketPong _, err = cdc.UnmarshalBinaryReader(server, &pkt, maxPingPongPacketSize) require.Nil(t, err) _, err = server.Write(cdc.MustMarshalBinary(PacketPing{})) require.Nil(t, err) _, err = cdc.UnmarshalBinaryReader(server, &pkt, maxPingPongPacketSize) require.Nil(t, err) _, err = server.Write(cdc.MustMarshalBinary(PacketPing{})) require.Nil(t, err) _, err = cdc.UnmarshalBinaryReader(server, &pkt, maxPingPongPacketSize) require.Nil(t, err) assert.True(t, mconn.IsRunning()) } func TestMConnectionPingPongs(t *testing.T) { // check that we are not leaking any go-routines defer leaktest.CheckTimeout(t, 10*time.Second)() server, client := net.Pipe() defer server.Close() defer client.Close() receivedCh := make(chan []byte) errorsCh := make(chan interface{}) onReceive := func(chID byte, msgBytes []byte) { receivedCh <- msgBytes } onError := func(r interface{}) { errorsCh <- r } mconn := createMConnectionWithCallbacks(client, onReceive, onError) err := mconn.Start() require.Nil(t, err) defer mconn.Stop() serverGotPing := make(chan struct{}) go func() { // read ping var pkt PacketPing _, err = cdc.UnmarshalBinaryReader(server, &pkt, maxPingPongPacketSize) require.Nil(t, err) serverGotPing <- struct{}{} // respond with pong _, err = server.Write(cdc.MustMarshalBinary(PacketPong{})) require.Nil(t, err) time.Sleep(mconn.config.PingInterval) // read ping _, err = cdc.UnmarshalBinaryReader(server, &pkt, maxPingPongPacketSize) require.Nil(t, err) // respond with pong _, err = server.Write(cdc.MustMarshalBinary(PacketPong{})) require.Nil(t, err) }() <-serverGotPing pongTimerExpired := (mconn.config.PongTimeout + 20*time.Millisecond) * 2 select { case msgBytes := <-receivedCh: t.Fatalf("Expected no data, but got %v", msgBytes) case err := <-errorsCh: t.Fatalf("Expected no error, but got %v", err) case <-time.After(2 * pongTimerExpired): assert.True(t, mconn.IsRunning()) } } func TestMConnectionStopsAndReturnsError(t *testing.T) { server, client := NetPipe() defer server.Close() // nolint: errcheck defer client.Close() // nolint: errcheck receivedCh := make(chan []byte) errorsCh := make(chan interface{}) onReceive := func(chID byte, msgBytes []byte) { receivedCh <- msgBytes } onError := func(r interface{}) { errorsCh <- r } mconn := createMConnectionWithCallbacks(client, onReceive, onError) err := mconn.Start() require.Nil(t, err) defer mconn.Stop() if err := client.Close(); err != nil { t.Error(err) } select { case receivedBytes := <-receivedCh: t.Fatalf("Expected error, got %v", receivedBytes) case err := <-errorsCh: assert.NotNil(t, err) assert.False(t, mconn.IsRunning()) case <-time.After(500 * time.Millisecond): t.Fatal("Did not receive error in 500ms") } } func newClientAndServerConnsForReadErrors(t *testing.T, chOnErr chan struct{}) (*MConnection, *MConnection) { server, client := NetPipe() onReceive := func(chID byte, msgBytes []byte) {} onError := func(r interface{}) {} // create client conn with two channels chDescs := []*ChannelDescriptor{ {ID: 0x01, Priority: 1, SendQueueCapacity: 1}, {ID: 0x02, Priority: 1, SendQueueCapacity: 1}, } mconnClient := NewMConnection(client, chDescs, onReceive, onError) mconnClient.SetLogger(log.TestingLogger().With("module", "client")) err := mconnClient.Start() require.Nil(t, err) // create server conn with 1 channel // it fires on chOnErr when there's an error serverLogger := log.TestingLogger().With("module", "server") onError = func(r interface{}) { chOnErr <- struct{}{} } mconnServer := createMConnectionWithCallbacks(server, onReceive, onError) mconnServer.SetLogger(serverLogger) err = mconnServer.Start() require.Nil(t, err) return mconnClient, mconnServer } func expectSend(ch chan struct{}) bool { after := time.After(time.Second * 5) select { case <-ch: return true case <-after: return false } } func TestMConnectionReadErrorBadEncoding(t *testing.T) { chOnErr := make(chan struct{}) mconnClient, mconnServer := newClientAndServerConnsForReadErrors(t, chOnErr) defer mconnClient.Stop() defer mconnServer.Stop() client := mconnClient.conn // send badly encoded msgPacket bz := cdc.MustMarshalBinary(PacketMsg{}) bz[4] += 0x01 // Invalid prefix bytes. // Write it. _, err := client.Write(bz) assert.Nil(t, err) assert.True(t, expectSend(chOnErr), "badly encoded msgPacket") } func TestMConnectionReadErrorUnknownChannel(t *testing.T) { chOnErr := make(chan struct{}) mconnClient, mconnServer := newClientAndServerConnsForReadErrors(t, chOnErr) defer mconnClient.Stop() defer mconnServer.Stop() msg := []byte("Ant-Man") // fail to send msg on channel unknown by client assert.False(t, mconnClient.Send(0x03, msg)) // send msg on channel unknown by the server. // should cause an error assert.True(t, mconnClient.Send(0x02, msg)) assert.True(t, expectSend(chOnErr), "unknown channel") } func TestMConnectionReadErrorLongMessage(t *testing.T) { chOnErr := make(chan struct{}) chOnRcv := make(chan struct{}) mconnClient, mconnServer := newClientAndServerConnsForReadErrors(t, chOnErr) defer mconnClient.Stop() defer mconnServer.Stop() mconnServer.onReceive = func(chID byte, msgBytes []byte) { chOnRcv <- struct{}{} } client := mconnClient.conn // send msg thats just right var err error var buf = new(bytes.Buffer) var packet = PacketMsg{ ChannelID: 0x01, EOF: 1, Bytes: make([]byte, mconnClient.config.MaxPacketMsgSize-emptyPacketMsgSize()), } _, err = cdc.MarshalBinaryWriter(buf, packet) assert.Nil(t, err) _, err = client.Write(buf.Bytes()) assert.Nil(t, err) assert.True(t, expectSend(chOnRcv), "msg just right") assert.False(t, expectSend(chOnErr), "msg just right") // send msg thats too long buf = new(bytes.Buffer) packet = PacketMsg{ ChannelID: 0x01, EOF: 1, Bytes: make([]byte, mconnClient.config.MaxPacketMsgSize+1), } _, err = cdc.MarshalBinaryWriter(buf, packet) assert.Nil(t, err) _, err = client.Write(buf.Bytes()) assert.NotNil(t, err) assert.False(t, expectSend(chOnRcv), "msg too long") assert.True(t, expectSend(chOnErr), "msg too long") } func TestMConnectionReadErrorUnknownMsgType(t *testing.T) { chOnErr := make(chan struct{}) mconnClient, mconnServer := newClientAndServerConnsForReadErrors(t, chOnErr) defer mconnClient.Stop() defer mconnServer.Stop() // send msg with unknown msg type err := error(nil) err = amino.EncodeUvarint(mconnClient.conn, 4) assert.Nil(t, err) _, err = mconnClient.conn.Write([]byte{0xFF, 0xFF, 0xFF, 0xFF}) assert.Nil(t, err) assert.True(t, expectSend(chOnErr), "unknown msg type") } func TestMConnectionTrySend(t *testing.T) { server, client := NetPipe() defer server.Close() defer client.Close() mconn := createTestMConnection(client) err := mconn.Start() require.Nil(t, err) defer mconn.Stop() msg := []byte("Semicolon-Woman") resultCh := make(chan string, 2) assert.True(t, mconn.TrySend(0x01, msg)) server.Read(make([]byte, len(msg))) assert.True(t, mconn.CanSend(0x01)) assert.True(t, mconn.TrySend(0x01, msg)) assert.False(t, mconn.CanSend(0x01)) go func() { mconn.TrySend(0x01, msg) resultCh <- "TrySend" }() assert.False(t, mconn.CanSend(0x01)) assert.False(t, mconn.TrySend(0x01, msg)) assert.Equal(t, "TrySend", <-resultCh) }