diff options
Diffstat (limited to 'pkg/ascii')
-rw-r--r-- | pkg/ascii/ascii.go | 306 | ||||
-rw-r--r-- | pkg/ascii/ascii_test.go | 455 |
2 files changed, 0 insertions, 761 deletions
diff --git a/pkg/ascii/ascii.go b/pkg/ascii/ascii.go deleted file mode 100644 index d91a240..0000000 --- a/pkg/ascii/ascii.go +++ /dev/null @@ -1,306 +0,0 @@ -// package ascii implements an ASCII key-value parser. -// -// The top-most (de)serialize must operate on a struct pointer. A struct may -// contain other structs, in which case all tag names should be unique. Public -// fields without tag names are ignored. Private fields are also ignored. -// -// The supported field types are: -// - struct -// - string (no empty strings) -// - uint64 (only digits in ASCII representation) -// - byte array (only lower-case hex in ASCII representation) -// - slice of uint64 (no empty slices) -// - slice of byte array (no empty slices) -// -// A key must not contain an encoding's end-of-key value. -// A value must not contain an encoding's end-of-value value. -// -// For additional details, please refer to the Sigsum v0 API documentation. -// -package ascii - -import ( - "bytes" - "fmt" - "io" - "io/ioutil" - "reflect" - "strconv" - "strings" - - "git.sigsum.org/sigsum-go/pkg/hex" -) - -var StdEncoding = NewEncoding("ascii", "=", "\n") - -type Encoding struct { - identifier string - endOfKey string - endOfValue string -} - -func NewEncoding(id, eok, eov string) *Encoding { - return &Encoding{ - identifier: id, - endOfKey: eok, - endOfValue: eov, - } -} - -type someValue struct { - v reflect.Value - ok bool -} - -// Serialize tries to serialize an interface as ASCII key-value pairs -func (e *Encoding) Serialize(w io.Writer, i interface{}) error { - v, err := dereferenceStructPointer(i) - if err != nil { - return err - } - - t := v.Type() - for i := 0; i < t.NumField(); i++ { - switch v.Field(i).Type().Kind() { - case reflect.Struct: - if err := e.Serialize(w, v.Field(i).Addr().Interface()); err != nil { - return err - } - default: - if t.Field(i).PkgPath != "" { - continue // skip private field - } - key, ok := t.Field(i).Tag.Lookup(e.identifier) - if !ok { - continue // skip public field without tag - } - - if strings.Contains(key, e.endOfKey) { - return fmt.Errorf("ascii: key %q contains end-of-key character", key) - } - if err := e.write(w, key, v.Field(i)); err != nil { - return err - } - } - } - return nil -} - -func (e *Encoding) write(w io.Writer, key string, v reflect.Value) error { - t := v.Type() - switch t { - case reflect.TypeOf(uint64(0)): - val := fmt.Sprintf("%d", v.Uint()) - return e.writeOne(w, key, val) - } - - k := t.Kind() - switch k { - case reflect.Array: - if kind := t.Elem().Kind(); kind != reflect.Uint8 { - return fmt.Errorf("ascii: array kind not supported: %v", kind) - } - - arrayLen := v.Len() - array := make([]byte, arrayLen, arrayLen) - for i := 0; i < arrayLen; i++ { - array[i] = uint8(v.Index(i).Uint()) - } - - val := hex.Serialize(array) - return e.writeOne(w, key, val) - - case reflect.Slice: - kind := t.Elem().Kind() - if kind != reflect.Array && kind != reflect.Uint64 { - return fmt.Errorf("ascii: slice kind not supported: %v", kind) - } - if v.Len() == 0 { - return fmt.Errorf("ascii: slice must not be empty") - } - - var err error - for i := 0; i < v.Len(); i++ { - err = e.write(w, key, v.Index(i)) - } - return err - - case reflect.String: - if v.Len() == 0 { - return fmt.Errorf("ascii: string must not be empty") - } - return e.writeOne(w, key, v.String()) - } - - return fmt.Errorf("ascii: unsupported type %v and kind %v", t, k) -} - -func (e *Encoding) writeOne(w io.Writer, key, value string) error { - _, err := w.Write([]byte(key + e.endOfKey + value + e.endOfValue)) - return err -} - -// Deserialize tries to deserialize a buffer of ASCII key-value pairs -func (e *Encoding) Deserialize(r io.Reader, i interface{}) error { - m := make(map[string]*someValue) - if err := e.mapKeys(i, m); err != nil { - return err - } - - buf, err := ioutil.ReadAll(r) - if err != nil { - return fmt.Errorf("ascii: failed reading incoming buffer") - } - - // trim end of buffer so that loop does not run on an empty line - if len(buf) <= len(e.endOfValue) { - return fmt.Errorf("ascii: buffer contains no key-value pair") - } - offset := len(buf) - len(e.endOfValue) - if !bytes.Equal(buf[offset:], []byte(e.endOfValue)) { - return fmt.Errorf("ascii: buffer must end with endOfValue") - } - buf = buf[:offset] - - for _, kv := range bytes.Split(buf, []byte(e.endOfValue)) { - split := bytes.Split(kv, []byte(e.endOfKey)) - if len(split) == 1 { - return fmt.Errorf("ascii: missing key-value pair in %q", string(kv)) - } - - key := string(split[0]) - value := string(bytes.Join(split[1:], nil)) - ref, ok := m[key] - if !ok { - return fmt.Errorf("ascii: unexpected key %q", key) - } - if len(value) == 0 { - fmt.Errorf("ascii: missing value for key %q", key) - } - if err := setKey(ref, key, value); err != nil { - return err - } - } - return requireValues(m) -} - -func (e *Encoding) mapKeys(i interface{}, m map[string]*someValue) error { - v, err := dereferenceStructPointer(i) - if err != nil { - return err - } - - t := v.Type() - for i := 0; i < t.NumField(); i++ { - switch v.Field(i).Type().Kind() { - case reflect.Struct: - i := v.Field(i).Addr().Interface() - e.mapKeys(i, m) // return is always nil - default: - if t.Field(i).PkgPath != "" { - continue // skip private field - } - key, ok := t.Field(i).Tag.Lookup(e.identifier) - if !ok { - continue // skip public field without tag - } - m[key] = &someValue{ - v: v.Field(i), - } - } - } - return nil -} - -func setKey(ref *someValue, key, value string) error { - v := ref.v - if v.Kind() == reflect.Ptr && !v.IsNil() { - v = v.Elem() - } - - t := v.Type() - switch t { - case reflect.TypeOf(uint64(0)): - num, err := strconv.ParseUint(value, 10, 64) - if err != nil { - return err - } - - ref.ok = true - v.SetUint(num) - return nil - } - - k := t.Kind() - switch k { - case reflect.Array: - arrayLen := v.Len() - b, err := hex.Deserialize(value) - if err != nil { - return err - } - if len(b) != arrayLen { - return fmt.Errorf("ascii: invalid array size for key %q", key) - } - - ref.ok = true - reflect.Copy(v, reflect.ValueOf(b)) - return nil - - case reflect.Slice: - sliceType := t - kind := sliceType.Elem().Kind() - if kind != reflect.Array && kind != reflect.Uint64 { - return fmt.Errorf("ascii: slice kind not supported: %v", kind) - } - - if v.IsNil() { - v.Set(reflect.MakeSlice(sliceType, 0, 0)) - } - sv := &someValue{ - v: reflect.New(sliceType.Elem()), - } - if err := setKey(sv, key, value); err != nil { - return err - } - - ref.ok = true - v.Set(reflect.Append(v, sv.v.Elem())) - return nil - - case reflect.String: - if len(value) == 0 { - return fmt.Errorf("ascii: string must not be empty") - } - - ref.ok = true - v.SetString(value) - return nil - } - - return fmt.Errorf("ascii: unsupported type %v and kind %v", t, k) -} - -func requireValues(m map[string]*someValue) error { - for k, v := range m { - if !v.ok { - return fmt.Errorf("ascii: missing value for key %q", k) - } - } - return nil -} - -func dereferenceStructPointer(i interface{}) (*reflect.Value, error) { - v := reflect.ValueOf(i) - if v.Kind() != reflect.Ptr { - return nil, fmt.Errorf("ascii: interface value must be pointer") - } - if v.IsNil() { - return nil, fmt.Errorf("ascii: interface value must be non-nil pointer") - } - v = v.Elem() - if v.Type().Kind() != reflect.Struct { - return nil, fmt.Errorf("ascii: interface value must point to struct") - } - return &v, nil -} diff --git a/pkg/ascii/ascii_test.go b/pkg/ascii/ascii_test.go deleted file mode 100644 index 8aee70c..0000000 --- a/pkg/ascii/ascii_test.go +++ /dev/null @@ -1,455 +0,0 @@ -package ascii - -import ( - "bytes" - "reflect" - "testing" -) - -type testStruct struct { - Num uint64 `ascii/test:"num"` - Struct testStructOther - Skip uint64 - skip uint64 -} - -type testStructOther struct { - Array testArray `ascii/test:"array"` - Slice []testArray `ascii/test:"slice"` - String string `ascii/test:"string"` -} - -type testArray [2]byte - -type testStructUnsupportedType struct { - ByteSlice []byte `ascii/test:"byte_slice"` -} - -func TestSerialize(t *testing.T) { - e := NewEncoding("ascii/test", "<--", ";;") - for _, table := range []struct { - desc string - want string - err bool - i interface{} - }{ - { - desc: "invalid: not pointer to struct", - err: true, - i: testStruct{}, - }, - { - desc: "invalid: struct with invalid key", - err: true, - i: &struct { - Num uint64 `ascii/test:"num<--nom"` - }{ - Num: 1, - }, - }, - { - desc: "invalid: struct with invalid type", - err: true, - i: &testStructUnsupportedType{ - ByteSlice: []byte("hellow"), - }, - }, - { - desc: "invalid: struct with invalid type and kind", - err: true, - i: &struct { - Struct testStructUnsupportedType - }{ - Struct: testStructUnsupportedType{ - ByteSlice: []byte("hellow"), - }, - }, - }, - { - desc: "valid", - want: "num<--1;;array<--01fe;;slice<--01fe;;slice<--00ff;;string<--hellow;;", - i: &testStruct{ - Num: 1, - Struct: testStructOther{ - Array: testArray{1, 254}, - Slice: []testArray{ - testArray{1, 254}, - testArray{0, 255}, - }, - String: "hellow", - }, - }, - }, - } { - b := bytes.NewBuffer(nil) - err := e.Serialize(b, table.i) - if got, want := err != nil, table.err; got != want { - t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.desc, err) - } - if err != nil { - continue - } - if got, want := string(b.Bytes()), table.want; got != want { - t.Errorf("got buf %s but wanted %s in test %q", got, want, table.desc) - } - } -} - -func TestWrite(t *testing.T) { - e := NewEncoding("ascii/test", "<--", ";;") - for _, table := range []struct { - desc string - want string - err bool - i interface{} - }{ - { - desc: "invalid: array with wrong type", - err: true, - i: [2]string{"first", "second"}, - }, - { - desc: "invalid: slice with wrong type", - err: true, - i: []string{"first", "second"}, - }, - { - desc: "invalid: empty slice with right type", - err: true, - i: make([][2]byte, 0), - }, - { - desc: "invalid: empty string", - err: true, - i: "", - }, - { - desc: "invalid: unsupported type and kind", - err: true, - i: int32(0), - }, - { - desc: "valid: uint64", - want: "some key<--1;;", - i: uint64(1), - }, - { - desc: "valid: byte array", - want: "some key<--01fe;;", - i: [2]byte{1, 254}, - }, - { - desc: "valid: slice array", - want: "some key<--01fe;;some key<--00ff;;", - i: [][2]byte{ - [2]byte{1, 254}, - [2]byte{0, 255}, - }, - }, - { - desc: "valid: slice uint64", - want: "some key<--1;;some key<--2;;", - i: []uint64{1, 2}, - }, - { - desc: "valid: string", - want: "some key<--some value;;", - i: "some value", - }, - } { - buf := bytes.NewBuffer(nil) - err := e.write(buf, "some key", reflect.ValueOf(table.i)) - if got, want := err != nil, table.err; got != want { - t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.desc, err) - } - if err != nil { - continue - } - if got, want := string(buf.Bytes()), table.want; got != want { - t.Errorf("got buf %s but wanted %s in test %q", got, want, table.desc) - } - } -} - -func TestWriteOne(t *testing.T) { - buf := bytes.NewBuffer(nil) - e := NewEncoding("ascii/test", "<--", ";;") - e.writeOne(buf, "some key", "some value") - want := "some key<--some value;;" - if got := string(buf.Bytes()); got != want { - t.Errorf("got buf %s but wanted %s", got, want) - } -} - -func TestDeserialize(t *testing.T) { - e := NewEncoding("ascii/test", "<--", ";;") - for _, table := range []struct { - desc string - buf string - want interface{} - err bool - }{ - { - desc: "invalid: interface must be pointer to struct", - buf: ";", - want: uint64(0), - err: true, - }, - { - desc: "invalid: buffer too small", - buf: ";", - want: testStruct{}, - err: true, - }, - { - desc: "invalid: buffer must end with endOfValue", - buf: "num<--1;;string<--hellow;;array<--01fe;;slice<--01fe;;slice<--00ff^^", - want: testStruct{}, - err: true, - }, - { - desc: "invalid: missing key num", - buf: "string<--hellow;;array<--01fe;;slice<--01fe;;slice<--00ff;;", - want: testStruct{}, - err: true, - }, - { - desc: "invalid: missing key-value pair on num line", - buf: "string<--hellow;;num;;array<--01fe;;slice<--01fe;;slice<--00ff;;", - want: testStruct{}, - err: true, - }, - { - desc: "invalid: missing value for key num", - buf: "num<--;;string<--hellow;;array<--01fe;;slice<--01fe;;slice<--00ff;;", - want: testStruct{}, - err: true, - }, - { - desc: "invalid: value for key num must be digits only", - buf: "num<--+1;;string<--hellow;;array<--01fe;;slice<--01fe;;slice<--00ff;;", - want: testStruct{}, - err: true, - }, - { - desc: "invalid: missing field for key num2", - buf: "num<--1;;string<--hellow;;num2<--2;;array<--01fe;;slice<--01fe;;slice<--00ff;;", - want: testStruct{}, - err: true, - }, - { - desc: "valid", - buf: "num<--1;;string<--hellow;;array<--01fe;;slice<--01fe;;slice<--00ff;;", - want: testStruct{ - Num: 1, - Struct: testStructOther{ - Array: testArray{1, 254}, - Slice: []testArray{ - testArray{1, 254}, - testArray{0, 255}, - }, - String: "hellow", - }, - }, - }, - } { - v := reflect.New(reflect.TypeOf(table.want)) - err := e.Deserialize(bytes.NewBuffer([]byte(table.buf)), v.Interface()) - if got, want := err != nil, table.err; got != want { - t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.desc, err) - } - if err != nil { - continue - } - - v = v.Elem() // have pointer to struct, get just struct as in table - if got, want := v.Interface(), table.want; !reflect.DeepEqual(got, want) { - t.Errorf("got interface %v but wanted %v in test %q", got, want, table.desc) - } - } - -} - -func TestMapKeys(t *testing.T) { - s := testStruct{} - m := make(map[string]*someValue) - e := NewEncoding("ascii/test", "<--", ";;") - if err := e.mapKeys(s, m); err == nil { - t.Errorf("expected mapping to fail without pointer") - } - if err := e.mapKeys(&s, m); err != nil { - t.Errorf("expected mapping to succeed") - return - } - - wantKeys := []string{"num", "array", "slice", "string"} - if got, want := len(m), len(wantKeys); got != want { - t.Errorf("got %d keys, wanted %d", got, want) - } - for _, key := range wantKeys { - if _, ok := m[key]; !ok { - t.Errorf("expected key %q in map", key) - } - } -} - -func TestSetKey(t *testing.T) { - for _, table := range []struct { - desc string - key string - value string - want interface{} - err bool - }{ - { - desc: "invalid: unsupported type and kind", - key: "num", - value: "1", - want: uint32(1), - err: true, - }, - // uint64 - { - desc: "invalid: uint64: underflow", - key: "num", - value: "-1", - want: uint64(0), - err: true, - }, - { - desc: "invalid: uint64: overflow", - key: "num", - value: "18446744073709551616", - want: uint64(0), - err: true, - }, - { - desc: "invalid: uint64: not a number", - key: "num", - value: "+1", - want: uint64(0), - err: true, - }, - { - desc: "invalid: uint64: number with white space", - key: "num", - value: "1 ", - want: uint64(0), - err: true, - }, - { - desc: "valid: uint64", - key: "num", - value: "1", - want: uint64(1), - }, - // string - { - desc: "invalid: string: empty", - key: "string", - value: "", - want: "", - err: true, - }, - { - desc: "valid: string", - key: "string", - value: "hellow", - want: "hellow", - }, - // array - { - desc: "invalid: array: bad hex", - key: "array", - value: "00fE", - want: [2]byte{}, - err: true, - }, - { - desc: "invalid: array: wrong size", - key: "array", - value: "01fe", - want: [3]byte{}, - err: true, - }, - { - desc: "valid: array", - key: "num", - value: "01fe", - want: [2]byte{1, 254}, - }, - // slice - { - desc: "invalid: slice: bad type", - key: "slice", - value: "01fe", - want: []string{ - "hello", - }, - err: true, - }, - { - desc: "invalid: bad hex", - key: "slice", - value: "01FE", - want: [][2]byte{ - [2]byte{1, 254}, - }, - err: true, - }, - { - desc: "valid: slice", - key: "slice", - value: "01fe", - want: [][2]byte{ - [2]byte{1, 254}, - }, - }, - { - desc: "valid: slice", - key: "slice", - value: "4711", - want: []uint64{4711}, - }, - } { - ref := &someValue{ - v: reflect.New(reflect.TypeOf(table.want)), - } - err := setKey(ref, table.key, table.value) - if got, want := err != nil, table.err; got != want { - t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.desc, err) - } - if err != nil { - continue - } - - ref.v = ref.v.Elem() // get same type as table - if got, want := ref.v.Interface(), table.want; !reflect.DeepEqual(got, want) { - t.Errorf("got interface %v but wanted %v in test %q", got, want, table.desc) - } - if got, want := ref.ok, true; got != want { - t.Errorf("got ok %v but wanted %v in test %q", got, want, table.desc) - } - } -} - -func TestDereferenceStructPointer(t *testing.T) { - var ts testStruct - if _, err := dereferenceStructPointer(ts); err == nil { - t.Errorf("should have failed dereferencing non-pointer") - } - - var tsp *testStruct - if _, err := dereferenceStructPointer(tsp); err == nil { - t.Errorf("should have failed dereferencing nil-pointer") - } - - var ta testArray - if _, err := dereferenceStructPointer(&ta); err == nil { - t.Errorf("should have failed dereferencing non-struct pointer") - } - - if _, err := dereferenceStructPointer(&ts); err != nil { - t.Errorf("should have succeeded dereferencing pointer to struct") - } -} |