| // Copyright 2023 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 frob is a fast restricted object encoder/decoder in the |
| // spirit of encoding/gob. |
| // |
| // As with gob, types that recursively contain functions, channels, |
| // and unsafe.Pointers cannot be encoded, but frob has these |
| // additional restrictions: |
| // |
| // - Interface values are not supported; this avoids the need for |
| // the encoding to describe types. |
| // |
| // - Types that recursively contain private struct fields are not |
| // permitted. |
| // |
| // - The encoding is unspecified and subject to change, so the encoder |
| // and decoder must exactly agree on their implementation and on the |
| // definitions of the target types. |
| // |
| // - Lengths (of arrays, slices, and maps) are currently assumed to |
| // fit in 32 bits. |
| // |
| // - There is no error handling. All errors are reported by panicking. |
| // |
| // - Values are serialized as trees, not graphs, so shared subgraphs |
| // are encoded repeatedly. |
| // |
| // - No attempt is made to detect cyclic data structures. |
| package frob |
| |
| import ( |
| "encoding/binary" |
| "fmt" |
| "math" |
| "reflect" |
| "sync" |
| ) |
| |
| // A Codec[T] is an immutable encoder and decoder for values of type T. |
| type Codec[T any] struct{ frob *frob } |
| |
| // CodecFor[T] returns a codec for values of type T. |
| // It panics if type T is unsuitable. |
| func CodecFor[T any]() Codec[T] { |
| frobsMu.Lock() |
| defer frobsMu.Unlock() |
| return Codec[T]{frobFor(reflect.TypeOf((*T)(nil)).Elem())} |
| } |
| |
| func (codec Codec[T]) Encode(v T) []byte { return codec.frob.Encode(v) } |
| func (codec Codec[T]) Decode(data []byte, ptr *T) { codec.frob.Decode(data, ptr) } |
| |
| var ( |
| frobsMu sync.Mutex |
| frobs = make(map[reflect.Type]*frob) |
| ) |
| |
| // A frob is an encoder/decoder for a specific type. |
| type frob struct { |
| t reflect.Type |
| kind reflect.Kind |
| elems []*frob // elem (array/slice/ptr), key+value (map), fields (struct) |
| } |
| |
| // frobFor returns the frob for a particular type. |
| // Precondition: caller holds frobsMu. |
| func frobFor(t reflect.Type) *frob { |
| fr, ok := frobs[t] |
| if !ok { |
| fr = &frob{t: t, kind: t.Kind()} |
| frobs[t] = fr |
| |
| switch fr.kind { |
| case reflect.Bool, |
| reflect.Int, |
| reflect.Int8, |
| reflect.Int16, |
| reflect.Int32, |
| reflect.Int64, |
| reflect.Uint, |
| reflect.Uint8, |
| reflect.Uint16, |
| reflect.Uint32, |
| reflect.Uint64, |
| reflect.Uintptr, |
| reflect.Float32, |
| reflect.Float64, |
| reflect.Complex64, |
| reflect.Complex128, |
| reflect.String: |
| |
| case reflect.Array, |
| reflect.Slice, |
| reflect.Ptr: // TODO(adonovan): after go1.18, use Pointer |
| fr.addElem(fr.t.Elem()) |
| |
| case reflect.Map: |
| fr.addElem(fr.t.Key()) |
| fr.addElem(fr.t.Elem()) |
| |
| case reflect.Struct: |
| for i := 0; i < fr.t.NumField(); i++ { |
| field := fr.t.Field(i) |
| if field.PkgPath != "" { |
| panic(fmt.Sprintf("unexported field %v", field)) |
| } |
| fr.addElem(field.Type) |
| } |
| |
| default: |
| // chan, func, interface, unsafe.Pointer |
| panic(fmt.Sprintf("type %v is not supported by frob", fr.t)) |
| } |
| } |
| return fr |
| } |
| |
| func (fr *frob) addElem(t reflect.Type) { |
| fr.elems = append(fr.elems, frobFor(t)) |
| } |
| |
| const magic = "frob" |
| |
| func (fr *frob) Encode(v any) []byte { |
| rv := reflect.ValueOf(v) |
| if rv.Type() != fr.t { |
| panic(fmt.Sprintf("got %v, want %v", rv.Type(), fr.t)) |
| } |
| w := &writer{} |
| w.bytes([]byte(magic)) |
| fr.encode(w, rv) |
| if uint64(len(w.data))>>32 != 0 { |
| panic("too large") // includes all cases where len doesn't fit in 32 bits |
| } |
| return w.data |
| } |
| |
| // encode appends the encoding of value v, whose type must be fr.t. |
| func (fr *frob) encode(out *writer, v reflect.Value) { |
| switch fr.kind { |
| case reflect.Bool: |
| var b byte |
| if v.Bool() { |
| b = 1 |
| } |
| out.uint8(b) |
| case reflect.Int: |
| out.uint64(uint64(v.Int())) |
| case reflect.Int8: |
| out.uint8(uint8(v.Int())) |
| case reflect.Int16: |
| out.uint16(uint16(v.Int())) |
| case reflect.Int32: |
| out.uint32(uint32(v.Int())) |
| case reflect.Int64: |
| out.uint64(uint64(v.Int())) |
| case reflect.Uint: |
| out.uint64(v.Uint()) |
| case reflect.Uint8: |
| out.uint8(uint8(v.Uint())) |
| case reflect.Uint16: |
| out.uint16(uint16(v.Uint())) |
| case reflect.Uint32: |
| out.uint32(uint32(v.Uint())) |
| case reflect.Uint64: |
| out.uint64(v.Uint()) |
| case reflect.Uintptr: |
| out.uint64(v.Uint()) |
| case reflect.Float32: |
| out.uint32(math.Float32bits(float32(v.Float()))) |
| case reflect.Float64: |
| out.uint64(math.Float64bits(v.Float())) |
| case reflect.Complex64: |
| z := complex64(v.Complex()) |
| out.uint32(math.Float32bits(real(z))) |
| out.uint32(math.Float32bits(imag(z))) |
| case reflect.Complex128: |
| z := v.Complex() |
| out.uint64(math.Float64bits(real(z))) |
| out.uint64(math.Float64bits(imag(z))) |
| |
| case reflect.Array: |
| len := v.Type().Len() |
| elem := fr.elems[0] |
| for i := 0; i < len; i++ { |
| elem.encode(out, v.Index(i)) |
| } |
| |
| case reflect.Slice: |
| len := v.Len() |
| out.uint32(uint32(len)) |
| if len > 0 { |
| elem := fr.elems[0] |
| if elem.kind == reflect.Uint8 { |
| // []byte fast path |
| out.bytes(v.Bytes()) |
| } else { |
| for i := 0; i < len; i++ { |
| elem.encode(out, v.Index(i)) |
| } |
| } |
| } |
| |
| case reflect.Map: |
| len := v.Len() |
| out.uint32(uint32(len)) |
| if len > 0 { |
| kfrob, vfrob := fr.elems[0], fr.elems[1] |
| for iter := v.MapRange(); iter.Next(); { |
| kfrob.encode(out, iter.Key()) |
| vfrob.encode(out, iter.Value()) |
| } |
| } |
| |
| case reflect.Ptr: // TODO(adonovan): after go1.18, use Pointer |
| if v.IsNil() { |
| out.uint8(0) |
| } else { |
| out.uint8(1) |
| fr.elems[0].encode(out, v.Elem()) |
| } |
| |
| case reflect.String: |
| len := v.Len() |
| out.uint32(uint32(len)) |
| if len > 0 { |
| out.data = append(out.data, v.String()...) |
| } |
| |
| case reflect.Struct: |
| for i, elem := range fr.elems { |
| elem.encode(out, v.Field(i)) |
| } |
| |
| default: |
| panic(fr.t) |
| } |
| } |
| |
| func (fr *frob) Decode(data []byte, ptr any) { |
| rv := reflect.ValueOf(ptr).Elem() |
| if rv.Type() != fr.t { |
| panic(fmt.Sprintf("got %v, want %v", rv.Type(), fr.t)) |
| } |
| rd := &reader{data} |
| if string(rd.bytes(4)) != magic { |
| panic("not a frob-encoded message") |
| } |
| fr.decode(rd, rv) |
| if len(rd.data) > 0 { |
| panic("surplus bytes") |
| } |
| } |
| |
| // decode reads from in, decodes a value, and sets addr to it. |
| // addr must be a zero-initialized addressable variable of type fr.t. |
| func (fr *frob) decode(in *reader, addr reflect.Value) { |
| switch fr.kind { |
| case reflect.Bool: |
| addr.SetBool(in.uint8() != 0) |
| case reflect.Int: |
| addr.SetInt(int64(in.uint64())) |
| case reflect.Int8: |
| addr.SetInt(int64(in.uint8())) |
| case reflect.Int16: |
| addr.SetInt(int64(in.uint16())) |
| case reflect.Int32: |
| addr.SetInt(int64(in.uint32())) |
| case reflect.Int64: |
| addr.SetInt(int64(in.uint64())) |
| case reflect.Uint: |
| addr.SetUint(in.uint64()) |
| case reflect.Uint8: |
| addr.SetUint(uint64(in.uint8())) |
| case reflect.Uint16: |
| addr.SetUint(uint64(in.uint16())) |
| case reflect.Uint32: |
| addr.SetUint(uint64(in.uint32())) |
| case reflect.Uint64: |
| addr.SetUint(in.uint64()) |
| case reflect.Uintptr: |
| addr.SetUint(in.uint64()) |
| case reflect.Float32: |
| addr.SetFloat(float64(math.Float32frombits(in.uint32()))) |
| case reflect.Float64: |
| addr.SetFloat(math.Float64frombits(in.uint64())) |
| case reflect.Complex64: |
| addr.SetComplex(complex128(complex( |
| math.Float32frombits(in.uint32()), |
| math.Float32frombits(in.uint32()), |
| ))) |
| case reflect.Complex128: |
| addr.SetComplex(complex( |
| math.Float64frombits(in.uint64()), |
| math.Float64frombits(in.uint64()), |
| )) |
| |
| case reflect.Array: |
| len := fr.t.Len() |
| for i := 0; i < len; i++ { |
| fr.elems[0].decode(in, addr.Index(i)) |
| } |
| |
| case reflect.Slice: |
| len := int(in.uint32()) |
| if len > 0 { |
| elem := fr.elems[0] |
| if elem.kind == reflect.Uint8 { |
| // []byte fast path |
| // (Not addr.SetBytes: we must make a copy.) |
| addr.Set(reflect.AppendSlice(addr, reflect.ValueOf(in.bytes(len)))) |
| } else { |
| addr.Set(reflect.MakeSlice(fr.t, len, len)) |
| for i := 0; i < len; i++ { |
| elem.decode(in, addr.Index(i)) |
| } |
| } |
| } |
| |
| case reflect.Map: |
| len := int(in.uint32()) |
| if len > 0 { |
| m := reflect.MakeMapWithSize(fr.t, len) |
| addr.Set(m) |
| kfrob, vfrob := fr.elems[0], fr.elems[1] |
| k := reflect.New(kfrob.t).Elem() |
| v := reflect.New(vfrob.t).Elem() |
| kzero := reflect.Zero(kfrob.t) |
| vzero := reflect.Zero(vfrob.t) |
| for i := 0; i < len; i++ { |
| // TODO(adonovan): use SetZero from go1.20. |
| // k.SetZero() |
| // v.SetZero() |
| k.Set(kzero) |
| v.Set(vzero) |
| kfrob.decode(in, k) |
| vfrob.decode(in, v) |
| m.SetMapIndex(k, v) |
| } |
| } |
| |
| case reflect.Ptr: // TODO(adonovan): after go1.18, use Pointer |
| isNil := in.uint8() == 0 |
| if !isNil { |
| ptr := reflect.New(fr.elems[0].t) |
| addr.Set(ptr) |
| fr.elems[0].decode(in, ptr.Elem()) |
| } |
| |
| case reflect.String: |
| len := int(in.uint32()) |
| if len > 0 { |
| addr.SetString(string(in.bytes(len))) |
| } |
| |
| case reflect.Struct: |
| for i, elem := range fr.elems { |
| elem.decode(in, addr.Field(i)) |
| } |
| |
| default: |
| panic(fr.t) |
| } |
| } |
| |
| var le = binary.LittleEndian |
| |
| type reader struct{ data []byte } |
| |
| func (r *reader) uint8() uint8 { |
| v := r.data[0] |
| r.data = r.data[1:] |
| return v |
| } |
| |
| func (r *reader) uint16() uint16 { |
| v := le.Uint16(r.data) |
| r.data = r.data[2:] |
| return v |
| } |
| |
| func (r *reader) uint32() uint32 { |
| v := le.Uint32(r.data) |
| r.data = r.data[4:] |
| return v |
| } |
| |
| func (r *reader) uint64() uint64 { |
| v := le.Uint64(r.data) |
| r.data = r.data[8:] |
| return v |
| } |
| |
| func (r *reader) bytes(n int) []byte { |
| v := r.data[:n] |
| r.data = r.data[n:] |
| return v |
| } |
| |
| type writer struct{ data []byte } |
| |
| func (w *writer) uint8(v uint8) { w.data = append(w.data, v) } |
| func (w *writer) uint16(v uint16) { w.data = appendUint16(w.data, v) } |
| func (w *writer) uint32(v uint32) { w.data = appendUint32(w.data, v) } |
| func (w *writer) uint64(v uint64) { w.data = appendUint64(w.data, v) } |
| func (w *writer) bytes(v []byte) { w.data = append(w.data, v...) } |
| |
| // TODO(adonovan): delete these as in go1.19 they are methods on LittleEndian: |
| |
| func appendUint16(b []byte, v uint16) []byte { |
| return append(b, |
| byte(v), |
| byte(v>>8), |
| ) |
| } |
| |
| func appendUint32(b []byte, v uint32) []byte { |
| return append(b, |
| byte(v), |
| byte(v>>8), |
| byte(v>>16), |
| byte(v>>24), |
| ) |
| } |
| |
| func appendUint64(b []byte, v uint64) []byte { |
| return append(b, |
| byte(v), |
| byte(v>>8), |
| byte(v>>16), |
| byte(v>>24), |
| byte(v>>32), |
| byte(v>>40), |
| byte(v>>48), |
| byte(v>>56), |
| ) |
| } |