roughtime.googlesource.com/roughtime.git@v0.0.0-20201210012726-dd529367052d/go/client/client_test.go (about) 1 // Copyright 2016 The Roughtime Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. */ 14 15 package main 16 17 import ( 18 "crypto/rand" 19 "encoding/json" 20 "math/big" 21 "net" 22 "strconv" 23 "sync" 24 "testing" 25 "time" 26 27 "golang.org/x/crypto/ed25519" 28 "roughtime.googlesource.com/go/config" 29 "roughtime.googlesource.com/go/protocol" 30 ) 31 32 const ( 33 nonsenseReply = ^time.Duration(0) 34 maxRadius = 10 * time.Second 35 ) 36 37 type timeSpan struct { 38 midpoint uint64 39 radius time.Duration 40 } 41 42 var timeEstablishmentTests = []struct { 43 quorum int 44 times []timeSpan 45 shouldEstablish bool 46 shouldSignalMisbehaviour bool 47 shouldHaveErrors []int 48 }{ 49 { 50 quorum: 1, 51 times: []timeSpan{ 52 timeSpan{10, 5}, 53 }, 54 shouldEstablish: true, 55 }, 56 { 57 quorum: 1, 58 times: []timeSpan{ 59 timeSpan{10, 5}, 60 timeSpan{20, 5}, 61 }, 62 shouldEstablish: true, 63 }, 64 { 65 quorum: 2, 66 times: []timeSpan{ 67 timeSpan{100e6, maxRadius}, 68 timeSpan{200e6, maxRadius}, 69 }, 70 shouldEstablish: false, 71 }, 72 { 73 quorum: 2, 74 times: []timeSpan{ 75 timeSpan{175e6, maxRadius}, 76 timeSpan{200e6, maxRadius}, 77 timeSpan{201e6, maxRadius}, 78 }, 79 shouldEstablish: true, 80 }, 81 { 82 quorum: 3, 83 times: []timeSpan{ 84 timeSpan{100e6, maxRadius}, 85 timeSpan{101e6, maxRadius}, 86 timeSpan{102e6, maxRadius}, 87 }, 88 shouldEstablish: true, 89 }, 90 { 91 quorum: 3, 92 times: []timeSpan{ 93 timeSpan{175e6, maxRadius}, 94 timeSpan{175e6, maxRadius}, 95 timeSpan{200e6, maxRadius}, 96 timeSpan{200e6, maxRadius}, 97 timeSpan{200e6, maxRadius}, 98 timeSpan{200e6, maxRadius}, 99 timeSpan{200e6, maxRadius}, 100 timeSpan{200e6, maxRadius}, 101 timeSpan{200e6, maxRadius}, 102 timeSpan{175e6, maxRadius}, 103 }, 104 shouldEstablish: true, 105 }, 106 { 107 // An excessive radius should be rejected as invalid. 108 quorum: 3, 109 times: []timeSpan{ 110 timeSpan{200e6, 1 * time.Hour}, 111 timeSpan{200e6, maxRadius}, 112 timeSpan{200e6, maxRadius}, 113 }, 114 shouldHaveErrors: []int{0}, 115 shouldEstablish: false, 116 }, 117 { 118 // A zero radius is acceptable if the midpoint is reasonable. 119 quorum: 3, 120 times: []timeSpan{ 121 timeSpan{200e6, 0}, 122 timeSpan{200e6, maxRadius}, 123 timeSpan{200e6, maxRadius}, 124 }, 125 shouldEstablish: true, 126 }, 127 { 128 quorum: 3, 129 times: []timeSpan{ 130 timeSpan{201e6, 1 * time.Second}, 131 timeSpan{201e6, 2 * time.Second}, 132 timeSpan{201e6, 3 * time.Second}, 133 }, 134 shouldEstablish: true, 135 }, 136 { 137 quorum: 2, 138 times: []timeSpan{ 139 timeSpan{100e6, maxRadius}, 140 timeSpan{200e6, maxRadius}, 141 timeSpan{200e6, maxRadius}, 142 }, 143 shouldEstablish: true, 144 shouldSignalMisbehaviour: true, 145 }, 146 { 147 quorum: 2, 148 times: []timeSpan{ 149 timeSpan{0, nonsenseReply}, 150 timeSpan{0, nonsenseReply}, 151 timeSpan{200e6, maxRadius}, 152 timeSpan{200e6, maxRadius}, 153 }, 154 shouldEstablish: true, 155 shouldHaveErrors: []int{0, 1}, 156 }, 157 } 158 159 func TestEstablishment(t *testing.T) { 160 client := &Client{ 161 nowFunc: func() time.Duration { 162 // The monotonic clock always returns zero to avoid 163 // query latency affecting the results. 164 return 0 165 }, 166 167 Permutation: func(n int) []int { 168 // The permutation is fixed so that "servers" will be 169 // queried in the order given. 170 ret := make([]int, n) 171 for i := range ret { 172 ret[i] = i 173 } 174 175 return ret 176 }, 177 178 MaxRadius: maxRadius, 179 MaxDifference: 60 * time.Second, 180 QueryTimeout: 30 * time.Second, 181 NumQueries: 1, 182 } 183 184 var waitGroup sync.WaitGroup 185 defer waitGroup.Wait() 186 187 for i, test := range timeEstablishmentTests { 188 var handles []*serverHandle 189 var servers []config.Server 190 191 for j, span := range test.times { 192 handle, err := startServer(&waitGroup, span) 193 if err != nil { 194 t.Fatal(err) 195 } 196 197 handles = append(handles, handle) 198 servers = append(servers, config.Server{ 199 Name: strconv.Itoa(j), 200 PublicKey: handle.publicKey, 201 Addresses: []config.ServerAddress{ 202 config.ServerAddress{ 203 Protocol: "udp", 204 Address: handle.addr.String(), 205 }, 206 }, 207 }) 208 defer handle.Close() 209 } 210 211 var chain config.Chain 212 result, err := client.EstablishTime(&chain, test.quorum, servers) 213 if err != nil { 214 t.Fatal(err) 215 } 216 217 if test.shouldEstablish != (result.MonoUTCDelta != nil) { 218 t.Errorf("#%d: time establishment mismatch, wanted: %t", i, test.shouldEstablish) 219 } 220 221 if test.shouldEstablish && len(chain.Links) < test.quorum { 222 t.Errorf("#%d: chain too short (%d) to be valid", i, len(chain.Links)) 223 } 224 225 // Serialize and reparse chain to ensure that it's valid. 226 chainBytes, err := json.MarshalIndent(chain, "", " ") 227 if err != nil { 228 t.Fatal(err) 229 } 230 231 if _, err := LoadChain(chainBytes); err != nil { 232 t.Errorf("#%d: resulting chain does not parse: %s", i, err) 233 } 234 235 if test.shouldSignalMisbehaviour != result.OutOfRangeAnswer { 236 t.Errorf("#%d: misbehaviour mismatch, wanted: %t", i, test.shouldSignalMisbehaviour) 237 } 238 239 if len(result.ServerErrors) != len(test.shouldHaveErrors) { 240 t.Errorf("#%d: server errors mismatch, got %#v but wanted errors from #%v", i, result.ServerErrors, test.shouldHaveErrors) 241 } 242 243 for _, serverNumber := range test.shouldHaveErrors { 244 if _, ok := result.ServerErrors[strconv.Itoa(serverNumber)]; !ok { 245 t.Errorf("#%d: missing error for server %d", i, serverNumber) 246 } 247 } 248 } 249 } 250 251 type serverHandle struct { 252 publicKey []byte 253 addr *net.UDPAddr 254 } 255 256 func (handle *serverHandle) Close() { 257 conn, err := net.DialUDP("udp", nil, handle.addr) 258 if err != nil { 259 panic(err) 260 } 261 262 conn.Write([]byte{0}) 263 } 264 265 func startServer(wg *sync.WaitGroup, span timeSpan) (*serverHandle, error) { 266 rootPublic, rootPrivate, err := ed25519.GenerateKey(rand.Reader) 267 if err != nil { 268 return nil, err 269 } 270 271 onlinePublicKey, onlinePrivateKey, err := ed25519.GenerateKey(rand.Reader) 272 if err != nil { 273 return nil, err 274 } 275 276 cert, err := protocol.CreateCertificate(0, ^uint64(0), onlinePublicKey, rootPrivate) 277 if err != nil { 278 return nil, err 279 } 280 281 conn, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 0}) 282 if err != nil { 283 return nil, err 284 } 285 286 localAddr, ok := conn.LocalAddr().(*net.UDPAddr) 287 if !ok { 288 panic("not a UDP address") 289 } 290 291 wg.Add(1) 292 293 go func() { 294 var packetBuf [protocol.MinRequestSize]byte 295 defer wg.Done() 296 297 for { 298 n, sourceAddr, err := conn.ReadFromUDP(packetBuf[:]) 299 if err != nil { 300 panic(err) 301 } 302 303 if n == 1 && packetBuf[0] == 0 { 304 return 305 } 306 307 if span.radius == nonsenseReply { 308 conn.WriteToUDP([]byte{1, 2, 3, 4, 5}, sourceAddr) 309 continue 310 } 311 312 packet, err := protocol.Decode(packetBuf[:n]) 313 if err != nil { 314 println(n) 315 panic(err) 316 } 317 318 nonce, ok := packet[protocol.TagNonce] 319 if !ok || len(nonce) != protocol.NonceSize { 320 panic("missing nonce") 321 } 322 323 replies, err := protocol.CreateReplies([][]byte{nonce}, span.midpoint, uint32(span.radius/time.Microsecond), cert, onlinePrivateKey) 324 if err != nil { 325 panic(err) 326 } 327 328 conn.WriteToUDP(replies[0], sourceAddr) 329 } 330 }() 331 332 return &serverHandle{ 333 publicKey: rootPublic, 334 addr: localAddr, 335 }, nil 336 } 337 338 func TestFindNOverlapping(t *testing.T) { 339 type sample struct { 340 min int64 341 max int64 342 } 343 testcases := []struct { 344 samples []sample 345 maxN int 346 }{ 347 { 348 samples: []sample{ 349 {0, 2}, 350 {1, 3}, 351 }, 352 maxN: 2, 353 }, 354 { 355 samples: []sample{ 356 {0, 2}, 357 {1, 3}, 358 {4, 5}, 359 }, 360 maxN: 2, 361 }, 362 { 363 samples: []sample{ 364 {0, 10}, 365 {1, 2}, 366 {5, 10}, 367 {6, 10}, 368 }, 369 maxN: 3, 370 }, 371 } 372 for i, tc := range testcases { 373 samples := make([]*timeSample, len(tc.samples)) 374 for j, s := range tc.samples { 375 samples[j] = &timeSample{ 376 base: big.NewInt(0), 377 min: big.NewInt(s.min), 378 max: big.NewInt(s.max), 379 } 380 } 381 for n := 1; n <= len(samples); n++ { 382 expectedOk := n <= tc.maxN 383 _, ok := findNOverlapping(samples, n) 384 if ok != expectedOk { 385 t.Errorf("#%d: findNOverlapping(n=%d) returned %v, wanted %v", i, n, ok, expectedOk) 386 } 387 } 388 } 389 }