diff --git a/watchtower/wtclient/fuzz_test.go b/watchtower/wtclient/fuzz_test.go new file mode 100644 index 000000000..f8fb1fc42 --- /dev/null +++ b/watchtower/wtclient/fuzz_test.go @@ -0,0 +1,120 @@ +package wtclient + +import ( + "encoding/binary" + "net" + "testing" + + "github.com/stretchr/testify/require" +) + +// getAddress generates an address from the input data or returns nil if there +// aren't enough bytes remaining in the input data. +func getAddress(data *[]byte) net.Addr { + if len(*data) < 6 { + return nil + } + + addr := &net.TCPAddr{ + IP: net.IP((*data)[0:4]), + Port: int(binary.BigEndian.Uint16((*data)[4:6])), + } + + *data = (*data)[6:] + + return addr +} + +// getAddressIterator returns an AddressIterator pre-filled with addresses +// generated from the input data. +func getAddressIterator(data *[]byte) AddressIterator { + var addrs []net.Addr + + // Always attempt to generate at least one address from the input data. + // Continue generating addresses if the next data byte is 0xff. + for addr := getAddress(data); addr != nil; addr = getAddress(data) { + addrs = append(addrs, addr) + + // Only read another address if the next byte is 0xff. + if len(*data) < 1 || (*data)[0] != 0xff { + break + } + *data = (*data)[1:] + } + + iter, err := newAddressIterator(addrs...) + if err != nil { + return nil + } + + return iter +} + +// FuzzAddressIterator tests that addressIterator does not panic for any +// sequence of methods called and that the iterator's list is never empty and +// never contains a nil address. +func FuzzAddressIterator(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + iter := getAddressIterator(&data) + if iter == nil { + return + } + + // Use the next byte in data to determine the next method call. + for len(data) >= 1 { + cmd := data[0] + data = data[1:] + + switch cmd { + case 0x00: + addr, err := iter.Next() + if err == nil { + require.NotNil(t, addr) + } + case 0x01: + addr, err := iter.NextAndLock() + if err == nil { + require.NotNil(t, addr) + } + case 0x02: + addr := iter.Peek() + require.NotNil(t, addr) + case 0x03: + addr := iter.PeekAndLock() + require.NotNil(t, addr) + case 0x04: + if addr := getAddress(&data); addr != nil { + iter.ReleaseLock(addr) + } + case 0x05: + if addr := getAddress(&data); addr != nil { + iter.Add(addr) + } + case 0x06: + if addr := getAddress(&data); addr != nil { + _ = iter.Remove(addr) + } + case 0x07: + _ = iter.HasLocked() + case 0x08: + addrs := iter.GetAll() + require.NotEmpty(t, addrs) + for _, addr := range addrs { + require.NotNil(t, addr) + } + case 0x09: + iter.Reset() + case 0x0a: + iter = iter.Copy() + default: + // By returning instead of continuing, we + // provide backwards compatibility for our + // corpus. If we continued here, some current + // inputs would cause a different sequence of + // events if we later added a new case to the + // switch. + return + } + } + }) +}