github.com/google/trillian-examples@v0.0.0-20240520080811-0d40d35cef0e/binary_transparency/firmware/internal/ftmap/mapdb.go (about) 1 // Copyright 2021 Google LLC 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 // https://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 ftmap 16 17 import ( 18 "database/sql" 19 "encoding/json" 20 "errors" 21 "fmt" 22 "time" 23 24 "github.com/google/trillian-examples/binary_transparency/firmware/api" 25 "github.com/google/trillian/experimental/batchmap" 26 "github.com/google/trillian/types" 27 ) 28 29 // NoRevisionsFound is returned when the DB appears valid but has no revisions in it. 30 type NoRevisionsFound = error 31 32 // MapDB provides read/write access to the generated Map tiles. 33 type MapDB struct { 34 db *sql.DB 35 } 36 37 // NewMapDB creates a MapDB using a file at the given location. 38 // If the file doesn't exist it will be created. 39 func NewMapDB(location string) (*MapDB, error) { 40 db, err := sql.Open("sqlite3", location) 41 if err != nil { 42 return nil, err 43 } 44 mapDB := &MapDB{ 45 db: db, 46 } 47 return mapDB, mapDB.Init() 48 } 49 50 // Init creates the database tables if needed. 51 func (d *MapDB) Init() error { 52 if _, err := d.db.Exec("CREATE TABLE IF NOT EXISTS revisions (revision INTEGER PRIMARY KEY, datetime TIMESTAMP, logroot BLOB, count INTEGER)"); err != nil { 53 return err 54 } 55 if _, err := d.db.Exec("CREATE TABLE IF NOT EXISTS tiles (revision INTEGER, path BLOB, tile BLOB, PRIMARY KEY (revision, path))"); err != nil { 56 return err 57 } 58 if _, err := d.db.Exec("CREATE TABLE IF NOT EXISTS logs (deviceID BLOB, revision INTEGER, leaves BLOB, PRIMARY KEY (deviceID, revision))"); err != nil { 59 return err 60 } 61 // We use an INTEGER for a boolean to make life easy across multiple DB implementations. 62 if _, err := d.db.Exec("CREATE TABLE IF NOT EXISTS aggregations (fwLogIndex INTEGER, revision INTEGER, good INTEGER, PRIMARY KEY (fwLogIndex, revision))"); err != nil { 63 return err 64 } 65 return nil 66 } 67 68 // NextWriteRevision gets the revision that the next generation of the map should be written at. 69 func (d *MapDB) NextWriteRevision() (int, error) { 70 var rev sql.NullInt32 71 // TODO(mhutchinson): This should be updated to include the max of "tiles" or "logs". 72 if err := d.db.QueryRow("SELECT MAX(revision) FROM tiles").Scan(&rev); err != nil { 73 return 0, fmt.Errorf("failed to get max revision: %v", err) 74 } 75 if rev.Valid { 76 return int(rev.Int32) + 1, nil 77 } 78 return 0, nil 79 } 80 81 // LatestRevision gets the metadata for the last completed write. 82 func (d *MapDB) LatestRevision() (rev int, logroot types.LogRootV1, count int64, err error) { 83 var sqlRev sql.NullInt32 84 var lcpRaw []byte 85 if err := d.db.QueryRow("SELECT revision, logroot, count FROM revisions ORDER BY revision DESC LIMIT 1").Scan(&sqlRev, &lcpRaw, &count); err != nil { 86 return 0, types.LogRootV1{}, 0, fmt.Errorf("failed to get latest revision: %v", err) 87 } 88 if sqlRev.Valid { 89 if err := logroot.UnmarshalBinary(lcpRaw); err != nil { 90 return 0, types.LogRootV1{}, 0, fmt.Errorf("failed to get unmarshal log root: %v", err) 91 } 92 return int(sqlRev.Int32), logroot, count, nil 93 } 94 return 0, types.LogRootV1{}, 0, NoRevisionsFound(errors.New("no revisions found")) 95 } 96 97 // Tile gets the tile at the given path in the given revision of the map. 98 func (d *MapDB) Tile(revision int, path []byte) (*batchmap.Tile, error) { 99 var bs []byte 100 if err := d.db.QueryRow("SELECT tile FROM tiles WHERE revision=? AND path=?", revision, path).Scan(&bs); err != nil { 101 return nil, err 102 } 103 tile := &batchmap.Tile{} 104 if err := json.Unmarshal(bs, tile); err != nil { 105 return nil, fmt.Errorf("failed to parse tile at revision=%d, path=%x: %v", revision, path, err) 106 } 107 return tile, nil 108 } 109 110 // Aggregation gets the aggregation for the firmware at the given log index. 111 func (d *MapDB) Aggregation(revision int, fwLogIndex uint64) (api.AggregatedFirmware, error) { 112 var good int 113 if err := d.db.QueryRow("SELECT good FROM aggregations WHERE fwLogIndex=? AND revision=?", fwLogIndex, revision).Scan(&good); err != nil { 114 return api.AggregatedFirmware{}, err 115 } 116 return api.AggregatedFirmware{ 117 Index: fwLogIndex, 118 Good: good > 0, 119 }, nil 120 } 121 122 // WriteRevision writes the metadata for a completed run into the database. 123 // If this method isn't called then the tiles may be written but this revision will be 124 // skipped by sensible readers because the provenance information isn't available. 125 func (d *MapDB) WriteRevision(rev int, logCheckpoint []byte, count int64) error { 126 now := time.Now() 127 _, err := d.db.Exec("INSERT INTO revisions (revision, datetime, logroot, count) VALUES (?, ?, ?, ?)", rev, now, logCheckpoint, count) 128 if err != nil { 129 return fmt.Errorf("failed to write revision: %w", err) 130 } 131 return nil 132 } 133 134 // Versions gets the log of versions for the given module in the given map revision. 135 func (d *MapDB) Versions(revision int, module string) ([]string, error) { 136 var bs []byte 137 if err := d.db.QueryRow("SELECT leaves FROM logs WHERE revision=? AND module=?", revision, module).Scan(&bs); err != nil { 138 return nil, err 139 } 140 var versions []string 141 if err := json.Unmarshal(bs, &versions); err != nil { 142 return nil, fmt.Errorf("failed to parse tile at revision=%d, path=%x: %v", revision, module, err) 143 } 144 return versions, nil 145 }