wire_test.go raw

   1  package btcwire
   2  
   3  import (
   4  	"bytes"
   5  	"encoding/binary"
   6  	"errors"
   7  	"fmt"
   8  	"io"
   9  	"testing"
  10  
  11  	"git.smesh.lol/nostrino/pkg/crypto/chainhash"
  12  )
  13  
  14  // frameHeader builds a 24-byte frame header with the given fields.
  15  func frameHeader(magic uint32, command [CommandSize]byte, length uint32) []byte {
  16  	hdr := make([]byte, MessageHeaderSize)
  17  	binary.BigEndian.PutUint32(hdr[0:4], magic)
  18  	copy(hdr[4:4+CommandSize], command[:])
  19  	binary.LittleEndian.PutUint32(hdr[16:20], length)
  20  	sum := Checksum(nil)
  21  	copy(hdr[20:24], sum[:])
  22  	return hdr
  23  }
  24  
  25  func TestChecksum(t *testing.T) {
  26  	if got, want := Checksum(nil), [4]byte{0x5d, 0xf6, 0xe0, 0xe2}; got != want {
  27  		t.Fatalf("Checksum(nil) = %x, want %x", got, want)
  28  	}
  29  	payload := []byte("nostrino compact filter client")
  30  	sum := Checksum(payload)
  31  	full := chainhash.DoubleHashB(payload)
  32  	if !bytes.Equal(sum[:], full[:4]) {
  33  		t.Fatalf("Checksum = %x, want first 4 bytes of %x", sum, full)
  34  	}
  35  }
  36  
  37  // TestEncodeMessageLayout pins the four bytes of every network magic as they
  38  // appear on the wire. These are the strings Bitcoin prints for each network
  39  // read left to right: a node that receives any other order sees an unknown
  40  // network and drops the connection before it parses the version payload.
  41  func TestEncodeMessageLayout(t *testing.T) {
  42  	wantMagic := []struct {
  43  		name  string
  44  		magic uint32
  45  		bytes []byte
  46  	}{
  47  		{"mainnet", MainNet, []byte{0xf9, 0xbe, 0xb4, 0xd9}},
  48  		{"testnet3", TestNet3, []byte{0x0b, 0x11, 0x09, 0x07}},
  49  		{"regtest", RegTest, []byte{0xfa, 0xbf, 0xb5, 0xda}},
  50  		{"signet", SigNet, []byte{0x0a, 0x03, 0xcf, 0x40}},
  51  	}
  52  	payload := []byte("hello, wire")
  53  	for _, network := range wantMagic {
  54  		t.Run(network.name, func(t *testing.T) {
  55  			frame, err := EncodeMessage(network.magic, CmdPing, payload)
  56  			if err != nil {
  57  				t.Fatalf("EncodeMessage: %v", err)
  58  			}
  59  			var want []byte
  60  			want = append(want, network.bytes...)
  61  			// "ping" NUL padded to 12 bytes.
  62  			want = append(want, []byte("ping")...)
  63  			want = append(want, make([]byte, CommandSize-len("ping"))...)
  64  			// Payload length, little endian.
  65  			want = append(want, byte(len(payload)), 0, 0, 0)
  66  			// Checksum.
  67  			sum := Checksum(payload)
  68  			want = append(want, sum[:]...)
  69  			want = append(want, payload...)
  70  			if !bytes.Equal(frame, want) {
  71  				t.Fatalf("frame = %x, want %x", frame, want)
  72  			}
  73  		})
  74  	}
  75  }
  76  
  77  // TestTheReversedMagicIsRejected: the pre-fix codec wrote each magic little
  78  // endian, which reverses its four bytes on the wire. A round trip through the
  79  // broken encoder and decoder agrees with itself, so it proves nothing; this
  80  // test decodes the exact bytes the old code produced and requires a refusal.
  81  func TestTheReversedMagicIsRejected(t *testing.T) {
  82  	// A valid mainnet ping, as the fixed codec writes it.
  83  	good, err := EncodeMessage(MainNet, CmdPing, nil)
  84  	if err != nil {
  85  		t.Fatalf("EncodeMessage: %v", err)
  86  	}
  87  	if got, want := good[0:4], []byte{0xf9, 0xbe, 0xb4, 0xd9}; !bytes.Equal(got, want) {
  88  		t.Fatalf("mainnet magic on the wire = %x, want %x", got, want)
  89  	}
  90  
  91  	// The same frame with the magic byte-reversed, which is what the old
  92  	// little-endian encoder put on the wire.
  93  	reversed := append([]byte(nil), good...)
  94  	copy(reversed[0:4], []byte{0xd9, 0xb4, 0xbe, 0xf9})
  95  
  96  	if _, err := DecodeMessage(MainNet, bytes.NewReader(reversed)); !errors.Is(err, ErrBadMagic) {
  97  		t.Fatalf("decoding the reversed mainnet magic: %v, want ErrBadMagic", err)
  98  	}
  99  
 100  	// The same bug on regtest: da b5 bf fa instead of fa bf b5 da.
 101  	regtest, err := EncodeMessage(RegTest, CmdPing, nil)
 102  	if err != nil {
 103  		t.Fatalf("EncodeMessage: %v", err)
 104  	}
 105  	if got, want := regtest[0:4], []byte{0xfa, 0xbf, 0xb5, 0xda}; !bytes.Equal(got, want) {
 106  		t.Fatalf("regtest magic on the wire = %x, want %x", got, want)
 107  	}
 108  	reversedRegtest := append([]byte(nil), regtest...)
 109  	copy(reversedRegtest[0:4], []byte{0xda, 0xb5, 0xbf, 0xfa})
 110  	if _, err := DecodeMessage(RegTest, bytes.NewReader(reversedRegtest)); !errors.Is(err, ErrBadMagic) {
 111  		t.Fatalf("decoding the reversed regtest magic: %v, want ErrBadMagic", err)
 112  	}
 113  }
 114  
 115  func TestMessageRoundTrip(t *testing.T) {
 116  	magics := []uint32{MainNet, TestNet3, RegTest, SigNet}
 117  	payloads := [][]byte{
 118  		nil,
 119  		{},
 120  		{0x00},
 121  		bytes.Repeat([]byte{0xab}, 253),
 122  		bytes.Repeat([]byte{0xcd}, 1000),
 123  	}
 124  	for _, magic := range magics {
 125  		for _, command := range allCommands {
 126  			for _, payload := range payloads {
 127  				name := fmt.Sprintf("%08x/%s/%d", magic, command, len(payload))
 128  				t.Run(name, func(t *testing.T) {
 129  					frame, err := EncodeMessage(magic, command, payload)
 130  					if err != nil {
 131  						t.Fatalf("EncodeMessage: %v", err)
 132  					}
 133  					if got, want := len(frame), MessageHeaderSize+len(payload); got != want {
 134  						t.Fatalf("frame length = %d, want %d", got, want)
 135  					}
 136  					msg, err := DecodeMessage(magic, bytes.NewReader(frame))
 137  					if err != nil {
 138  						t.Fatalf("DecodeMessage: %v", err)
 139  					}
 140  					if msg.Command != command {
 141  						t.Fatalf("command = %q, want %q", msg.Command, command)
 142  					}
 143  					if !bytes.Equal(msg.Payload, payload) {
 144  						t.Fatalf("payload = %x, want %x", msg.Payload, payload)
 145  					}
 146  				})
 147  			}
 148  		}
 149  	}
 150  }
 151  
 152  func TestDecodeMessageErrors(t *testing.T) {
 153  	pingPayload := make([]byte, 8)
 154  	goodPing, err := EncodeMessage(MainNet, CmdPing, pingPayload)
 155  	if err != nil {
 156  		t.Fatalf("EncodeMessage: %v", err)
 157  	}
 158  	var zeroCommand [CommandSize]byte
 159  	var badCharCommand [CommandSize]byte
 160  	copy(badCharCommand[:], "pi\x01ng")
 161  
 162  	tests := []struct {
 163  		name  string
 164  		frame func() []byte
 165  		want  error
 166  	}{
 167  		{
 168  			name:  "empty input",
 169  			frame: func() []byte { return nil },
 170  			want:  io.EOF,
 171  		},
 172  		{
 173  			name: "bad magic",
 174  			frame: func() []byte {
 175  				f := append([]byte(nil), goodPing...)
 176  				binary.BigEndian.PutUint32(f[0:4], TestNet3)
 177  				return f
 178  			},
 179  			want: ErrBadMagic,
 180  		},
 181  		{
 182  			name: "bad checksum",
 183  			frame: func() []byte {
 184  				f, err := EncodeMessage(MainNet, CmdTx, []byte{1, 2, 3})
 185  				if err != nil {
 186  					t.Fatalf("EncodeMessage: %v", err)
 187  				}
 188  				f[len(f)-1] ^= 0xff
 189  				return f
 190  			},
 191  			want: ErrBadChecksum,
 192  		},
 193  		{
 194  			name:  "truncated header",
 195  			frame: func() []byte { return goodPing[:10] },
 196  			want:  io.ErrUnexpectedEOF,
 197  		},
 198  		{
 199  			name:  "truncated payload",
 200  			frame: func() []byte { return goodPing[:len(goodPing)-1] },
 201  			want:  io.ErrUnexpectedEOF,
 202  		},
 203  		{
 204  			name:  "missing payload",
 205  			frame: func() []byte { return goodPing[:MessageHeaderSize] },
 206  			want:  io.ErrUnexpectedEOF,
 207  		},
 208  		{
 209  			name: "oversize payload",
 210  			frame: func() []byte {
 211  				var command [CommandSize]byte
 212  				copy(command[:], CmdTx)
 213  				return frameHeader(MainNet, command, MaxPayload+1)
 214  			},
 215  			want: ErrPayloadTooLarge,
 216  		},
 217  		{
 218  			name: "all NUL command",
 219  			frame: func() []byte {
 220  				return frameHeader(MainNet, zeroCommand, 0)
 221  			},
 222  			want: ErrInvalidCommand,
 223  		},
 224  		{
 225  			name: "non-printable command",
 226  			frame: func() []byte {
 227  				return frameHeader(MainNet, badCharCommand, 0)
 228  			},
 229  			want: ErrInvalidCommand,
 230  		},
 231  	}
 232  	for _, tc := range tests {
 233  		t.Run(tc.name, func(t *testing.T) {
 234  			_, err := DecodeMessage(MainNet, bytes.NewReader(tc.frame()))
 235  			if !errors.Is(err, tc.want) {
 236  				t.Fatalf("DecodeMessage error = %v, want %v", err, tc.want)
 237  			}
 238  		})
 239  	}
 240  }
 241  
 242  func TestDecodeUnknownCommand(t *testing.T) {
 243  	// Commands the package does not implement must still frame correctly; the
 244  	// caller decides what to do with them.
 245  	payload := []byte{0xde, 0xad}
 246  	frame, err := EncodeMessage(MainNet, "notacommand", payload)
 247  	if err != nil {
 248  		t.Fatalf("EncodeMessage: %v", err)
 249  	}
 250  	msg, err := DecodeMessage(MainNet, bytes.NewReader(frame))
 251  	if err != nil {
 252  		t.Fatalf("DecodeMessage: %v", err)
 253  	}
 254  	if msg.Command != "notacommand" {
 255  		t.Fatalf("command = %q, want %q", msg.Command, "notacommand")
 256  	}
 257  	if !bytes.Equal(msg.Payload, payload) {
 258  		t.Fatalf("payload = %x, want %x", msg.Payload, payload)
 259  	}
 260  }
 261  
 262  func TestEncodeMessageErrors(t *testing.T) {
 263  	tests := []struct {
 264  		name    string
 265  		command string
 266  		payload []byte
 267  		want    error
 268  	}{
 269  		{"empty command", "", nil, ErrInvalidCommand},
 270  		{"command too long", "thirteenbytes", nil, ErrInvalidCommand},
 271  		{"command with NUL", "pi\x00ng", nil, ErrInvalidCommand},
 272  		{"command with DEL", "ping\x7f", nil, ErrInvalidCommand},
 273  		{"command with high bit", "ping\xff", nil, ErrInvalidCommand},
 274  		{"oversize payload", CmdTx, make([]byte, MaxPayload+1), ErrPayloadTooLarge},
 275  	}
 276  	for _, tc := range tests {
 277  		t.Run(tc.name, func(t *testing.T) {
 278  			_, err := EncodeMessage(MainNet, tc.command, tc.payload)
 279  			if !errors.Is(err, tc.want) {
 280  				t.Fatalf("EncodeMessage error = %v, want %v", err, tc.want)
 281  			}
 282  		})
 283  	}
 284  }
 285  
 286  func TestCommandConstants(t *testing.T) {
 287  	seen := make(map[string]bool, len(allCommands))
 288  	for _, command := range allCommands {
 289  		if len(command) == 0 {
 290  			t.Fatal("empty command constant")
 291  		}
 292  		if len(command) > CommandSize {
 293  			t.Fatalf("command %q is %d bytes, maximum is %d", command, len(command), CommandSize)
 294  		}
 295  		for i := 0; i < len(command); i++ {
 296  			if c := command[i]; c < 0x20 || c > 0x7e {
 297  				t.Fatalf("command %q contains non-printable byte 0x%02x", command, c)
 298  			}
 299  		}
 300  		if seen[command] {
 301  			t.Fatalf("duplicate command %q", command)
 302  		}
 303  		seen[command] = true
 304  		frame, err := EncodeMessage(MainNet, command, nil)
 305  		if err != nil {
 306  			t.Fatalf("EncodeMessage(%q): %v", command, err)
 307  		}
 308  		msg, err := DecodeMessage(MainNet, bytes.NewReader(frame))
 309  		if err != nil {
 310  			t.Fatalf("DecodeMessage(%q): %v", command, err)
 311  		}
 312  		if msg.Command != command {
 313  			t.Fatalf("decoded command = %q, want %q", msg.Command, command)
 314  		}
 315  	}
 316  }
 317  
 318  func TestReader(t *testing.T) {
 319  	var buf bytes.Buffer
 320  	var wantCommands []string
 321  	var wantPayloads [][]byte
 322  	for i, command := range []string{CmdVersion, CmdVerAck, CmdPing, CmdPong} {
 323  		payload := bytes.Repeat([]byte{byte(i)}, i)
 324  		frame, err := EncodeMessage(MainNet, command, payload)
 325  		if err != nil {
 326  			t.Fatalf("EncodeMessage: %v", err)
 327  		}
 328  		buf.Write(frame)
 329  		wantCommands = append(wantCommands, command)
 330  		wantPayloads = append(wantPayloads, payload)
 331  	}
 332  	r := NewReader(&buf, MainNet)
 333  	if r.Magic() != MainNet {
 334  		t.Fatalf("Magic() = %08x, want %08x", r.Magic(), MainNet)
 335  	}
 336  	for i := 0; ; i++ {
 337  		msg, err := r.Next()
 338  		if errors.Is(err, io.EOF) {
 339  			if i != len(wantCommands) {
 340  				t.Fatalf("stream ended after %d messages, want %d", i, len(wantCommands))
 341  			}
 342  			break
 343  		}
 344  		if err != nil {
 345  			t.Fatalf("Next: %v", err)
 346  		}
 347  		if i >= len(wantCommands) {
 348  			t.Fatalf("unexpected extra message %q", msg.Command)
 349  		}
 350  		if msg.Command != wantCommands[i] {
 351  			t.Fatalf("message %d command = %q, want %q", i, msg.Command, wantCommands[i])
 352  		}
 353  		if !bytes.Equal(msg.Payload, wantPayloads[i]) {
 354  			t.Fatalf("message %d payload = %x, want %x", i, msg.Payload, wantPayloads[i])
 355  		}
 356  	}
 357  }
 358  
 359  func TestReaderCleanEOF(t *testing.T) {
 360  	r := NewReader(bytes.NewReader(nil), MainNet)
 361  	if _, err := r.Next(); !errors.Is(err, io.EOF) {
 362  		t.Fatalf("Next on empty stream = %v, want io.EOF", err)
 363  	}
 364  }
 365  
 366  func TestReaderTruncatedStream(t *testing.T) {
 367  	frame, err := EncodeMessage(MainNet, CmdPing, make([]byte, 8))
 368  	if err != nil {
 369  		t.Fatalf("EncodeMessage: %v", err)
 370  	}
 371  	stream := append(append([]byte(nil), frame...), frame[:5]...)
 372  	r := NewReader(bytes.NewReader(stream), MainNet)
 373  	if _, err := r.Next(); err != nil {
 374  		t.Fatalf("first Next: %v", err)
 375  	}
 376  	_, err = r.Next()
 377  	if !errors.Is(err, io.ErrUnexpectedEOF) {
 378  		t.Fatalf("second Next = %v, want io.ErrUnexpectedEOF", err)
 379  	}
 380  }
 381  
 382  func TestReaderHeaderOnlyFrame(t *testing.T) {
 383  	// A frame header that declares a payload, followed by end of stream, is
 384  	// truncation, not a clean end of the message stream.
 385  	frame, err := EncodeMessage(MainNet, CmdPing, make([]byte, 8))
 386  	if err != nil {
 387  		t.Fatalf("EncodeMessage: %v", err)
 388  	}
 389  	r := NewReader(bytes.NewReader(frame[:MessageHeaderSize]), MainNet)
 390  	if _, err := r.Next(); !errors.Is(err, io.ErrUnexpectedEOF) {
 391  		t.Fatalf("Next = %v, want io.ErrUnexpectedEOF", err)
 392  	}
 393  }
 394  
 395  func TestEncodeMessagePayloadError(t *testing.T) {
 396  	headers := &Headers{Headers: make([]BlockHeader, MaxBlockHeadersPerMsg+1)}
 397  	if _, err := EncodeMessagePayload(MainNet, CmdHeaders, headers); !errors.Is(err, ErrTooManyItems) {
 398  		t.Fatalf("EncodeMessagePayload error = %v, want ErrTooManyItems", err)
 399  	}
 400  }
 401  
 402  func TestMessageDecodeError(t *testing.T) {
 403  	frame, err := EncodeMessage(MainNet, CmdVerAck, []byte{0x01})
 404  	if err != nil {
 405  		t.Fatalf("EncodeMessage: %v", err)
 406  	}
 407  	msg, err := DecodeMessage(MainNet, bytes.NewReader(frame))
 408  	if err != nil {
 409  		t.Fatalf("DecodeMessage: %v", err)
 410  	}
 411  	if err := msg.Decode(&VerAck{}); !errors.Is(err, ErrInvalidPayload) {
 412  		t.Fatalf("Decode error = %v, want ErrInvalidPayload", err)
 413  	}
 414  }
 415  
 416  // TestThePayloadLimitIsEnforcedBeforeAllocation: a frame that claims more than the cap is
 417  // refused from its header alone, so a hostile peer cannot make a block read allocate 32 MiB.
 418  func TestThePayloadLimitIsEnforcedBeforeAllocation(t *testing.T) {
 419  	const magic = RegTest
 420  
 421  	header := make([]byte, MessageHeaderSize)
 422  	putMagic(header[0:4], magic)
 423  	copy(header[4:4+CommandSize], CmdBlock)
 424  	binary.LittleEndian.PutUint32(header[16:20], 9<<20)
 425  
 426  	if _, err := decodeMessageLimit(magic, bytes.NewReader(header), 8<<20); !errors.Is(err, ErrPayloadTooLarge) {
 427  		t.Fatalf("a 9 MiB frame under an 8 MiB cap: %v", err)
 428  	}
 429  
 430  	// The reader carries the same limit, which is what the peer uses.
 431  	reader := NewReader(bytes.NewReader(header), magic)
 432  	reader.SetLimit(8 << 20)
 433  
 434  	if _, err := reader.Next(); !errors.Is(err, ErrPayloadTooLarge) {
 435  		t.Fatalf("the reader: %v", err)
 436  	}
 437  }
 438  
 439  // TestTheReaderLimitResets: a block read must not lower the cap every other read lives under.
 440  func TestTheReaderLimitResets(t *testing.T) {
 441  	reader := NewReader(bytes.NewReader(nil), 1)
 442  	reader.SetLimit(8 << 20)
 443  	reader.SetLimit(MaxPayload)
 444  
 445  	if reader.limit != MaxPayload {
 446  		t.Fatalf("the limit is %d", reader.limit)
 447  	}
 448  }
 449