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  }