aboutsummaryrefslogtreecommitdiff
path: root/pkg/ascii/ascii.go
diff options
context:
space:
mode:
Diffstat (limited to 'pkg/ascii/ascii.go')
-rw-r--r--pkg/ascii/ascii.go305
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
+}