]> Cypherpunks repositories - keks.git/commitdiff
Big code reorganisation
authorSergey Matveev <stargrave@stargrave.org>
Wed, 11 Dec 2024 15:46:04 +0000 (18:46 +0300)
committerSergey Matveev <stargrave@stargrave.org>
Wed, 11 Dec 2024 16:01:27 +0000 (19:01 +0300)
Much more clearer separated types.

28 files changed:
gyac/atom/be/be.go [moved from gyac/be.go with 88% similarity]
gyac/atom/dec.go [new file with mode: 0644]
gyac/atom/enc.go [new file with mode: 0644]
gyac/atom/raw.go [new file with mode: 0644]
gyac/atom/type_string.go [new file with mode: 0644]
gyac/atomtype_string.go [deleted file]
gyac/cmd/print/main.go
gyac/cmd/test-vector-anys/main.go
gyac/cmd/test-vector-manual/main.go
gyac/convert-fuzz-input-to-testdata [deleted file]
gyac/dec.go
gyac/enc.go
gyac/fromgo.go [new file with mode: 0644]
gyac/fuzz_test.go
gyac/itemtype_string.go [deleted file]
gyac/mapstruct/dec.go [new file with mode: 0644]
gyac/mapstruct/map.go [new file with mode: 0644]
gyac/mk-fuzz-testdata [new file with mode: 0755]
gyac/reflect.go [deleted file]
gyac/togo.go [new file with mode: 0644]
gyac/types/type.go [new file with mode: 0644]
gyac/types/type_string.go [new file with mode: 0644]
gyac/yacpki/algo.go
gyac/yacpki/cer.go
gyac/yacpki/cmd/yacertool/main.go
gyac/yacpki/cmd/yacsdtool/main.go
gyac/yacpki/prv.go
gyac/yacpki/signed-data.go

similarity index 88%
rename from gyac/be.go
rename to gyac/atom/be/be.go
index 0a2f92ffe875243fcf1fbe8ab7eb569a613a67001d74661b34f1f8541e939a05..f37d54e6afa300e34506210936de0247e1b65de462e657c8d76f6fd1b63670b1 100644 (file)
 // You should have received a copy of the GNU Lesser General Public
 // License along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
-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 (file)
index 0000000..653bcb4
--- /dev/null
@@ -0,0 +1,279 @@
+// gyac -- Go YAC encoder implementation
+// Copyright (C) 2024-2025 Sergey Matveev <stargrave@stargrave.org>
+//
+// 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 <http://www.gnu.org/licenses/>.
+
+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 (file)
index 0000000..df9ceee
--- /dev/null
@@ -0,0 +1,178 @@
+// gyac -- Go YAC encoder implementation
+// Copyright (C) 2024-2025 Sergey Matveev <stargrave@stargrave.org>
+//
+// 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 <http://www.gnu.org/licenses/>.
+
+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 (file)
index 0000000..ddc30d8
--- /dev/null
@@ -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 (file)
index 0000000..b35d04b
--- /dev/null
@@ -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 (file)
index dab86b4..0000000
+++ /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) + ")"
-       }
-}
index 4469b54d31fa7d75daef5f41241536bde5ac62696836a076bdff0187e4f00930..f49b61028d0decedc85798084c234ea277c5f56fda982a5feae2bc38822e1efe 100644 (file)
@@ -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)
        }
index e6fb7a524ba3f09d5b48db0c6b2171a93b8c24763bf71c2767efb8e3027467cc..2e5d03d06a55beb386478debf61968cbaba83fe8dee702594008ac7de5702711 100644 (file)
@@ -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)))
 }
index 2f2e23c763e67ff3128df7740ae5fbef5f0bea90c300e1292069ce27835ab784..d9fdbb21d6581c5d2973a8009fe11e8e1afca3a50d787a7e1d2c0d3905a45b57 100644 (file)
@@ -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 (executable)
index c4386e3..0000000
+++ /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
index a3e685c03429ff1bb17f5a19d6a0cfa870f867b3a92b473372774a8e478879d6..244e5245d7205e5e859d36d8eea047dc3bbbc75807b50c4314c559f0c2653c77 100644 (file)
 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)
 }
index 04b27c22763e550b03c348ffb5a1603a28b1ab246eb5a05ab15193239073de02..756ec97b504371eeaf0eee6400e4dd33d0fc1f568fb39f4585bc777b69ee8181 100644 (file)
@@ -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 (file)
index 0000000..9b48623
--- /dev/null
@@ -0,0 +1,176 @@
+// gyac -- Go YAC encoder implementation
+// Copyright (C) 2024-2025 Sergey Matveev <stargrave@stargrave.org>
+//
+// 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 <http://www.gnu.org/licenses/>.
+
+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))
+       }
+}
index a4340493a4b9fac43dd551d4c6b100b6fa591dc1c5f23ff4ebd449cc54329197..795ab88c2eab083d43fddbd5360ec4d6cbc3325aba81b00b087b6e2a21c6e85f 100644 (file)
@@ -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 (file)
index cd99d50..0000000
+++ /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 (file)
index 0000000..fadf768
--- /dev/null
@@ -0,0 +1,37 @@
+// gyac -- Go YAC encoder implementation
+// Copyright (C) 2024-2025 Sergey Matveev <stargrave@stargrave.org>
+//
+// 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 <http://www.gnu.org/licenses/>.
+
+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 (file)
index 0000000..bc3b3a2
--- /dev/null
@@ -0,0 +1,30 @@
+// gyac -- Go YAC encoder implementation
+// Copyright (C) 2024-2025 Sergey Matveev <stargrave@stargrave.org>
+//
+// 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 <http://www.gnu.org/licenses/>.
+
+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 (executable)
index 0000000..2540a99
--- /dev/null
@@ -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 (file)
index f860c59..0000000
+++ /dev/null
@@ -1,263 +0,0 @@
-// gyac -- Go YAC encoder implementation
-// Copyright (C) 2024-2025 Sergey Matveev <stargrave@stargrave.org>
-//
-// 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 <http://www.gnu.org/licenses/>.
-
-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 (file)
index 0000000..ec10fa7
--- /dev/null
@@ -0,0 +1,89 @@
+// gyac -- Go YAC encoder implementation
+// Copyright (C) 2024-2025 Sergey Matveev <stargrave@stargrave.org>
+//
+// 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 <http://www.gnu.org/licenses/>.
+
+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 (file)
index 0000000..ba0d90a
--- /dev/null
@@ -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 (file)
index 0000000..c21de88
--- /dev/null
@@ -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]]
+}
index 150ab0b03b5488ed9d00e3cea0041b31400268d3cc38baf28773d02b2380a6ba..f2c6e4ec4aadbc8a2aa5065743f54a45ee4a5275dd7210e0373a5740e9c0e662 100644 (file)
@@ -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)
index 5f22521570b6b1b6e0ece4ca1af94a117453f5e38b51adbdd1f5d50cabb4f0c2..7d4b2747c3ef69a31ab27020f262f8f68359ba66880bdba24560630a214be38e 100644 (file)
@@ -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 {
index b51a0c54a8e3bf6e6b703f19b04c2297088e95135d10fbd5b944dff337d173b6..3e8d37fdd0260ed9b63464bd3b7b606a184d4d82dcdf46ac9d57a55008d3fac2 100644 (file)
@@ -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)
        }
index be8f330630121b381d83eae12d91e06498f2468e00c197e2a0e5ad9936974317..16d08f2450ae123299bdfa3c8e9d6d1b06c80b506c9ef923994d44784506630a 100644 (file)
@@ -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)
                }
index 48b73c1505c8e2745b0fb69da8c9b707e6448ed214fe7bdb1708f709282a5697..70a08c210d3b2bf56feff05f94f3743e2ab1f6518c480a22a893e2e0cc28c089 100644 (file)
@@ -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
        }
index 124bfebf4e2d980ea1bb4711d1c3c487f09a015923448ac43a84c3db0920d1bf..04bc6719a96719ec398171d84fc3f857cfa403f22badf3930aed9315349680d4 100644 (file)
@@ -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 {