From ddb2e620c9529315ddf2785bc08da42dc1027cd6314e3a9be1125274851ff67f Mon Sep 17 00:00:00 2001 From: Sergey Matveev Date: Wed, 11 Dec 2024 18:46:04 +0300 Subject: [PATCH] Big code reorganisation Much more clearer separated types. --- gyac/{ => atom/be}/be.go | 7 +- gyac/atom/dec.go | 279 +++++++++++++++++++++++ gyac/atom/enc.go | 178 +++++++++++++++ gyac/atom/raw.go | 15 ++ gyac/atom/type_string.go | 66 ++++++ gyac/atomtype_string.go | 66 ------ gyac/cmd/print/main.go | 2 +- gyac/cmd/test-vector-anys/main.go | 13 +- gyac/cmd/test-vector-manual/main.go | 192 ++++++++-------- gyac/convert-fuzz-input-to-testdata | 6 - gyac/dec.go | 340 +++------------------------- gyac/enc.go | 223 +++--------------- gyac/fromgo.go | 176 ++++++++++++++ gyac/fuzz_test.go | 4 +- gyac/itemtype_string.go | 37 --- gyac/mapstruct/dec.go | 37 +++ gyac/mapstruct/map.go | 30 +++ gyac/mk-fuzz-testdata | 10 + gyac/reflect.go | 263 --------------------- gyac/togo.go | 89 ++++++++ gyac/types/type.go | 22 ++ gyac/types/type_string.go | 37 +++ gyac/yacpki/algo.go | 2 +- gyac/yacpki/cer.go | 5 +- gyac/yacpki/cmd/yacertool/main.go | 6 +- gyac/yacpki/cmd/yacsdtool/main.go | 2 +- gyac/yacpki/prv.go | 4 +- gyac/yacpki/signed-data.go | 10 +- 28 files changed, 1129 insertions(+), 992 deletions(-) rename gyac/{ => atom/be}/be.go (88%) create mode 100644 gyac/atom/dec.go create mode 100644 gyac/atom/enc.go create mode 100644 gyac/atom/raw.go create mode 100644 gyac/atom/type_string.go delete mode 100644 gyac/atomtype_string.go delete mode 100755 gyac/convert-fuzz-input-to-testdata create mode 100644 gyac/fromgo.go delete mode 100644 gyac/itemtype_string.go create mode 100644 gyac/mapstruct/dec.go create mode 100644 gyac/mapstruct/map.go create mode 100755 gyac/mk-fuzz-testdata delete mode 100644 gyac/reflect.go create mode 100644 gyac/togo.go create mode 100644 gyac/types/type.go create mode 100644 gyac/types/type_string.go diff --git a/gyac/be.go b/gyac/atom/be/be.go similarity index 88% rename from gyac/be.go rename to gyac/atom/be/be.go index 0a2f92f..f37d54e 100644 --- a/gyac/be.go +++ b/gyac/atom/be/be.go @@ -13,16 +13,17 @@ // You should have received a copy of the GNU Lesser General Public // License along with this program. If not, see . -package gyac +// Variable-length big-endian integers storage. +package be -func FromBE(buf []byte) (v uint64) { +func Get(buf []byte) (v uint64) { for i := 0; i < len(buf); i++ { v |= uint64(buf[i]) << ((len(buf) - i - 1) * 8) } return } -func ToBE(buf []byte, v uint64) { +func Put(buf []byte, v uint64) { for i := 0; i < len(buf); i++ { buf[i] = byte((v & (0xFF << ((len(buf) - i - 1) * 8)) >> ((len(buf) - i - 1) * 8)) & 0xFF) } diff --git a/gyac/atom/dec.go b/gyac/atom/dec.go new file mode 100644 index 0000000..653bcb4 --- /dev/null +++ b/gyac/atom/dec.go @@ -0,0 +1,279 @@ +// gyac -- Go YAC encoder implementation +// Copyright (C) 2024-2025 Sergey Matveev +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation, version 3 of the License. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this program. If not, see . + +package atom + +import ( + "errors" + "math/big" + "strings" + "unicode/utf8" + "unsafe" + + "github.com/google/uuid" + + "go.cypherpunks.su/yac/gyac/atom/be" + "go.cypherpunks.su/yac/gyac/types" +) + +var ( + ErrNotEnough = errors.New("not enough data") + ErrLenTooBig = errors.New("string len >1<<60") + ErrIntNonMinimal = errors.New("int non minimal") + ErrUnknownType = errors.New("unknown type") + ErrBadUTF8 = errors.New("invalid UTF-8") + ErrBadInt = errors.New("bad int value") +) + +func Decode(buf []byte) (t types.Type, v any, off int, err error) { + off = 1 + if len(buf) < 1 { + err = ErrNotEnough + return + } + tag := buf[0] + if (tag & Strings) > 0 { + l := int(tag & 63) + if (tag & IsUTF8) == 0 { + t = types.Bin + } else { + t = types.Str + } + ll := 0 + switch l { + case 61: + ll = 1 + case 62: + ll = 2 + l += ((1 << 8) - 1) + case 63: + ll = 8 + l += ((1 << 8) - 1) + ((1 << 16) - 1) + } + if ll != 0 { + off += ll + if len(buf) < off { + err = ErrNotEnough + return + } + ul := be.Get(buf[1 : 1+ll]) + if ul > (1<<63)-(63+((1<<8)-1)+((1<<16)-1)) { + err = ErrLenTooBig + return + } + l += int(ul) + } + off += l + if off <= 0 { + err = ErrLenTooBig + return + } + if len(buf) < off { + err = ErrNotEnough + return + } + if t == types.Bin { + v = buf[1+ll : 1+ll+l] + } else { + s := unsafe.String(unsafe.SliceData(buf[1+ll:]), l) + v = s + if !utf8.ValidString(s) { + err = ErrBadUTF8 + } + if strings.Contains(s, "\x00") { + err = ErrBadUTF8 + } + } + return + } + switch Type(tag) { + case EOC: + t = types.EOC + case NIL: + t = types.NIL + case False: + t = types.Bool + v = false + case True: + t = types.Bool + v = true + case UUID: + off += 16 + t = types.UUID + if len(buf) < off { + err = ErrNotEnough + return + } + v, err = uuid.FromBytes(buf[1 : 1+16]) + + case List: + t = types.List + case Map: + t = types.Map + case Blob: + t = types.Blob + off += 8 + if len(buf) < off { + err = ErrNotEnough + return + } + chunkLen := be.Get(buf[1 : 1+8]) + if chunkLen >= (1<<63)-1 { + err = ErrLenTooBig + return + } + chunkLen++ + v = chunkLen + + case PInt, NInt: + if Type(tag) == PInt { + t = types.UInt + } else { + t = types.Int + } + var binOff int + if len(buf) < 2 { + err = ErrNotEnough + return + } + if buf[1]&Strings == 0 { + err = ErrBadInt + return + } + var binT types.Type + var binV any + binT, binV, binOff, err = Decode(buf[1:]) + off += binOff + if err != nil { + return + } + if binT != types.Bin { + err = ErrBadInt + return + } + raw := binV.([]byte) + if len(raw) == 0 { + if t == types.UInt { + v = uint64(0) + } else { + v = int64(-1) + } + return + } + if raw[0] == 0 { + err = ErrIntNonMinimal + return + } + if len(raw) > 8 { + bi := big.NewInt(0) + bi = bi.SetBytes(raw) + if t == types.Int { + n1 := big.NewInt(-1) + bi = bi.Sub(n1, bi) + } + t = types.BigInt + v = bi + return + } + i := be.Get(raw) + if t == types.UInt { + v = i + } else { + if i >= (1 << 63) { + bi := big.NewInt(0) + bi = bi.SetBytes(raw) + n1 := big.NewInt(-1) + bi = bi.Sub(n1, bi) + t = types.BigInt + v = bi + } else { + v = -1 - int64(i) + } + } + return + + case Float16, Float32, Float64, Float128, Float256: + var l int + switch Type(tag) { + case Float16: + l = 2 + case Float32: + l = 4 + case Float64: + l = 8 + case Float128: + l = 16 + case Float256: + l = 32 + } + off += l + if len(buf) < off { + t = types.Float + err = ErrNotEnough + return + } + t = types.Raw + v = &Raw{T: Type(tag), V: buf[1 : 1+l]} + + case TAI64, TAI64N, TAI64NA: + var l int + switch Type(tag) { + case TAI64: + l = 8 + case TAI64N: + l = 12 + case TAI64NA: + l = 16 + } + off += l + if len(buf) < off { + err = ErrNotEnough + return + } + t = types.TAI64 + v = buf[1 : 1+l] + if be.Get(buf[1:1+8]) > (1 << 63) { + err = errors.New("reserved TAI64 values in use") + return + } + if l > 8 { + nsecs := be.Get(buf[1+8 : 1+8+4]) + if l == 12 && nsecs == 0 { + err = errors.New("non-minimal TAI64N") + return + } + if nsecs > 999999999 { + err = errors.New("too many nanoseconds") + return + } + } + if l > 12 { + asecs := be.Get(buf[1+8+4 : 1+8+4+4]) + if asecs == 0 { + err = errors.New("non-minimal TAI64NA") + return + } + if asecs > 999999999 { + err = errors.New("too many attoseconds") + return + } + } + + default: + err = ErrUnknownType + return + } + return +} diff --git a/gyac/atom/enc.go b/gyac/atom/enc.go new file mode 100644 index 0000000..df9ceee --- /dev/null +++ b/gyac/atom/enc.go @@ -0,0 +1,178 @@ +// gyac -- Go YAC encoder implementation +// Copyright (C) 2024-2025 Sergey Matveev +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation, version 3 of the License. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this program. If not, see . + +package atom + +import ( + "math/big" + + "github.com/google/uuid" + + "go.cypherpunks.su/yac/gyac/atom/be" +) + +var bigIntZero = big.NewInt(0) + +type Type byte + +//go:generate stringer -type=Type +const ( + EOC Type = 0x00 + NIL Type = 0x01 + False Type = 0x02 + True Type = 0x03 + UUID Type = 0x04 + List Type = 0x08 + Map Type = 0x09 + Blob Type = 0x0B + PInt Type = 0x0C + NInt Type = 0x0D + Float16 Type = 0x10 + Float32 Type = 0x11 + Float64 Type = 0x12 + Float128 Type = 0x13 + Float256 Type = 0x14 + TAI64 Type = 0x18 + TAI64N Type = 0x19 + TAI64NA Type = 0x1A + + Strings = 0x80 + IsUTF8 = 0x40 +) + +func EOCEncode(buf []byte) []byte { + return append(buf, byte(EOC)) +} + +func NILEncode(buf []byte) []byte { + return append(buf, byte(NIL)) +} + +func BoolEncode(buf []byte, v bool) []byte { + if v { + return append(buf, byte(True)) + } + return append(buf, byte(False)) +} + +func UUIDEncode(buf []byte, v uuid.UUID) []byte { + return append(append(buf, byte(UUID)), v[:]...) +} + +func atomUintEncode(v uint64) (buf []byte) { + if v == 0 { + return BinEncode(nil, []byte{}) + } + l := 0 + for ; l < 7; l++ { + if v < (1 << ((l + 1) * 8)) { + break + } + } + buf = make([]byte, l+1) + be.Put(buf, v) + return BinEncode(nil, buf) +} + +func UIntEncode(buf []byte, v uint64) []byte { + return append(buf, append([]byte{byte(PInt)}, atomUintEncode(v)...)...) +} + +func IntEncode(buf []byte, v int64) []byte { + if v >= 0 { + return UIntEncode(buf, uint64(v)) + } + return append(buf, append([]byte{byte(NInt)}, + atomUintEncode(uint64(-(v+1)))...)...) +} + +func BigIntEncode(buf []byte, v *big.Int) []byte { + // TODO: fallback to U?IntEncode for small values + if v.Cmp(bigIntZero) >= 0 { + return append(buf, BinEncode([]byte{byte(PInt)}, v.Bytes())...) + } + n1 := big.NewInt(-1) + v = v.Abs(v) + v = v.Add(v, n1) + return append(buf, BinEncode([]byte{byte(NInt)}, v.Bytes())...) +} + +func ListEncode(buf []byte) []byte { + return append(buf, byte(List)) +} + +func MapEncode(buf []byte) []byte { + return append(buf, byte(Map)) +} + +func BlobEncode(buf []byte, chunkLen int) []byte { + l := make([]byte, 9) + l[0] = byte(Blob) + be.Put(l[1:], uint64(chunkLen-1)) + return append(buf, l...) +} + +func atomStrEncode(buf, data []byte, utf8 bool) []byte { + var lv int + var l []byte + if len(data) >= 63+((1<<8)-1)+((1<<16)-1) { + lv = 63 + l = make([]byte, 8) + be.Put(l, uint64(len(data)-(lv+((1<<8)-1)+((1<<16)-1)))) + } else if len(data) >= 62+255 { + lv = 62 + l = make([]byte, 2) + be.Put(l, uint64(len(data)-(lv+((1<<8)-1)))) + } else if len(data) >= 61 { + lv = 61 + l = []byte{byte(len(data) - lv)} + } else { + lv = len(data) + } + b := byte(Strings | lv) + if utf8 { + b |= IsUTF8 + } + return append(append(append(buf, b), l...), data...) +} + +func StrEncode(buf []byte, str string) []byte { + return atomStrEncode(buf, []byte(str), true) +} + +func BinEncode(buf, bin []byte) []byte { + return atomStrEncode(buf, bin, false) +} + +func ChunkEncode(buf, chunk []byte) []byte { + return append(append(buf, byte(NIL)), chunk...) +} + +func TAI64Encode(buf, tai []byte) []byte { + switch len(tai) { + case 8: + return append(append(buf, byte(TAI64)), tai...) + case 12: + return append(append(buf, byte(TAI64N)), tai...) + case 16: + return append(append(buf, byte(TAI64NA)), tai...) + default: + panic("wrong TAI64 value") + } +} + +func RawEncode(buf []byte, raw *Raw) []byte { + return append(append(buf, byte(raw.T)), raw.V...) +} diff --git a/gyac/atom/raw.go b/gyac/atom/raw.go new file mode 100644 index 0000000..ddc30d8 --- /dev/null +++ b/gyac/atom/raw.go @@ -0,0 +1,15 @@ +package atom + +import ( + "encoding/hex" + "fmt" +) + +type Raw struct { + V []byte + T Type +} + +func (raw *Raw) String() string { + return fmt.Sprintf("RAW(%v, %s)", raw.T, hex.EncodeToString(raw.V)) +} diff --git a/gyac/atom/type_string.go b/gyac/atom/type_string.go new file mode 100644 index 0000000..b35d04b --- /dev/null +++ b/gyac/atom/type_string.go @@ -0,0 +1,66 @@ +// Code generated by "stringer -type=Type"; DO NOT EDIT. + +package atom + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[EOC-0] + _ = x[NIL-1] + _ = x[False-2] + _ = x[True-3] + _ = x[UUID-4] + _ = x[List-8] + _ = x[Map-9] + _ = x[Blob-11] + _ = x[PInt-12] + _ = x[NInt-13] + _ = x[Float16-16] + _ = x[Float32-17] + _ = x[Float64-18] + _ = x[Float128-19] + _ = x[Float256-20] + _ = x[TAI64-24] + _ = x[TAI64N-25] + _ = x[TAI64NA-26] +} + +const ( + _Type_name_0 = "EOCNILFalseTrueUUID" + _Type_name_1 = "ListMap" + _Type_name_2 = "BlobPIntNInt" + _Type_name_3 = "Float16Float32Float64Float128Float256" + _Type_name_4 = "TAI64TAI64NTAI64NA" +) + +var ( + _Type_index_0 = [...]uint8{0, 3, 6, 11, 15, 19} + _Type_index_1 = [...]uint8{0, 4, 7} + _Type_index_2 = [...]uint8{0, 4, 8, 12} + _Type_index_3 = [...]uint8{0, 7, 14, 21, 29, 37} + _Type_index_4 = [...]uint8{0, 5, 11, 18} +) + +func (i Type) String() string { + switch { + case i <= 4: + return _Type_name_0[_Type_index_0[i]:_Type_index_0[i+1]] + case 8 <= i && i <= 9: + i -= 8 + return _Type_name_1[_Type_index_1[i]:_Type_index_1[i+1]] + case 11 <= i && i <= 13: + i -= 11 + return _Type_name_2[_Type_index_2[i]:_Type_index_2[i+1]] + case 16 <= i && i <= 20: + i -= 16 + return _Type_name_3[_Type_index_3[i]:_Type_index_3[i+1]] + case 24 <= i && i <= 26: + i -= 24 + return _Type_name_4[_Type_index_4[i]:_Type_index_4[i+1]] + default: + return "Type(" + strconv.FormatInt(int64(i), 10) + ")" + } +} diff --git a/gyac/atomtype_string.go b/gyac/atomtype_string.go deleted file mode 100644 index dab86b4..0000000 --- a/gyac/atomtype_string.go +++ /dev/null @@ -1,66 +0,0 @@ -// Code generated by "stringer -type=AtomType"; DO NOT EDIT. - -package gyac - -import "strconv" - -func _() { - // An "invalid array index" compiler error signifies that the constant values have changed. - // Re-run the stringer command to generate them again. - var x [1]struct{} - _ = x[AtomEOC-0] - _ = x[AtomNIL-1] - _ = x[AtomFalse-2] - _ = x[AtomTrue-3] - _ = x[AtomUUID-4] - _ = x[AtomList-8] - _ = x[AtomMap-9] - _ = x[AtomBlob-11] - _ = x[AtomPInt-12] - _ = x[AtomNInt-13] - _ = x[AtomFloat16-16] - _ = x[AtomFloat32-17] - _ = x[AtomFloat64-18] - _ = x[AtomFloat128-19] - _ = x[AtomFloat256-20] - _ = x[AtomTAI64-24] - _ = x[AtomTAI64N-25] - _ = x[AtomTAI64NA-26] -} - -const ( - _AtomType_name_0 = "AtomEOCAtomNILAtomFalseAtomTrueAtomUUID" - _AtomType_name_1 = "AtomListAtomMap" - _AtomType_name_2 = "AtomBlobAtomPIntAtomNInt" - _AtomType_name_3 = "AtomFloat16AtomFloat32AtomFloat64AtomFloat128AtomFloat256" - _AtomType_name_4 = "AtomTAI64AtomTAI64NAtomTAI64NA" -) - -var ( - _AtomType_index_0 = [...]uint8{0, 7, 14, 23, 31, 39} - _AtomType_index_1 = [...]uint8{0, 8, 15} - _AtomType_index_2 = [...]uint8{0, 8, 16, 24} - _AtomType_index_3 = [...]uint8{0, 11, 22, 33, 45, 57} - _AtomType_index_4 = [...]uint8{0, 9, 19, 30} -) - -func (i AtomType) String() string { - switch { - case i <= 4: - return _AtomType_name_0[_AtomType_index_0[i]:_AtomType_index_0[i+1]] - case 8 <= i && i <= 9: - i -= 8 - return _AtomType_name_1[_AtomType_index_1[i]:_AtomType_index_1[i+1]] - case 11 <= i && i <= 13: - i -= 11 - return _AtomType_name_2[_AtomType_index_2[i]:_AtomType_index_2[i+1]] - case 16 <= i && i <= 20: - i -= 16 - return _AtomType_name_3[_AtomType_index_3[i]:_AtomType_index_3[i+1]] - case 24 <= i && i <= 26: - i -= 24 - return _AtomType_name_4[_AtomType_index_4[i]:_AtomType_index_4[i+1]] - default: - return "AtomType(" + strconv.FormatInt(int64(i), 10) + ")" - } -} diff --git a/gyac/cmd/print/main.go b/gyac/cmd/print/main.go index 4469b54..f49b610 100644 --- a/gyac/cmd/print/main.go +++ b/gyac/cmd/print/main.go @@ -15,7 +15,7 @@ func main() { if err != nil { log.Fatal(err) } - item, tail, err := gyac.ItemDecode(data) + item, tail, err := gyac.Decode(data) if err != nil { log.Fatal(err) } diff --git a/gyac/cmd/test-vector-anys/main.go b/gyac/cmd/test-vector-anys/main.go index e6fb7a5..2e5d03d 100644 --- a/gyac/cmd/test-vector-anys/main.go +++ b/gyac/cmd/test-vector-anys/main.go @@ -9,6 +9,7 @@ import ( "github.com/google/uuid" "go.cypherpunks.su/yac/gyac" + "go.cypherpunks.su/yac/gyac/atom" ) func mustHexDec(s string) []byte { @@ -86,8 +87,8 @@ func main() { map[string]any{}, gyac.MakeBlob(123, []byte{}), uuid.Nil, - &gyac.Raw{ - T: gyac.AtomTAI64, + &atom.Raw{ + T: atom.TAI64, V: []byte("\x00\x00\x00\x00\x00\x00\x00\x00"), }, }, @@ -95,15 +96,15 @@ func main() { time.Unix(1234567890, 0), time.Unix(1234567890, 456*1000), time.Unix(1234567890, 456789), - &gyac.Raw{ - T: gyac.AtomTAI64NA, + &atom.Raw{ + T: atom.TAI64NA, V: []byte("\x40\x00\x00\x00\x49\x96\x02\xF4\x00\x06\xF8\x55\x07\x5B\xCD\x15"), }, }, "floats": []any{ - &gyac.Raw{T: gyac.AtomFloat32, V: []byte("\x01\x02\x03\x04")}, + &atom.Raw{T: atom.Float32, V: []byte("\x01\x02\x03\x04")}, }, "uuid": uuid.MustParse("0e875e3f-d385-49eb-87b4-be42d641c367"), } - fmt.Println(hex.EncodeToString(gyac.ItemFromGo(data).Encode(nil))) + fmt.Println(hex.EncodeToString(gyac.FromGo(data).Encode(nil))) } diff --git a/gyac/cmd/test-vector-manual/main.go b/gyac/cmd/test-vector-manual/main.go index 2f2e23c..d9fdbb2 100644 --- a/gyac/cmd/test-vector-manual/main.go +++ b/gyac/cmd/test-vector-manual/main.go @@ -9,7 +9,7 @@ import ( "github.com/google/uuid" "go.cypherpunks.su/tai64n/v4" - "go.cypherpunks.su/yac/gyac" + "go.cypherpunks.su/yac/gyac/atom" ) func mustHexDec(s string) []byte { @@ -23,180 +23,180 @@ func mustHexDec(s string) []byte { func main() { buf := make([]byte, 0, 68*1024) { - buf = gyac.AtomMapEncode(buf) + buf = atom.MapEncode(buf) { - buf = gyac.AtomStrEncode(buf, "nil") - buf = gyac.AtomNILEncode(buf) + buf = atom.StrEncode(buf, "nil") + buf = atom.NILEncode(buf) } { - buf = gyac.AtomStrEncode(buf, "str") - buf = gyac.AtomMapEncode(buf) + buf = atom.StrEncode(buf, "str") + buf = atom.MapEncode(buf) { - buf = gyac.AtomStrEncode(buf, "bin") - buf = gyac.AtomListEncode(buf) + buf = atom.StrEncode(buf, "bin") + buf = atom.ListEncode(buf) { - buf = gyac.AtomBinEncode(buf, []byte("")) - buf = gyac.AtomBinEncode(buf, bytes.Repeat([]byte{'0'}, 60)) - buf = gyac.AtomBinEncode(buf, bytes.Repeat([]byte{'1'}, 61)) - buf = gyac.AtomBinEncode(buf, bytes.Repeat([]byte{'2'}, 255)) - buf = gyac.AtomBinEncode(buf, bytes.Repeat([]byte{'A'}, 61+255)) - buf = gyac.AtomBinEncode(buf, bytes.Repeat([]byte{'B'}, 62+255)) - buf = gyac.AtomBinEncode(buf, bytes.Repeat([]byte{'3'}, 1024)) - buf = gyac.AtomBinEncode(buf, bytes.Repeat([]byte{'4'}, 63+255+65535+1)) + buf = atom.BinEncode(buf, []byte("")) + buf = atom.BinEncode(buf, bytes.Repeat([]byte{'0'}, 60)) + buf = atom.BinEncode(buf, bytes.Repeat([]byte{'1'}, 61)) + buf = atom.BinEncode(buf, bytes.Repeat([]byte{'2'}, 255)) + buf = atom.BinEncode(buf, bytes.Repeat([]byte{'A'}, 61+255)) + buf = atom.BinEncode(buf, bytes.Repeat([]byte{'B'}, 62+255)) + buf = atom.BinEncode(buf, bytes.Repeat([]byte{'3'}, 1024)) + buf = atom.BinEncode(buf, bytes.Repeat([]byte{'4'}, 63+255+65535+1)) } - buf = gyac.AtomEOCEncode(buf) + buf = atom.EOCEncode(buf) { - buf = gyac.AtomStrEncode(buf, "utf8") - buf = gyac.AtomStrEncode(buf, "привет мир") + buf = atom.StrEncode(buf, "utf8") + buf = atom.StrEncode(buf, "привет мир") } } - buf = gyac.AtomEOCEncode(buf) + buf = atom.EOCEncode(buf) } { - buf = gyac.AtomStrEncode(buf, "blob") - buf = gyac.AtomListEncode(buf) + buf = atom.StrEncode(buf, "blob") + buf = atom.ListEncode(buf) { - buf = gyac.AtomBlobEncode(buf, 12) - buf = gyac.AtomBinEncode(buf, []byte{'5'}) + buf = atom.BlobEncode(buf, 12) + buf = atom.BinEncode(buf, []byte{'5'}) } { - buf = gyac.AtomBlobEncode(buf, 12) - buf = gyac.AtomChunkEncode(buf, bytes.Repeat([]byte{'6'}, 12)) - buf = gyac.AtomBinEncode(buf, []byte{}) + buf = atom.BlobEncode(buf, 12) + buf = atom.ChunkEncode(buf, bytes.Repeat([]byte{'6'}, 12)) + buf = atom.BinEncode(buf, []byte{}) } { - buf = gyac.AtomBlobEncode(buf, 12) - buf = gyac.AtomChunkEncode(buf, bytes.Repeat([]byte{'7'}, 12)) - buf = gyac.AtomBinEncode(buf, []byte{'7'}) + buf = atom.BlobEncode(buf, 12) + buf = atom.ChunkEncode(buf, bytes.Repeat([]byte{'7'}, 12)) + buf = atom.BinEncode(buf, []byte{'7'}) } { - buf = gyac.AtomBlobEncode(buf, 5) - buf = gyac.AtomChunkEncode(buf, []byte("12345")) - buf = gyac.AtomChunkEncode(buf, []byte("67890")) - buf = gyac.AtomBinEncode(buf, []byte{'-'}) + buf = atom.BlobEncode(buf, 5) + buf = atom.ChunkEncode(buf, []byte("12345")) + buf = atom.ChunkEncode(buf, []byte("67890")) + buf = atom.BinEncode(buf, []byte{'-'}) } - buf = gyac.AtomEOCEncode(buf) + buf = atom.EOCEncode(buf) } { - buf = gyac.AtomStrEncode(buf, "bool") - buf = gyac.AtomListEncode(buf) - buf = gyac.AtomBoolEncode(buf, true) - buf = gyac.AtomBoolEncode(buf, false) - buf = gyac.AtomEOCEncode(buf) + buf = atom.StrEncode(buf, "bool") + buf = atom.ListEncode(buf) + buf = atom.BoolEncode(buf, true) + buf = atom.BoolEncode(buf, false) + buf = atom.EOCEncode(buf) } { - buf = gyac.AtomStrEncode(buf, "ints") - buf = gyac.AtomMapEncode(buf) + buf = atom.StrEncode(buf, "ints") + buf = atom.MapEncode(buf) { - buf = gyac.AtomStrEncode(buf, "neg") - buf = gyac.AtomListEncode(buf) - buf = gyac.AtomIntEncode(buf, -1) - buf = gyac.AtomIntEncode(buf, -2) - buf = gyac.AtomIntEncode(buf, -32) - buf = gyac.AtomIntEncode(buf, -33) - buf = gyac.AtomIntEncode(buf, -123) - buf = gyac.AtomIntEncode(buf, -1234) - buf = gyac.AtomIntEncode(buf, -12345678) + buf = atom.StrEncode(buf, "neg") + buf = atom.ListEncode(buf) + buf = atom.IntEncode(buf, -1) + buf = atom.IntEncode(buf, -2) + buf = atom.IntEncode(buf, -32) + buf = atom.IntEncode(buf, -33) + buf = atom.IntEncode(buf, -123) + buf = atom.IntEncode(buf, -1234) + buf = atom.IntEncode(buf, -12345678) b := big.NewInt(0) b.SetBytes(mustHexDec("0100000000000000000000")) b = b.Neg(b) - buf = gyac.AtomBigIntEncode(buf, b) + buf = atom.BigIntEncode(buf, b) b.SetBytes(mustHexDec("0100000000000000000000000000000001")) b = b.Neg(b) - buf = gyac.AtomBigIntEncode(buf, b) + buf = atom.BigIntEncode(buf, b) b.SetBytes(mustHexDec("e5a461280341856d4ad908a69ea5f3ccc10c7882142bb7d801cc380f26b6b4d69632024ee521f8cfafb443d49a2a3d0cc73bb4757e882f5396ed302b418210d0d49d71be86ca699cf5ee3bd6d57ed658e69316229644ba650c92d7f0d4db29c3ad1dfa9979166f4c6e79561a58f8e2c63d08df4e2246ed1f64d2d613a19d8c9a6870e6188e2f3ad40c038fda30452f8ddfcd212a6a974bc25ec6a0564c66a7d28750ff9db458b74441e49ee5e82dbf4974d645678e0ad031f97aaba855451eef17a89b42821e530816dd5793a83b7a82e8ede81e7f3395691f761784f8bc627961cd40845ee908a40b9d1f01927b38eb1a7d4efd60db0944f7ec1b832b7e6eb1833f9a351576ad5de571fae8865da7514f06b0fbf38c1f2a8538f5d38b4e18001ccbb9ddcb488530f6086d14744d8b5672166e48e9ef93772575db66b6f257c6ffad6e2c291510c5ed02e1a8b24b44ec1e2a91686238e8defd18c01998634a5076a6b7f85fc81a1d61a15b2c528dfa082ce3e3e2ca649ac04817ec5c123e0b761ab103f780c014f021bbeb7ea3b86e0ca1c833e38ef5c897a6d7e1f4a2398c490b3d65e2f45c7fae402d1df1698b6fddb185481664871c2664bfd1686b2b3372783f1856f6247a3f8437a2818f68b7c4ea13a5f57b73c72870b684045f15")) b = b.Neg(b) - buf = gyac.AtomBigIntEncode(buf, b) - buf = gyac.AtomEOCEncode(buf) + buf = atom.BigIntEncode(buf, b) + buf = atom.EOCEncode(buf) } { - buf = gyac.AtomStrEncode(buf, "pos") - buf = gyac.AtomListEncode(buf) - buf = gyac.AtomUIntEncode(buf, 0) - buf = gyac.AtomUIntEncode(buf, 1) - buf = gyac.AtomUIntEncode(buf, 31) - buf = gyac.AtomUIntEncode(buf, 32) - buf = gyac.AtomUIntEncode(buf, 123) - buf = gyac.AtomUIntEncode(buf, 1234) - buf = gyac.AtomUIntEncode(buf, 12345678) + buf = atom.StrEncode(buf, "pos") + buf = atom.ListEncode(buf) + buf = atom.UIntEncode(buf, 0) + buf = atom.UIntEncode(buf, 1) + buf = atom.UIntEncode(buf, 31) + buf = atom.UIntEncode(buf, 32) + buf = atom.UIntEncode(buf, 123) + buf = atom.UIntEncode(buf, 1234) + buf = atom.UIntEncode(buf, 12345678) b := big.NewInt(0) b.SetBytes(mustHexDec("0100000000000000000000")) - buf = gyac.AtomBigIntEncode(buf, b) + buf = atom.BigIntEncode(buf, b) b.SetBytes(mustHexDec("0100000000000000000000000000000000")) - buf = gyac.AtomBigIntEncode(buf, b) - buf = gyac.AtomEOCEncode(buf) + buf = atom.BigIntEncode(buf, b) + buf = atom.EOCEncode(buf) } - buf = gyac.AtomEOCEncode(buf) + buf = atom.EOCEncode(buf) } { - buf = gyac.AtomStrEncode(buf, "uuid") - buf = gyac.AtomUUIDEncode(buf, + buf = atom.StrEncode(buf, "uuid") + buf = atom.UUIDEncode(buf, uuid.MustParse("0e875e3f-d385-49eb-87b4-be42d641c367")) } { - buf = gyac.AtomStrEncode(buf, "dates") - buf = gyac.AtomListEncode(buf) + buf = atom.StrEncode(buf, "dates") + buf = atom.ListEncode(buf) { var tai tai64n.TAI64 t := time.Unix(1234567890, 0) t = tai64n.Leapsecs.Add(t) tai.FromTime(t) - buf = gyac.AtomTAI64Encode(buf, tai[:]) + buf = atom.TAI64Encode(buf, tai[:]) } { var tai tai64n.TAI64N t := time.Unix(1234567890, 456*1000) t = tai64n.Leapsecs.Add(t) tai.FromTime(t) - buf = gyac.AtomTAI64Encode(buf, tai[:]) + buf = atom.TAI64Encode(buf, tai[:]) } { var tai tai64n.TAI64N t := time.Unix(1234567890, 456789) t = tai64n.Leapsecs.Add(t) tai.FromTime(t) - buf = gyac.AtomTAI64Encode(buf, tai[:]) + buf = atom.TAI64Encode(buf, tai[:]) } - buf = gyac.AtomRawEncode(buf, &gyac.Raw{ - T: gyac.AtomTAI64NA, + buf = atom.RawEncode(buf, &atom.Raw{ + T: atom.TAI64NA, V: []byte("\x40\x00\x00\x00\x49\x96\x02\xF4\x00\x06\xF8\x55\x07\x5B\xCD\x15"), }) - buf = gyac.AtomEOCEncode(buf) + buf = atom.EOCEncode(buf) } { - buf = gyac.AtomStrEncode(buf, "floats") - buf = gyac.AtomListEncode(buf) - buf = gyac.AtomRawEncode(buf, &gyac.Raw{ - T: gyac.AtomFloat32, + buf = atom.StrEncode(buf, "floats") + buf = atom.ListEncode(buf) + buf = atom.RawEncode(buf, &atom.Raw{ + T: atom.Float32, V: []byte("\x01\x02\x03\x04"), }) - buf = gyac.AtomEOCEncode(buf) + buf = atom.EOCEncode(buf) } { - buf = gyac.AtomStrEncode(buf, "empties") - buf = gyac.AtomListEncode(buf) + buf = atom.StrEncode(buf, "empties") + buf = atom.ListEncode(buf) { - buf = gyac.AtomListEncode(buf) - buf = gyac.AtomEOCEncode(buf) + buf = atom.ListEncode(buf) + buf = atom.EOCEncode(buf) } { - buf = gyac.AtomMapEncode(buf) - buf = gyac.AtomEOCEncode(buf) + buf = atom.MapEncode(buf) + buf = atom.EOCEncode(buf) } { - buf = gyac.AtomBlobEncode(buf, 123) - buf = gyac.AtomBinEncode(buf, []byte{}) + buf = atom.BlobEncode(buf, 123) + buf = atom.BinEncode(buf, []byte{}) } - buf = gyac.AtomUUIDEncode(buf, uuid.Nil) - buf = gyac.AtomRawEncode(buf, &gyac.Raw{ - T: gyac.AtomTAI64, + buf = atom.UUIDEncode(buf, uuid.Nil) + buf = atom.RawEncode(buf, &atom.Raw{ + T: atom.TAI64, V: []byte("\x00\x00\x00\x00\x00\x00\x00\x00"), }) - buf = gyac.AtomEOCEncode(buf) + buf = atom.EOCEncode(buf) } - buf = gyac.AtomEOCEncode(buf) + buf = atom.EOCEncode(buf) } fmt.Println(hex.EncodeToString(buf)) } diff --git a/gyac/convert-fuzz-input-to-testdata b/gyac/convert-fuzz-input-to-testdata deleted file mode 100755 index c4386e3..0000000 --- a/gyac/convert-fuzz-input-to-testdata +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/sh -e - -dst=testdata/fuzz/FuzzItemDecode -mkdir -p $dst -# go install golang.org/x/tools/cmd/file2fuzz@latest -file2fuzz -o $dst fuzz-input diff --git a/gyac/dec.go b/gyac/dec.go index a3e685c..244e524 100644 --- a/gyac/dec.go +++ b/gyac/dec.go @@ -16,65 +16,15 @@ package gyac import ( - "encoding/hex" "errors" - "fmt" - "math/big" - "strings" - "unicode/utf8" - "unsafe" - "github.com/google/uuid" + "go.cypherpunks.su/yac/gyac/atom" + "go.cypherpunks.su/yac/gyac/types" ) -type ItemType byte - const ParseMaxRecursionDepth = 1 << 10 -//go:generate stringer -type=ItemType -const ( - ItemEOC ItemType = iota - ItemNIL - ItemBool - ItemUUID - ItemUInt - ItemInt - ItemBigInt - ItemList - ItemMap - ItemBlob - ItemFloat - ItemTAI64 - ItemBin - ItemStr - ItemRaw -) - -type Item struct { - V any - T byte -} - -func (i *Item) Typ() ItemType { - return ItemType(i.T) -} - -type Raw struct { - V []byte - T AtomType -} - -func (raw *Raw) String() string { - return fmt.Sprintf("RAW(%v, %s)", raw.T, hex.EncodeToString(raw.V)) -} - var ( - ErrNotEnough = errors.New("not enough data") - ErrLenTooBig = errors.New("string len >1<<60") - ErrIntNonMinimal = errors.New("int non minimal") - ErrUnknownType = errors.New("unknown type") - ErrBadUTF8 = errors.New("invalid UTF-8") - ErrBadInt = errors.New("bad int value") ErrMapBadKey = errors.New("map bad key") ErrMapUnordered = errors.New("map unordered") ErrBlobBadAtom = errors.New("blob unexpected atom") @@ -82,247 +32,12 @@ var ( ErrUnexpectedEOC = errors.New("unexpected EOC") ) -func AtomDecode(buf []byte) (item *Item, off int, err error) { - off = 1 - if len(buf) < 1 { - err = ErrNotEnough - return - } - item = &Item{T: buf[0]} - if (item.T & AtomStrings) > 0 { - l := int(item.T & 63) - if (item.T & AtomIsUTF8) == 0 { - item.T = byte(ItemBin) - } else { - item.T = byte(ItemStr) - } - ll := 0 - switch l { - case 61: - ll = 1 - case 62: - ll = 2 - l += ((1 << 8) - 1) - case 63: - ll = 8 - l += ((1 << 8) - 1) + ((1 << 16) - 1) - } - if ll != 0 { - off += ll - if len(buf) < off { - err = ErrNotEnough - return - } - ul := FromBE(buf[1 : 1+ll]) - if ul > (1<<63)-(63+((1<<8)-1)+((1<<16)-1)) { - err = ErrLenTooBig - return - } - l += int(ul) - } - off += l - if off <= 0 { - err = ErrLenTooBig - return - } - if len(buf) < off { - err = ErrNotEnough - return - } - if item.Typ() == ItemBin { - item.V = buf[1+ll : 1+ll+l] - } else { - s := unsafe.String(unsafe.SliceData(buf[1+ll:]), l) - item.V = s - if !utf8.ValidString(s) { - err = ErrBadUTF8 - } - if strings.Contains(s, "\x00") { - err = ErrBadUTF8 - } - } - return - } - switch AtomType(item.T) { - case AtomEOC: - item.T = byte(ItemEOC) - case AtomNIL: - item.T = byte(ItemNIL) - case AtomFalse: - item.T = byte(ItemBool) - item.V = false - case AtomTrue: - item.T = byte(ItemBool) - item.V = true - case AtomUUID: - off += 16 - item.T = byte(ItemUUID) - if len(buf) < off { - err = ErrNotEnough - return - } - item.V, err = uuid.FromBytes(buf[1 : 1+16]) - - case AtomList: - item.T = byte(ItemList) - case AtomMap: - item.T = byte(ItemMap) - case AtomBlob: - item.T = byte(ItemBlob) - off += 8 - if len(buf) < off { - err = ErrNotEnough - return - } - chunkLen := FromBE(buf[1 : 1+8]) - if chunkLen >= (1<<63)-1 { - err = ErrLenTooBig - return - } - chunkLen++ - item.V = chunkLen - - case AtomPInt, AtomNInt: - if AtomType(item.T) == AtomPInt { - item.T = byte(ItemUInt) - } else { - item.T = byte(ItemInt) - } - var bin *Item - var binOff int - if len(buf) < 2 { - err = ErrNotEnough - return - } - if buf[1]&AtomStrings == 0 { - err = ErrBadInt - return - } - bin, binOff, err = AtomDecode(buf[1:]) - off += binOff - if err != nil { - return - } - if ItemType(bin.T) != ItemBin { - err = ErrBadInt - return - } - raw := bin.V.([]byte) - if len(raw) == 0 { - if item.Typ() == ItemUInt { - item.V = uint64(0) - } else { - item.V = int64(-1) - } - return - } - if raw[0] == 0 { - err = ErrIntNonMinimal - return - } - if len(raw) > 8 { - bi := big.NewInt(0) - bi = bi.SetBytes(raw) - if item.Typ() == ItemInt { - n1 := big.NewInt(-1) - bi = bi.Sub(n1, bi) - } - item.T = byte(ItemBigInt) - item.V = bi - return - } - v := FromBE(raw) - if item.Typ() == ItemUInt { - item.V = v - } else { - if v >= (1 << 63) { - bi := big.NewInt(0) - bi = bi.SetBytes(raw) - n1 := big.NewInt(-1) - bi = bi.Sub(n1, bi) - item.T = byte(ItemBigInt) - item.V = bi - } else { - item.V = -1 - int64(v) - } - } - return - - case AtomFloat16, AtomFloat32, AtomFloat64, AtomFloat128, AtomFloat256: - var l int - switch AtomType(item.T) { - case AtomFloat16: - l = 2 - case AtomFloat32: - l = 4 - case AtomFloat64: - l = 8 - case AtomFloat128: - l = 16 - case AtomFloat256: - l = 32 - } - off += l - if len(buf) < off { - item.T = byte(ItemFloat) - err = ErrNotEnough - return - } - item.T = byte(ItemRaw) - item.V = &Raw{T: AtomType(buf[0]), V: buf[1 : 1+l]} - - case AtomTAI64, AtomTAI64N, AtomTAI64NA: - var l int - switch item.T { - case byte(AtomTAI64): - l = 8 - case byte(AtomTAI64N): - l = 12 - case byte(AtomTAI64NA): - l = 16 - } - off += l - if len(buf) < off { - err = ErrNotEnough - return - } - item.T = byte(ItemTAI64) - item.V = buf[1 : 1+l] - if FromBE(buf[1:1+8]) > (1 << 63) { - err = errors.New("reserved TAI64 values in use") - return - } - if l > 8 { - nsecs := FromBE(buf[1+8 : 1+8+4]) - if l == 12 && nsecs == 0 { - err = errors.New("non-minimal TAI64N") - return - } - if nsecs > 999999999 { - err = errors.New("too many nanoseconds") - return - } - } - if l > 12 { - asecs := FromBE(buf[1+8+4 : 1+8+4+4]) - if asecs == 0 { - err = errors.New("non-minimal TAI64NA") - return - } - if asecs > 999999999 { - err = errors.New("too many attoseconds") - return - } - } - - default: - err = ErrUnknownType - return - } - return +type Item struct { + V any + T types.Type } -func itemDecode( +func decode( buf []byte, allowContainers, expectEOC bool, recursionDepth int, @@ -332,57 +47,58 @@ func itemDecode( return } var off int - item, off, err = AtomDecode(buf) + item = &Item{} + item.T, item.V, off, err = atom.Decode(buf) if err != nil { return } buf = buf[off:] tail = buf - switch item.Typ() { - case ItemEOC: + switch item.T { + case types.EOC: if !expectEOC { err = ErrUnexpectedEOC return } - case ItemList: + case types.List: if !allowContainers { - err = ErrUnknownType + err = atom.ErrUnknownType return } var sub *Item var v []*Item for { - sub, buf, err = itemDecode(buf, true, true, recursionDepth+1) + sub, buf, err = decode(buf, true, true, recursionDepth+1) tail = buf if err != nil { tail = buf return } - if sub.T == byte(ItemEOC) { + if sub.T == types.EOC { break } v = append(v, sub) } item.V = v return - case ItemMap: + case types.Map: if !allowContainers { - err = ErrUnknownType + err = atom.ErrUnknownType return } v := make(map[string]*Item) var sub *Item var keyPrev string for { - sub, buf, err = itemDecode(buf, false, true, recursionDepth+1) + sub, buf, err = decode(buf, false, true, recursionDepth+1) tail = buf if err != nil { return } - if sub.T == byte(ItemEOC) { + if sub.T == types.EOC { break } - if sub.T != byte(ItemStr) { + if sub.T != types.Str { err = ErrMapBadKey return } @@ -401,7 +117,7 @@ func itemDecode( } keyPrev = s } - sub, buf, err = itemDecode(buf, true, false, recursionDepth+1) + sub, buf, err = decode(buf, true, false, recursionDepth+1) tail = buf if err != nil { return @@ -410,9 +126,9 @@ func itemDecode( } item.V = v return - case ItemBlob: + case types.Blob: if !allowContainers { - err = ErrUnknownType + err = atom.ErrUnknownType return } chunkLen := int(item.V.(uint64)) @@ -420,21 +136,21 @@ func itemDecode( var sub *Item BlobCycle: for { - sub, buf, err = itemDecode(buf, false, true, recursionDepth+1) + sub, buf, err = decode(buf, false, true, recursionDepth+1) tail = buf if err != nil { return } switch sub.T { - case byte(ItemNIL): + case types.NIL: if len(buf) <= chunkLen { - err = ErrNotEnough + err = atom.ErrNotEnough return } v.Chunks = append(v.Chunks, buf[:chunkLen]) buf = buf[chunkLen:] tail = buf - case byte(ItemBin): + case types.Bin: b := sub.V.([]byte) if len(b) >= chunkLen { err = ErrBlobBadTerm @@ -455,6 +171,6 @@ func itemDecode( return } -func ItemDecode(buf []byte) (item *Item, tail []byte, err error) { - return itemDecode(buf, true, false, 0) +func Decode(buf []byte) (item *Item, tail []byte, err error) { + return decode(buf, true, false, 0) } diff --git a/gyac/enc.go b/gyac/enc.go index 04b27c2..756ec97 100644 --- a/gyac/enc.go +++ b/gyac/enc.go @@ -20,221 +20,70 @@ import ( "sort" "github.com/google/uuid" -) - -var BigIntZero = big.NewInt(0) - -type AtomType byte -//go:generate stringer -type=AtomType -const ( - AtomEOC AtomType = 0x00 - AtomNIL AtomType = 0x01 - AtomFalse AtomType = 0x02 - AtomTrue AtomType = 0x03 - AtomUUID AtomType = 0x04 - AtomList AtomType = 0x08 - AtomMap AtomType = 0x09 - AtomBlob AtomType = 0x0B - AtomPInt AtomType = 0x0C - AtomNInt AtomType = 0x0D - AtomFloat16 AtomType = 0x10 - AtomFloat32 AtomType = 0x11 - AtomFloat64 AtomType = 0x12 - AtomFloat128 AtomType = 0x13 - AtomFloat256 AtomType = 0x14 - AtomTAI64 AtomType = 0x18 - AtomTAI64N AtomType = 0x19 - AtomTAI64NA AtomType = 0x1A - - AtomStrings = 0x80 - AtomIsUTF8 = 0x40 + "go.cypherpunks.su/yac/gyac/atom" + "go.cypherpunks.su/yac/gyac/types" ) -func AtomEOCEncode(buf []byte) []byte { - return append(buf, byte(AtomEOC)) -} - -func AtomNILEncode(buf []byte) []byte { - return append(buf, byte(AtomNIL)) -} - -func AtomBoolEncode(buf []byte, v bool) []byte { - if v { - return append(buf, byte(AtomTrue)) - } - return append(buf, byte(AtomFalse)) -} - -func AtomUUIDEncode(buf []byte, v uuid.UUID) []byte { - return append(append(buf, byte(AtomUUID)), v[:]...) -} - -func atomUintEncode(v uint64) (buf []byte) { - if v == 0 { - return AtomBinEncode(nil, []byte{}) - } - l := 0 - for ; l < 7; l++ { - if v < (1 << ((l + 1) * 8)) { - break - } - } - buf = make([]byte, l+1) - ToBE(buf, v) - return AtomBinEncode(nil, buf) -} - -func AtomUIntEncode(buf []byte, v uint64) []byte { - return append(buf, append([]byte{byte(AtomPInt)}, atomUintEncode(v)...)...) -} - -func AtomIntEncode(buf []byte, v int64) []byte { - if v >= 0 { - return AtomUIntEncode(buf, uint64(v)) - } - return append(buf, append([]byte{byte(AtomNInt)}, - atomUintEncode(uint64(-(v+1)))...)...) -} - -func AtomBigIntEncode(buf []byte, v *big.Int) []byte { - // TODO: fallback to U?IntEncode for small values - if v.Cmp(BigIntZero) >= 0 { - return append(buf, AtomBinEncode([]byte{byte(AtomPInt)}, v.Bytes())...) - } - n1 := big.NewInt(-1) - v = v.Abs(v) - v = v.Add(v, n1) - return append(buf, AtomBinEncode([]byte{byte(AtomNInt)}, v.Bytes())...) -} - -func AtomListEncode(buf []byte) []byte { - return append(buf, byte(AtomList)) -} - -func AtomMapEncode(buf []byte) []byte { - return append(buf, byte(AtomMap)) -} - -func AtomBlobEncode(buf []byte, chunkLen int) []byte { - l := make([]byte, 9) - l[0] = byte(AtomBlob) - ToBE(l[1:], uint64(chunkLen-1)) - return append(buf, l...) -} - -func atomStrEncode(buf, data []byte, utf8 bool) []byte { - var lv int - var l []byte - if len(data) >= 63+((1<<8)-1)+((1<<16)-1) { - lv = 63 - l = make([]byte, 8) - ToBE(l, uint64(len(data)-(lv+((1<<8)-1)+((1<<16)-1)))) - } else if len(data) >= 62+255 { - lv = 62 - l = make([]byte, 2) - ToBE(l, uint64(len(data)-(lv+((1<<8)-1)))) - } else if len(data) >= 61 { - lv = 61 - l = []byte{byte(len(data) - lv)} - } else { - lv = len(data) - } - b := byte(AtomStrings | lv) - if utf8 { - b |= AtomIsUTF8 - } - return append(append(append(buf, b), l...), data...) -} - -func AtomStrEncode(buf []byte, str string) []byte { - return atomStrEncode(buf, []byte(str), true) -} - -func AtomBinEncode(buf, bin []byte) []byte { - return atomStrEncode(buf, bin, false) -} - -func AtomChunkEncode(buf, chunk []byte) []byte { - return append(append(buf, byte(AtomNIL)), chunk...) -} - -func AtomTAI64Encode(buf, tai []byte) []byte { - switch len(tai) { - case 8: - return append(append(buf, byte(AtomTAI64)), tai...) - case 12: - return append(append(buf, byte(AtomTAI64N)), tai...) - case 16: - return append(append(buf, byte(AtomTAI64NA)), tai...) - default: - panic("wrong TAI64 value") - } -} - -func AtomRawEncode(buf []byte, raw *Raw) []byte { - return append(append(buf, byte(raw.T)), raw.V...) -} - func (item *Item) Encode(buf []byte) []byte { - switch item.Typ() { - case ItemNIL: - return AtomNILEncode(buf) - case ItemBool: - return AtomBoolEncode(buf, item.V.(bool)) - case ItemUUID: - return AtomUUIDEncode(buf, item.V.(uuid.UUID)) - case ItemUInt: - return AtomUIntEncode(buf, item.V.(uint64)) - case ItemInt: - return AtomIntEncode(buf, item.V.(int64)) - case ItemBigInt: - return AtomBigIntEncode(buf, item.V.(*big.Int)) - case ItemList: - buf = AtomListEncode(buf) + switch item.T { + case types.NIL: + return atom.NILEncode(buf) + case types.Bool: + return atom.BoolEncode(buf, item.V.(bool)) + case types.UUID: + return atom.UUIDEncode(buf, item.V.(uuid.UUID)) + case types.UInt: + return atom.UIntEncode(buf, item.V.(uint64)) + case types.Int: + return atom.IntEncode(buf, item.V.(int64)) + case types.BigInt: + return atom.BigIntEncode(buf, item.V.(*big.Int)) + case types.List: + buf = atom.ListEncode(buf) for _, v := range item.V.([]*Item) { buf = v.Encode(buf) } - buf = AtomEOCEncode(buf) - case ItemMap: + buf = atom.EOCEncode(buf) + case types.Map: m := item.V.(map[string]*Item) keys := make([]string, 0, len(m)) for k := range m { keys = append(keys, k) } sort.Sort(ByLenFirst(keys)) - buf = AtomMapEncode(buf) + buf = atom.MapEncode(buf) for _, k := range keys { - buf = AtomStrEncode(buf, k) + buf = atom.StrEncode(buf, k) buf = m[k].Encode(buf) } - buf = AtomEOCEncode(buf) - case ItemBlob: + buf = atom.EOCEncode(buf) + case types.Blob: blob := item.V.(*Blob) - buf = AtomBlobEncode(buf, blob.ChunkLen) + buf = atom.BlobEncode(buf, blob.ChunkLen) for _, chunk := range blob.Chunks { if len(chunk) == blob.ChunkLen { - buf = AtomChunkEncode(buf, chunk) + buf = atom.ChunkEncode(buf, chunk) } } if len(blob.Chunks) == 0 { - buf = AtomBinEncode(buf, []byte{}) + buf = atom.BinEncode(buf, []byte{}) } else { last := blob.Chunks[len(blob.Chunks)-1] if len(last) == blob.ChunkLen { - buf = AtomBinEncode(buf, []byte{}) + buf = atom.BinEncode(buf, []byte{}) } else { - buf = AtomBinEncode(buf, last) + buf = atom.BinEncode(buf, last) } } - case ItemTAI64: - return AtomTAI64Encode(buf, item.V.([]byte)) - case ItemBin: - return AtomBinEncode(buf, item.V.([]byte)) - case ItemStr: - return AtomStrEncode(buf, item.V.(string)) - case ItemRaw: - return AtomRawEncode(buf, item.V.(*Raw)) + case types.TAI64: + return atom.TAI64Encode(buf, item.V.([]byte)) + case types.Bin: + return atom.BinEncode(buf, item.V.([]byte)) + case types.Str: + return atom.StrEncode(buf, item.V.(string)) + case types.Raw: + return atom.RawEncode(buf, item.V.(*atom.Raw)) default: panic("unhandled type") } diff --git a/gyac/fromgo.go b/gyac/fromgo.go new file mode 100644 index 0000000..9b48623 --- /dev/null +++ b/gyac/fromgo.go @@ -0,0 +1,176 @@ +// gyac -- Go YAC encoder implementation +// Copyright (C) 2024-2025 Sergey Matveev +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation, version 3 of the License. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this program. If not, see . + +package gyac + +import ( + "fmt" + "math/big" + "reflect" + "strings" + "time" + + "github.com/google/uuid" + "go.cypherpunks.su/tai64n/v4" + "go.cypherpunks.su/yac/gyac/atom" + "go.cypherpunks.su/yac/gyac/types" +) + +func structTagRead(f reflect.StructField) (name string, omit bool) { + name = f.Name + v, ok := f.Tag.Lookup("yac") + if !ok { + return + } + opts := strings.Split(v, ",") + if opts[0] != "" { + name = opts[0] + } + if len(opts) == 2 && opts[1] == "omitempty" { + omit = true + } + return +} + +func FromGo(v any) *Item { + if v == nil { + return &Item{T: types.NIL} + } + rv := reflect.ValueOf(v) + if b, ok := v.([]byte); ok { + return &Item{T: types.Bin, V: b} + } + switch v := v.(type) { + case *Blob: + return &Item{T: types.Blob, V: v} + case time.Time: + t := tai64n.Leapsecs.Add(v) + var taiRaw []byte + if t.Nanosecond() > 0 { + var tai tai64n.TAI64N + tai.FromTime(t) + taiRaw = tai[:] + } else { + var tai tai64n.TAI64 + tai.FromTime(t) + taiRaw = tai[:] + } + return &Item{T: types.TAI64, V: taiRaw} + case *atom.Raw: + return &Item{T: types.Raw, V: v} + case *big.Int: + return &Item{T: types.BigInt, V: v} + } + switch reflect.TypeOf(v).Kind() { + case reflect.Pointer: + if rv.IsNil() { + return &Item{T: types.NIL} + } + return FromGo(rv.Elem().Interface()) + case reflect.Slice: + var ret []*Item + if anys, ok := v.([]any); ok { + for _, v := range anys { + ret = append(ret, FromGo(v)) + } + } else { + rv = reflect.ValueOf(v) + for i := 0; i < rv.Len(); i++ { + ret = append(ret, FromGo(rv.Index(i).Interface())) + } + } + return &Item{T: types.List, V: ret} + case reflect.Map: + ret := make(map[string]*Item, rv.Len()) + iter := rv.MapRange() + for iter.Next() { + ret[iter.Key().String()] = FromGo(iter.Value().Interface()) + } + return &Item{T: types.Map, V: ret} + } + { + t := rv.Type() + if t.Kind() == reflect.Struct { + ret := make(map[string]*Item) + for _, f := range reflect.VisibleFields(t) { + fv := rv.FieldByIndex(f.Index) + name, omit := structTagRead(f) + var empty bool + item := FromGo(fv.Interface()) + switch item.T { + case types.NIL: + empty = true + case types.List: + if len(item.V.([]*Item)) == 0 { + empty = true + } + case types.Map: + if len(item.V.(map[string]*Item)) == 0 { + empty = true + } + } + if !(omit && empty) { + ret[name] = item + } + } + return &Item{T: types.Map, V: ret} + } + } + switch v := v.(type) { + case bool: + return &Item{T: types.Bool, V: v} + case uuid.UUID: + return &Item{T: types.UUID, V: v} + case uint: + return &Item{T: types.UInt, V: uint64(v)} + case uint8: + return &Item{T: types.UInt, V: uint64(v)} + case uint16: + return &Item{T: types.UInt, V: uint64(v)} + case uint32: + return &Item{T: types.UInt, V: uint64(v)} + case uint64: + return &Item{T: types.UInt, V: v} + case int: + if v >= 0 { + return &Item{T: types.UInt, V: uint64(v)} + } + return &Item{T: types.Int, V: int64(v)} + case int8: + if v >= 0 { + return &Item{T: types.UInt, V: uint64(v)} + } + return &Item{T: types.Int, V: int64(v)} + case int16: + if v >= 0 { + return &Item{T: types.UInt, V: uint64(v)} + } + return &Item{T: types.Int, V: int64(v)} + case int32: + if v >= 0 { + return &Item{T: types.UInt, V: uint64(v)} + } + return &Item{T: types.Int, V: int64(v)} + case int64: + if v >= 0 { + return &Item{T: types.UInt, V: uint64(v)} + } + return &Item{T: types.Int, V: v} + case string: + return &Item{T: types.Str, V: v} + default: + panic(fmt.Errorf("unhandled type: %+v", v)) + } +} diff --git a/gyac/fuzz_test.go b/gyac/fuzz_test.go index a434049..795ab88 100644 --- a/gyac/fuzz_test.go +++ b/gyac/fuzz_test.go @@ -10,10 +10,10 @@ func FuzzItemDecode(f *testing.F) { var err error var tail []byte f.Fuzz(func(t *testing.T, b []byte) { - item, tail, err = ItemDecode(b) + item, tail, err = Decode(b) if err == nil { if !bytes.Equal( - append(ItemFromGo(item.ToGo()).Encode(nil), tail...), + append(FromGo(item.ToGo()).Encode(nil), tail...), b, ) { t.Fail() diff --git a/gyac/itemtype_string.go b/gyac/itemtype_string.go deleted file mode 100644 index cd99d50..0000000 --- a/gyac/itemtype_string.go +++ /dev/null @@ -1,37 +0,0 @@ -// Code generated by "stringer -type=ItemType"; DO NOT EDIT. - -package gyac - -import "strconv" - -func _() { - // An "invalid array index" compiler error signifies that the constant values have changed. - // Re-run the stringer command to generate them again. - var x [1]struct{} - _ = x[ItemEOC-0] - _ = x[ItemNIL-1] - _ = x[ItemBool-2] - _ = x[ItemUUID-3] - _ = x[ItemUInt-4] - _ = x[ItemInt-5] - _ = x[ItemBigInt-6] - _ = x[ItemList-7] - _ = x[ItemMap-8] - _ = x[ItemBlob-9] - _ = x[ItemFloat-10] - _ = x[ItemTAI64-11] - _ = x[ItemBin-12] - _ = x[ItemStr-13] - _ = x[ItemRaw-14] -} - -const _ItemType_name = "ItemEOCItemNILItemBoolItemUUIDItemUIntItemIntItemBigIntItemListItemMapItemBlobItemFloatItemTAI64ItemBinItemStrItemRaw" - -var _ItemType_index = [...]uint8{0, 7, 14, 22, 30, 38, 45, 55, 63, 70, 78, 87, 96, 103, 110, 117} - -func (i ItemType) String() string { - if i >= ItemType(len(_ItemType_index)-1) { - return "ItemType(" + strconv.FormatInt(int64(i), 10) + ")" - } - return _ItemType_name[_ItemType_index[i]:_ItemType_index[i+1]] -} diff --git a/gyac/mapstruct/dec.go b/gyac/mapstruct/dec.go new file mode 100644 index 0000000..fadf768 --- /dev/null +++ b/gyac/mapstruct/dec.go @@ -0,0 +1,37 @@ +// gyac -- Go YAC encoder implementation +// Copyright (C) 2024-2025 Sergey Matveev +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation, version 3 of the License. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this program. If not, see . + +package mapstruct + +import ( + "errors" + + "go.cypherpunks.su/yac/gyac" + "go.cypherpunks.su/yac/gyac/types" +) + +func Decode(dst any, raw []byte) (tail []byte, err error) { + var item *gyac.Item + item, tail, err = gyac.Decode(raw) + if err != nil { + return + } + if item.T != types.Map { + err = errors.New("non-map") + return + } + err = FromMap(dst, item.ToGo().(map[string]any)) + return +} diff --git a/gyac/mapstruct/map.go b/gyac/mapstruct/map.go new file mode 100644 index 0000000..bc3b3a2 --- /dev/null +++ b/gyac/mapstruct/map.go @@ -0,0 +1,30 @@ +// gyac -- Go YAC encoder implementation +// Copyright (C) 2024-2025 Sergey Matveev +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation, version 3 of the License. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this program. If not, see . + +package mapstruct + +import ( + "github.com/mitchellh/mapstructure" +) + +func FromMap(dst any, src map[string]any) error { + decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ + Result: dst, TagName: "yac", + }) + if err != nil { + return err + } + return decoder.Decode(src) +} diff --git a/gyac/mk-fuzz-testdata b/gyac/mk-fuzz-testdata new file mode 100755 index 0000000..2540a99 --- /dev/null +++ b/gyac/mk-fuzz-testdata @@ -0,0 +1,10 @@ +#!/bin/sh -e + +mkdir fuzz-input +cd fuzz-input +PATH="../../tyac:$PATH" ../../tyac/mk-fuzz-inputs +cd .. +dst=testdata/fuzz/FuzzItemDecode +mkdir -p $dst +# go install golang.org/x/tools/cmd/file2fuzz@latest +file2fuzz -o $dst fuzz-input/* diff --git a/gyac/reflect.go b/gyac/reflect.go deleted file mode 100644 index f860c59..0000000 --- a/gyac/reflect.go +++ /dev/null @@ -1,263 +0,0 @@ -// gyac -- Go YAC encoder implementation -// Copyright (C) 2024-2025 Sergey Matveev -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as -// published by the Free Software Foundation, version 3 of the License. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public -// License along with this program. If not, see . - -package gyac - -import ( - "errors" - "fmt" - "math/big" - "reflect" - "strings" - "time" - - "github.com/google/uuid" - "github.com/mitchellh/mapstructure" - "go.cypherpunks.su/tai64n/v4" -) - -func (v *Item) ToGo() any { - switch ItemType(v.T) { - case ItemNIL: - return nil - case ItemBool: - return v.V.(bool) - case ItemUUID: - return v.V.(uuid.UUID) - case ItemUInt: - return v.V.(uint64) - case ItemInt: - return v.V.(int64) - case ItemList: - var ret []any - for _, v := range v.V.([]*Item) { - ret = append(ret, v.ToGo()) - } - return ret - case ItemMap: - ret := make(map[string]any) - for k, v := range v.V.(map[string]*Item) { - ret[k] = v.ToGo() - } - return ret - case ItemBlob: - return v.V.(*Blob) - case ItemBigInt: - return v.V.(*big.Int) - case ItemFloat: - panic("float is unsupported") - case ItemTAI64: - raw := v.V.([]byte) - switch len(raw) { - case tai64n.TAI64Size: - tai := tai64n.TAI64(raw) - t, isLeap := tai64n.Leapsecs.Sub(tai.Time()) - if isLeap { - return &Raw{T: AtomTAI64, V: raw} - } - return t - case tai64n.TAI64NSize: - tai := tai64n.TAI64N(raw) - t, isLeap := tai64n.Leapsecs.Sub(tai.Time()) - if isLeap { - return &Raw{T: AtomTAI64N, V: raw} - } - return t - case tai64n.TAI64NASize: - return &Raw{T: AtomTAI64NA, V: raw} - default: - panic("unexpected TAI size") - } - case ItemBin: - return v.V.([]byte) - case ItemStr: - return v.V.(string) - case ItemRaw: - return v.V.(*Raw) - default: - panic(fmt.Errorf("unhandled type: %+v", v)) - } -} - -func structTagRead(f reflect.StructField) (name string, omit bool) { - name = f.Name - v, ok := f.Tag.Lookup("yac") - if !ok { - return - } - opts := strings.Split(v, ",") - if opts[0] != "" { - name = opts[0] - } - if len(opts) == 2 && opts[1] == "omitempty" { - omit = true - } - return -} - -func ItemFromGo(v any) *Item { - if v == nil { - return &Item{T: byte(ItemNIL)} - } - rv := reflect.ValueOf(v) - if b, ok := v.([]byte); ok { - return &Item{T: byte(ItemBin), V: b} - } - switch v := v.(type) { - case *Blob: - return &Item{T: byte(ItemBlob), V: v} - case time.Time: - t := tai64n.Leapsecs.Add(v) - var taiRaw []byte - if t.Nanosecond() > 0 { - var tai tai64n.TAI64N - tai.FromTime(t) - taiRaw = tai[:] - } else { - var tai tai64n.TAI64 - tai.FromTime(t) - taiRaw = tai[:] - } - return &Item{T: byte(ItemTAI64), V: taiRaw} - case *Raw: - return &Item{T: byte(ItemRaw), V: v} - case *big.Int: - return &Item{T: byte(ItemBigInt), V: v} - } - switch reflect.TypeOf(v).Kind() { - case reflect.Pointer: - if rv.IsNil() { - return &Item{T: byte(ItemNIL)} - } - return ItemFromGo(rv.Elem().Interface()) - case reflect.Slice: - var ret []*Item - if anys, ok := v.([]any); ok { - for _, v := range anys { - ret = append(ret, ItemFromGo(v)) - } - } else { - rv = reflect.ValueOf(v) - for i := 0; i < rv.Len(); i++ { - ret = append(ret, ItemFromGo(rv.Index(i).Interface())) - } - } - return &Item{T: byte(ItemList), V: ret} - case reflect.Map: - ret := make(map[string]*Item) - iter := rv.MapRange() - for iter.Next() { - ret[iter.Key().String()] = ItemFromGo(iter.Value().Interface()) - } - return &Item{T: byte(ItemMap), V: ret} - } - { - t := rv.Type() - if t.Kind() == reflect.Struct { - ret := make(map[string]*Item) - for _, f := range reflect.VisibleFields(t) { - fv := rv.FieldByIndex(f.Index) - name, omit := structTagRead(f) - var empty bool - item := ItemFromGo(fv.Interface()) - switch ItemType(item.T) { - case ItemNIL: - empty = true - case ItemList: - if len(item.V.([]*Item)) == 0 { - empty = true - } - case ItemMap: - if len(item.V.(map[string]*Item)) == 0 { - empty = true - } - } - if !(omit && empty) { - ret[name] = item - } - } - return &Item{T: byte(ItemMap), V: ret} - } - } - switch v := v.(type) { - case bool: - return &Item{T: byte(ItemBool), V: v} - case uuid.UUID: - return &Item{T: byte(ItemUUID), V: v} - case uint: - return &Item{T: byte(ItemUInt), V: uint64(v)} - case uint8: - return &Item{T: byte(ItemUInt), V: uint64(v)} - case uint16: - return &Item{T: byte(ItemUInt), V: uint64(v)} - case uint32: - return &Item{T: byte(ItemUInt), V: uint64(v)} - case uint64: - return &Item{T: byte(ItemUInt), V: v} - case int: - if v >= 0 { - return &Item{T: byte(ItemUInt), V: uint64(v)} - } - return &Item{T: byte(ItemInt), V: int64(v)} - case int8: - if v >= 0 { - return &Item{T: byte(ItemUInt), V: uint64(v)} - } - return &Item{T: byte(ItemInt), V: int64(v)} - case int16: - if v >= 0 { - return &Item{T: byte(ItemUInt), V: uint64(v)} - } - return &Item{T: byte(ItemInt), V: int64(v)} - case int32: - if v >= 0 { - return &Item{T: byte(ItemUInt), V: uint64(v)} - } - return &Item{T: byte(ItemInt), V: int64(v)} - case int64: - if v >= 0 { - return &Item{T: byte(ItemUInt), V: uint64(v)} - } - return &Item{T: byte(ItemInt), V: v} - case string: - return &Item{T: byte(ItemStr), V: v} - default: - panic(fmt.Errorf("unhandled type: %+v", v)) - } -} - -func MapToStruct(dst any, src map[string]any) error { - decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ - Result: dst, TagName: "yac", - }) - if err != nil { - return err - } - return decoder.Decode(src) -} - -func DecodeToStruct(dst any, raw []byte) (tail []byte, err error) { - var item *Item - item, tail, err = ItemDecode(raw) - if err != nil { - return - } - if item.Typ() != ItemMap { - err = errors.New("non-map") - return - } - err = MapToStruct(dst, item.ToGo().(map[string]any)) - return -} diff --git a/gyac/togo.go b/gyac/togo.go new file mode 100644 index 0000000..ec10fa7 --- /dev/null +++ b/gyac/togo.go @@ -0,0 +1,89 @@ +// gyac -- Go YAC encoder implementation +// Copyright (C) 2024-2025 Sergey Matveev +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation, version 3 of the License. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this program. If not, see . + +package gyac + +import ( + "fmt" + "math/big" + + "github.com/google/uuid" + "go.cypherpunks.su/tai64n/v4" + "go.cypherpunks.su/yac/gyac/atom" + "go.cypherpunks.su/yac/gyac/types" +) + +func (item *Item) ToGo() any { + switch item.T { + case types.NIL: + return nil + case types.Bool: + return item.V.(bool) + case types.UUID: + return item.V.(uuid.UUID) + case types.UInt: + return item.V.(uint64) + case types.Int: + return item.V.(int64) + case types.List: + var ret []any + for _, v := range item.V.([]*Item) { + ret = append(ret, v.ToGo()) + } + return ret + case types.Map: + ret := make(map[string]any) + for k, v := range item.V.(map[string]*Item) { + ret[k] = v.ToGo() + } + return ret + case types.Blob: + return item.V.(*Blob) + case types.BigInt: + return item.V.(*big.Int) + case types.Float: + panic("float is unsupported") + case types.TAI64: + raw := item.V.([]byte) + switch len(raw) { + case tai64n.TAI64Size: + tai := tai64n.TAI64(raw) + t, isLeap := tai64n.Leapsecs.Sub(tai.Time()) + if isLeap { + return &atom.Raw{T: atom.TAI64, V: raw} + } + return t + case tai64n.TAI64NSize: + tai := tai64n.TAI64N(raw) + t, isLeap := tai64n.Leapsecs.Sub(tai.Time()) + if isLeap { + return &atom.Raw{T: atom.TAI64N, V: raw} + } + return t + case tai64n.TAI64NASize: + return &atom.Raw{T: atom.TAI64NA, V: raw} + default: + panic("unexpected TAI size") + } + case types.Bin: + return item.V.([]byte) + case types.Str: + return item.V.(string) + case types.Raw: + return item.V.(*atom.Raw) + default: + panic(fmt.Errorf("unhandled type: %+v", item)) + } +} diff --git a/gyac/types/type.go b/gyac/types/type.go new file mode 100644 index 0000000..ba0d90a --- /dev/null +++ b/gyac/types/type.go @@ -0,0 +1,22 @@ +package types + +type Type byte + +//go:generate stringer -type=Type +const ( + EOC Type = iota + NIL + Bool + UUID + UInt + Int + BigInt + List + Map + Blob + Float + TAI64 + Bin + Str + Raw +) diff --git a/gyac/types/type_string.go b/gyac/types/type_string.go new file mode 100644 index 0000000..c21de88 --- /dev/null +++ b/gyac/types/type_string.go @@ -0,0 +1,37 @@ +// Code generated by "stringer -type=Type"; DO NOT EDIT. + +package types + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[EOC-0] + _ = x[NIL-1] + _ = x[Bool-2] + _ = x[UUID-3] + _ = x[UInt-4] + _ = x[Int-5] + _ = x[BigInt-6] + _ = x[List-7] + _ = x[Map-8] + _ = x[Blob-9] + _ = x[Float-10] + _ = x[TAI64-11] + _ = x[Bin-12] + _ = x[Str-13] + _ = x[Raw-14] +} + +const _Type_name = "EOCNILBoolUUIDUIntIntBigIntListMapBlobFloatTAI64BinStrRaw" + +var _Type_index = [...]uint8{0, 3, 6, 10, 14, 18, 21, 27, 31, 34, 38, 43, 48, 51, 54, 57} + +func (i Type) String() string { + if i >= Type(len(_Type_index)-1) { + return "Type(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _Type_name[_Type_index[i]:_Type_index[i+1]] +} diff --git a/gyac/yacpki/algo.go b/gyac/yacpki/algo.go index 150ab0b..f2c6e4e 100644 --- a/gyac/yacpki/algo.go +++ b/gyac/yacpki/algo.go @@ -44,7 +44,7 @@ func (av *AV) Id() (id uuid.UUID) { default: panic("unsupported algorithm") } - utils.MustWrite(hasher, gyac.ItemFromGo(av).Encode(nil)) + utils.MustWrite(hasher, gyac.FromGo(av).Encode(nil)) id, err := uuid.NewRandomFromReader(bytes.NewReader(hasher.Sum(nil))) if err != nil { panic(err) diff --git a/gyac/yacpki/cer.go b/gyac/yacpki/cer.go index 5f22521..7d4b274 100644 --- a/gyac/yacpki/cer.go +++ b/gyac/yacpki/cer.go @@ -12,6 +12,7 @@ import ( "go.cypherpunks.su/gogost/v6/gost34112012256" "go.cypherpunks.su/gogost/v6/gost34112012512" "go.cypherpunks.su/yac/gyac" + "go.cypherpunks.su/yac/gyac/mapstruct" "go.cypherpunks.su/yac/gyac/yacpki/ed25519-blake2b/ed25519" "go.cypherpunks.su/yac/gyac/yacpki/utils" ) @@ -64,7 +65,7 @@ func (sd *SignedData) CerParse() error { var load CerLoad var err error if v, ok := sd.Load.V.(map[string]any); ok { - err = gyac.MapToStruct(&load, v) + err = mapstruct.FromMap(&load, v) } else { err = errors.New("CerParse: wrong /load/v") } @@ -185,7 +186,7 @@ func (sd *SignedData) CerCheckSignatureFrom(parent *CerLoad) (err error) { return } tbs := SignedDataTBS{T: sd.Load.T, V: sd.Load.V, TBS: sig.TBS} - return parent.CheckSignature(gyac.ItemFromGo(tbs).Encode(nil), sig.Sign.V) + return parent.CheckSignature(gyac.FromGo(tbs).Encode(nil), sig.Sign.V) } func (sd *SignedData) CerLoad() *CerLoad { diff --git a/gyac/yacpki/cmd/yacertool/main.go b/gyac/yacpki/cmd/yacertool/main.go index b51a0c5..3e8d37f 100644 --- a/gyac/yacpki/cmd/yacertool/main.go +++ b/gyac/yacpki/cmd/yacertool/main.go @@ -143,7 +143,7 @@ func main() { } prv = prvEd25519 pubRaw = pubEd25519[:] - err = os.WriteFile(*prvPath, gyac.ItemFromGo( + err = os.WriteFile(*prvPath, gyac.FromGo( yacpki.AV{A: *algo, V: prvEd25519.Seed()}, ).Encode(nil), 0o600) if err != nil { @@ -175,7 +175,7 @@ func main() { if err != nil { log.Fatal(err) } - raw := gyac.ItemFromGo(yacpki.AV{A: *algo, V: prvKey.RawBE()}).Encode(nil) + raw := gyac.FromGo(yacpki.AV{A: *algo, V: prvKey.RawBE()}).Encode(nil) prv, err = yacpki.PrvParse(raw) if err != nil { log.Fatal(err) @@ -216,7 +216,7 @@ func main() { log.Fatal(err) } - err = os.WriteFile(*cerPath, gyac.ItemFromGo(sd).Encode(nil), 0o666) + err = os.WriteFile(*cerPath, gyac.FromGo(sd).Encode(nil), 0o666) if err != nil { log.Fatal(err) } diff --git a/gyac/yacpki/cmd/yacsdtool/main.go b/gyac/yacpki/cmd/yacsdtool/main.go index be8f330..16d08f2 100644 --- a/gyac/yacpki/cmd/yacsdtool/main.go +++ b/gyac/yacpki/cmd/yacsdtool/main.go @@ -98,7 +98,7 @@ func main() { if err != nil { log.Fatal(err) } - err = os.WriteFile(*sdPath, gyac.ItemFromGo(sd).Encode(nil), 0o666) + err = os.WriteFile(*sdPath, gyac.FromGo(sd).Encode(nil), 0o666) if err != nil { log.Fatal(err) } diff --git a/gyac/yacpki/prv.go b/gyac/yacpki/prv.go index 48b73c1..70a08c2 100644 --- a/gyac/yacpki/prv.go +++ b/gyac/yacpki/prv.go @@ -8,14 +8,14 @@ import ( "go.cypherpunks.su/gogost/v6/gost3410" "go.cypherpunks.su/gogost/v6/gost34112012256" "go.cypherpunks.su/gogost/v6/gost34112012512" - "go.cypherpunks.su/yac/gyac" + "go.cypherpunks.su/yac/gyac/mapstruct" "go.cypherpunks.su/yac/gyac/yacpki/ed25519-blake2b/ed25519" ) func PrvParse(data []byte) (prv crypto.Signer, err error) { var av AV var tail []byte - tail, err = gyac.DecodeToStruct(&av, data) + tail, err = mapstruct.Decode(&av, data) if err != nil { return } diff --git a/gyac/yacpki/signed-data.go b/gyac/yacpki/signed-data.go index 124bfeb..04bc671 100644 --- a/gyac/yacpki/signed-data.go +++ b/gyac/yacpki/signed-data.go @@ -8,6 +8,8 @@ import ( "github.com/google/uuid" "go.cypherpunks.su/yac/gyac" + "go.cypherpunks.su/yac/gyac/mapstruct" + "go.cypherpunks.su/yac/gyac/types" ) type SignedDataLoad struct { @@ -44,7 +46,7 @@ type SignedData struct { func SignedDataParse(data []byte) (sd *SignedData, tail []byte, err error) { var item *gyac.Item - item, tail, err = gyac.ItemDecode(data) + item, tail, err = gyac.Decode(data) if err != nil { return } @@ -53,12 +55,12 @@ func SignedDataParse(data []byte) (sd *SignedData, tail []byte, err error) { } func SignedDataParseItem(item *gyac.Item) (sd *SignedData, err error) { - if item.Typ() != gyac.ItemMap { + if item.T != types.Map { err = errors.New("SignedDataParse: non-map") return } var _sd SignedData - err = gyac.MapToStruct(&_sd, item.ToGo().(map[string]any)) + err = mapstruct.FromMap(&_sd, item.ToGo().(map[string]any)) if err != nil { return } @@ -136,7 +138,7 @@ func (sd *SignedData) SignWith( sig.Sign.A = parent.Pub[0].A sig.Sign.V, err = prv.Sign( rand.Reader, - gyac.ItemFromGo(sdTBS).Encode(nil), + gyac.FromGo(sdTBS).Encode(nil), crypto.Hash(0), ) if err != nil { -- 2.50.0