mirror of https://github.com/docker/docs.git
validation tests
Signed-off-by: David Lawrence <david.lawrence@docker.com> (github: endophage)
This commit is contained in:
parent
0ece438313
commit
6616bed616
|
@ -8,3 +8,4 @@ cross
|
||||||
*.swp
|
*.swp
|
||||||
.idea
|
.idea
|
||||||
*.iml
|
*.iml
|
||||||
|
coverage.out
|
||||||
|
|
|
@ -5,6 +5,11 @@
|
||||||
"./..."
|
"./..."
|
||||||
],
|
],
|
||||||
"Deps": [
|
"Deps": [
|
||||||
|
{
|
||||||
|
"ImportPath": "code.google.com/p/gosqlite/sqlite3",
|
||||||
|
"Comment": "null-16",
|
||||||
|
"Rev": "74691fb6f83716190870cde1b658538dd4b18eb0"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/BurntSushi/toml",
|
"ImportPath": "github.com/BurntSushi/toml",
|
||||||
"Rev": "bd2bdf7f18f849530ef7a1c29a4290217cab32a1"
|
"Rev": "bd2bdf7f18f849530ef7a1c29a4290217cab32a1"
|
||||||
|
@ -95,7 +100,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/endophage/gotuf",
|
"ImportPath": "github.com/endophage/gotuf",
|
||||||
"Rev": "78f263e5a0b2f74b8336ea9faef0e40eef1f439d"
|
"Rev": "4c04df9067a595ead06309f38021ea445acc1d1c"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/go-sql-driver/mysql",
|
"ImportPath": "github.com/go-sql-driver/mysql",
|
||||||
|
@ -106,6 +111,10 @@
|
||||||
"ImportPath": "github.com/golang/protobuf/proto",
|
"ImportPath": "github.com/golang/protobuf/proto",
|
||||||
"Rev": "655cdfa588ea190e901bc5590e65d5621688847c"
|
"Rev": "655cdfa588ea190e901bc5590e65d5621688847c"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/google/gofuzz",
|
||||||
|
"Rev": "bbcb9da2d746f8bdbd6a936686a0a6067ada0ec5"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/gorilla/context",
|
"ImportPath": "github.com/gorilla/context",
|
||||||
"Rev": "14f550f51af52180c2eefed15e5fd18d63c0a64a"
|
"Rev": "14f550f51af52180c2eefed15e5fd18d63c0a64a"
|
||||||
|
|
498
Godeps/_workspace/src/code.google.com/p/gosqlite/sqlite3/driver.go
generated
vendored
Normal file
498
Godeps/_workspace/src/code.google.com/p/gosqlite/sqlite3/driver.go
generated
vendored
Normal file
|
@ -0,0 +1,498 @@
|
||||||
|
// Copyright 2010 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package sqlite3 provides access to the SQLite library, version 3.
|
||||||
|
//
|
||||||
|
// The package has no exported API.
|
||||||
|
// It registers a driver for the standard Go database/sql package.
|
||||||
|
//
|
||||||
|
// import _ "code.google.com/p/gosqlite/sqlite3"
|
||||||
|
//
|
||||||
|
// (For an alternate, earlier API, see the code.google.com/p/gosqlite/sqlite package.)
|
||||||
|
package sqlite
|
||||||
|
|
||||||
|
/*
|
||||||
|
#cgo LDFLAGS: -lsqlite3
|
||||||
|
|
||||||
|
#include <sqlite3.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
// These wrappers are necessary because SQLITE_TRANSIENT
|
||||||
|
// is a pointer constant, and cgo doesn't translate them correctly.
|
||||||
|
// The definition in sqlite3.h is:
|
||||||
|
//
|
||||||
|
// typedef void (*sqlite3_destructor_type)(void*);
|
||||||
|
// #define SQLITE_STATIC ((sqlite3_destructor_type)0)
|
||||||
|
// #define SQLITE_TRANSIENT ((sqlite3_destructor_type)-1)
|
||||||
|
|
||||||
|
static int my_bind_text(sqlite3_stmt *stmt, int n, char *p, int np) {
|
||||||
|
return sqlite3_bind_text(stmt, n, p, np, SQLITE_TRANSIENT);
|
||||||
|
}
|
||||||
|
static int my_bind_blob(sqlite3_stmt *stmt, int n, void *p, int np) {
|
||||||
|
return sqlite3_bind_blob(stmt, n, p, np, SQLITE_TRANSIENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
*/
|
||||||
|
import "C"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"database/sql/driver"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
sql.Register("sqlite3", impl{})
|
||||||
|
}
|
||||||
|
|
||||||
|
type errno int
|
||||||
|
|
||||||
|
func (e errno) Error() string {
|
||||||
|
s := errText[e]
|
||||||
|
if s == "" {
|
||||||
|
return fmt.Sprintf("errno %d", int(e))
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
errError error = errno(1) // /* SQL error or missing database */
|
||||||
|
errInternal error = errno(2) // /* Internal logic error in SQLite */
|
||||||
|
errPerm error = errno(3) // /* Access permission denied */
|
||||||
|
errAbort error = errno(4) // /* Callback routine requested an abort */
|
||||||
|
errBusy error = errno(5) // /* The database file is locked */
|
||||||
|
errLocked error = errno(6) // /* A table in the database is locked */
|
||||||
|
errNoMem error = errno(7) // /* A malloc() failed */
|
||||||
|
errReadOnly error = errno(8) // /* Attempt to write a readonly database */
|
||||||
|
errInterrupt error = errno(9) // /* Operation terminated by sqlite3_interrupt()*/
|
||||||
|
errIOErr error = errno(10) // /* Some kind of disk I/O error occurred */
|
||||||
|
errCorrupt error = errno(11) // /* The database disk image is malformed */
|
||||||
|
errFull error = errno(13) // /* Insertion failed because database is full */
|
||||||
|
errCantOpen error = errno(14) // /* Unable to open the database file */
|
||||||
|
errEmpty error = errno(16) // /* Database is empty */
|
||||||
|
errSchema error = errno(17) // /* The database schema changed */
|
||||||
|
errTooBig error = errno(18) // /* String or BLOB exceeds size limit */
|
||||||
|
errConstraint error = errno(19) // /* Abort due to constraint violation */
|
||||||
|
errMismatch error = errno(20) // /* Data type mismatch */
|
||||||
|
errMisuse error = errno(21) // /* Library used incorrectly */
|
||||||
|
errNolfs error = errno(22) // /* Uses OS features not supported on host */
|
||||||
|
errAuth error = errno(23) // /* Authorization denied */
|
||||||
|
errFormat error = errno(24) // /* Auxiliary database format error */
|
||||||
|
errRange error = errno(25) // /* 2nd parameter to sqlite3_bind out of range */
|
||||||
|
errNotDB error = errno(26) // /* File opened that is not a database file */
|
||||||
|
stepRow = errno(100) // /* sqlite3_step() has another row ready */
|
||||||
|
stepDone = errno(101) // /* sqlite3_step() has finished executing */
|
||||||
|
)
|
||||||
|
|
||||||
|
var errText = map[errno]string{
|
||||||
|
1: "SQL error or missing database",
|
||||||
|
2: "Internal logic error in SQLite",
|
||||||
|
3: "Access permission denied",
|
||||||
|
4: "Callback routine requested an abort",
|
||||||
|
5: "The database file is locked",
|
||||||
|
6: "A table in the database is locked",
|
||||||
|
7: "A malloc() failed",
|
||||||
|
8: "Attempt to write a readonly database",
|
||||||
|
9: "Operation terminated by sqlite3_interrupt()*/",
|
||||||
|
10: "Some kind of disk I/O error occurred",
|
||||||
|
11: "The database disk image is malformed",
|
||||||
|
12: "NOT USED. Table or record not found",
|
||||||
|
13: "Insertion failed because database is full",
|
||||||
|
14: "Unable to open the database file",
|
||||||
|
15: "NOT USED. Database lock protocol error",
|
||||||
|
16: "Database is empty",
|
||||||
|
17: "The database schema changed",
|
||||||
|
18: "String or BLOB exceeds size limit",
|
||||||
|
19: "Abort due to constraint violation",
|
||||||
|
20: "Data type mismatch",
|
||||||
|
21: "Library used incorrectly",
|
||||||
|
22: "Uses OS features not supported on host",
|
||||||
|
23: "Authorization denied",
|
||||||
|
24: "Auxiliary database format error",
|
||||||
|
25: "2nd parameter to sqlite3_bind out of range",
|
||||||
|
26: "File opened that is not a database file",
|
||||||
|
100: "sqlite3_step() has another row ready",
|
||||||
|
101: "sqlite3_step() has finished executing",
|
||||||
|
}
|
||||||
|
|
||||||
|
type impl struct{}
|
||||||
|
|
||||||
|
func (impl) Open(name string) (driver.Conn, error) {
|
||||||
|
if C.sqlite3_threadsafe() == 0 {
|
||||||
|
return nil, errors.New("sqlite library was not compiled for thread-safe operation")
|
||||||
|
}
|
||||||
|
|
||||||
|
var db *C.sqlite3
|
||||||
|
cname := C.CString(name)
|
||||||
|
defer C.free(unsafe.Pointer(cname))
|
||||||
|
rv := C.sqlite3_open_v2(cname, &db,
|
||||||
|
C.SQLITE_OPEN_FULLMUTEX|
|
||||||
|
C.SQLITE_OPEN_READWRITE|
|
||||||
|
C.SQLITE_OPEN_CREATE,
|
||||||
|
nil)
|
||||||
|
if rv != 0 {
|
||||||
|
return nil, errno(rv)
|
||||||
|
}
|
||||||
|
if db == nil {
|
||||||
|
return nil, errors.New("sqlite succeeded without returning a database")
|
||||||
|
}
|
||||||
|
return &conn{db: db}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type conn struct {
|
||||||
|
db *C.sqlite3
|
||||||
|
closed bool
|
||||||
|
tx bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *conn) error(rv C.int) error {
|
||||||
|
if rv == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if rv == 21 || c.closed {
|
||||||
|
return errno(rv)
|
||||||
|
}
|
||||||
|
return errors.New(errno(rv).Error() + ": " + C.GoString(C.sqlite3_errmsg(c.db)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *conn) Prepare(cmd string) (driver.Stmt, error) {
|
||||||
|
if c.closed {
|
||||||
|
panic("database/sql/driver: misuse of sqlite driver: Prepare after Close")
|
||||||
|
}
|
||||||
|
cmdstr := C.CString(cmd)
|
||||||
|
defer C.free(unsafe.Pointer(cmdstr))
|
||||||
|
var s *C.sqlite3_stmt
|
||||||
|
var tail *C.char
|
||||||
|
rv := C.sqlite3_prepare_v2(c.db, cmdstr, C.int(len(cmd)+1), &s, &tail)
|
||||||
|
if rv != 0 {
|
||||||
|
return nil, c.error(rv)
|
||||||
|
}
|
||||||
|
return &stmt{c: c, stmt: s, sql: cmd, t0: time.Now()}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *conn) Close() error {
|
||||||
|
if c.closed {
|
||||||
|
panic("database/sql/driver: misuse of sqlite driver: multiple Close")
|
||||||
|
}
|
||||||
|
c.closed = true
|
||||||
|
rv := C.sqlite3_close(c.db)
|
||||||
|
c.db = nil
|
||||||
|
return c.error(rv)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *conn) exec(cmd string) error {
|
||||||
|
cstring := C.CString(cmd)
|
||||||
|
defer C.free(unsafe.Pointer(cstring))
|
||||||
|
rv := C.sqlite3_exec(c.db, cstring, nil, nil, nil)
|
||||||
|
return c.error(rv)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *conn) Begin() (driver.Tx, error) {
|
||||||
|
if c.tx {
|
||||||
|
panic("database/sql/driver: misuse of sqlite driver: multiple Tx")
|
||||||
|
}
|
||||||
|
if err := c.exec("BEGIN TRANSACTION"); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
c.tx = true
|
||||||
|
return &tx{c}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type tx struct {
|
||||||
|
c *conn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *tx) Commit() error {
|
||||||
|
if t.c == nil || !t.c.tx {
|
||||||
|
panic("database/sql/driver: misuse of sqlite driver: extra Commit")
|
||||||
|
}
|
||||||
|
t.c.tx = false
|
||||||
|
err := t.c.exec("COMMIT TRANSACTION")
|
||||||
|
t.c = nil
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *tx) Rollback() error {
|
||||||
|
if t.c == nil || !t.c.tx {
|
||||||
|
panic("database/sql/driver: misuse of sqlite driver: extra Rollback")
|
||||||
|
}
|
||||||
|
t.c.tx = false
|
||||||
|
err := t.c.exec("ROLLBACK")
|
||||||
|
t.c = nil
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
type stmt struct {
|
||||||
|
c *conn
|
||||||
|
stmt *C.sqlite3_stmt
|
||||||
|
err error
|
||||||
|
t0 time.Time
|
||||||
|
sql string
|
||||||
|
args string
|
||||||
|
closed bool
|
||||||
|
rows bool
|
||||||
|
colnames []string
|
||||||
|
coltypes []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stmt) Close() error {
|
||||||
|
if s.rows {
|
||||||
|
panic("database/sql/driver: misuse of sqlite driver: Close with active Rows")
|
||||||
|
}
|
||||||
|
if s.closed {
|
||||||
|
panic("database/sql/driver: misuse of sqlite driver: double Close of Stmt")
|
||||||
|
}
|
||||||
|
s.closed = true
|
||||||
|
rv := C.sqlite3_finalize(s.stmt)
|
||||||
|
if rv != 0 {
|
||||||
|
return s.c.error(rv)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stmt) NumInput() int {
|
||||||
|
if s.closed {
|
||||||
|
panic("database/sql/driver: misuse of sqlite driver: NumInput after Close")
|
||||||
|
}
|
||||||
|
return int(C.sqlite3_bind_parameter_count(s.stmt))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stmt) reset() error {
|
||||||
|
return s.c.error(C.sqlite3_reset(s.stmt))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stmt) start(args []driver.Value) error {
|
||||||
|
if err := s.reset(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
n := int(C.sqlite3_bind_parameter_count(s.stmt))
|
||||||
|
if n != len(args) {
|
||||||
|
return fmt.Errorf("incorrect argument count for command: have %d want %d", len(args), n)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, v := range args {
|
||||||
|
var str string
|
||||||
|
switch v := v.(type) {
|
||||||
|
case nil:
|
||||||
|
if rv := C.sqlite3_bind_null(s.stmt, C.int(i+1)); rv != 0 {
|
||||||
|
return s.c.error(rv)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
|
||||||
|
case float64:
|
||||||
|
if rv := C.sqlite3_bind_double(s.stmt, C.int(i+1), C.double(v)); rv != 0 {
|
||||||
|
return s.c.error(rv)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
|
||||||
|
case int64:
|
||||||
|
if rv := C.sqlite3_bind_int64(s.stmt, C.int(i+1), C.sqlite3_int64(v)); rv != 0 {
|
||||||
|
return s.c.error(rv)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
|
||||||
|
case []byte:
|
||||||
|
var p *byte
|
||||||
|
if len(v) > 0 {
|
||||||
|
p = &v[0]
|
||||||
|
}
|
||||||
|
if rv := C.my_bind_blob(s.stmt, C.int(i+1), unsafe.Pointer(p), C.int(len(v))); rv != 0 {
|
||||||
|
return s.c.error(rv)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
|
||||||
|
case bool:
|
||||||
|
var vi int64
|
||||||
|
if v {
|
||||||
|
vi = 1
|
||||||
|
}
|
||||||
|
if rv := C.sqlite3_bind_int64(s.stmt, C.int(i+1), C.sqlite3_int64(vi)); rv != 0 {
|
||||||
|
return s.c.error(rv)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
|
||||||
|
case time.Time:
|
||||||
|
str = v.UTC().Format(timefmt[0])
|
||||||
|
|
||||||
|
case string:
|
||||||
|
str = v
|
||||||
|
|
||||||
|
default:
|
||||||
|
str = fmt.Sprint(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
cstr := C.CString(str)
|
||||||
|
rv := C.my_bind_text(s.stmt, C.int(i+1), cstr, C.int(len(str)))
|
||||||
|
C.free(unsafe.Pointer(cstr))
|
||||||
|
if rv != 0 {
|
||||||
|
return s.c.error(rv)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stmt) Exec(args []driver.Value) (driver.Result, error) {
|
||||||
|
if s.closed {
|
||||||
|
panic("database/sql/driver: misuse of sqlite driver: Exec after Close")
|
||||||
|
}
|
||||||
|
if s.rows {
|
||||||
|
panic("database/sql/driver: misuse of sqlite driver: Exec with active Rows")
|
||||||
|
}
|
||||||
|
|
||||||
|
err := s.start(args)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
rv := C.sqlite3_step(s.stmt)
|
||||||
|
if errno(rv) != stepDone {
|
||||||
|
if rv == 0 {
|
||||||
|
rv = 21 // errMisuse
|
||||||
|
}
|
||||||
|
return nil, s.c.error(rv)
|
||||||
|
}
|
||||||
|
|
||||||
|
id := int64(C.sqlite3_last_insert_rowid(s.c.db))
|
||||||
|
rows := int64(C.sqlite3_changes(s.c.db))
|
||||||
|
return &result{id, rows}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stmt) Query(args []driver.Value) (driver.Rows, error) {
|
||||||
|
if s.closed {
|
||||||
|
panic("database/sql/driver: misuse of sqlite driver: Query after Close")
|
||||||
|
}
|
||||||
|
if s.rows {
|
||||||
|
panic("database/sql/driver: misuse of sqlite driver: Query with active Rows")
|
||||||
|
}
|
||||||
|
|
||||||
|
err := s.start(args)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
s.rows = true
|
||||||
|
if s.colnames == nil {
|
||||||
|
n := int64(C.sqlite3_column_count(s.stmt))
|
||||||
|
s.colnames = make([]string, n)
|
||||||
|
s.coltypes = make([]string, n)
|
||||||
|
for i := range s.colnames {
|
||||||
|
s.colnames[i] = C.GoString(C.sqlite3_column_name(s.stmt, C.int(i)))
|
||||||
|
s.coltypes[i] = strings.ToLower(C.GoString(C.sqlite3_column_decltype(s.stmt, C.int(i))))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &rows{s}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type rows struct {
|
||||||
|
s *stmt
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *rows) Columns() []string {
|
||||||
|
if r.s == nil {
|
||||||
|
panic("database/sql/driver: misuse of sqlite driver: Columns of closed Rows")
|
||||||
|
}
|
||||||
|
return r.s.colnames
|
||||||
|
}
|
||||||
|
|
||||||
|
const maxslice = 1<<31 - 1
|
||||||
|
|
||||||
|
var timefmt = []string{
|
||||||
|
"2006-01-02 15:04:05.999999999",
|
||||||
|
"2006-01-02T15:04:05.999999999",
|
||||||
|
"2006-01-02 15:04:05",
|
||||||
|
"2006-01-02T15:04:05",
|
||||||
|
"2006-01-02 15:04",
|
||||||
|
"2006-01-02T15:04",
|
||||||
|
"2006-01-02",
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *rows) Next(dst []driver.Value) error {
|
||||||
|
if r.s == nil {
|
||||||
|
panic("database/sql/driver: misuse of sqlite driver: Next of closed Rows")
|
||||||
|
}
|
||||||
|
|
||||||
|
rv := C.sqlite3_step(r.s.stmt)
|
||||||
|
if errno(rv) != stepRow {
|
||||||
|
if errno(rv) == stepDone {
|
||||||
|
return io.EOF
|
||||||
|
}
|
||||||
|
if rv == 0 {
|
||||||
|
rv = 21
|
||||||
|
}
|
||||||
|
return r.s.c.error(rv)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range dst {
|
||||||
|
switch typ := C.sqlite3_column_type(r.s.stmt, C.int(i)); typ {
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unexpected sqlite3 column type %d", typ)
|
||||||
|
case C.SQLITE_INTEGER:
|
||||||
|
val := int64(C.sqlite3_column_int64(r.s.stmt, C.int(i)))
|
||||||
|
switch r.s.coltypes[i] {
|
||||||
|
case "timestamp", "datetime":
|
||||||
|
dst[i] = time.Unix(val, 0).UTC()
|
||||||
|
case "boolean":
|
||||||
|
dst[i] = val > 0
|
||||||
|
default:
|
||||||
|
dst[i] = val
|
||||||
|
}
|
||||||
|
|
||||||
|
case C.SQLITE_FLOAT:
|
||||||
|
dst[i] = float64(C.sqlite3_column_double(r.s.stmt, C.int(i)))
|
||||||
|
|
||||||
|
case C.SQLITE_BLOB, C.SQLITE_TEXT:
|
||||||
|
n := int(C.sqlite3_column_bytes(r.s.stmt, C.int(i)))
|
||||||
|
var b []byte
|
||||||
|
if n > 0 {
|
||||||
|
p := C.sqlite3_column_blob(r.s.stmt, C.int(i))
|
||||||
|
b = (*[maxslice]byte)(unsafe.Pointer(p))[:n]
|
||||||
|
}
|
||||||
|
dst[i] = b
|
||||||
|
switch r.s.coltypes[i] {
|
||||||
|
case "timestamp", "datetime":
|
||||||
|
dst[i] = time.Time{}
|
||||||
|
s := string(b)
|
||||||
|
for _, f := range timefmt {
|
||||||
|
if t, err := time.Parse(f, s); err == nil {
|
||||||
|
dst[i] = t
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case C.SQLITE_NULL:
|
||||||
|
dst[i] = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *rows) Close() error {
|
||||||
|
if r.s == nil {
|
||||||
|
panic("database/sql/driver: misuse of sqlite driver: Close of closed Rows")
|
||||||
|
}
|
||||||
|
r.s.rows = false
|
||||||
|
r.s = nil
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type result struct {
|
||||||
|
id int64
|
||||||
|
rows int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *result) LastInsertId() (int64, error) {
|
||||||
|
return r.id, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *result) RowsAffected() (int64, error) {
|
||||||
|
return r.rows, nil
|
||||||
|
}
|
99
Godeps/_workspace/src/github.com/endophage/gotuf/testutils/repo.go
generated
vendored
Normal file
99
Godeps/_workspace/src/github.com/endophage/gotuf/testutils/repo.go
generated
vendored
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
package testutils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"math/rand"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/endophage/gotuf/data"
|
||||||
|
"github.com/endophage/gotuf/utils"
|
||||||
|
fuzz "github.com/google/gofuzz"
|
||||||
|
|
||||||
|
tuf "github.com/endophage/gotuf"
|
||||||
|
"github.com/endophage/gotuf/keys"
|
||||||
|
"github.com/endophage/gotuf/signed"
|
||||||
|
)
|
||||||
|
|
||||||
|
// EmptyRepo creates an in memory key database, crypto service
|
||||||
|
// and initializes a repo with no targets or delegations.
|
||||||
|
func EmptyRepo() (*keys.KeyDB, *tuf.TufRepo, signed.CryptoService) {
|
||||||
|
c := signed.NewEd25519()
|
||||||
|
kdb := keys.NewDB()
|
||||||
|
r := tuf.NewTufRepo(kdb, c)
|
||||||
|
|
||||||
|
for _, role := range []string{"root", "targets", "snapshot", "timestamp"} {
|
||||||
|
key, _ := c.Create(role, data.ED25519Key)
|
||||||
|
role, _ := data.NewRole(role, 1, []string{key.ID()}, nil, nil)
|
||||||
|
kdb.AddKey(key)
|
||||||
|
kdb.AddRole(role)
|
||||||
|
}
|
||||||
|
|
||||||
|
r.InitRepo(false)
|
||||||
|
return kdb, r, c
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddTarget(role string, r *tuf.TufRepo) (name string, meta data.FileMeta, content []byte, err error) {
|
||||||
|
randness := fuzz.Continue{}
|
||||||
|
content = RandomByteSlice(1024)
|
||||||
|
name = randness.RandString()
|
||||||
|
t := data.FileMeta{
|
||||||
|
Length: int64(len(content)),
|
||||||
|
Hashes: data.Hashes{
|
||||||
|
"sha256": utils.DoHash("sha256", content),
|
||||||
|
"sha512": utils.DoHash("sha512", content),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
files := data.Files{name: t}
|
||||||
|
_, err = r.AddTargets(role, files)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func RandomByteSlice(maxSize int) []byte {
|
||||||
|
r := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||||
|
contentSize := r.Intn(maxSize)
|
||||||
|
content := make([]byte, contentSize)
|
||||||
|
for i := range content {
|
||||||
|
content[i] = byte(r.Int63() & 0xff)
|
||||||
|
}
|
||||||
|
return content
|
||||||
|
}
|
||||||
|
|
||||||
|
func Sign(repo *tuf.TufRepo) (root, targets, snapshot, timestamp *data.Signed, err error) {
|
||||||
|
root, err = repo.SignRoot(data.DefaultExpires("root"), nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, nil, err
|
||||||
|
}
|
||||||
|
targets, err = repo.SignTargets("targets", data.DefaultExpires("targets"), nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, nil, err
|
||||||
|
}
|
||||||
|
snapshot, err = repo.SignSnapshot(data.DefaultExpires("snapshot"), nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, nil, err
|
||||||
|
}
|
||||||
|
timestamp, err = repo.SignTimestamp(data.DefaultExpires("timestamp"), nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, nil, err
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func Serialize(sRoot, sTargets, sSnapshot, sTimestamp *data.Signed) (root, targets, snapshot, timestamp []byte, err error) {
|
||||||
|
root, err = json.Marshal(sRoot)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, nil, err
|
||||||
|
}
|
||||||
|
targets, err = json.Marshal(sTargets)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, nil, err
|
||||||
|
}
|
||||||
|
snapshot, err = json.Marshal(sSnapshot)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, nil, err
|
||||||
|
}
|
||||||
|
timestamp, err = json.Marshal(sTimestamp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, nil, err
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
language: go
|
||||||
|
|
||||||
|
go:
|
||||||
|
- 1.4
|
||||||
|
- 1.3
|
||||||
|
- 1.2
|
||||||
|
- tip
|
||||||
|
|
||||||
|
install:
|
||||||
|
- if ! go get code.google.com/p/go.tools/cmd/cover; then go get golang.org/x/tools/cmd/cover; fi
|
||||||
|
|
||||||
|
script:
|
||||||
|
- go test -cover
|
|
@ -0,0 +1,67 @@
|
||||||
|
# How to contribute #
|
||||||
|
|
||||||
|
We'd love to accept your patches and contributions to this project. There are
|
||||||
|
a just a few small guidelines you need to follow.
|
||||||
|
|
||||||
|
|
||||||
|
## Contributor License Agreement ##
|
||||||
|
|
||||||
|
Contributions to any Google project must be accompanied by a Contributor
|
||||||
|
License Agreement. This is not a copyright **assignment**, it simply gives
|
||||||
|
Google permission to use and redistribute your contributions as part of the
|
||||||
|
project.
|
||||||
|
|
||||||
|
* If you are an individual writing original source code and you're sure you
|
||||||
|
own the intellectual property, then you'll need to sign an [individual
|
||||||
|
CLA][].
|
||||||
|
|
||||||
|
* If you work for a company that wants to allow you to contribute your work,
|
||||||
|
then you'll need to sign a [corporate CLA][].
|
||||||
|
|
||||||
|
You generally only need to submit a CLA once, so if you've already submitted
|
||||||
|
one (even if it was for a different project), you probably don't need to do it
|
||||||
|
again.
|
||||||
|
|
||||||
|
[individual CLA]: https://developers.google.com/open-source/cla/individual
|
||||||
|
[corporate CLA]: https://developers.google.com/open-source/cla/corporate
|
||||||
|
|
||||||
|
|
||||||
|
## Submitting a patch ##
|
||||||
|
|
||||||
|
1. It's generally best to start by opening a new issue describing the bug or
|
||||||
|
feature you're intending to fix. Even if you think it's relatively minor,
|
||||||
|
it's helpful to know what people are working on. Mention in the initial
|
||||||
|
issue that you are planning to work on that bug or feature so that it can
|
||||||
|
be assigned to you.
|
||||||
|
|
||||||
|
1. Follow the normal process of [forking][] the project, and setup a new
|
||||||
|
branch to work in. It's important that each group of changes be done in
|
||||||
|
separate branches in order to ensure that a pull request only includes the
|
||||||
|
commits related to that bug or feature.
|
||||||
|
|
||||||
|
1. Go makes it very simple to ensure properly formatted code, so always run
|
||||||
|
`go fmt` on your code before committing it. You should also run
|
||||||
|
[golint][] over your code. As noted in the [golint readme][], it's not
|
||||||
|
strictly necessary that your code be completely "lint-free", but this will
|
||||||
|
help you find common style issues.
|
||||||
|
|
||||||
|
1. Any significant changes should almost always be accompanied by tests. The
|
||||||
|
project already has good test coverage, so look at some of the existing
|
||||||
|
tests if you're unsure how to go about it. [gocov][] and [gocov-html][]
|
||||||
|
are invaluable tools for seeing which parts of your code aren't being
|
||||||
|
exercised by your tests.
|
||||||
|
|
||||||
|
1. Do your best to have [well-formed commit messages][] for each change.
|
||||||
|
This provides consistency throughout the project, and ensures that commit
|
||||||
|
messages are able to be formatted properly by various git tools.
|
||||||
|
|
||||||
|
1. Finally, push the commits to your fork and submit a [pull request][].
|
||||||
|
|
||||||
|
[forking]: https://help.github.com/articles/fork-a-repo
|
||||||
|
[golint]: https://github.com/golang/lint
|
||||||
|
[golint readme]: https://github.com/golang/lint/blob/master/README
|
||||||
|
[gocov]: https://github.com/axw/gocov
|
||||||
|
[gocov-html]: https://github.com/matm/gocov-html
|
||||||
|
[well-formed commit messages]: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html
|
||||||
|
[squash]: http://git-scm.com/book/en/Git-Tools-Rewriting-History#Squashing-Commits
|
||||||
|
[pull request]: https://help.github.com/articles/creating-a-pull-request
|
|
@ -0,0 +1,202 @@
|
||||||
|
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
|
@ -0,0 +1,71 @@
|
||||||
|
gofuzz
|
||||||
|
======
|
||||||
|
|
||||||
|
gofuzz is a library for populating go objects with random values.
|
||||||
|
|
||||||
|
[](https://godoc.org/github.com/google/gofuzz)
|
||||||
|
[](https://travis-ci.org/google/gofuzz)
|
||||||
|
|
||||||
|
This is useful for testing:
|
||||||
|
|
||||||
|
* Do your project's objects really serialize/unserialize correctly in all cases?
|
||||||
|
* Is there an incorrectly formatted object that will cause your project to panic?
|
||||||
|
|
||||||
|
Import with ```import "github.com/google/gofuzz"```
|
||||||
|
|
||||||
|
You can use it on single variables:
|
||||||
|
```
|
||||||
|
f := fuzz.New()
|
||||||
|
var myInt int
|
||||||
|
f.Fuzz(&myInt) // myInt gets a random value.
|
||||||
|
```
|
||||||
|
|
||||||
|
You can use it on maps:
|
||||||
|
```
|
||||||
|
f := fuzz.New().NilChance(0).NumElements(1, 1)
|
||||||
|
var myMap map[ComplexKeyType]string
|
||||||
|
f.Fuzz(&myMap) // myMap will have exactly one element.
|
||||||
|
```
|
||||||
|
|
||||||
|
Customize the chance of getting a nil pointer:
|
||||||
|
```
|
||||||
|
f := fuzz.New().NilChance(.5)
|
||||||
|
var fancyStruct struct {
|
||||||
|
A, B, C, D *string
|
||||||
|
}
|
||||||
|
f.Fuzz(&fancyStruct) // About half the pointers should be set.
|
||||||
|
```
|
||||||
|
|
||||||
|
You can even customize the randomization completely if needed:
|
||||||
|
```
|
||||||
|
type MyEnum string
|
||||||
|
const (
|
||||||
|
A MyEnum = "A"
|
||||||
|
B MyEnum = "B"
|
||||||
|
)
|
||||||
|
type MyInfo struct {
|
||||||
|
Type MyEnum
|
||||||
|
AInfo *string
|
||||||
|
BInfo *string
|
||||||
|
}
|
||||||
|
|
||||||
|
f := fuzz.New().NilChance(0).Funcs(
|
||||||
|
func(e *MyInfo, c fuzz.Continue) {
|
||||||
|
switch c.Intn(2) {
|
||||||
|
case 0:
|
||||||
|
e.Type = A
|
||||||
|
c.Fuzz(&e.AInfo)
|
||||||
|
case 1:
|
||||||
|
e.Type = B
|
||||||
|
c.Fuzz(&e.BInfo)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
var myObject MyInfo
|
||||||
|
f.Fuzz(&myObject) // Type will correspond to whether A or B info is set.
|
||||||
|
```
|
||||||
|
|
||||||
|
See more examples in ```example_test.go```.
|
||||||
|
|
||||||
|
Happy testing!
|
|
@ -0,0 +1,18 @@
|
||||||
|
/*
|
||||||
|
Copyright 2014 Google Inc. All rights reserved.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Package fuzz is a library for populating go objects with random values.
|
||||||
|
package fuzz
|
|
@ -0,0 +1,225 @@
|
||||||
|
/*
|
||||||
|
Copyright 2014 Google Inc. All rights reserved.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package fuzz_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
|
|
||||||
|
"github.com/google/gofuzz"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ExampleSimple() {
|
||||||
|
type MyType struct {
|
||||||
|
A string
|
||||||
|
B string
|
||||||
|
C int
|
||||||
|
D struct {
|
||||||
|
E float64
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
f := fuzz.New()
|
||||||
|
object := MyType{}
|
||||||
|
|
||||||
|
uniqueObjects := map[MyType]int{}
|
||||||
|
|
||||||
|
for i := 0; i < 1000; i++ {
|
||||||
|
f.Fuzz(&object)
|
||||||
|
uniqueObjects[object]++
|
||||||
|
}
|
||||||
|
fmt.Printf("Got %v unique objects.\n", len(uniqueObjects))
|
||||||
|
// Output:
|
||||||
|
// Got 1000 unique objects.
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleCustom() {
|
||||||
|
type MyType struct {
|
||||||
|
A int
|
||||||
|
B string
|
||||||
|
}
|
||||||
|
|
||||||
|
counter := 0
|
||||||
|
f := fuzz.New().Funcs(
|
||||||
|
func(i *int, c fuzz.Continue) {
|
||||||
|
*i = counter
|
||||||
|
counter++
|
||||||
|
},
|
||||||
|
)
|
||||||
|
object := MyType{}
|
||||||
|
|
||||||
|
uniqueObjects := map[MyType]int{}
|
||||||
|
|
||||||
|
for i := 0; i < 100; i++ {
|
||||||
|
f.Fuzz(&object)
|
||||||
|
if object.A != i {
|
||||||
|
fmt.Printf("Unexpected value: %#v\n", object)
|
||||||
|
}
|
||||||
|
uniqueObjects[object]++
|
||||||
|
}
|
||||||
|
fmt.Printf("Got %v unique objects.\n", len(uniqueObjects))
|
||||||
|
// Output:
|
||||||
|
// Got 100 unique objects.
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleComplex() {
|
||||||
|
type OtherType struct {
|
||||||
|
A string
|
||||||
|
B string
|
||||||
|
}
|
||||||
|
type MyType struct {
|
||||||
|
Pointer *OtherType
|
||||||
|
Map map[string]OtherType
|
||||||
|
PointerMap *map[string]OtherType
|
||||||
|
Slice []OtherType
|
||||||
|
SlicePointer []*OtherType
|
||||||
|
PointerSlicePointer *[]*OtherType
|
||||||
|
}
|
||||||
|
|
||||||
|
f := fuzz.New().RandSource(rand.NewSource(0)).NilChance(0).NumElements(1, 1).Funcs(
|
||||||
|
func(o *OtherType, c fuzz.Continue) {
|
||||||
|
o.A = "Foo"
|
||||||
|
o.B = "Bar"
|
||||||
|
},
|
||||||
|
func(op **OtherType, c fuzz.Continue) {
|
||||||
|
*op = &OtherType{"A", "B"}
|
||||||
|
},
|
||||||
|
func(m map[string]OtherType, c fuzz.Continue) {
|
||||||
|
m["Works Because"] = OtherType{
|
||||||
|
"Fuzzer",
|
||||||
|
"Preallocated",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
object := MyType{}
|
||||||
|
f.Fuzz(&object)
|
||||||
|
bytes, err := json.MarshalIndent(&object, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("error: %v\n", err)
|
||||||
|
}
|
||||||
|
fmt.Printf("%s\n", string(bytes))
|
||||||
|
// Output:
|
||||||
|
// {
|
||||||
|
// "Pointer": {
|
||||||
|
// "A": "A",
|
||||||
|
// "B": "B"
|
||||||
|
// },
|
||||||
|
// "Map": {
|
||||||
|
// "Works Because": {
|
||||||
|
// "A": "Fuzzer",
|
||||||
|
// "B": "Preallocated"
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// "PointerMap": {
|
||||||
|
// "Works Because": {
|
||||||
|
// "A": "Fuzzer",
|
||||||
|
// "B": "Preallocated"
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// "Slice": [
|
||||||
|
// {
|
||||||
|
// "A": "Foo",
|
||||||
|
// "B": "Bar"
|
||||||
|
// }
|
||||||
|
// ],
|
||||||
|
// "SlicePointer": [
|
||||||
|
// {
|
||||||
|
// "A": "A",
|
||||||
|
// "B": "B"
|
||||||
|
// }
|
||||||
|
// ],
|
||||||
|
// "PointerSlicePointer": [
|
||||||
|
// {
|
||||||
|
// "A": "A",
|
||||||
|
// "B": "B"
|
||||||
|
// }
|
||||||
|
// ]
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleMap() {
|
||||||
|
f := fuzz.New().NilChance(0).NumElements(1, 1)
|
||||||
|
var myMap map[struct{ A, B, C int }]string
|
||||||
|
f.Fuzz(&myMap)
|
||||||
|
fmt.Printf("myMap has %v element(s).\n", len(myMap))
|
||||||
|
// Output:
|
||||||
|
// myMap has 1 element(s).
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleSingle() {
|
||||||
|
f := fuzz.New()
|
||||||
|
var i int
|
||||||
|
f.Fuzz(&i)
|
||||||
|
|
||||||
|
// Technically, we'd expect this to fail one out of 2 billion attempts...
|
||||||
|
fmt.Printf("(i == 0) == %v", i == 0)
|
||||||
|
// Output:
|
||||||
|
// (i == 0) == false
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleEnum() {
|
||||||
|
type MyEnum string
|
||||||
|
const (
|
||||||
|
A MyEnum = "A"
|
||||||
|
B MyEnum = "B"
|
||||||
|
)
|
||||||
|
type MyInfo struct {
|
||||||
|
Type MyEnum
|
||||||
|
AInfo *string
|
||||||
|
BInfo *string
|
||||||
|
}
|
||||||
|
|
||||||
|
f := fuzz.New().NilChance(0).Funcs(
|
||||||
|
func(e *MyInfo, c fuzz.Continue) {
|
||||||
|
// Note c's embedded Rand allows for direct use.
|
||||||
|
// We could also use c.RandBool() here.
|
||||||
|
switch c.Intn(2) {
|
||||||
|
case 0:
|
||||||
|
e.Type = A
|
||||||
|
c.Fuzz(&e.AInfo)
|
||||||
|
case 1:
|
||||||
|
e.Type = B
|
||||||
|
c.Fuzz(&e.BInfo)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
for i := 0; i < 100; i++ {
|
||||||
|
var myObject MyInfo
|
||||||
|
f.Fuzz(&myObject)
|
||||||
|
switch myObject.Type {
|
||||||
|
case A:
|
||||||
|
if myObject.AInfo == nil {
|
||||||
|
fmt.Println("AInfo should have been set!")
|
||||||
|
}
|
||||||
|
if myObject.BInfo != nil {
|
||||||
|
fmt.Println("BInfo should NOT have been set!")
|
||||||
|
}
|
||||||
|
case B:
|
||||||
|
if myObject.BInfo == nil {
|
||||||
|
fmt.Println("BInfo should have been set!")
|
||||||
|
}
|
||||||
|
if myObject.AInfo != nil {
|
||||||
|
fmt.Println("AInfo should NOT have been set!")
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
fmt.Println("Invalid enum value!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Output:
|
||||||
|
}
|
|
@ -0,0 +1,446 @@
|
||||||
|
/*
|
||||||
|
Copyright 2014 Google Inc. All rights reserved.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package fuzz
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
|
"reflect"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// fuzzFuncMap is a map from a type to a fuzzFunc that handles that type.
|
||||||
|
type fuzzFuncMap map[reflect.Type]reflect.Value
|
||||||
|
|
||||||
|
// Fuzzer knows how to fill any object with random fields.
|
||||||
|
type Fuzzer struct {
|
||||||
|
fuzzFuncs fuzzFuncMap
|
||||||
|
defaultFuzzFuncs fuzzFuncMap
|
||||||
|
r *rand.Rand
|
||||||
|
nilChance float64
|
||||||
|
minElements int
|
||||||
|
maxElements int
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns a new Fuzzer. Customize your Fuzzer further by calling Funcs,
|
||||||
|
// RandSource, NilChance, or NumElements in any order.
|
||||||
|
func New() *Fuzzer {
|
||||||
|
f := &Fuzzer{
|
||||||
|
defaultFuzzFuncs: fuzzFuncMap{
|
||||||
|
reflect.TypeOf(&time.Time{}): reflect.ValueOf(fuzzTime),
|
||||||
|
},
|
||||||
|
|
||||||
|
fuzzFuncs: fuzzFuncMap{},
|
||||||
|
r: rand.New(rand.NewSource(time.Now().UnixNano())),
|
||||||
|
nilChance: .2,
|
||||||
|
minElements: 1,
|
||||||
|
maxElements: 10,
|
||||||
|
}
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
// Funcs adds each entry in fuzzFuncs as a custom fuzzing function.
|
||||||
|
//
|
||||||
|
// Each entry in fuzzFuncs must be a function taking two parameters.
|
||||||
|
// The first parameter must be a pointer or map. It is the variable that
|
||||||
|
// function will fill with random data. The second parameter must be a
|
||||||
|
// fuzz.Continue, which will provide a source of randomness and a way
|
||||||
|
// to automatically continue fuzzing smaller pieces of the first parameter.
|
||||||
|
//
|
||||||
|
// These functions are called sensibly, e.g., if you wanted custom string
|
||||||
|
// fuzzing, the function `func(s *string, c fuzz.Continue)` would get
|
||||||
|
// called and passed the address of strings. Maps and pointers will always
|
||||||
|
// be made/new'd for you, ignoring the NilChange option. For slices, it
|
||||||
|
// doesn't make much sense to pre-create them--Fuzzer doesn't know how
|
||||||
|
// long you want your slice--so take a pointer to a slice, and make it
|
||||||
|
// yourself. (If you don't want your map/pointer type pre-made, take a
|
||||||
|
// pointer to it, and make it yourself.) See the examples for a range of
|
||||||
|
// custom functions.
|
||||||
|
func (f *Fuzzer) Funcs(fuzzFuncs ...interface{}) *Fuzzer {
|
||||||
|
for i := range fuzzFuncs {
|
||||||
|
v := reflect.ValueOf(fuzzFuncs[i])
|
||||||
|
if v.Kind() != reflect.Func {
|
||||||
|
panic("Need only funcs!")
|
||||||
|
}
|
||||||
|
t := v.Type()
|
||||||
|
if t.NumIn() != 2 || t.NumOut() != 0 {
|
||||||
|
panic("Need 2 in and 0 out params!")
|
||||||
|
}
|
||||||
|
argT := t.In(0)
|
||||||
|
switch argT.Kind() {
|
||||||
|
case reflect.Ptr, reflect.Map:
|
||||||
|
default:
|
||||||
|
panic("fuzzFunc must take pointer or map type")
|
||||||
|
}
|
||||||
|
if t.In(1) != reflect.TypeOf(Continue{}) {
|
||||||
|
panic("fuzzFunc's second parameter must be type fuzz.Continue")
|
||||||
|
}
|
||||||
|
f.fuzzFuncs[argT] = v
|
||||||
|
}
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
// RandSource causes f to get values from the given source of randomness.
|
||||||
|
// Use if you want deterministic fuzzing.
|
||||||
|
func (f *Fuzzer) RandSource(s rand.Source) *Fuzzer {
|
||||||
|
f.r = rand.New(s)
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
// NilChance sets the probability of creating a nil pointer, map, or slice to
|
||||||
|
// 'p'. 'p' should be between 0 (no nils) and 1 (all nils), inclusive.
|
||||||
|
func (f *Fuzzer) NilChance(p float64) *Fuzzer {
|
||||||
|
if p < 0 || p > 1 {
|
||||||
|
panic("p should be between 0 and 1, inclusive.")
|
||||||
|
}
|
||||||
|
f.nilChance = p
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
// NumElements sets the minimum and maximum number of elements that will be
|
||||||
|
// added to a non-nil map or slice.
|
||||||
|
func (f *Fuzzer) NumElements(atLeast, atMost int) *Fuzzer {
|
||||||
|
if atLeast > atMost {
|
||||||
|
panic("atLeast must be <= atMost")
|
||||||
|
}
|
||||||
|
if atLeast < 0 {
|
||||||
|
panic("atLeast must be >= 0")
|
||||||
|
}
|
||||||
|
f.minElements = atLeast
|
||||||
|
f.maxElements = atMost
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Fuzzer) genElementCount() int {
|
||||||
|
if f.minElements == f.maxElements {
|
||||||
|
return f.minElements
|
||||||
|
}
|
||||||
|
return f.minElements + f.r.Intn(f.maxElements-f.minElements)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Fuzzer) genShouldFill() bool {
|
||||||
|
return f.r.Float64() > f.nilChance
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fuzz recursively fills all of obj's fields with something random. First
|
||||||
|
// this tries to find a custom fuzz function (see Funcs). If there is no
|
||||||
|
// custom function this tests whether the object implements fuzz.Interface and,
|
||||||
|
// if so, calls Fuzz on it to fuzz itself. If that fails, this will see if
|
||||||
|
// there is a default fuzz function provided by this package. If all of that
|
||||||
|
// fails, this will generate random values for all primitive fields and then
|
||||||
|
// recurse for all non-primitives.
|
||||||
|
//
|
||||||
|
// Not safe for cyclic or tree-like structs!
|
||||||
|
//
|
||||||
|
// obj must be a pointer. Only exported (public) fields can be set (thanks, golang :/ )
|
||||||
|
// Intended for tests, so will panic on bad input or unimplemented fields.
|
||||||
|
func (f *Fuzzer) Fuzz(obj interface{}) {
|
||||||
|
v := reflect.ValueOf(obj)
|
||||||
|
if v.Kind() != reflect.Ptr {
|
||||||
|
panic("needed ptr!")
|
||||||
|
}
|
||||||
|
v = v.Elem()
|
||||||
|
f.doFuzz(v, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FuzzNoCustom is just like Fuzz, except that any custom fuzz function for
|
||||||
|
// obj's type will not be called and obj will not be tested for fuzz.Interface
|
||||||
|
// conformance. This applies only to obj and not other instances of obj's
|
||||||
|
// type.
|
||||||
|
// Not safe for cyclic or tree-like structs!
|
||||||
|
// obj must be a pointer. Only exported (public) fields can be set (thanks, golang :/ )
|
||||||
|
// Intended for tests, so will panic on bad input or unimplemented fields.
|
||||||
|
func (f *Fuzzer) FuzzNoCustom(obj interface{}) {
|
||||||
|
v := reflect.ValueOf(obj)
|
||||||
|
if v.Kind() != reflect.Ptr {
|
||||||
|
panic("needed ptr!")
|
||||||
|
}
|
||||||
|
v = v.Elem()
|
||||||
|
f.doFuzz(v, flagNoCustomFuzz)
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Do not try to find a custom fuzz function. Does not apply recursively.
|
||||||
|
flagNoCustomFuzz uint64 = 1 << iota
|
||||||
|
)
|
||||||
|
|
||||||
|
func (f *Fuzzer) doFuzz(v reflect.Value, flags uint64) {
|
||||||
|
if !v.CanSet() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if flags&flagNoCustomFuzz == 0 {
|
||||||
|
// Check for both pointer and non-pointer custom functions.
|
||||||
|
if v.CanAddr() && f.tryCustom(v.Addr()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if f.tryCustom(v) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if fn, ok := fillFuncMap[v.Kind()]; ok {
|
||||||
|
fn(v, f.r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch v.Kind() {
|
||||||
|
case reflect.Map:
|
||||||
|
if f.genShouldFill() {
|
||||||
|
v.Set(reflect.MakeMap(v.Type()))
|
||||||
|
n := f.genElementCount()
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
key := reflect.New(v.Type().Key()).Elem()
|
||||||
|
f.doFuzz(key, 0)
|
||||||
|
val := reflect.New(v.Type().Elem()).Elem()
|
||||||
|
f.doFuzz(val, 0)
|
||||||
|
v.SetMapIndex(key, val)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
v.Set(reflect.Zero(v.Type()))
|
||||||
|
case reflect.Ptr:
|
||||||
|
if f.genShouldFill() {
|
||||||
|
v.Set(reflect.New(v.Type().Elem()))
|
||||||
|
f.doFuzz(v.Elem(), 0)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
v.Set(reflect.Zero(v.Type()))
|
||||||
|
case reflect.Slice:
|
||||||
|
if f.genShouldFill() {
|
||||||
|
n := f.genElementCount()
|
||||||
|
v.Set(reflect.MakeSlice(v.Type(), n, n))
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
f.doFuzz(v.Index(i), 0)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
v.Set(reflect.Zero(v.Type()))
|
||||||
|
case reflect.Struct:
|
||||||
|
for i := 0; i < v.NumField(); i++ {
|
||||||
|
f.doFuzz(v.Field(i), 0)
|
||||||
|
}
|
||||||
|
case reflect.Array:
|
||||||
|
fallthrough
|
||||||
|
case reflect.Chan:
|
||||||
|
fallthrough
|
||||||
|
case reflect.Func:
|
||||||
|
fallthrough
|
||||||
|
case reflect.Interface:
|
||||||
|
fallthrough
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("Can't handle %#v", v.Interface()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// tryCustom searches for custom handlers, and returns true iff it finds a match
|
||||||
|
// and successfully randomizes v.
|
||||||
|
func (f *Fuzzer) tryCustom(v reflect.Value) bool {
|
||||||
|
// First: see if we have a fuzz function for it.
|
||||||
|
doCustom, ok := f.fuzzFuncs[v.Type()]
|
||||||
|
if !ok {
|
||||||
|
// Second: see if it can fuzz itself.
|
||||||
|
if v.CanInterface() {
|
||||||
|
intf := v.Interface()
|
||||||
|
if fuzzable, ok := intf.(Interface); ok {
|
||||||
|
fuzzable.Fuzz(Continue{f: f, Rand: f.r})
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Finally: see if there is a default fuzz function.
|
||||||
|
doCustom, ok = f.defaultFuzzFuncs[v.Type()]
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch v.Kind() {
|
||||||
|
case reflect.Ptr:
|
||||||
|
if v.IsNil() {
|
||||||
|
if !v.CanSet() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
v.Set(reflect.New(v.Type().Elem()))
|
||||||
|
}
|
||||||
|
case reflect.Map:
|
||||||
|
if v.IsNil() {
|
||||||
|
if !v.CanSet() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
v.Set(reflect.MakeMap(v.Type()))
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
doCustom.Call([]reflect.Value{v, reflect.ValueOf(Continue{
|
||||||
|
f: f,
|
||||||
|
Rand: f.r,
|
||||||
|
})})
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Interface represents an object that knows how to fuzz itself. Any time we
|
||||||
|
// find a type that implements this interface we will delegate the act of
|
||||||
|
// fuzzing itself.
|
||||||
|
type Interface interface {
|
||||||
|
Fuzz(c Continue)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Continue can be passed to custom fuzzing functions to allow them to use
|
||||||
|
// the correct source of randomness and to continue fuzzing their members.
|
||||||
|
type Continue struct {
|
||||||
|
f *Fuzzer
|
||||||
|
|
||||||
|
// For convenience, Continue implements rand.Rand via embedding.
|
||||||
|
// Use this for generating any randomness if you want your fuzzing
|
||||||
|
// to be repeatable for a given seed.
|
||||||
|
*rand.Rand
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fuzz continues fuzzing obj. obj must be a pointer.
|
||||||
|
func (c Continue) Fuzz(obj interface{}) {
|
||||||
|
v := reflect.ValueOf(obj)
|
||||||
|
if v.Kind() != reflect.Ptr {
|
||||||
|
panic("needed ptr!")
|
||||||
|
}
|
||||||
|
v = v.Elem()
|
||||||
|
c.f.doFuzz(v, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FuzzNoCustom continues fuzzing obj, except that any custom fuzz function for
|
||||||
|
// obj's type will not be called and obj will not be tested for fuzz.Interface
|
||||||
|
// conformance. This applies only to obj and not other instances of obj's
|
||||||
|
// type.
|
||||||
|
func (c Continue) FuzzNoCustom(obj interface{}) {
|
||||||
|
v := reflect.ValueOf(obj)
|
||||||
|
if v.Kind() != reflect.Ptr {
|
||||||
|
panic("needed ptr!")
|
||||||
|
}
|
||||||
|
v = v.Elem()
|
||||||
|
c.f.doFuzz(v, flagNoCustomFuzz)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RandString makes a random string up to 20 characters long. The returned string
|
||||||
|
// may include a variety of (valid) UTF-8 encodings.
|
||||||
|
func (c Continue) RandString() string {
|
||||||
|
return randString(c.Rand)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RandUint64 makes random 64 bit numbers.
|
||||||
|
// Weirdly, rand doesn't have a function that gives you 64 random bits.
|
||||||
|
func (c Continue) RandUint64() uint64 {
|
||||||
|
return randUint64(c.Rand)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RandBool returns true or false randomly.
|
||||||
|
func (c Continue) RandBool() bool {
|
||||||
|
return randBool(c.Rand)
|
||||||
|
}
|
||||||
|
|
||||||
|
func fuzzInt(v reflect.Value, r *rand.Rand) {
|
||||||
|
v.SetInt(int64(randUint64(r)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func fuzzUint(v reflect.Value, r *rand.Rand) {
|
||||||
|
v.SetUint(randUint64(r))
|
||||||
|
}
|
||||||
|
|
||||||
|
func fuzzTime(t *time.Time, c Continue) {
|
||||||
|
var sec, nsec int64
|
||||||
|
// Allow for about 1000 years of random time values, which keeps things
|
||||||
|
// like JSON parsing reasonably happy.
|
||||||
|
sec = c.Rand.Int63n(1000 * 365 * 24 * 60 * 60)
|
||||||
|
c.Fuzz(&nsec)
|
||||||
|
*t = time.Unix(sec, nsec)
|
||||||
|
}
|
||||||
|
|
||||||
|
var fillFuncMap = map[reflect.Kind]func(reflect.Value, *rand.Rand){
|
||||||
|
reflect.Bool: func(v reflect.Value, r *rand.Rand) {
|
||||||
|
v.SetBool(randBool(r))
|
||||||
|
},
|
||||||
|
reflect.Int: fuzzInt,
|
||||||
|
reflect.Int8: fuzzInt,
|
||||||
|
reflect.Int16: fuzzInt,
|
||||||
|
reflect.Int32: fuzzInt,
|
||||||
|
reflect.Int64: fuzzInt,
|
||||||
|
reflect.Uint: fuzzUint,
|
||||||
|
reflect.Uint8: fuzzUint,
|
||||||
|
reflect.Uint16: fuzzUint,
|
||||||
|
reflect.Uint32: fuzzUint,
|
||||||
|
reflect.Uint64: fuzzUint,
|
||||||
|
reflect.Uintptr: fuzzUint,
|
||||||
|
reflect.Float32: func(v reflect.Value, r *rand.Rand) {
|
||||||
|
v.SetFloat(float64(r.Float32()))
|
||||||
|
},
|
||||||
|
reflect.Float64: func(v reflect.Value, r *rand.Rand) {
|
||||||
|
v.SetFloat(r.Float64())
|
||||||
|
},
|
||||||
|
reflect.Complex64: func(v reflect.Value, r *rand.Rand) {
|
||||||
|
panic("unimplemented")
|
||||||
|
},
|
||||||
|
reflect.Complex128: func(v reflect.Value, r *rand.Rand) {
|
||||||
|
panic("unimplemented")
|
||||||
|
},
|
||||||
|
reflect.String: func(v reflect.Value, r *rand.Rand) {
|
||||||
|
v.SetString(randString(r))
|
||||||
|
},
|
||||||
|
reflect.UnsafePointer: func(v reflect.Value, r *rand.Rand) {
|
||||||
|
panic("unimplemented")
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// randBool returns true or false randomly.
|
||||||
|
func randBool(r *rand.Rand) bool {
|
||||||
|
if r.Int()&1 == 1 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
type charRange struct {
|
||||||
|
first, last rune
|
||||||
|
}
|
||||||
|
|
||||||
|
// choose returns a random unicode character from the given range, using the
|
||||||
|
// given randomness source.
|
||||||
|
func (r *charRange) choose(rand *rand.Rand) rune {
|
||||||
|
count := int64(r.last - r.first)
|
||||||
|
return r.first + rune(rand.Int63n(count))
|
||||||
|
}
|
||||||
|
|
||||||
|
var unicodeRanges = []charRange{
|
||||||
|
{' ', '~'}, // ASCII characters
|
||||||
|
{'\u00a0', '\u02af'}, // Multi-byte encoded characters
|
||||||
|
{'\u4e00', '\u9fff'}, // Common CJK (even longer encodings)
|
||||||
|
}
|
||||||
|
|
||||||
|
// randString makes a random string up to 20 characters long. The returned string
|
||||||
|
// may include a variety of (valid) UTF-8 encodings.
|
||||||
|
func randString(r *rand.Rand) string {
|
||||||
|
n := r.Intn(20)
|
||||||
|
runes := make([]rune, n)
|
||||||
|
for i := range runes {
|
||||||
|
runes[i] = unicodeRanges[r.Intn(len(unicodeRanges))].choose(r)
|
||||||
|
}
|
||||||
|
return string(runes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// randUint64 makes random 64 bit numbers.
|
||||||
|
// Weirdly, rand doesn't have a function that gives you 64 random bits.
|
||||||
|
func randUint64(r *rand.Rand) uint64 {
|
||||||
|
return uint64(r.Uint32())<<32 | uint64(r.Uint32())
|
||||||
|
}
|
|
@ -0,0 +1,384 @@
|
||||||
|
/*
|
||||||
|
Copyright 2014 Google Inc. All rights reserved.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package fuzz
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFuzz_basic(t *testing.T) {
|
||||||
|
obj := &struct {
|
||||||
|
I int
|
||||||
|
I8 int8
|
||||||
|
I16 int16
|
||||||
|
I32 int32
|
||||||
|
I64 int64
|
||||||
|
U uint
|
||||||
|
U8 uint8
|
||||||
|
U16 uint16
|
||||||
|
U32 uint32
|
||||||
|
U64 uint64
|
||||||
|
Uptr uintptr
|
||||||
|
S string
|
||||||
|
B bool
|
||||||
|
T time.Time
|
||||||
|
}{}
|
||||||
|
|
||||||
|
failed := map[string]int{}
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
New().Fuzz(obj)
|
||||||
|
|
||||||
|
if n, v := "i", obj.I; v == 0 {
|
||||||
|
failed[n] = failed[n] + 1
|
||||||
|
}
|
||||||
|
if n, v := "i8", obj.I8; v == 0 {
|
||||||
|
failed[n] = failed[n] + 1
|
||||||
|
}
|
||||||
|
if n, v := "i16", obj.I16; v == 0 {
|
||||||
|
failed[n] = failed[n] + 1
|
||||||
|
}
|
||||||
|
if n, v := "i32", obj.I32; v == 0 {
|
||||||
|
failed[n] = failed[n] + 1
|
||||||
|
}
|
||||||
|
if n, v := "i64", obj.I64; v == 0 {
|
||||||
|
failed[n] = failed[n] + 1
|
||||||
|
}
|
||||||
|
if n, v := "u", obj.U; v == 0 {
|
||||||
|
failed[n] = failed[n] + 1
|
||||||
|
}
|
||||||
|
if n, v := "u8", obj.U8; v == 0 {
|
||||||
|
failed[n] = failed[n] + 1
|
||||||
|
}
|
||||||
|
if n, v := "u16", obj.U16; v == 0 {
|
||||||
|
failed[n] = failed[n] + 1
|
||||||
|
}
|
||||||
|
if n, v := "u32", obj.U32; v == 0 {
|
||||||
|
failed[n] = failed[n] + 1
|
||||||
|
}
|
||||||
|
if n, v := "u64", obj.U64; v == 0 {
|
||||||
|
failed[n] = failed[n] + 1
|
||||||
|
}
|
||||||
|
if n, v := "uptr", obj.Uptr; v == 0 {
|
||||||
|
failed[n] = failed[n] + 1
|
||||||
|
}
|
||||||
|
if n, v := "s", obj.S; v == "" {
|
||||||
|
failed[n] = failed[n] + 1
|
||||||
|
}
|
||||||
|
if n, v := "b", obj.B; v == false {
|
||||||
|
failed[n] = failed[n] + 1
|
||||||
|
}
|
||||||
|
if n, v := "t", obj.T; v.IsZero() {
|
||||||
|
failed[n] = failed[n] + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
checkFailed(t, failed)
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkFailed(t *testing.T, failed map[string]int) {
|
||||||
|
for k, v := range failed {
|
||||||
|
if v > 8 {
|
||||||
|
t.Errorf("%v seems to not be getting set, was zero value %v times", k, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFuzz_structptr(t *testing.T) {
|
||||||
|
obj := &struct {
|
||||||
|
A *struct {
|
||||||
|
S string
|
||||||
|
}
|
||||||
|
}{}
|
||||||
|
|
||||||
|
f := New().NilChance(.5)
|
||||||
|
failed := map[string]int{}
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
f.Fuzz(obj)
|
||||||
|
|
||||||
|
if n, v := "a not nil", obj.A; v == nil {
|
||||||
|
failed[n] = failed[n] + 1
|
||||||
|
}
|
||||||
|
if n, v := "a nil", obj.A; v != nil {
|
||||||
|
failed[n] = failed[n] + 1
|
||||||
|
}
|
||||||
|
if n, v := "as", obj.A; v == nil || v.S == "" {
|
||||||
|
failed[n] = failed[n] + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
checkFailed(t, failed)
|
||||||
|
}
|
||||||
|
|
||||||
|
// tryFuzz tries fuzzing up to 20 times. Fail if check() never passes, report the highest
|
||||||
|
// stage it ever got to.
|
||||||
|
func tryFuzz(t *testing.T, f *Fuzzer, obj interface{}, check func() (stage int, passed bool)) {
|
||||||
|
maxStage := 0
|
||||||
|
for i := 0; i < 20; i++ {
|
||||||
|
f.Fuzz(obj)
|
||||||
|
stage, passed := check()
|
||||||
|
if stage > maxStage {
|
||||||
|
maxStage = stage
|
||||||
|
}
|
||||||
|
if passed {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.Errorf("Only ever got to stage %v", maxStage)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFuzz_structmap(t *testing.T) {
|
||||||
|
obj := &struct {
|
||||||
|
A map[struct {
|
||||||
|
S string
|
||||||
|
}]struct {
|
||||||
|
S2 string
|
||||||
|
}
|
||||||
|
B map[string]string
|
||||||
|
}{}
|
||||||
|
|
||||||
|
tryFuzz(t, New(), obj, func() (int, bool) {
|
||||||
|
if obj.A == nil {
|
||||||
|
return 1, false
|
||||||
|
}
|
||||||
|
if len(obj.A) == 0 {
|
||||||
|
return 2, false
|
||||||
|
}
|
||||||
|
for k, v := range obj.A {
|
||||||
|
if k.S == "" {
|
||||||
|
return 3, false
|
||||||
|
}
|
||||||
|
if v.S2 == "" {
|
||||||
|
return 4, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if obj.B == nil {
|
||||||
|
return 5, false
|
||||||
|
}
|
||||||
|
if len(obj.B) == 0 {
|
||||||
|
return 6, false
|
||||||
|
}
|
||||||
|
for k, v := range obj.B {
|
||||||
|
if k == "" {
|
||||||
|
return 7, false
|
||||||
|
}
|
||||||
|
if v == "" {
|
||||||
|
return 8, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 9, true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFuzz_structslice(t *testing.T) {
|
||||||
|
obj := &struct {
|
||||||
|
A []struct {
|
||||||
|
S string
|
||||||
|
}
|
||||||
|
B []string
|
||||||
|
}{}
|
||||||
|
|
||||||
|
tryFuzz(t, New(), obj, func() (int, bool) {
|
||||||
|
if obj.A == nil {
|
||||||
|
return 1, false
|
||||||
|
}
|
||||||
|
if len(obj.A) == 0 {
|
||||||
|
return 2, false
|
||||||
|
}
|
||||||
|
for _, v := range obj.A {
|
||||||
|
if v.S == "" {
|
||||||
|
return 3, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if obj.B == nil {
|
||||||
|
return 4, false
|
||||||
|
}
|
||||||
|
if len(obj.B) == 0 {
|
||||||
|
return 5, false
|
||||||
|
}
|
||||||
|
for _, v := range obj.B {
|
||||||
|
if v == "" {
|
||||||
|
return 6, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 7, true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFuzz_custom(t *testing.T) {
|
||||||
|
obj := &struct {
|
||||||
|
A string
|
||||||
|
B *string
|
||||||
|
C map[string]string
|
||||||
|
D *map[string]string
|
||||||
|
}{}
|
||||||
|
|
||||||
|
testPhrase := "gotcalled"
|
||||||
|
testMap := map[string]string{"C": "D"}
|
||||||
|
f := New().Funcs(
|
||||||
|
func(s *string, c Continue) {
|
||||||
|
*s = testPhrase
|
||||||
|
},
|
||||||
|
func(m map[string]string, c Continue) {
|
||||||
|
m["C"] = "D"
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
tryFuzz(t, f, obj, func() (int, bool) {
|
||||||
|
if obj.A != testPhrase {
|
||||||
|
return 1, false
|
||||||
|
}
|
||||||
|
if obj.B == nil {
|
||||||
|
return 2, false
|
||||||
|
}
|
||||||
|
if *obj.B != testPhrase {
|
||||||
|
return 3, false
|
||||||
|
}
|
||||||
|
if e, a := testMap, obj.C; !reflect.DeepEqual(e, a) {
|
||||||
|
return 4, false
|
||||||
|
}
|
||||||
|
if obj.D == nil {
|
||||||
|
return 5, false
|
||||||
|
}
|
||||||
|
if e, a := testMap, *obj.D; !reflect.DeepEqual(e, a) {
|
||||||
|
return 6, false
|
||||||
|
}
|
||||||
|
return 7, true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type SelfFuzzer string
|
||||||
|
|
||||||
|
// Implement fuzz.Interface.
|
||||||
|
func (sf *SelfFuzzer) Fuzz(c Continue) {
|
||||||
|
*sf = selfFuzzerTestPhrase
|
||||||
|
}
|
||||||
|
|
||||||
|
const selfFuzzerTestPhrase = "was fuzzed"
|
||||||
|
|
||||||
|
func TestFuzz_interface(t *testing.T) {
|
||||||
|
f := New()
|
||||||
|
|
||||||
|
var obj1 SelfFuzzer
|
||||||
|
tryFuzz(t, f, &obj1, func() (int, bool) {
|
||||||
|
if obj1 != selfFuzzerTestPhrase {
|
||||||
|
return 1, false
|
||||||
|
}
|
||||||
|
return 1, true
|
||||||
|
})
|
||||||
|
|
||||||
|
var obj2 map[int]SelfFuzzer
|
||||||
|
tryFuzz(t, f, &obj2, func() (int, bool) {
|
||||||
|
for _, v := range obj2 {
|
||||||
|
if v != selfFuzzerTestPhrase {
|
||||||
|
return 1, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 1, true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFuzz_interfaceAndFunc(t *testing.T) {
|
||||||
|
const privateTestPhrase = "private phrase"
|
||||||
|
f := New().Funcs(
|
||||||
|
// This should take precedence over SelfFuzzer.Fuzz().
|
||||||
|
func(s *SelfFuzzer, c Continue) {
|
||||||
|
*s = privateTestPhrase
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
var obj1 SelfFuzzer
|
||||||
|
tryFuzz(t, f, &obj1, func() (int, bool) {
|
||||||
|
if obj1 != privateTestPhrase {
|
||||||
|
return 1, false
|
||||||
|
}
|
||||||
|
return 1, true
|
||||||
|
})
|
||||||
|
|
||||||
|
var obj2 map[int]SelfFuzzer
|
||||||
|
tryFuzz(t, f, &obj2, func() (int, bool) {
|
||||||
|
for _, v := range obj2 {
|
||||||
|
if v != privateTestPhrase {
|
||||||
|
return 1, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 1, true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFuzz_noCustom(t *testing.T) {
|
||||||
|
type Inner struct {
|
||||||
|
Str string
|
||||||
|
}
|
||||||
|
type Outer struct {
|
||||||
|
Str string
|
||||||
|
In Inner
|
||||||
|
}
|
||||||
|
|
||||||
|
testPhrase := "gotcalled"
|
||||||
|
f := New().Funcs(
|
||||||
|
func(outer *Outer, c Continue) {
|
||||||
|
outer.Str = testPhrase
|
||||||
|
c.Fuzz(&outer.In)
|
||||||
|
},
|
||||||
|
func(inner *Inner, c Continue) {
|
||||||
|
inner.Str = testPhrase
|
||||||
|
},
|
||||||
|
)
|
||||||
|
c := Continue{f: f, Rand: f.r}
|
||||||
|
|
||||||
|
// Fuzzer.Fuzz()
|
||||||
|
obj1 := Outer{}
|
||||||
|
f.Fuzz(&obj1)
|
||||||
|
if obj1.Str != testPhrase {
|
||||||
|
t.Errorf("expected Outer custom function to have been called")
|
||||||
|
}
|
||||||
|
if obj1.In.Str != testPhrase {
|
||||||
|
t.Errorf("expected Inner custom function to have been called")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Continue.Fuzz()
|
||||||
|
obj2 := Outer{}
|
||||||
|
c.Fuzz(&obj2)
|
||||||
|
if obj2.Str != testPhrase {
|
||||||
|
t.Errorf("expected Outer custom function to have been called")
|
||||||
|
}
|
||||||
|
if obj2.In.Str != testPhrase {
|
||||||
|
t.Errorf("expected Inner custom function to have been called")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fuzzer.FuzzNoCustom()
|
||||||
|
obj3 := Outer{}
|
||||||
|
f.FuzzNoCustom(&obj3)
|
||||||
|
if obj3.Str == testPhrase {
|
||||||
|
t.Errorf("expected Outer custom function to not have been called")
|
||||||
|
}
|
||||||
|
if obj3.In.Str != testPhrase {
|
||||||
|
t.Errorf("expected Inner custom function to have been called")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Continue.FuzzNoCustom()
|
||||||
|
obj4 := Outer{}
|
||||||
|
c.FuzzNoCustom(&obj4)
|
||||||
|
if obj4.Str == testPhrase {
|
||||||
|
t.Errorf("expected Outer custom function to not have been called")
|
||||||
|
}
|
||||||
|
if obj4.In.Str != testPhrase {
|
||||||
|
t.Errorf("expected Inner custom function to have been called")
|
||||||
|
}
|
||||||
|
}
|
|
@ -25,15 +25,15 @@ func (err ErrValidation) Error() string {
|
||||||
return fmt.Sprintf("An error occurred during validation: %s", err.msg)
|
return fmt.Sprintf("An error occurred during validation: %s", err.msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ErrBadHeirarchy represents a missing snapshot at this current time.
|
// ErrBadHierarchy represents a missing snapshot at this current time.
|
||||||
// When delegations are implemented it will also represent a missing
|
// When delegations are implemented it will also represent a missing
|
||||||
// delegation parent
|
// delegation parent
|
||||||
type ErrBadHeirarchy struct {
|
type ErrBadHierarchy struct {
|
||||||
msg string
|
msg string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (err ErrBadHeirarchy) Error() string {
|
func (err ErrBadHierarchy) Error() string {
|
||||||
return fmt.Sprintf("Heirarchy of updates in incorrect: %s", err.msg)
|
return fmt.Sprintf("Hierarchy of updates in incorrect: %s", err.msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ErrBadRoot represents a failure validating the root
|
// ErrBadRoot represents a failure validating the root
|
||||||
|
@ -77,14 +77,15 @@ func validateUpdate(gun string, updates []storage.MetaUpdate, store storage.Meta
|
||||||
for _, v := range updates {
|
for _, v := range updates {
|
||||||
roles[v.Role] = v
|
roles[v.Role] = v
|
||||||
}
|
}
|
||||||
if err := heirarchyOK(roles); err != nil {
|
if err := hierarchyOK(roles); err != nil {
|
||||||
logrus.Error("ErrBadHeirarchy: ", err.Error())
|
logrus.Error("ErrBadHierarchy: ", err.Error())
|
||||||
return ErrBadHeirarchy{msg: err.Error()}
|
return ErrBadHierarchy{msg: err.Error()}
|
||||||
}
|
}
|
||||||
|
logrus.Debug("Successfully validated hierarchy")
|
||||||
|
|
||||||
var root *data.SignedRoot
|
var root *data.SignedRoot
|
||||||
oldRootJSON, err := store.GetCurrent(gun, rootRole)
|
oldRootJSON, err := store.GetCurrent(gun, rootRole)
|
||||||
if _, ok := err.(*storage.ErrNotFound); !ok {
|
if _, ok := err.(*storage.ErrNotFound); err != nil && !ok {
|
||||||
// problem with storage. No expectation we can
|
// problem with storage. No expectation we can
|
||||||
// write if we can't read so bail.
|
// write if we can't read so bail.
|
||||||
logrus.Error("error reading previous root: ", err.Error())
|
logrus.Error("error reading previous root: ", err.Error())
|
||||||
|
@ -102,10 +103,19 @@ func validateUpdate(gun string, updates []storage.MetaUpdate, store storage.Meta
|
||||||
logrus.Error("ErrValidation: ", err.Error())
|
logrus.Error("ErrValidation: ", err.Error())
|
||||||
return ErrValidation{msg: err.Error()}
|
return ErrValidation{msg: err.Error()}
|
||||||
}
|
}
|
||||||
|
logrus.Debug("Successfully validated root")
|
||||||
} else {
|
} else {
|
||||||
if oldRootJSON == nil {
|
if oldRootJSON == nil {
|
||||||
return ErrValidation{msg: "no pre-existing root and no root provided in update."}
|
return ErrValidation{msg: "no pre-existing root and no root provided in update."}
|
||||||
}
|
}
|
||||||
|
parsedOldRoot := &data.SignedRoot{}
|
||||||
|
if err := json.Unmarshal(oldRootJSON, parsedOldRoot); err != nil {
|
||||||
|
return ErrValidation{msg: "pre-existing root is corrupted and no root provided in update."}
|
||||||
|
}
|
||||||
|
if err = repo.SetRoot(parsedOldRoot); err != nil {
|
||||||
|
logrus.Error("ErrValidation: ", err.Error())
|
||||||
|
return ErrValidation{msg: err.Error()}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: validate delegated targets roles.
|
// TODO: validate delegated targets roles.
|
||||||
|
@ -117,10 +127,11 @@ func validateUpdate(gun string, updates []storage.MetaUpdate, store storage.Meta
|
||||||
}
|
}
|
||||||
repo.SetTargets(targetsRole, t)
|
repo.SetTargets(targetsRole, t)
|
||||||
}
|
}
|
||||||
|
logrus.Debug("Successfully validated targets")
|
||||||
|
|
||||||
var oldSnap *data.SignedSnapshot
|
var oldSnap *data.SignedSnapshot
|
||||||
oldSnapJSON, err := store.GetCurrent(gun, snapshotRole)
|
oldSnapJSON, err := store.GetCurrent(gun, snapshotRole)
|
||||||
if _, ok := err.(*storage.ErrNotFound); !ok {
|
if _, ok := err.(*storage.ErrNotFound); err != nil && !ok {
|
||||||
// problem with storage. No expectation we can
|
// problem with storage. No expectation we can
|
||||||
// write if we can't read so bail.
|
// write if we can't read so bail.
|
||||||
logrus.Error("error reading previous snapshot: ", err.Error())
|
logrus.Error("error reading previous snapshot: ", err.Error())
|
||||||
|
@ -136,6 +147,7 @@ func validateUpdate(gun string, updates []storage.MetaUpdate, store storage.Meta
|
||||||
logrus.Error("ErrBadSnapshot: ", err.Error())
|
logrus.Error("ErrBadSnapshot: ", err.Error())
|
||||||
return ErrBadSnapshot{msg: err.Error()}
|
return ErrBadSnapshot{msg: err.Error()}
|
||||||
}
|
}
|
||||||
|
logrus.Debug("Successfully validated snapshot")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -151,21 +163,27 @@ func validateSnapshot(role string, oldSnap *data.SignedSnapshot, snapUpdate stor
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if oldSnap != nil {
|
|
||||||
snap, err := data.SnapshotFromSigned(s)
|
snap, err := data.SnapshotFromSigned(s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New("could not parse snapshot")
|
return errors.New("could not parse snapshot")
|
||||||
}
|
}
|
||||||
|
if !data.ValidTUFType(snap.Signed.Type, data.CanonicalSnapshotRole) {
|
||||||
|
return errors.New("snapshot has wrong type")
|
||||||
|
}
|
||||||
err = checkSnapshotEntries(role, oldSnap, snap, roles)
|
err = checkSnapshotEntries(role, oldSnap, snap, roles)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkSnapshotEntries(role string, oldSnap, snap *data.SignedSnapshot, roles map[string]storage.MetaUpdate) error {
|
func checkSnapshotEntries(role string, oldSnap, snap *data.SignedSnapshot, roles map[string]storage.MetaUpdate) error {
|
||||||
|
snapshotRole := data.RoleName(data.CanonicalSnapshotRole)
|
||||||
|
timestampRole := data.RoleName(data.CanonicalTimestampRole) // just in case
|
||||||
for r, update := range roles {
|
for r, update := range roles {
|
||||||
|
if r == snapshotRole || r == timestampRole {
|
||||||
|
continue
|
||||||
|
}
|
||||||
m, ok := snap.Signed.Meta[r]
|
m, ok := snap.Signed.Meta[r]
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("snapshot missing metadata for %s", r)
|
return fmt.Errorf("snapshot missing metadata for %s", r)
|
||||||
|
@ -204,10 +222,17 @@ func validateTargets(role string, roles map[string]storage.MetaUpdate, kdb *keys
|
||||||
if err := signed.Verify(s, role, 0, kdb); err != nil {
|
if err := signed.Verify(s, role, 0, kdb); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return data.TargetsFromSigned(s)
|
t, err := data.TargetsFromSigned(s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !data.ValidTUFType(t.Signed.Type, data.CanonicalTargetsRole) {
|
||||||
|
return nil, fmt.Errorf("%s has wrong type", role)
|
||||||
|
}
|
||||||
|
return t, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// check the snapshot is present. If it is, the heirarchy
|
// check the snapshot is present. If it is, the hierarchy
|
||||||
// of the update is OK. This seems like a simplistic check
|
// of the update is OK. This seems like a simplistic check
|
||||||
// but is completely sufficient for all possible use cases:
|
// but is completely sufficient for all possible use cases:
|
||||||
// 1. the user is updating only the snapshot.
|
// 1. the user is updating only the snapshot.
|
||||||
|
@ -219,7 +244,7 @@ func validateTargets(role string, roles map[string]storage.MetaUpdate, kdb *keys
|
||||||
// timestamp, the server will replace it on next
|
// timestamp, the server will replace it on next
|
||||||
// GET timestamp.jsonshould it detect the current
|
// GET timestamp.jsonshould it detect the current
|
||||||
// snapshot has a different hash to the one in the timestamp.
|
// snapshot has a different hash to the one in the timestamp.
|
||||||
func heirarchyOK(roles map[string]storage.MetaUpdate) error {
|
func hierarchyOK(roles map[string]storage.MetaUpdate) error {
|
||||||
snapshotRole := data.RoleName(data.CanonicalSnapshotRole)
|
snapshotRole := data.RoleName(data.CanonicalSnapshotRole)
|
||||||
if _, ok := roles[snapshotRole]; !ok {
|
if _, ok := roles[snapshotRole]; !ok {
|
||||||
return errors.New("snapshot missing from update")
|
return errors.New("snapshot missing from update")
|
||||||
|
@ -253,6 +278,9 @@ func validateRoot(gun string, oldRoot, newRoot []byte) (*data.SignedRoot, error)
|
||||||
// correct old key signatures.
|
// correct old key signatures.
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if !data.ValidTUFType(parsedNewRoot.Signed.Type, data.CanonicalRootRole) {
|
||||||
|
return nil, fmt.Errorf("root has wrong type")
|
||||||
|
}
|
||||||
return parsedNewRoot, nil
|
return parsedNewRoot, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -261,6 +289,10 @@ func validateRoot(gun string, oldRoot, newRoot []byte) (*data.SignedRoot, error)
|
||||||
// are valid.
|
// are valid.
|
||||||
func checkRoot(oldRoot, newRoot *data.SignedRoot) error {
|
func checkRoot(oldRoot, newRoot *data.SignedRoot) error {
|
||||||
rootRole := data.RoleName(data.CanonicalRootRole)
|
rootRole := data.RoleName(data.CanonicalRootRole)
|
||||||
|
targetsRole := data.RoleName(data.CanonicalTargetsRole)
|
||||||
|
snapshotRole := data.RoleName(data.CanonicalSnapshotRole)
|
||||||
|
timestampRole := data.RoleName(data.CanonicalTimestampRole)
|
||||||
|
|
||||||
var oldRootRole *data.RootRole
|
var oldRootRole *data.RootRole
|
||||||
newRootRole, ok := newRoot.Signed.Roles[rootRole]
|
newRootRole, ok := newRoot.Signed.Roles[rootRole]
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -311,14 +343,35 @@ func checkRoot(oldRoot, newRoot *data.SignedRoot) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
newSigned, err := newRoot.ToSigned()
|
newSigned, err := newRoot.ToSigned()
|
||||||
if rotation {
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if rotation {
|
||||||
err = signed.VerifyRoot(newSigned, oldThreshold, oldKeys)
|
err = signed.VerifyRoot(newSigned, oldThreshold, oldKeys)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("rotation detected and new root was not signed with at least %d old keys", oldThreshold)
|
return fmt.Errorf("rotation detected and new root was not signed with at least %d old keys", oldThreshold)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return signed.VerifyRoot(newSigned, newRootRole.Threshold, newKeys)
|
err = signed.VerifyRoot(newSigned, newRootRole.Threshold, newKeys)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
root, err := data.RootFromSigned(newSigned)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// at a minimum, check the 4 required roles are present
|
||||||
|
for _, r := range []string{rootRole, targetsRole, snapshotRole, timestampRole} {
|
||||||
|
role, ok := root.Signed.Roles[r]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("missing required %s role from root", r)
|
||||||
|
}
|
||||||
|
if role.Threshold < 1 {
|
||||||
|
return fmt.Errorf("%s role has invalid threshold", r)
|
||||||
|
}
|
||||||
|
if len(role.KeyIDs) < role.Threshold {
|
||||||
|
return fmt.Errorf("%s role has insufficient number of keys", r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,893 @@
|
||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/endophage/gotuf/data"
|
||||||
|
"github.com/endophage/gotuf/signed"
|
||||||
|
"github.com/endophage/gotuf/testutils"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
"github.com/docker/notary/server/storage"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestValidateEmptyNew(t *testing.T) {
|
||||||
|
_, repo, _ := testutils.EmptyRepo()
|
||||||
|
store := storage.NewMemStorage()
|
||||||
|
|
||||||
|
r, tg, sn, ts, err := testutils.Sign(repo)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
root, targets, snapshot, timestamp, err := testutils.Serialize(r, tg, sn, ts)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
updates := []storage.MetaUpdate{
|
||||||
|
{
|
||||||
|
Role: "root",
|
||||||
|
Version: 1,
|
||||||
|
Data: root,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Role: "targets",
|
||||||
|
Version: 1,
|
||||||
|
Data: targets,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Role: "snapshot",
|
||||||
|
Version: 1,
|
||||||
|
Data: snapshot,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Role: "timestamp",
|
||||||
|
Version: 1,
|
||||||
|
Data: timestamp,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err = validateUpdate("testGUN", updates, store)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateNoNewRoot(t *testing.T) {
|
||||||
|
_, repo, _ := testutils.EmptyRepo()
|
||||||
|
store := storage.NewMemStorage()
|
||||||
|
|
||||||
|
r, tg, sn, ts, err := testutils.Sign(repo)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
root, targets, snapshot, timestamp, err := testutils.Serialize(r, tg, sn, ts)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
store.UpdateCurrent(
|
||||||
|
"testGUN",
|
||||||
|
storage.MetaUpdate{
|
||||||
|
Role: "root",
|
||||||
|
Version: 1,
|
||||||
|
Data: root,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
updates := []storage.MetaUpdate{
|
||||||
|
{
|
||||||
|
Role: "targets",
|
||||||
|
Version: 1,
|
||||||
|
Data: targets,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Role: "snapshot",
|
||||||
|
Version: 1,
|
||||||
|
Data: snapshot,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Role: "timestamp",
|
||||||
|
Version: 1,
|
||||||
|
Data: timestamp,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err = validateUpdate("testGUN", updates, store)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateNoNewTargets(t *testing.T) {
|
||||||
|
_, repo, _ := testutils.EmptyRepo()
|
||||||
|
store := storage.NewMemStorage()
|
||||||
|
|
||||||
|
r, tg, sn, ts, err := testutils.Sign(repo)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
root, targets, snapshot, timestamp, err := testutils.Serialize(r, tg, sn, ts)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
store.UpdateCurrent(
|
||||||
|
"testGUN",
|
||||||
|
storage.MetaUpdate{
|
||||||
|
Role: "targets",
|
||||||
|
Version: 1,
|
||||||
|
Data: targets,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
updates := []storage.MetaUpdate{
|
||||||
|
{
|
||||||
|
Role: "root",
|
||||||
|
Version: 1,
|
||||||
|
Data: root,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Role: "snapshot",
|
||||||
|
Version: 1,
|
||||||
|
Data: snapshot,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Role: "timestamp",
|
||||||
|
Version: 1,
|
||||||
|
Data: timestamp,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err = validateUpdate("testGUN", updates, store)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateOnlySnapshot(t *testing.T) {
|
||||||
|
_, repo, _ := testutils.EmptyRepo()
|
||||||
|
store := storage.NewMemStorage()
|
||||||
|
|
||||||
|
r, tg, sn, ts, err := testutils.Sign(repo)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
root, targets, snapshot, _, err := testutils.Serialize(r, tg, sn, ts)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
store.UpdateCurrent(
|
||||||
|
"testGUN",
|
||||||
|
storage.MetaUpdate{
|
||||||
|
Role: "root",
|
||||||
|
Version: 1,
|
||||||
|
Data: root,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
store.UpdateCurrent(
|
||||||
|
"testGUN",
|
||||||
|
storage.MetaUpdate{
|
||||||
|
Role: "targets",
|
||||||
|
Version: 1,
|
||||||
|
Data: targets,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
updates := []storage.MetaUpdate{
|
||||||
|
{
|
||||||
|
Role: "snapshot",
|
||||||
|
Version: 1,
|
||||||
|
Data: snapshot,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err = validateUpdate("testGUN", updates, store)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateOldRoot(t *testing.T) {
|
||||||
|
_, repo, _ := testutils.EmptyRepo()
|
||||||
|
store := storage.NewMemStorage()
|
||||||
|
|
||||||
|
r, tg, sn, ts, err := testutils.Sign(repo)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
root, targets, snapshot, timestamp, err := testutils.Serialize(r, tg, sn, ts)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
store.UpdateCurrent(
|
||||||
|
"testGUN",
|
||||||
|
storage.MetaUpdate{
|
||||||
|
Role: "root",
|
||||||
|
Version: 1,
|
||||||
|
Data: root,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
updates := []storage.MetaUpdate{
|
||||||
|
{
|
||||||
|
Role: "root",
|
||||||
|
Version: 1,
|
||||||
|
Data: root,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Role: "targets",
|
||||||
|
Version: 1,
|
||||||
|
Data: targets,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Role: "snapshot",
|
||||||
|
Version: 1,
|
||||||
|
Data: snapshot,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Role: "timestamp",
|
||||||
|
Version: 1,
|
||||||
|
Data: timestamp,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err = validateUpdate("testGUN", updates, store)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateRootRotation(t *testing.T) {
|
||||||
|
_, repo, crypto := testutils.EmptyRepo()
|
||||||
|
store := storage.NewMemStorage()
|
||||||
|
|
||||||
|
r, tg, sn, ts, err := testutils.Sign(repo)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
root, targets, snapshot, timestamp, err := testutils.Serialize(r, tg, sn, ts)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
store.UpdateCurrent(
|
||||||
|
"testGUN",
|
||||||
|
storage.MetaUpdate{
|
||||||
|
Role: "root",
|
||||||
|
Version: 1,
|
||||||
|
Data: root,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
oldRootRole := repo.Root.Signed.Roles["root"]
|
||||||
|
oldRootKey := repo.Root.Signed.Keys[oldRootRole.KeyIDs[0]]
|
||||||
|
|
||||||
|
rootKey, err := crypto.Create("root", data.ED25519Key)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
rootRole, err := data.NewRole("root", 1, []string{rootKey.ID()}, nil, nil)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
delete(repo.Root.Signed.Keys, oldRootRole.KeyIDs[0])
|
||||||
|
|
||||||
|
repo.Root.Signed.Roles["root"] = &rootRole.RootRole
|
||||||
|
repo.Root.Signed.Keys[rootKey.ID()] = data.NewPrivateKey(rootKey.Algorithm(), rootKey.Public(), nil)
|
||||||
|
|
||||||
|
r, err = repo.SignRoot(data.DefaultExpires(data.CanonicalRootRole), nil)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
err = signed.Sign(crypto, r, rootKey, oldRootKey)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
rt, err := data.RootFromSigned(r)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
repo.SetRoot(rt)
|
||||||
|
|
||||||
|
sn, err = repo.SignSnapshot(data.DefaultExpires(data.CanonicalSnapshotRole), nil)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
root, targets, snapshot, timestamp, err = testutils.Serialize(r, tg, sn, ts)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
updates := []storage.MetaUpdate{
|
||||||
|
{
|
||||||
|
Role: "root",
|
||||||
|
Version: 1,
|
||||||
|
Data: root,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Role: "targets",
|
||||||
|
Version: 1,
|
||||||
|
Data: targets,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Role: "snapshot",
|
||||||
|
Version: 1,
|
||||||
|
Data: snapshot,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Role: "timestamp",
|
||||||
|
Version: 1,
|
||||||
|
Data: timestamp,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err = validateUpdate("testGUN", updates, store)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateNoRoot(t *testing.T) {
|
||||||
|
_, repo, _ := testutils.EmptyRepo()
|
||||||
|
store := storage.NewMemStorage()
|
||||||
|
|
||||||
|
r, tg, sn, ts, err := testutils.Sign(repo)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
_, targets, snapshot, timestamp, err := testutils.Serialize(r, tg, sn, ts)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
updates := []storage.MetaUpdate{
|
||||||
|
{
|
||||||
|
Role: "targets",
|
||||||
|
Version: 1,
|
||||||
|
Data: targets,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Role: "snapshot",
|
||||||
|
Version: 1,
|
||||||
|
Data: snapshot,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Role: "timestamp",
|
||||||
|
Version: 1,
|
||||||
|
Data: timestamp,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err = validateUpdate("testGUN", updates, store)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.IsType(t, ErrValidation{}, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateSnapshotMissing(t *testing.T) {
|
||||||
|
_, repo, _ := testutils.EmptyRepo()
|
||||||
|
store := storage.NewMemStorage()
|
||||||
|
|
||||||
|
r, tg, sn, ts, err := testutils.Sign(repo)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
root, targets, _, _, err := testutils.Serialize(r, tg, sn, ts)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
updates := []storage.MetaUpdate{
|
||||||
|
{
|
||||||
|
Role: "root",
|
||||||
|
Version: 1,
|
||||||
|
Data: root,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Role: "targets",
|
||||||
|
Version: 1,
|
||||||
|
Data: targets,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err = validateUpdate("testGUN", updates, store)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.IsType(t, ErrBadHierarchy{}, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ### Role missing negative tests ###
|
||||||
|
// These tests remove a role from the Root file and
|
||||||
|
// check for a ErrBadRoot
|
||||||
|
func TestValidateRootRoleMissing(t *testing.T) {
|
||||||
|
_, repo, _ := testutils.EmptyRepo()
|
||||||
|
store := storage.NewMemStorage()
|
||||||
|
|
||||||
|
delete(repo.Root.Signed.Roles, "root")
|
||||||
|
|
||||||
|
r, tg, sn, ts, err := testutils.Sign(repo)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
root, targets, snapshot, timestamp, err := testutils.Serialize(r, tg, sn, ts)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
updates := []storage.MetaUpdate{
|
||||||
|
{
|
||||||
|
Role: "root",
|
||||||
|
Version: 1,
|
||||||
|
Data: root,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Role: "targets",
|
||||||
|
Version: 1,
|
||||||
|
Data: targets,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Role: "snapshot",
|
||||||
|
Version: 1,
|
||||||
|
Data: snapshot,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Role: "timestamp",
|
||||||
|
Version: 1,
|
||||||
|
Data: timestamp,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err = validateUpdate("testGUN", updates, store)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.IsType(t, ErrBadRoot{}, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateTargetsRoleMissing(t *testing.T) {
|
||||||
|
_, repo, _ := testutils.EmptyRepo()
|
||||||
|
store := storage.NewMemStorage()
|
||||||
|
|
||||||
|
delete(repo.Root.Signed.Roles, "targets")
|
||||||
|
|
||||||
|
r, tg, sn, ts, err := testutils.Sign(repo)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
root, targets, snapshot, timestamp, err := testutils.Serialize(r, tg, sn, ts)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
updates := []storage.MetaUpdate{
|
||||||
|
{
|
||||||
|
Role: "root",
|
||||||
|
Version: 1,
|
||||||
|
Data: root,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Role: "targets",
|
||||||
|
Version: 1,
|
||||||
|
Data: targets,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Role: "snapshot",
|
||||||
|
Version: 1,
|
||||||
|
Data: snapshot,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Role: "timestamp",
|
||||||
|
Version: 1,
|
||||||
|
Data: timestamp,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err = validateUpdate("testGUN", updates, store)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.IsType(t, ErrBadRoot{}, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateSnapshotRoleMissing(t *testing.T) {
|
||||||
|
_, repo, _ := testutils.EmptyRepo()
|
||||||
|
store := storage.NewMemStorage()
|
||||||
|
|
||||||
|
delete(repo.Root.Signed.Roles, "snapshot")
|
||||||
|
|
||||||
|
r, tg, sn, ts, err := testutils.Sign(repo)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
root, targets, snapshot, timestamp, err := testutils.Serialize(r, tg, sn, ts)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
updates := []storage.MetaUpdate{
|
||||||
|
{
|
||||||
|
Role: "root",
|
||||||
|
Version: 1,
|
||||||
|
Data: root,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Role: "targets",
|
||||||
|
Version: 1,
|
||||||
|
Data: targets,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Role: "snapshot",
|
||||||
|
Version: 1,
|
||||||
|
Data: snapshot,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Role: "timestamp",
|
||||||
|
Version: 1,
|
||||||
|
Data: timestamp,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err = validateUpdate("testGUN", updates, store)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.IsType(t, ErrBadRoot{}, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ### End role missing negative tests ###
|
||||||
|
|
||||||
|
// ### Signature missing negative tests ###
|
||||||
|
func TestValidateRootSigMissing(t *testing.T) {
|
||||||
|
_, repo, _ := testutils.EmptyRepo()
|
||||||
|
store := storage.NewMemStorage()
|
||||||
|
|
||||||
|
delete(repo.Root.Signed.Roles, "snapshot")
|
||||||
|
|
||||||
|
r, tg, sn, ts, err := testutils.Sign(repo)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
r.Signatures = nil
|
||||||
|
|
||||||
|
root, targets, snapshot, timestamp, err := testutils.Serialize(r, tg, sn, ts)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
updates := []storage.MetaUpdate{
|
||||||
|
{
|
||||||
|
Role: "root",
|
||||||
|
Version: 1,
|
||||||
|
Data: root,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Role: "targets",
|
||||||
|
Version: 1,
|
||||||
|
Data: targets,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Role: "snapshot",
|
||||||
|
Version: 1,
|
||||||
|
Data: snapshot,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Role: "timestamp",
|
||||||
|
Version: 1,
|
||||||
|
Data: timestamp,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err = validateUpdate("testGUN", updates, store)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.IsType(t, ErrBadRoot{}, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateTargetsSigMissing(t *testing.T) {
|
||||||
|
_, repo, _ := testutils.EmptyRepo()
|
||||||
|
store := storage.NewMemStorage()
|
||||||
|
|
||||||
|
r, tg, sn, ts, err := testutils.Sign(repo)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
tg.Signatures = nil
|
||||||
|
|
||||||
|
root, targets, snapshot, timestamp, err := testutils.Serialize(r, tg, sn, ts)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
updates := []storage.MetaUpdate{
|
||||||
|
{
|
||||||
|
Role: "root",
|
||||||
|
Version: 1,
|
||||||
|
Data: root,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Role: "targets",
|
||||||
|
Version: 1,
|
||||||
|
Data: targets,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Role: "snapshot",
|
||||||
|
Version: 1,
|
||||||
|
Data: snapshot,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Role: "timestamp",
|
||||||
|
Version: 1,
|
||||||
|
Data: timestamp,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err = validateUpdate("testGUN", updates, store)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.IsType(t, ErrBadTargets{}, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateSnapshotSigMissing(t *testing.T) {
|
||||||
|
_, repo, _ := testutils.EmptyRepo()
|
||||||
|
store := storage.NewMemStorage()
|
||||||
|
|
||||||
|
r, tg, sn, ts, err := testutils.Sign(repo)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
sn.Signatures = nil
|
||||||
|
|
||||||
|
root, targets, snapshot, timestamp, err := testutils.Serialize(r, tg, sn, ts)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
updates := []storage.MetaUpdate{
|
||||||
|
{
|
||||||
|
Role: "root",
|
||||||
|
Version: 1,
|
||||||
|
Data: root,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Role: "targets",
|
||||||
|
Version: 1,
|
||||||
|
Data: targets,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Role: "snapshot",
|
||||||
|
Version: 1,
|
||||||
|
Data: snapshot,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Role: "timestamp",
|
||||||
|
Version: 1,
|
||||||
|
Data: timestamp,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err = validateUpdate("testGUN", updates, store)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.IsType(t, ErrBadSnapshot{}, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ### End signature missing negative tests ###
|
||||||
|
|
||||||
|
// ### Corrupted metadata negative tests ###
|
||||||
|
func TestValidateRootCorrupt(t *testing.T) {
|
||||||
|
_, repo, _ := testutils.EmptyRepo()
|
||||||
|
store := storage.NewMemStorage()
|
||||||
|
|
||||||
|
r, tg, sn, ts, err := testutils.Sign(repo)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
root, targets, snapshot, timestamp, err := testutils.Serialize(r, tg, sn, ts)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// flip all the bits in the first byte
|
||||||
|
root[0] = root[0] ^ 0xff
|
||||||
|
|
||||||
|
updates := []storage.MetaUpdate{
|
||||||
|
{
|
||||||
|
Role: "root",
|
||||||
|
Version: 1,
|
||||||
|
Data: root,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Role: "targets",
|
||||||
|
Version: 1,
|
||||||
|
Data: targets,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Role: "snapshot",
|
||||||
|
Version: 1,
|
||||||
|
Data: snapshot,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Role: "timestamp",
|
||||||
|
Version: 1,
|
||||||
|
Data: timestamp,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err = validateUpdate("testGUN", updates, store)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.IsType(t, ErrBadRoot{}, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateTargetsCorrupt(t *testing.T) {
|
||||||
|
_, repo, _ := testutils.EmptyRepo()
|
||||||
|
store := storage.NewMemStorage()
|
||||||
|
|
||||||
|
r, tg, sn, ts, err := testutils.Sign(repo)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
root, targets, snapshot, timestamp, err := testutils.Serialize(r, tg, sn, ts)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// flip all the bits in the first byte
|
||||||
|
targets[0] = targets[0] ^ 0xff
|
||||||
|
|
||||||
|
updates := []storage.MetaUpdate{
|
||||||
|
{
|
||||||
|
Role: "root",
|
||||||
|
Version: 1,
|
||||||
|
Data: root,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Role: "targets",
|
||||||
|
Version: 1,
|
||||||
|
Data: targets,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Role: "snapshot",
|
||||||
|
Version: 1,
|
||||||
|
Data: snapshot,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Role: "timestamp",
|
||||||
|
Version: 1,
|
||||||
|
Data: timestamp,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err = validateUpdate("testGUN", updates, store)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.IsType(t, ErrBadTargets{}, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateSnapshotCorrupt(t *testing.T) {
|
||||||
|
_, repo, _ := testutils.EmptyRepo()
|
||||||
|
store := storage.NewMemStorage()
|
||||||
|
|
||||||
|
r, tg, sn, ts, err := testutils.Sign(repo)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
root, targets, snapshot, timestamp, err := testutils.Serialize(r, tg, sn, ts)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// flip all the bits in the first byte
|
||||||
|
snapshot[0] = snapshot[0] ^ 0xff
|
||||||
|
|
||||||
|
updates := []storage.MetaUpdate{
|
||||||
|
{
|
||||||
|
Role: "root",
|
||||||
|
Version: 1,
|
||||||
|
Data: root,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Role: "targets",
|
||||||
|
Version: 1,
|
||||||
|
Data: targets,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Role: "snapshot",
|
||||||
|
Version: 1,
|
||||||
|
Data: snapshot,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Role: "timestamp",
|
||||||
|
Version: 1,
|
||||||
|
Data: timestamp,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err = validateUpdate("testGUN", updates, store)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.IsType(t, ErrBadSnapshot{}, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ### End corrupted metadata negative tests ###
|
||||||
|
|
||||||
|
// ### Snapshot size mismatch negative tests ###
|
||||||
|
func TestValidateRootModifiedSize(t *testing.T) {
|
||||||
|
_, repo, _ := testutils.EmptyRepo()
|
||||||
|
store := storage.NewMemStorage()
|
||||||
|
|
||||||
|
r, tg, sn, ts, err := testutils.Sign(repo)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// add another copy of the signature so the hash is different
|
||||||
|
r.Signatures = append(r.Signatures, r.Signatures[0])
|
||||||
|
|
||||||
|
root, targets, snapshot, timestamp, err := testutils.Serialize(r, tg, sn, ts)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// flip all the bits in the first byte
|
||||||
|
root[0] = root[0] ^ 0xff
|
||||||
|
|
||||||
|
updates := []storage.MetaUpdate{
|
||||||
|
{
|
||||||
|
Role: "root",
|
||||||
|
Version: 1,
|
||||||
|
Data: root,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Role: "targets",
|
||||||
|
Version: 1,
|
||||||
|
Data: targets,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Role: "snapshot",
|
||||||
|
Version: 1,
|
||||||
|
Data: snapshot,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Role: "timestamp",
|
||||||
|
Version: 1,
|
||||||
|
Data: timestamp,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err = validateUpdate("testGUN", updates, store)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.IsType(t, ErrBadRoot{}, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateTargetsModifiedSize(t *testing.T) {
|
||||||
|
_, repo, _ := testutils.EmptyRepo()
|
||||||
|
store := storage.NewMemStorage()
|
||||||
|
|
||||||
|
r, tg, sn, ts, err := testutils.Sign(repo)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// add another copy of the signature so the hash is different
|
||||||
|
tg.Signatures = append(tg.Signatures, tg.Signatures[0])
|
||||||
|
|
||||||
|
root, targets, snapshot, timestamp, err := testutils.Serialize(r, tg, sn, ts)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
updates := []storage.MetaUpdate{
|
||||||
|
{
|
||||||
|
Role: "root",
|
||||||
|
Version: 1,
|
||||||
|
Data: root,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Role: "targets",
|
||||||
|
Version: 1,
|
||||||
|
Data: targets,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Role: "snapshot",
|
||||||
|
Version: 1,
|
||||||
|
Data: snapshot,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Role: "timestamp",
|
||||||
|
Version: 1,
|
||||||
|
Data: timestamp,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err = validateUpdate("testGUN", updates, store)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.IsType(t, ErrBadSnapshot{}, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ### End snapshot size mismatch negative tests ###
|
||||||
|
|
||||||
|
// ### Snapshot hash mismatch negative tests ###
|
||||||
|
func TestValidateRootModifiedHash(t *testing.T) {
|
||||||
|
_, repo, _ := testutils.EmptyRepo()
|
||||||
|
store := storage.NewMemStorage()
|
||||||
|
|
||||||
|
r, tg, sn, ts, err := testutils.Sign(repo)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
snap, err := data.SnapshotFromSigned(sn)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
snap.Signed.Meta["root"].Hashes["sha256"][0] = snap.Signed.Meta["root"].Hashes["sha256"][0] ^ 0xff
|
||||||
|
|
||||||
|
sn, err = snap.ToSigned()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
root, targets, snapshot, timestamp, err := testutils.Serialize(r, tg, sn, ts)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
updates := []storage.MetaUpdate{
|
||||||
|
{
|
||||||
|
Role: "root",
|
||||||
|
Version: 1,
|
||||||
|
Data: root,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Role: "targets",
|
||||||
|
Version: 1,
|
||||||
|
Data: targets,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Role: "snapshot",
|
||||||
|
Version: 1,
|
||||||
|
Data: snapshot,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Role: "timestamp",
|
||||||
|
Version: 1,
|
||||||
|
Data: timestamp,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err = validateUpdate("testGUN", updates, store)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.IsType(t, ErrBadSnapshot{}, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateTargetsModifiedHash(t *testing.T) {
|
||||||
|
_, repo, _ := testutils.EmptyRepo()
|
||||||
|
store := storage.NewMemStorage()
|
||||||
|
|
||||||
|
r, tg, sn, ts, err := testutils.Sign(repo)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
snap, err := data.SnapshotFromSigned(sn)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
snap.Signed.Meta["targets"].Hashes["sha256"][0] = snap.Signed.Meta["targets"].Hashes["sha256"][0] ^ 0xff
|
||||||
|
|
||||||
|
sn, err = snap.ToSigned()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
root, targets, snapshot, timestamp, err := testutils.Serialize(r, tg, sn, ts)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
updates := []storage.MetaUpdate{
|
||||||
|
{
|
||||||
|
Role: "root",
|
||||||
|
Version: 1,
|
||||||
|
Data: root,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Role: "targets",
|
||||||
|
Version: 1,
|
||||||
|
Data: targets,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Role: "snapshot",
|
||||||
|
Version: 1,
|
||||||
|
Data: snapshot,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Role: "timestamp",
|
||||||
|
Version: 1,
|
||||||
|
Data: timestamp,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err = validateUpdate("testGUN", updates, store)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.IsType(t, ErrBadSnapshot{}, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ### End snapshot hash mismatch negative tests ###
|
Loading…
Reference in New Issue