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 }