github.com/decred/dcrlnd@v0.7.6/watchtower/wtdb/codec_test.go (about)

     1  package wtdb_test
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/binary"
     6  	"io"
     7  	"math/rand"
     8  	"net"
     9  	"reflect"
    10  	"testing"
    11  	"testing/quick"
    12  
    13  	"github.com/decred/dcrd/dcrec/secp256k1/v4"
    14  	"github.com/decred/dcrlnd/tor"
    15  	"github.com/decred/dcrlnd/watchtower/wtdb"
    16  )
    17  
    18  func randPubKey() (*secp256k1.PublicKey, error) {
    19  	priv, err := secp256k1.GeneratePrivateKey()
    20  	if err != nil {
    21  		return nil, err
    22  	}
    23  
    24  	return priv.PubKey(), nil
    25  }
    26  
    27  func randTCP4Addr(r *rand.Rand) (*net.TCPAddr, error) {
    28  	var ip [4]byte
    29  	if _, err := r.Read(ip[:]); err != nil {
    30  		return nil, err
    31  	}
    32  
    33  	var port [2]byte
    34  	if _, err := r.Read(port[:]); err != nil {
    35  		return nil, err
    36  	}
    37  
    38  	addrIP := net.IP(ip[:])
    39  	addrPort := int(binary.BigEndian.Uint16(port[:]))
    40  
    41  	return &net.TCPAddr{IP: addrIP, Port: addrPort}, nil
    42  }
    43  
    44  func randTCP6Addr(r *rand.Rand) (*net.TCPAddr, error) {
    45  	var ip [16]byte
    46  	if _, err := r.Read(ip[:]); err != nil {
    47  		return nil, err
    48  	}
    49  
    50  	var port [2]byte
    51  	if _, err := r.Read(port[:]); err != nil {
    52  		return nil, err
    53  	}
    54  
    55  	addrIP := net.IP(ip[:])
    56  	addrPort := int(binary.BigEndian.Uint16(port[:]))
    57  
    58  	return &net.TCPAddr{IP: addrIP, Port: addrPort}, nil
    59  }
    60  
    61  func randV2OnionAddr(r *rand.Rand) (*tor.OnionAddr, error) {
    62  	var serviceID [tor.V2DecodedLen]byte
    63  	if _, err := r.Read(serviceID[:]); err != nil {
    64  		return nil, err
    65  	}
    66  
    67  	var port [2]byte
    68  	if _, err := r.Read(port[:]); err != nil {
    69  		return nil, err
    70  	}
    71  
    72  	onionService := tor.Base32Encoding.EncodeToString(serviceID[:])
    73  	onionService += tor.OnionSuffix
    74  	addrPort := int(binary.BigEndian.Uint16(port[:]))
    75  
    76  	return &tor.OnionAddr{OnionService: onionService, Port: addrPort}, nil
    77  }
    78  
    79  func randV3OnionAddr(r *rand.Rand) (*tor.OnionAddr, error) {
    80  	var serviceID [tor.V3DecodedLen]byte
    81  	if _, err := r.Read(serviceID[:]); err != nil {
    82  		return nil, err
    83  	}
    84  
    85  	var port [2]byte
    86  	if _, err := r.Read(port[:]); err != nil {
    87  		return nil, err
    88  	}
    89  
    90  	onionService := tor.Base32Encoding.EncodeToString(serviceID[:])
    91  	onionService += tor.OnionSuffix
    92  	addrPort := int(binary.BigEndian.Uint16(port[:]))
    93  
    94  	return &tor.OnionAddr{OnionService: onionService, Port: addrPort}, nil
    95  }
    96  
    97  func randAddrs(r *rand.Rand) ([]net.Addr, error) {
    98  	tcp4Addr, err := randTCP4Addr(r)
    99  	if err != nil {
   100  		return nil, err
   101  	}
   102  
   103  	tcp6Addr, err := randTCP6Addr(r)
   104  	if err != nil {
   105  		return nil, err
   106  	}
   107  
   108  	v2OnionAddr, err := randV2OnionAddr(r)
   109  	if err != nil {
   110  		return nil, err
   111  	}
   112  
   113  	v3OnionAddr, err := randV3OnionAddr(r)
   114  	if err != nil {
   115  		return nil, err
   116  	}
   117  
   118  	return []net.Addr{tcp4Addr, tcp6Addr, v2OnionAddr, v3OnionAddr}, nil
   119  }
   120  
   121  // dbObject is abstract object support encoding and decoding.
   122  type dbObject interface {
   123  	Encode(io.Writer) error
   124  	Decode(io.Reader) error
   125  }
   126  
   127  // TestCodec serializes and deserializes wtdb objects in order to test that that
   128  // the codec understands all of the required field types. The test also asserts
   129  // that decoding an object into another results in an equivalent object.
   130  func TestCodec(tt *testing.T) {
   131  
   132  	var t *testing.T
   133  	mainScenario := func(obj dbObject) bool {
   134  		// Ensure encoding the object succeeds.
   135  		var b bytes.Buffer
   136  		err := obj.Encode(&b)
   137  		if err != nil {
   138  			t.Fatalf("unable to encode: %v", err)
   139  			return false
   140  		}
   141  
   142  		var obj2 dbObject
   143  		switch obj.(type) {
   144  		case *wtdb.SessionInfo:
   145  			obj2 = &wtdb.SessionInfo{}
   146  		case *wtdb.SessionStateUpdate:
   147  			obj2 = &wtdb.SessionStateUpdate{}
   148  		case *wtdb.ClientSessionBody:
   149  			obj2 = &wtdb.ClientSessionBody{}
   150  		case *wtdb.CommittedUpdateBody:
   151  			obj2 = &wtdb.CommittedUpdateBody{}
   152  		case *wtdb.BackupID:
   153  			obj2 = &wtdb.BackupID{}
   154  		case *wtdb.Tower:
   155  			obj2 = &wtdb.Tower{}
   156  		case *wtdb.ClientChanSummary:
   157  			obj2 = &wtdb.ClientChanSummary{}
   158  		default:
   159  			t.Fatalf("unknown type: %T", obj)
   160  			return false
   161  		}
   162  
   163  		// Ensure decoding the object succeeds.
   164  		err = obj2.Decode(bytes.NewReader(b.Bytes()))
   165  		if err != nil {
   166  			t.Fatalf("unable to decode: %v", err)
   167  			return false
   168  		}
   169  
   170  		// Assert the original and decoded object match.
   171  		if !reflect.DeepEqual(obj, obj2) {
   172  			t.Fatalf("encode/decode mismatch, want: %v, "+
   173  				"got: %v", obj, obj2)
   174  			return false
   175  		}
   176  
   177  		return true
   178  	}
   179  
   180  	customTypeGen := map[string]func([]reflect.Value, *rand.Rand){
   181  		"Tower": func(v []reflect.Value, r *rand.Rand) {
   182  			pk, err := randPubKey()
   183  			if err != nil {
   184  				t.Fatalf("unable to generate pubkey: %v", err)
   185  				return
   186  			}
   187  
   188  			addrs, err := randAddrs(r)
   189  			if err != nil {
   190  				t.Fatalf("unable to generate addrs: %v", err)
   191  				return
   192  			}
   193  
   194  			obj := wtdb.Tower{
   195  				IdentityKey: pk,
   196  				Addresses:   addrs,
   197  			}
   198  
   199  			v[0] = reflect.ValueOf(obj)
   200  		},
   201  	}
   202  
   203  	tests := []struct {
   204  		name     string
   205  		scenario interface{}
   206  	}{
   207  		{
   208  			name: "SessionInfo",
   209  			scenario: func(obj wtdb.SessionInfo) bool {
   210  				return mainScenario(&obj)
   211  			},
   212  		},
   213  		{
   214  			name: "SessionStateUpdate",
   215  			scenario: func(obj wtdb.SessionStateUpdate) bool {
   216  				return mainScenario(&obj)
   217  			},
   218  		},
   219  		{
   220  			name: "ClientSessionBody",
   221  			scenario: func(obj wtdb.ClientSessionBody) bool {
   222  				return mainScenario(&obj)
   223  			},
   224  		},
   225  		{
   226  			name: "CommittedUpdateBody",
   227  			scenario: func(obj wtdb.CommittedUpdateBody) bool {
   228  				return mainScenario(&obj)
   229  			},
   230  		},
   231  		{
   232  			name: "BackupID",
   233  			scenario: func(obj wtdb.BackupID) bool {
   234  				return mainScenario(&obj)
   235  			},
   236  		},
   237  		{
   238  			name: "Tower",
   239  			scenario: func(obj wtdb.Tower) bool {
   240  				return mainScenario(&obj)
   241  			},
   242  		},
   243  		{
   244  			name: "ClientChanSummary",
   245  			scenario: func(obj wtdb.ClientChanSummary) bool {
   246  				return mainScenario(&obj)
   247  			},
   248  		},
   249  	}
   250  
   251  	for _, test := range tests {
   252  		tt.Run(test.name, func(h *testing.T) {
   253  			t = h
   254  
   255  			var config *quick.Config
   256  			if valueGen, ok := customTypeGen[test.name]; ok {
   257  				config = &quick.Config{
   258  					Values: valueGen,
   259  				}
   260  			}
   261  
   262  			err := quick.Check(test.scenario, config)
   263  			if err != nil {
   264  				t.Fatalf("fuzz checks for msg=%s failed: %v",
   265  					test.name, err)
   266  			}
   267  		})
   268  	}
   269  }