blob: a8da311bb1bb7155a4e347b02d5d4e22c9261fca [file] [log] [blame]
// Copyright 2014 The Go 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 runner
import (
"encoding/binary"
"fmt"
"io"
"net"
"time"
)
// opcodePacket signals a packet, encoded with a 32-bit length prefix, followed
// by the payload.
const opcodePacket = byte('P')
// opcodeTimeout signals a read timeout, encoded by a 64-bit number of
// nanoseconds. On receipt, the peer should reply with
// opcodeTimeoutAck. opcodeTimeout may only be sent by the Go side.
const opcodeTimeout = byte('T')
// opcodeTimeoutAck acknowledges a read timeout. This opcode has no payload and
// may only be sent by the C side. Timeout ACKs act as a synchronization point
// at the timeout, to bracket one flight of messages from C.
const opcodeTimeoutAck = byte('t')
type packetAdaptor struct {
net.Conn
debug *recordingConn
}
// newPacketAdaptor wraps a reliable streaming net.Conn into a reliable
// packet-based net.Conn. The stream contains packets and control commands,
// distinguished by a one byte opcode.
func newPacketAdaptor(conn net.Conn) *packetAdaptor {
return &packetAdaptor{conn, nil}
}
func (p *packetAdaptor) log(message string, data []byte) {
if p.debug == nil {
return
}
p.debug.LogSpecial(message, data)
}
func (p *packetAdaptor) readOpcode() (byte, error) {
out := make([]byte, 1)
if _, err := io.ReadFull(p.Conn, out); err != nil {
return 0, err
}
return out[0], nil
}
func (p *packetAdaptor) readPacketBody() ([]byte, error) {
var length uint32
if err := binary.Read(p.Conn, binary.BigEndian, &length); err != nil {
return nil, err
}
out := make([]byte, length)
if _, err := io.ReadFull(p.Conn, out); err != nil {
return nil, err
}
return out, nil
}
func (p *packetAdaptor) Read(b []byte) (int, error) {
opcode, err := p.readOpcode()
if err != nil {
return 0, err
}
if opcode != opcodePacket {
return 0, fmt.Errorf("unexpected opcode '%d'", opcode)
}
out, err := p.readPacketBody()
if err != nil {
return 0, err
}
return copy(b, out), nil
}
func (p *packetAdaptor) Write(b []byte) (int, error) {
payload := make([]byte, 1+4+len(b))
payload[0] = opcodePacket
binary.BigEndian.PutUint32(payload[1:5], uint32(len(b)))
copy(payload[5:], b)
if _, err := p.Conn.Write(payload); err != nil {
return 0, err
}
return len(b), nil
}
// SendReadTimeout instructs the peer to simulate a read timeout. It then waits
// for acknowledgement of the timeout, buffering any packets received since
// then. The packets are then returned.
func (p *packetAdaptor) SendReadTimeout(d time.Duration) ([][]byte, error) {
p.log("Simulating read timeout: "+d.String(), nil)
payload := make([]byte, 1+8)
payload[0] = opcodeTimeout
binary.BigEndian.PutUint64(payload[1:], uint64(d.Nanoseconds()))
if _, err := p.Conn.Write(payload); err != nil {
return nil, err
}
var packets [][]byte
for {
opcode, err := p.readOpcode()
if err != nil {
return nil, err
}
switch opcode {
case opcodeTimeoutAck:
p.log("Received timeout ACK", nil)
// Done! Return the packets buffered and continue.
return packets, nil
case opcodePacket:
// Buffer the packet for the caller to process.
packet, err := p.readPacketBody()
if err != nil {
return nil, err
}
p.log("Simulating dropped packet", packet)
packets = append(packets, packet)
default:
return nil, fmt.Errorf("unexpected opcode '%d'", opcode)
}
}
}
type replayAdaptor struct {
net.Conn
prevWrite []byte
}
// newReplayAdaptor wraps a packeted net.Conn. It transforms it into
// one which, after writing a packet, always replays the previous
// write.
func newReplayAdaptor(conn net.Conn) net.Conn {
return &replayAdaptor{Conn: conn}
}
func (r *replayAdaptor) Write(b []byte) (int, error) {
n, err := r.Conn.Write(b)
// Replay the previous packet and save the current one to
// replay next.
if r.prevWrite != nil {
r.Conn.Write(r.prevWrite)
}
r.prevWrite = append(r.prevWrite[:0], b...)
return n, err
}
type damageAdaptor struct {
net.Conn
damage bool
}
// newDamageAdaptor wraps a packeted net.Conn. It transforms it into one which
// optionally damages the final byte of every Write() call.
func newDamageAdaptor(conn net.Conn) *damageAdaptor {
return &damageAdaptor{Conn: conn}
}
func (d *damageAdaptor) setDamage(damage bool) {
d.damage = damage
}
func (d *damageAdaptor) Write(b []byte) (int, error) {
if d.damage && len(b) > 0 {
b = append([]byte{}, b...)
b[len(b)-1]++
}
return d.Conn.Write(b)
}