Much more clearer separated types.
// 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)
}
--- /dev/null
+// 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
+}
--- /dev/null
+// 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...)
+}
--- /dev/null
+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))
+}
--- /dev/null
+// 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) + ")"
+ }
+}
+++ /dev/null
-// 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) + ")"
- }
-}
if err != nil {
log.Fatal(err)
}
- item, tail, err := gyac.ItemDecode(data)
+ item, tail, err := gyac.Decode(data)
if err != nil {
log.Fatal(err)
}
"github.com/google/uuid"
"go.cypherpunks.su/yac/gyac"
+ "go.cypherpunks.su/yac/gyac/atom"
)
func mustHexDec(s string) []byte {
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"),
},
},
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)))
}
"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 {
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))
}
+++ /dev/null
-#!/bin/sh -e
-
-dst=testdata/fuzz/FuzzItemDecode
-mkdir -p $dst
-# go install golang.org/x/tools/cmd/file2fuzz@latest
-file2fuzz -o $dst fuzz-input
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")
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,
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
}
}
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
}
item.V = v
return
- case ItemBlob:
+ case types.Blob:
if !allowContainers {
- err = ErrUnknownType
+ err = atom.ErrUnknownType
return
}
chunkLen := int(item.V.(uint64))
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
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)
}
"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")
}
--- /dev/null
+// 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))
+ }
+}
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()
+++ /dev/null
-// 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]]
-}
--- /dev/null
+// 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
+}
--- /dev/null
+// 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)
+}
--- /dev/null
+#!/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/*
+++ /dev/null
-// 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
-}
--- /dev/null
+// 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))
+ }
+}
--- /dev/null
+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
+)
--- /dev/null
+// 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]]
+}
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)
"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"
)
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")
}
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 {
}
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 {
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)
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)
}
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)
}
"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
}
"github.com/google/uuid"
"go.cypherpunks.su/yac/gyac"
+ "go.cypherpunks.su/yac/gyac/mapstruct"
+ "go.cypherpunks.su/yac/gyac/types"
)
type SignedDataLoad 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
}
}
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
}
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 {