diff options
author | Rasmus Dahlberg <rasmus@mullvad.net> | 2021-12-20 14:37:31 +0100 |
---|---|---|
committer | Rasmus Dahlberg <rasmus@mullvad.net> | 2021-12-20 18:23:45 +0100 |
commit | 2171b71e920d286a9527a4dddd05c00eceb6af83 (patch) | |
tree | 658ce0d97985a15224783f784f4b5391eacd837a /pkg/ascii/ascii.go | |
parent | 79aa7d7a0318db9913d7cec5473ef51ef2e04593 (diff) |
ascii: Add sigsum ASCII-parser and tests
Diffstat (limited to 'pkg/ascii/ascii.go')
-rw-r--r-- | pkg/ascii/ascii.go | 305 |
1 files changed, 305 insertions, 0 deletions
diff --git a/pkg/ascii/ascii.go b/pkg/ascii/ascii.go new file mode 100644 index 0000000..350890c --- /dev/null +++ b/pkg/ascii/ascii.go @@ -0,0 +1,305 @@ +// 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" + "reflect" + "strconv" + "strings" + + "git.sigsum.org/sigsum-lib-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 := io.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 +} |