From: Sergey Matveev Date: Thu, 6 Mar 2025 09:30:43 +0000 (+0300) Subject: HEXLET instead of UUID X-Git-Url: http://www.git.cypherpunks.su/?a=commitdiff_plain;h=a3755b2db716077a4542f18783f0b4ec54214d14c9c4e48f983fc9bd5d347dea;p=keks.git HEXLET instead of UUID UUID does not have all values of its Version field to be validly acceptable. So not all possible 128-bit values are valid UUIDs. Either we force UUID validation in all decoders, or we do not require that value to be UUID at all. But it is still convenient to be pretty printed. --- diff --git a/go/atom-decode.go b/go/atom-decode.go index 87b9580..efd9d03 100644 --- a/go/atom-decode.go +++ b/go/atom-decode.go @@ -22,7 +22,6 @@ import ( "unicode/utf8" "unsafe" - "github.com/google/uuid" "go.cypherpunks.su/keks/be" "go.cypherpunks.su/keks/types" "go.cypherpunks.su/tai64n/v4" @@ -85,19 +84,15 @@ func (ctx *Decoder) DecodeAtom() (t types.Type, err error) { case AtomTrue: t = types.Bool ctx.bools = append(ctx.bools, true) - case AtomUUID: + case AtomHexlet: var s string s, err = ctx.getBytes(16) if err != nil { return } - var v uuid.UUID - v, err = uuid.FromBytes([]byte(s)) - if err != nil { - return - } - t = types.UUID - ctx.uuids = append(ctx.uuids, v) + t = types.Hexlet + v := Hexlet([]byte(s)) + ctx.hexlets = append(ctx.hexlets, &v) case AtomList: t = types.List case AtomMap: diff --git a/go/atom-encode.go b/go/atom-encode.go index 59206b2..11283d0 100644 --- a/go/atom-encode.go +++ b/go/atom-encode.go @@ -21,7 +21,6 @@ import ( "io" "math/big" - "github.com/google/uuid" "go.cypherpunks.su/keks/be" "go.cypherpunks.su/tai64n/v4" ) @@ -50,9 +49,9 @@ func BoolEncode(w io.Writer, v bool) (written int64, err error) { return } -// Write an encoded UUID atom. -func UUIDEncode(w io.Writer, v *uuid.UUID) (written int64, err error) { - return io.Copy(w, bytes.NewReader(append([]byte{byte(AtomUUID)}, v[:]...))) +// Write an encoded Hexlet atom. +func HexletEncode(w io.Writer, v *Hexlet) (written int64, err error) { + return io.Copy(w, bytes.NewReader(append([]byte{byte(AtomHexlet)}, v[:]...))) } // Write an encoded Magic atom. diff --git a/go/atomtype_string.go b/go/atomtype_string.go index dfb719f..303c569 100644 --- a/go/atomtype_string.go +++ b/go/atomtype_string.go @@ -12,7 +12,7 @@ func _() { _ = x[AtomNIL-1] _ = x[AtomFalse-2] _ = x[AtomTrue-3] - _ = x[AtomUUID-4] + _ = x[AtomHexlet-4] _ = x[AtomList-8] _ = x[AtomMap-9] _ = x[AtomBLOB-11] @@ -30,7 +30,7 @@ func _() { } const ( - _AtomType_name_0 = "AtomEOCAtomNILAtomFalseAtomTrueAtomUUID" + _AtomType_name_0 = "AtomEOCAtomNILAtomFalseAtomTrueAtomHexlet" _AtomType_name_1 = "AtomListAtomMap" _AtomType_name_2 = "AtomBLOBAtomPIntAtomNInt" _AtomType_name_3 = "AtomFloat16AtomFloat32AtomFloat64AtomFloat128AtomFloat256" @@ -39,7 +39,7 @@ const ( ) var ( - _AtomType_index_0 = [...]uint8{0, 7, 14, 23, 31, 39} + _AtomType_index_0 = [...]uint8{0, 7, 14, 23, 31, 41} _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} diff --git a/go/cm/cmd/enctool/main.go b/go/cm/cmd/enctool/main.go index 0b55450..2b4b16c 100644 --- a/go/cm/cmd/enctool/main.go +++ b/go/cm/cmd/enctool/main.go @@ -142,7 +142,7 @@ func parsePrv(data []byte) (av cm.AV, tail []byte, err error) { func main() { log.SetFlags(log.Lshortfile) flag.Usage = usage - setSalt := flag.String("id", "", "Set that /id instead of autogeneration") + setId := flag.String("id", "", "Set that /id instead of autogeneration") includeTo := flag.Bool("include-to", false, `Include "to" field in KEMs`) passphrase := flag.Bool("p", false, "Use passphrase") balloonS := flag.Int("balloon-s", 1<<17, "Balloon's space cost") @@ -448,10 +448,10 @@ func main() { } } else { var id uuid.UUID - if *setSalt == "" { + if *setId == "" { id, err = uuid.NewRandom() } else { - id, err = uuid.Parse(*setSalt) + id, err = uuid.Parse(*setId) } if err != nil { log.Fatal(err) diff --git a/go/cmd/pp/main.go b/go/cmd/pp/main.go index a53f397..cedfd42 100644 --- a/go/cmd/pp/main.go +++ b/go/cmd/pp/main.go @@ -142,8 +142,9 @@ func printer(iter *keks.Iterator, where []string, count int, inList, inMap bool) } else { fmt.Println("FALSE") } - case types.UUID: - fmt.Println(iter.UUID()) + case types.Hexlet: + h := iter.Hexlet() + fmt.Println(h.UUID(), h.IP()) case types.UInt: fmt.Println(iter.UInt()) case types.Int: diff --git a/go/cmd/test-vector-anys/main.go b/go/cmd/test-vector-anys/main.go index dced936..f85c83e 100644 --- a/go/cmd/test-vector-anys/main.go +++ b/go/cmd/test-vector-anys/main.go @@ -6,6 +6,7 @@ import ( "fmt" "log" "math/big" + "net" "strings" "time" @@ -97,6 +98,7 @@ func main() { keks.Raw(append([]byte{byte(keks.AtomFloat32)}, mustHexDec("01020304")...)), }, "uuid": uuid.MustParse("0e875e3f-d385-49eb-87b4-be42d641c367"), + "ip": net.ParseIP("2001:db8:85a3:8d3:1319:8a2e:370:7348"), } var buf bytes.Buffer _, err := keks.Encode(&buf, keks.Magic("test-vector"), nil) diff --git a/go/cmd/test-vector-manual/main.go b/go/cmd/test-vector-manual/main.go index 0f1c043..d8d6679 100644 --- a/go/cmd/test-vector-manual/main.go +++ b/go/cmd/test-vector-manual/main.go @@ -6,6 +6,7 @@ import ( "fmt" "io" "math/big" + "net" "time" "github.com/google/uuid" @@ -36,6 +37,12 @@ func main() { mustEncode(keks.MagicEncode(&buf, keks.Magic("test-vector"))) { mustEncode(keks.ByteEncode(&buf, byte(keks.AtomMap))) + { + mustEncode(keks.StrEncode(&buf, "ip")) + ip := net.ParseIP("2001:db8:85a3:8d3:1319:8a2e:370:7348") + h := keks.Hexlet(ip[:]) + mustEncode(keks.HexletEncode(&buf, &h)) + } { mustEncode(keks.StrEncode(&buf, "nil")) mustEncode(keks.ByteEncode(&buf, byte(keks.AtomNIL))) @@ -126,7 +133,8 @@ func main() { { u := uuid.MustParse("0e875e3f-d385-49eb-87b4-be42d641c367") mustEncode(keks.StrEncode(&buf, "uuid")) - mustEncode(keks.UUIDEncode(&buf, &u)) + h := keks.Hexlet(u[:]) + mustEncode(keks.HexletEncode(&buf, &h)) } { mustEncode(keks.StrEncode(&buf, "dates")) @@ -179,7 +187,10 @@ func main() { mustEncode(keks.ByteEncode(&buf, byte(keks.AtomEOC))) } mustEncode(keks.BlobEncode(&buf, 123, bytes.NewReader([]byte{}))) - mustEncode(keks.UUIDEncode(&buf, &uuid.Nil)) + { + h := keks.Hexlet(uuid.Nil[:]) + mustEncode(keks.HexletEncode(&buf, &h)) + } mustEncode(io.Copy(&buf, bytes.NewReader(append( []byte{byte(keks.AtomTAI64)}, []byte("\x00\x00\x00\x00\x00\x00\x00\x00")..., diff --git a/go/cmd/textdump-tester/main.go b/go/cmd/textdump-tester/main.go index 77f84f8..d9076bb 100644 --- a/go/cmd/textdump-tester/main.go +++ b/go/cmd/textdump-tester/main.go @@ -29,7 +29,6 @@ import ( "strings" "time" - "github.com/google/uuid" "go.cypherpunks.su/keks" "go.cypherpunks.su/tai64n/v4" ) @@ -81,17 +80,14 @@ func checker(v any) { if !our { log.Fatalf("expected TRUE, got %+v\n", our) } - case "UUID": - our, ok := v.(uuid.UUID) + case "HEXLET": + our, ok := v.(*keks.Hexlet) if !ok { - log.Fatalf("expected UUID, got %+v\n", v) + log.Fatalf("expected HEXLET, got %+v\n", v) } - their, err := uuid.Parse(fields[1]) - if err != nil { - log.Fatal(err) - } - if our != their { - log.Fatalln("UUID differs:", our, their) + their := keks.Hexlet(mustDecodeHex(fields[1])) + if *our != their { + log.Fatalln("HEXLET differs:", our, their) } case "UTC": our, ok := v.(time.Time) diff --git a/go/ctx.go b/go/ctx.go index 01e053b..3fabcb6 100644 --- a/go/ctx.go +++ b/go/ctx.go @@ -19,7 +19,6 @@ import ( "io" "math/big" - "github.com/google/uuid" "go.cypherpunks.su/keks/types" "go.cypherpunks.su/tai64n/v4" ) @@ -50,13 +49,8 @@ type Decoder struct { R io.Reader B []byte - // After successful parsing of the data, it tells how many bytes - // were read. - Read int64 - opts *DecodeOpts - depth int8 types []types.Type depths []int8 offsets []int64 @@ -71,12 +65,18 @@ type Decoder struct { tai64ns []tai64n.TAI64N tai64s []tai64n.TAI64 uints []uint64 - uuids []uuid.UUID + hexlets []*Hexlet blobChunkLens []int64 blobChunkses [][]string readBuf []byte // used only by BlobDecoder + + // After successful parsing of the data, it tells how many bytes + // were read. + Read int64 + + depth int8 } // Initialise decoder that will read from b bytes. After the parse, diff --git a/go/encode.go b/go/encode.go index 5fd2324..fd065ab 100644 --- a/go/encode.go +++ b/go/encode.go @@ -20,6 +20,7 @@ import ( "fmt" "io" "math/big" + "net" "reflect" "sort" "strings" @@ -71,7 +72,17 @@ func Encode(w io.Writer, v any, opts *EncodeOpts) (written int64, err error) { case bool: return BoolEncode(w, v) case uuid.UUID: - return UUIDEncode(w, &v) + var h Hexlet + copy(h[:], v[:]) + return HexletEncode(w, &h) + case net.IP: + var h Hexlet + copy(h[:], v.To16()[:]) + return HexletEncode(w, &h) + case Hexlet: + return HexletEncode(w, &v) + case *Hexlet: + return HexletEncode(w, v) case tai64n.TAI64: return TAI64Encode(w, &v) case *tai64n.TAI64: diff --git a/go/hexlet.go b/go/hexlet.go new file mode 100644 index 0000000..ed15291 --- /dev/null +++ b/go/hexlet.go @@ -0,0 +1,18 @@ +package keks + +import ( + "net" + + "github.com/google/uuid" +) + +type Hexlet [16]byte + +func (h *Hexlet) UUID() (u uuid.UUID) { + copy(u[:], h[:]) + return +} + +func (h *Hexlet) IP() (ip net.IP) { + return net.IP(h[:]) +} diff --git a/go/uuid_test.go b/go/hexlet_test.go similarity index 75% rename from go/uuid_test.go rename to go/hexlet_test.go index fc14c91..8e0844a 100644 --- a/go/uuid_test.go +++ b/go/hexlet_test.go @@ -19,6 +19,7 @@ package keks import ( "bytes" "io" + "net" "testing" "testing/quick" @@ -33,11 +34,11 @@ func TestUUIDEncodeDecode(t *testing.T) { if err != nil { t.Fatal(err) } - casted, ok := decoded.(uuid.UUID) + casted, ok := decoded.(*Hexlet) if !ok { t.Fatal("failed to cast") } - if casted != obj { + if casted.UUID() != obj { t.Fatal("casted differs") } if !bytes.Equal(decoder.B, Junk) { @@ -71,14 +72,40 @@ func TestUUIDSymmetric(t *testing.T) { if err != nil { t.Fatal(err) } - casted, ok := decoded.(uuid.UUID) + casted, ok := decoded.(*Hexlet) if !ok { t.Fatal("failed to cast") } if !bytes.Equal(decoder.B, Junk) { t.Fatal("tail differs") } - return casted == obj + return casted.UUID() == obj + } + if err := quick.Check(f, nil); err != nil { + t.Fatal(err) + } +} + +func TestIPSymmetric(t *testing.T) { + f := func(raw [16]byte) bool { + obj := net.IP(raw[:]) + encoded, err := EncodeBuf(obj, nil) + if err != nil { + t.Fatal(err) + } + decoder := NewDecoderFromBytes(append(encoded, Junk...), nil) + decoded, err := decoder.Decode() + if err != nil { + t.Fatal(err) + } + casted, ok := decoded.(*Hexlet) + if !ok { + t.Fatal("failed to cast") + } + if !bytes.Equal(decoder.B, Junk) { + t.Fatal("tail differs") + } + return casted.IP().Equal(obj) } if err := quick.Check(f, nil); err != nil { t.Fatal(err) diff --git a/go/iter.go b/go/iter.go index d7cd6a1..4df2588 100644 --- a/go/iter.go +++ b/go/iter.go @@ -18,7 +18,6 @@ package keks import ( "math/big" - "github.com/google/uuid" "go.cypherpunks.su/keks/types" "go.cypherpunks.su/tai64n/v4" ) @@ -47,7 +46,7 @@ type Iterator struct { tai64ns int tai64s int uints int - uuids int + hexlets int } func (ctx *Decoder) Iter() *Iterator { @@ -59,8 +58,8 @@ func (iter *Iterator) Next() bool { switch iter.T { case types.Bool: iter.bools++ - case types.UUID: - iter.uuids++ + case types.Hexlet: + iter.hexlets++ case types.UInt: iter.uints++ case types.Int: @@ -103,8 +102,8 @@ func (iter *Iterator) Bool() bool { return iter.ctx.bools[iter.bools] } -func (iter *Iterator) UUID() uuid.UUID { - return iter.ctx.uuids[iter.uuids] +func (iter *Iterator) Hexlet() *Hexlet { + return iter.ctx.hexlets[iter.hexlets] } func (iter *Iterator) UInt() uint64 { diff --git a/go/type.go b/go/type.go index 26bee98..ecc8d3a 100644 --- a/go/type.go +++ b/go/type.go @@ -8,7 +8,7 @@ const ( AtomNIL AtomType = 0x01 AtomFalse AtomType = 0x02 AtomTrue AtomType = 0x03 - AtomUUID AtomType = 0x04 + AtomHexlet AtomType = 0x04 AtomList AtomType = 0x08 AtomMap AtomType = 0x09 AtomBLOB AtomType = 0x0B diff --git a/go/types/type.go b/go/types/type.go index 6fd03eb..cb30658 100644 --- a/go/types/type.go +++ b/go/types/type.go @@ -8,7 +8,7 @@ const ( EOC NIL Bool - UUID + Hexlet UInt Int BigInt diff --git a/go/types/type_string.go b/go/types/type_string.go index ab7ab4d..e7f353f 100644 --- a/go/types/type_string.go +++ b/go/types/type_string.go @@ -12,7 +12,7 @@ func _() { _ = x[EOC-1] _ = x[NIL-2] _ = x[Bool-3] - _ = x[UUID-4] + _ = x[Hexlet-4] _ = x[UInt-5] _ = x[Int-6] _ = x[BigInt-7] @@ -29,9 +29,9 @@ func _() { _ = x[Raw-18] } -const _Type_name = "InvalidEOCNILBoolUUIDUIntIntBigIntListMapBlobFloatTAI64TAI64NTAI64NAMagicBinStrRaw" +const _Type_name = "InvalidEOCNILBoolHexletUIntIntBigIntListMapBlobFloatTAI64TAI64NTAI64NAMagicBinStrRaw" -var _Type_index = [...]uint8{0, 7, 10, 13, 17, 21, 25, 28, 34, 38, 41, 45, 50, 55, 61, 68, 73, 76, 79, 82} +var _Type_index = [...]uint8{0, 7, 10, 13, 17, 23, 27, 30, 36, 40, 43, 47, 52, 57, 63, 70, 75, 78, 81, 84} func (i Type) String() string { if i >= Type(len(_Type_index)-1) { diff --git a/go/unmarshal.go b/go/unmarshal.go index 9b00943..8ff2c0f 100644 --- a/go/unmarshal.go +++ b/go/unmarshal.go @@ -45,8 +45,8 @@ func (ctx *Decoder) unmarshal(iter *Iterator) (v any, err error) { return nil, nil case types.Bool: return iter.Bool(), nil - case types.UUID: - return iter.UUID(), nil + case types.Hexlet: + return iter.Hexlet(), nil case types.UInt: return iter.UInt(), nil case types.Int: diff --git a/py3/keks.py b/py3/keks.py index 7675904..cc8e5b1 100755 --- a/py3/keks.py +++ b/py3/keks.py @@ -23,8 +23,9 @@ transparently replace JSON. It has :py:func:`loads` and :py:func:`dumps` functions, similar to native :py:module:`json` library's. KEKS supports dictionaries, lists, -None, booleans, UUID, floats (currently not implemented!), integers -(including big ones), datetime, Unicode and binary strings. +None, booleans, UUID, IPv6 addresses, floats (currently not +implemented!), integers (including big ones), datetime, UTF-8 and +binary strings. There is special :py:func:`keks.Raw` namedtuple, that holds arbitrary KEKS encoded data, that can not be represented in native Python types. @@ -36,6 +37,7 @@ from collections import namedtuple from datetime import datetime from datetime import timedelta from datetime import timezone +from ipaddress import IPv6Address from math import ceil as _ceil from uuid import UUID @@ -44,7 +46,7 @@ TagEOC = 0x00 TagNIL = 0x01 TagFalse = 0x02 TagTrue = 0x03 -TagUUID = 0x04 +TagHexlet = 0x04 TagList = 0x08 TagMap = 0x09 TagBlob = 0x0B @@ -71,7 +73,7 @@ TagEOCb = _byte(TagEOC) TagNILb = _byte(TagNIL) TagFalseb = _byte(TagFalse) TagTrueb = _byte(TagTrue) -TagUUIDb = _byte(TagUUID) +TagHexletb = _byte(TagHexlet) TagListb = _byte(TagList) TagMapb = _byte(TagMap) TagBlobb = _byte(TagBlob) @@ -136,6 +138,37 @@ class Magic: return "Magic(%r)" % self.v +class Hexlet: + __slots__ = ("v",) + + def __init__(self, v): + if isinstance(v, UUID): + self.v = v.bytes + elif isinstance(v, IPv6Address): + self.v = v.packed + else: + if len(v) != 16: + raise ValueError("wrong hexlet len") + self.v = v + + def __bytes__(self) -> bytes: + return self.v + + def __eq__(self, other) -> bool: + if not isinstance(other, self.__class__): + return False + return self.v == other.v + + def __repr__(self) -> str: + return "HEXLET(%s)" % self.asUUID() + + def asUUID(self) -> UUID: + return UUID(bytes=self.v) + + def asIP(self) -> IPv6Address: + return IPv6Address(self.v) + + Blob = namedtuple("Blob", ("l", "v")) @@ -222,8 +255,10 @@ def dumps(v): return TagFalseb if v is True: return TagTrueb - if isinstance(v, UUID): - return TagUUIDb + v.bytes + if isinstance(v, UUID) or isinstance(v, IPv6Address): + return dumps(Hexlet(v)) + if isinstance(v, Hexlet): + return TagHexletb + v.v if isinstance(v, float): raise NotImplementedError("no FLOAT* support") if isinstance(v, datetime): @@ -333,10 +368,10 @@ def _loads(v, sets=False, leapsecUTCAllow=False, _allowContainers=True): return False, v[1:] if b == TagTrue: return True, v[1:] - if b == TagUUID: + if b == TagHexlet: if len(v) < 1+16: raise NotEnoughData(1+16-len(v)) - return UUID(bytes=v[1:1+16]), v[1+16:] + return Hexlet(v[1:1+16]), v[1+16:] l = _floats.get(b) if l is not None: if len(v) < 1+l: @@ -495,7 +530,6 @@ def loads(v, **kwargs): if __name__ == "__main__": from argparse import ArgumentParser - from argparse import FileType parser = ArgumentParser(description="Decode KEKS file") parser.add_argument( "--nosets", action="store_true", diff --git a/py3/test-vector.py b/py3/test-vector.py index 115341d..c5e4b78 100644 --- a/py3/test-vector.py +++ b/py3/test-vector.py @@ -1,5 +1,6 @@ from datetime import datetime from datetime import timedelta +from ipaddress import IPv6Address from uuid import UUID import keks @@ -42,10 +43,11 @@ data = { [], {}, keks.Blob(123, b""), - UUID("00000000-0000-0000-0000-000000000000"), + keks.Hexlet(UUID("00000000-0000-0000-0000-000000000000")), keks.Raw(keks._byte(keks.TagTAI64) + bytes.fromhex("0000000000000000")), ], - "uuid": UUID("0e875e3f-d385-49eb-87b4-be42d641c367"), + "uuid": keks.Hexlet(UUID("0e875e3f-d385-49eb-87b4-be42d641c367")), + "ip": keks.Hexlet(IPv6Address("2001:db8:85a3:8d3:1319:8a2e:370:7348")), } data["dates"] = [ (datetime(1970, 1, 1) + timedelta(seconds=1234567890)), diff --git a/py3/tests/strategies.py b/py3/tests/strategies.py index 56da544..aca1a74 100644 --- a/py3/tests/strategies.py +++ b/py3/tests/strategies.py @@ -31,6 +31,7 @@ from hypothesis.strategies import uuids from keks import _byte from keks import Blob +from keks import Hexlet from keks import Magic from keks import Raw from keks import TagTAI64NA @@ -55,7 +56,7 @@ any_st = one_of( binary(max_size=32), text_st, none(), - uuids(), + uuids().map(Hexlet), datetimes(), blobs_st, tai64na_st, diff --git a/py3/tests/test_uuid.py b/py3/tests/test_hexlet.py similarity index 84% rename from py3/tests/test_uuid.py rename to py3/tests/test_hexlet.py index 40bdb6b..cdb85d2 100644 --- a/py3/tests/test_uuid.py +++ b/py3/tests/test_hexlet.py @@ -14,10 +14,12 @@ # You should have received a copy of the GNU Lesser General Public # License along with this program. If not, see . +from ipaddress import IPv6Address from unittest import TestCase from uuid import UUID from hypothesis import given +from hypothesis.strategies import ip_addresses from hypothesis.strategies import uuids from keks import dumps @@ -42,7 +44,7 @@ class TestUUID(TestCase): encoded: bytes = bytes.fromhex("0412345678123456781234567812345678") decoded: UUID decoded, tail = loads(encoded + junk) - self.assertEqual(decoded, UUID(uuid_str)) + self.assertEqual(decoded.asUUID(), UUID(uuid_str)) self.assertSequenceEqual(tail, junk) def test_not_enough_data(self) -> None: @@ -54,4 +56,11 @@ class TestUUID(TestCase): @given(uuids()) def test_symmetric(self, u: UUID) -> None: decoded, _ = loads(dumps(u)) - self.assertEqual(decoded, u) + self.assertEqual(decoded.asUUID(), u) + + +class TestIP(TestCase): + @given(ip_addresses(v=6)) + def test_symmetric(self, ip: IPv6Address) -> None: + decoded, _ = loads(dumps(ip)) + self.assertEqual(decoded.asIP(), ip) diff --git a/py3/tests/textdump-tester b/py3/tests/textdump-tester index b648fd4..7789e1c 100755 --- a/py3/tests/textdump-tester +++ b/py3/tests/textdump-tester @@ -12,7 +12,7 @@ # * NIL -- NIL is expected # * FALSE -- FALSE is expected # * TRUE -- TRUE is expected -# * UUID xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx -- UUID is expected +# * HEXLET xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx -- HEXLET is expected # * UTC xxxx-xx-xxTxx:xx:xxZ -- TAI64-encoded UTC is expected # * UTC xxxx-xx-xxTxx:xx:xx.xxxxxxZ -- TAI64N-encoded UTC is expected # * TAI64NA HEX(...) -- external TAI64NA-encoded time @@ -28,12 +28,12 @@ # * EOC -- nothing more expected, end of contents from datetime import datetime -from uuid import UUID from keks import Blob from keks import Magic from keks import Raw from keks import TagTAI64NA +from keks import Hexlet def textdump(v): @@ -43,8 +43,8 @@ def textdump(v): print("FALSE") elif v is True: print("TRUE") - elif isinstance(v, UUID): - print("UUID " + str(v)) + elif isinstance(v, Hexlet): + print("HEXLET " + bytes(v).hex()) elif isinstance(v, float): raise NotImplementedError("no FLOAT* support") elif isinstance(v, datetime): diff --git a/spec/encoding/hexlet.texi b/spec/encoding/hexlet.texi new file mode 100644 index 0000000..cff07c5 --- /dev/null +++ b/spec/encoding/hexlet.texi @@ -0,0 +1,27 @@ +@node HEXLET +@cindex HEXLET +@cindex UUID +@cindex IPv6 +@section HEXLET + +128-bit binary value. It can be used as a more convenient container for +16-byte binary strings, which will be pretty printed as +@url{https://datatracker.ietf.org/doc/html/rfc9562, UUID} or IPv6 address. + +Application is left responsible for UUID validation. + +Simplest decoder can safely replace HEXLET's tag with 0x90 and decode it +as ordinary 16-byte binary string. + +Example representations: + +@multitable @columnfractions .5 .5 + +@item Nil UUID @tab @code{04 00000000000000000000000000000000} +@item Max UUID @tab @code{04 FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF} +@item UUIDv4 @code{0e875e3f-d385-49eb-87b4-be42d641c367} @tab + @code{04 0E875E3FD38549EB87B4BE42D641C367} +@item @code{2001:db8::1234} IPv6 @tab + @code{04 20010db8000000000000000000001234} + +@end multitable diff --git a/spec/encoding/index.texi b/spec/encoding/index.texi index e8ab4b5..959897e 100644 --- a/spec/encoding/index.texi +++ b/spec/encoding/index.texi @@ -15,7 +15,7 @@ Possible values for the tag: @item 001 @tab 01 @tab @code{00000001} @tab 0 @tab @ref{Primitives, NIL} @item 002 @tab 02 @tab @code{00000010} @tab 0 @tab @ref{Primitives, FALSE} @item 003 @tab 03 @tab @code{00000011} @tab 0 @tab @ref{Primitives, TRUE} -@item 004 @tab 04 @tab @code{00000100} @tab 16 @tab @ref{UUID} +@item 004 @tab 04 @tab @code{00000100} @tab 16 @tab @ref{HEXLET} @item [...] @item 008 @tab 08 @tab @code{00001000} @tab 0 @tab @ref{LIST} @item 009 @tab 09 @tab @code{00001001} @tab 0 @tab @ref{MAP} @@ -54,7 +54,7 @@ Possible values for the tag: @include encoding/table.texi @include encoding/prim.texi -@include encoding/uuid.texi +@include encoding/hexlet.texi @include encoding/str.texi @include encoding/int.texi @include encoding/float.texi diff --git a/spec/encoding/table.texi b/spec/encoding/table.texi index e96f6f1..04509dd 100644 --- a/spec/encoding/table.texi +++ b/spec/encoding/table.texi @@ -8,7 +8,7 @@ @item 001 @tab 01 @tab @code{00000001} @tab 0 @tab @ref{Primitives, NIL} @item 002 @tab 02 @tab @code{00000010} @tab 0 @tab @ref{Primitives, FALSE} @item 003 @tab 03 @tab @code{00000011} @tab 0 @tab @ref{Primitives, TRUE} -@item 004 @tab 04 @tab @code{00000100} @tab 16 @tab @ref{UUID} +@item 004 @tab 04 @tab @code{00000100} @tab 16 @tab @ref{HEXLET} @item 005 @tab 05 @tab @code{00000101} @tab 0 @tab @item 006 @tab 06 @tab @code{00000110} @tab 0 @tab @item 007 @tab 07 @tab @code{00000111} @tab 0 @tab diff --git a/spec/encoding/uuid.texi b/spec/encoding/uuid.texi deleted file mode 100644 index 5063883..0000000 --- a/spec/encoding/uuid.texi +++ /dev/null @@ -1,17 +0,0 @@ -@node UUID -@cindex UUID -@section UUID - -128-bit big-endian @url{https://datatracker.ietf.org/doc/html/rfc9562, UUID}'s -value is placed after the tag. - -Example representations: - -@multitable @columnfractions .5 .5 - -@item Nil UUID @tab @code{04 00000000000000000000000000000000} -@item Max UUID @tab @code{04 FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF} -@item UUIDv4 @code{0e875e3f-d385-49eb-87b4-be42d641c367} @tab - @code{04 0E875E3FD38549EB87B4BE42D641C367} - -@end multitable diff --git a/tcl/keks.tcl b/tcl/keks.tcl index c21af7c..68cc9cf 100644 --- a/tcl/keks.tcl +++ b/tcl/keks.tcl @@ -29,7 +29,7 @@ proc NIL {} { char [expr 0x01] } proc FALSE {} { char [expr 0x02] } proc TRUE {} { char [expr 0x03] } -proc UUID {v} { +proc HEXLET {v} { set v [binary decode hex [string map {- ""} $v]] if {[string length $v] != 16} { error "bad len" } char [expr 0x04] @@ -236,7 +236,7 @@ proc RAW {t v} { add $v } -namespace export EOC NIL FALSE TRUE UUID MAGIC INT STR BIN RAW +namespace export EOC NIL FALSE TRUE HEXLET MAGIC INT STR BIN RAW namespace export TAI64 UTCFromISO namespace export LIST MAP SET LenFirstSort BLOB diff --git a/tcl/test-vector.tcl b/tcl/test-vector.tcl index 74b1281..482dc4a 100644 --- a/tcl/test-vector.tcl +++ b/tcl/test-vector.tcl @@ -56,7 +56,7 @@ MAP { {LIST {}} {MAP {}} {BLOB 123 ""} - {UUID "00000000-0000-0000-0000-000000000000"} + {HEXLET "00000000-0000-0000-0000-000000000000"} {RAW [expr 0x18] [binary decode hex "0000000000000000"]} }} dates {LIST { @@ -65,7 +65,8 @@ MAP { {TAI64 1234567890 456789} {TAI64 1234567890 456789 123456789} }} - uuid {UUID 0e875e3f-d385-49eb-87b4-be42d641c367} + uuid {HEXLET 0e875e3f-d385-49eb-87b4-be42d641c367} + ip {HEXLET 20010db8-85a3-08d3-1319-8a2e03707348} } puts [binary encode hex $::KEKS::buf]