github.com/google/trillian-examples@v0.0.0-20240520080811-0d40d35cef0e/binary_transparency/firmware/internal/client/mapclient_test.go (about) 1 // Copyright 2021 Google LLC. All Rights Reserved. 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 client_test 16 17 import ( 18 "bytes" 19 "context" 20 "fmt" 21 "net/http" 22 "net/http/httptest" 23 "strings" 24 "testing" 25 26 "github.com/google/go-cmp/cmp" 27 "github.com/google/trillian-examples/binary_transparency/firmware/api" 28 "github.com/google/trillian-examples/binary_transparency/firmware/internal/client" 29 ) 30 31 func TestMapCheckpoint(t *testing.T) { 32 for _, test := range []struct { 33 desc string 34 body string 35 want api.MapCheckpoint 36 wantErr bool 37 }{ 38 { 39 desc: "valid 1", 40 body: `{ "Revision": 42, "LogSize": 1, "LogCheckpoint": "QyE=", "RootHash": "EjQ="}`, 41 want: api.MapCheckpoint{Revision: 42, LogSize: 1, LogCheckpoint: []byte{0x43, 0x21}, RootHash: []byte{0x12, 0x34}}, 42 }, { 43 desc: "valid 2", 44 body: `{ "Revision": 42, "LogSize": 10, "LogCheckpoint": "/u1C", "RootHash": "NBI="}`, 45 want: api.MapCheckpoint{Revision: 42, LogSize: 10, LogCheckpoint: []byte{0xfe, 0xed, 0x42}, RootHash: []byte{0x34, 0x12}}, 46 }, { 47 desc: "garbage", 48 body: `garbage`, 49 wantErr: true, 50 }, 51 } { 52 t.Run(test.desc, func(t *testing.T) { 53 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 54 if !strings.HasSuffix(r.URL.Path, api.MapHTTPGetCheckpoint) { 55 t.Fatalf("Got unexpected HTTP request on %q", r.URL.Path) 56 } 57 fmt.Fprintln(w, test.body) 58 })) 59 defer ts.Close() 60 61 c, err := client.NewMapClient(ts.URL) 62 if err != nil { 63 t.Fatalf("Failed to create client: %q", err) 64 } 65 cp, err := c.MapCheckpoint() 66 switch { 67 case err != nil && !test.wantErr: 68 t.Fatalf("Got unexpected error %q", err) 69 case err == nil && test.wantErr: 70 t.Fatal("Got no error, but wanted error") 71 case err != nil && test.wantErr: 72 // expected error 73 default: 74 if d := cmp.Diff(cp, test.want); len(d) != 0 { 75 t.Fatalf("Got checkpoint with diff: %s", d) 76 } 77 } 78 }) 79 } 80 } 81 82 func TestAggregation(t *testing.T) { 83 for _, test := range []struct { 84 desc string 85 rev, index uint64 86 bodies map[string]string 87 wantAgg []byte 88 wantErr bool 89 }{ 90 { 91 desc: "valid 1", 92 rev: 1, 93 index: 0, 94 bodies: map[string]string{ 95 "/ftmap/v0/tile/in-revision/1/at-path/": `{"Path":"","Leaves":[{"Path":"Rg==","Hash":"M7DmUN5R2auo88WMjg+EcijUzfX085QdHuTzx7Rrwgs="},{"Path":"7A==","Hash":"fK4jTvvbd90D29jYrsBrmWNG83416K1WhgS5T5qYpEI="}]}`, 96 "/ftmap/v0/tile/in-revision/1/at-path/Rg==": `{"Path":"Rg==","Leaves":[{"Path":"IRCmyCYwSorPfJ/NXqlkZdVYvs4KKtRYuV27zLJjCg==","Hash":"sE0GFv87tf0j7YciRBVFk4pFKExwVRhwykxdPz70Dxw="}]}`, 97 "/ftmap/v0/aggregation/in-revision/1/for-firmware-at-index/0": `{"Index":0,"Good":true}`, 98 }, 99 wantAgg: []byte(`{"Index":0,"Good":true}`), 100 }, { 101 desc: "valid 2", 102 rev: 2, 103 index: 1, 104 bodies: map[string]string{ 105 "/ftmap/v0/tile/in-revision/2/at-path/": `{"Path":"","Leaves":[{"Path":"Rg==","Hash":"M7DmUN5R2auo88WMjg+EcijUzfX085QdHuTzx7Rrwgs="},{"Path":"7A==","Hash":"fK4jTvvbd90D29jYrsBrmWNG83416K1WhgS5T5qYpEI="}]}`, 106 "/ftmap/v0/tile/in-revision/2/at-path/7A==": `{"Path":"7A==","Leaves":[{"Path":"Kk19hKuMqnXxlJGG0LQ5y8LbzyPhmCCDMxRmPAowFw==","Hash":"+d6n+Cubqrkvx6vwQg0f2M3ZPub3a8jf/HICam0T3sM="},{"Path":"z+HuPYEme3qpfllqffSoL8jKc8VLtf3njh/nVoksCA==","Hash":"rxQDwfN/PhVD+lF2FtVkzUb9ha1G+4OHE7ZaIvSow9Y="}]}`, 107 "/ftmap/v0/aggregation/in-revision/2/for-firmware-at-index/1": `{"Index":1,"Good":false}`, 108 }, 109 wantAgg: []byte(`{"Index":1,"Good":false}`), 110 }, { 111 desc: "garbage", 112 rev: 42, 113 index: 22, 114 bodies: map[string]string{ 115 "/ftmap/v0/tile/in-revision/42/at-path/": `moose`, 116 "/ftmap/v0/tile/in-revision/42/at-path/RA==": `loose`, 117 "/ftmap/v0/aggregation/in-revision/42/for-firmware-at-index/22": `house`, 118 }, 119 wantErr: true, 120 }, 121 } { 122 t.Run(test.desc, func(t *testing.T) { 123 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 124 if body, ok := test.bodies[r.URL.Path]; ok { 125 fmt.Fprint(w, body) 126 } else { 127 t.Fatalf("Got unexpected HTTP request on %q", r.URL.Path) 128 } 129 })) 130 defer ts.Close() 131 132 c, err := client.NewMapClient(ts.URL) 133 if err != nil { 134 t.Fatalf("Failed to create client: %q", err) 135 } 136 agg, proof, err := c.Aggregation(context.Background(), test.rev, test.index) 137 switch { 138 case err != nil && !test.wantErr: 139 t.Fatalf("Got unexpected error %q", err) 140 case err == nil && test.wantErr: 141 t.Fatal("Got no error, but wanted error") 142 case err != nil && test.wantErr: 143 // expected error 144 default: 145 if !bytes.Equal(agg, test.wantAgg) { 146 t.Errorf("Got wrong aggregation (%x != %x)", agg, test.wantAgg) 147 } 148 // TODO(mhutchinson): more fully test the generated inclusion proof. 149 if proof.Key == nil || proof.Value == nil || len(proof.Proof) != 256 { 150 t.Errorf("Generated proof is malformed: %+v", proof) 151 } 152 } 153 }) 154 } 155 }