blob: a0fbbd971b9ff46408cd923b367c75368695eb2a [file] [log] [blame]
// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package websocket_test
import (
"encoding/gob"
"fmt"
"hash/crc64"
"io"
"math/rand"
"net"
"sync"
"testing"
"time"
"v.io/v23/rpc"
)
//go:generate v23 test generate
var crcTable *crc64.Table
func init() {
crcTable = crc64.MakeTable(crc64.ISO)
}
func newSender(t *testing.T, dialer rpc.DialerFunc, protocol, address string) net.Conn {
conn, err := dialer(protocol, address, time.Minute)
if err != nil {
t.Fatalf("unexpected error: %s", err)
return nil
}
return conn
}
func checkProtocols(conn net.Conn, tx string) error {
expectedProtocol := map[string]string{
"ws": "ws", "wsh": "tcp", "tcp": "tcp",
}
if got, want := conn.LocalAddr().Network(), expectedProtocol[tx]; got != want {
return fmt.Errorf("wrong local protocol: got %q, want %q", got, want)
}
// Can't tell that the remote protocol is really 'wsh'
if got, want := conn.RemoteAddr().Network(), expectedProtocol[tx]; got != want {
return fmt.Errorf("wrong remote protocol: got %q, want %q", got, want)
}
return nil
}
type packet struct {
Data []byte
Size int
CRC64 uint64
}
func createPacket() *packet {
p := &packet{}
p.Size = rand.Intn(4 * 1024)
p.Data = make([]byte, p.Size)
for i := 0; i < p.Size; i++ {
p.Data[i] = byte(rand.Int() & 0xff)
}
p.CRC64 = crc64.Checksum([]byte(p.Data), crcTable)
return p
}
func checkPacket(p *packet) error {
if got, want := len(p.Data), p.Size; got != want {
return fmt.Errorf("wrong sizes: got %d, want %d", got, want)
}
crc := crc64.Checksum(p.Data, crcTable)
if got, want := crc, p.CRC64; got != want {
return fmt.Errorf("wrong crc: got %d, want %d", got, want)
}
return nil
}
type backChannel struct {
crcChan chan uint64
byteChan chan []byte
errChan chan error
}
type bcTable struct {
ready *sync.Cond
sync.Mutex
bc map[string]*backChannel
}
var globalBCTable bcTable
func init() {
globalBCTable.ready = sync.NewCond(&globalBCTable)
globalBCTable.bc = make(map[string]*backChannel)
}
func (bt *bcTable) waitfor(key string) *backChannel {
bt.Lock()
defer bt.Unlock()
for {
bc := bt.bc[key]
if bc != nil {
delete(bt.bc, key)
return bc
}
bt.ready.Wait()
}
}
func (bt *bcTable) add(key string, bc *backChannel) {
bt.Lock()
bt.bc[key] = bc
bt.Unlock()
bt.ready.Broadcast()
}
func packetReceiver(t *testing.T, ln net.Listener, bc *backChannel) {
conn, err := ln.Accept()
if err != nil {
close(bc.crcChan)
close(bc.errChan)
return
}
globalBCTable.add(conn.RemoteAddr().String(), bc)
defer conn.Close()
dec := gob.NewDecoder(conn)
rxed := 0
for {
var p packet
err := dec.Decode(&p)
if err != nil {
if err != io.EOF {
bc.errChan <- fmt.Errorf("unexpected error: %s", err)
}
close(bc.crcChan)
close(bc.errChan)
return
}
if err := checkPacket(&p); err != nil {
bc.errChan <- fmt.Errorf("unexpected error: %s", err)
}
bc.crcChan <- p.CRC64
rxed++
}
}
func packetSender(t *testing.T, nPackets int, conn net.Conn) {
txCRCs := make([]uint64, nPackets)
enc := gob.NewEncoder(conn)
for i := 0; i < nPackets; i++ {
p := createPacket()
txCRCs[i] = p.CRC64
if err := enc.Encode(p); err != nil {
t.Fatalf("unexpected error: %s", err)
}
}
conn.Close() // Close the connection so that the receiver quits.
bc := globalBCTable.waitfor(conn.LocalAddr().String())
for err := range bc.errChan {
if err != nil {
t.Fatalf(err.Error())
}
}
rxed := 0
for rxCRC := range bc.crcChan {
if got, want := rxCRC, txCRCs[rxed]; got != want {
t.Errorf("%s -> %s: packet %d: mismatched CRCs: got %d, want %d", conn.LocalAddr().String(), conn.RemoteAddr().String(), rxed, got, want)
}
rxed++
}
if got, want := rxed, nPackets; got != want {
t.Fatalf("%s -> %s: got %d, want %d", conn.LocalAddr().String(), conn.RemoteAddr().String(), got, want)
}
}
func packetRunner(t *testing.T, ln net.Listener, dialer rpc.DialerFunc, protocol, address string) {
nPackets := 100
go packetReceiver(t, ln, &backChannel{
crcChan: make(chan uint64, nPackets),
errChan: make(chan error, nPackets),
})
conn := newSender(t, dialer, protocol, address)
if err := checkProtocols(conn, protocol); err != nil {
t.Fatalf(err.Error())
}
packetSender(t, nPackets, conn)
}
func byteReceiver(t *testing.T, ln net.Listener, bc *backChannel) {
conn, err := ln.Accept()
if err != nil {
close(bc.byteChan)
close(bc.errChan)
return
}
globalBCTable.add(conn.RemoteAddr().String(), bc)
defer conn.Close()
rxed := 0
for {
buf := make([]byte, rxed+1)
n, err := conn.Read(buf)
if err != nil {
if err != io.EOF {
bc.errChan <- fmt.Errorf("unexpected error: %s", err)
}
close(bc.byteChan)
close(bc.errChan)
return
}
if got, want := n, len(buf[:n]); got != want {
bc.errChan <- fmt.Errorf("%s -> %s: got %d bytes, expected %d", conn.LocalAddr().String(), conn.RemoteAddr().String(), got, want)
}
if got, want := buf[0], byte(0xff); got != want {
bc.errChan <- fmt.Errorf("%s -> %s: got %x, want %x", conn.LocalAddr().String(), conn.RemoteAddr().String(), got, want)
}
bc.byteChan <- buf[:n]
rxed++
}
}
func byteSender(t *testing.T, nIterations int, conn net.Conn) {
txBytes := make([][]byte, nIterations+1)
for i := 0; i < nIterations; i++ {
p := make([]byte, i+1)
p[0] = 0xff
for j := 1; j <= i; j++ {
p[j] = byte(64 + i) // start at ASCII A
}
txBytes[i] = p
n, err := conn.Write(p)
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
if got, want := n, i+1; got != want {
t.Fatalf("wrote %d, not %d bytes", got, want)
}
}
conn.Close()
bc := globalBCTable.waitfor(conn.LocalAddr().String())
for err := range bc.errChan {
if err != nil {
t.Fatalf(err.Error())
}
}
addr := fmt.Sprintf("%s -> %s", conn.LocalAddr().String(), conn.RemoteAddr().String())
rxed := 0
for rxBytes := range bc.byteChan {
if got, want := len(rxBytes), rxed+1; got != want {
t.Fatalf("%s: got %d, want %d bytes", addr, got, want)
}
if got, want := rxBytes[0], byte(0xff); got != want {
t.Fatalf("%s: got %x, want %x", addr, got, want)
}
for i := 0; i < len(rxBytes); i++ {
if got, want := rxBytes[i], txBytes[rxed][i]; got != want {
t.Fatalf("%s: got %c, want %c", addr, got, want)
}
}
rxed++
}
if got, want := rxed, nIterations; got != want {
t.Fatalf("%s: got %d, want %d", addr, got, want)
}
}
func byteRunner(t *testing.T, ln net.Listener, dialer rpc.DialerFunc, protocol, address string) {
nIterations := 10
go byteReceiver(t, ln, &backChannel{
byteChan: make(chan []byte, nIterations),
errChan: make(chan error, nIterations),
})
conn := newSender(t, dialer, protocol, address)
defer conn.Close()
if err := checkProtocols(conn, protocol); err != nil {
t.Fatalf(err.Error())
}
byteSender(t, nIterations, conn)
}