go/atom-decode.go | 13 ++++--------- go/atom-encode.go | 7 +++---- go/atomtype_string.go | 6 +++--- go/cm/cmd/enctool/main.go | 6 +++--- go/cmd/pp/main.go | 5 +++-- go/cmd/test-vector-anys/main.go | 2 ++ go/cmd/test-vector-manual/main.go | 15 +++++++++++++-- go/cmd/textdump-tester/main.go | 16 ++++++---------- go/ctx.go | 14 +++++++------- go/encode.go | 13 ++++++++++++- go/hexlet.go | 18 ++++++++++++++++++ go/iter.go | 11 +++++------ go/type.go | 2 +- go/types/type.go | 2 +- go/types/type_string.go | 6 +++--- go/unmarshal.go | 4 ++-- go/uuid_test.go => go/hexlet_test.go | 35 +++++++++++++++++++++++++++++++---- py3/keks.py | 52 +++++++++++++++++++++++++++++++++++++++++++--------- py3/test-vector.py | 6 ++++-- py3/tests/strategies.py | 3 ++- py3/tests/test_uuid.py => py3/tests/test_hexlet.py | 13 +++++++++++-- py3/tests/textdump-tester | 8 ++++---- spec/encoding/hexlet.texi | 27 +++++++++++++++++++++++++++ spec/encoding/index.texi | 4 ++-- spec/encoding/table.texi | 2 +- spec/encoding/uuid.texi | 17 ----------------- tcl/keks.tcl | 4 ++-- tcl/test-vector.tcl | 5 +++-- diff --git a/go/atom-decode.go b/go/atom-decode.go index 87b9580dce8b30627b71e95ecee30c36ad920c89d52d1ef2b5267139517d4c64..efd9d03f44b541fe55b46d71939e4a7931d0322a577a6c985db52c75e1aa4b2b 100644 --- a/go/atom-decode.go +++ b/go/atom-decode.go @@ -22,7 +22,6 @@ "strings" "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 @@ ctx.bools = append(ctx.bools, false) 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 59206b26e7a025f66879ff639a993579c8635b62d1dad3314284d36efee114c7..11283d0d5fe71751684b78a8e97616d1e4155dfe8ff5fc03651d8151b1fdf9fc 100644 --- a/go/atom-encode.go +++ b/go/atom-encode.go @@ -21,7 +21,6 @@ "errors" "io" "math/big" - "github.com/google/uuid" "go.cypherpunks.su/keks/be" "go.cypherpunks.su/tai64n/v4" ) @@ -50,9 +49,9 @@ written = 1 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 dfb719f03b6cfbca34d7432a1884a22543422dd77020ff686e2259d21d3cf988..303c569e31f3a17b15722ed11548f52c293a09630af58382e3b44d77d4d08aea 100644 --- a/go/atomtype_string.go +++ b/go/atomtype_string.go @@ -12,7 +12,7 @@ _ = x[AtomEOC-0] _ = 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 @@ _ = x[AtomMagic-75] } 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 @@ _AtomType_name_5 = "AtomMagic" ) 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 0b554505381858bcff62308b6a2256b8088ba77692a27e2dd2c6c3633886c4ad..2b4b16c1d90dbece14d46616aa0e032caf8c99db186647b921f1aaf4ba9c156e 100644 --- a/go/cm/cmd/enctool/main.go +++ b/go/cm/cmd/enctool/main.go @@ -142,7 +142,7 @@ 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 @@ log.Fatal(err) } } 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 a53f397e7ac61e65b95e29c96919cc476557f2c7ff558bca3cadb31e1f2223dc..cedfd4207d6c84f7fae2a5fdca4e951618e91c1d70e4ff2f108309933283e344 100644 --- a/go/cmd/pp/main.go +++ b/go/cmd/pp/main.go @@ -142,8 +142,9 @@ fmt.Println("TRUE") } 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 dced9365d022c53972323cfde9952153f0f21f79140f2591e2bfdb33565fbdc2..f85c83e96ab889fa86d98ca6929d13d85d7906c0e8617f69f604d1dbe693e542 100644 --- a/go/cmd/test-vector-anys/main.go +++ b/go/cmd/test-vector-anys/main.go @@ -6,6 +6,7 @@ "encoding/hex" "fmt" "log" "math/big" + "net" "strings" "time" @@ -97,6 +98,7 @@ "floats": []any{ 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 0f1c04344fcae93616ad12643bd7d21a45b583fe72a3da8d89f43c17e277e69b..d8d667959d1e88ac220519873f306c839812aef543f0b539a4ccde7ca17fda63 100644 --- a/go/cmd/test-vector-manual/main.go +++ b/go/cmd/test-vector-manual/main.go @@ -6,6 +6,7 @@ "encoding/hex" "fmt" "io" "math/big" + "net" "time" "github.com/google/uuid" @@ -36,6 +37,12 @@ var buf bytes.Buffer 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 @@ } { 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 @@ mustEncode(keks.ByteEncode(&buf, byte(keks.AtomMap))) 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 77f84f8760791b4a7faba946763db73c0413ec99fe3d6be1e559b741156501e4..d9076bb239c508db529c19cd9483b12311bdf1fd599a02c61af0085bc1bf3d8e 100644 --- a/go/cmd/textdump-tester/main.go +++ b/go/cmd/textdump-tester/main.go @@ -29,7 +29,6 @@ "strconv" "strings" "time" - "github.com/google/uuid" "go.cypherpunks.su/keks" "go.cypherpunks.su/tai64n/v4" ) @@ -81,17 +80,14 @@ } 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 01e053be49d4015372aa314a250f7c4758f1b9d83d39dcffc53203af2cd326bb..3fabcb6029f47336d283a3d3f2fb70e394d32dd04eadee568eb9b29a67e8221d 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 @@ tai64nas []tai64n.TAI64NA 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 5fd2324af95d7aa3e8589154956a58895dedf153b7e08d4323eace25cbbc53b7..fd065ab3e064b85ea57694311d6bf461531dd7a466fa2aa96751310a12f0ad86 100644 --- a/go/encode.go +++ b/go/encode.go @@ -20,6 +20,7 @@ "bytes" "fmt" "io" "math/big" + "net" "reflect" "sort" "strings" @@ -71,7 +72,17 @@ return BigIntEncode(w, v) 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 0000000000000000000000000000000000000000..ed15291c18236f51e1c016b8eea32e1020bc95a207090f1c5379efc005a6743a --- /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/iter.go b/go/iter.go index d7cd6a1bbb44bcc526c557a2ddfad83acfc896bc5e1e772d46a132640d271e39..4df258898c57536fe7d5961f95b2920531ce1b102912e3b8411f19bdd1723367 100644 --- a/go/iter.go +++ b/go/iter.go @@ -18,7 +18,6 @@ import ( "math/big" - "github.com/google/uuid" "go.cypherpunks.su/keks/types" "go.cypherpunks.su/tai64n/v4" ) @@ -47,7 +46,7 @@ tai64nas int 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 26bee98174807ccd55988c82002d5aa336897db04ddc6b3557e46fb470475d8f..ecc8d3a3193b4ed78685c1b50b90b577f7291c696e4a813461a4ab554f20baff 100644 --- a/go/type.go +++ b/go/type.go @@ -8,7 +8,7 @@ AtomEOC AtomType = 0x00 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 6fd03eb395532ebca4d86d8c616b311e5251fdbf38e9d755fca43a7e493224ff..cb3065862408106e8bb356ed9c7571d4f9f5c0d7bdc05f53ab53613c9f372a1f 100644 --- a/go/types/type.go +++ b/go/types/type.go @@ -8,7 +8,7 @@ Invalid Type = iota EOC NIL Bool - UUID + Hexlet UInt Int BigInt diff --git a/go/types/type_string.go b/go/types/type_string.go index ab7ab4d7e497139b534714ad61c8be1d6dc939bbedacff441bd165b19d984c74..e7f353fae9f1cf062a647c91b21fa04454a8cd2b3db91d6eb6cf2f2024558b0c 100644 --- a/go/types/type_string.go +++ b/go/types/type_string.go @@ -12,7 +12,7 @@ _ = x[Invalid-0] _ = 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 @@ _ = x[Str-17] _ = 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 9b00943a9580478f262620662c4453be6175e8b7ff0d8f77c52f29703230d905..8ff2c0ff748067cfd260d72a222d8228f9c32d5492c1165c6bd7f5f4abc2b86f 100644 --- a/go/unmarshal.go +++ b/go/unmarshal.go @@ -45,8 +45,8 @@ case types.NIL: 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/go/uuid_test.go b/go/hexlet_test.go rename from go/uuid_test.go rename to go/hexlet_test.go index fc14c915c8b860d663dcbb43a767e626ef65a348b112dfca68d335725ae9d18d..8e0844ad7b8d8b2f173f436fbff52046ff154a2f33e78de01efbb3ba4089c6df 100644 --- a/go/uuid_test.go +++ b/go/hexlet_test.go @@ -19,6 +19,7 @@ import ( "bytes" "io" + "net" "testing" "testing/quick" @@ -33,11 +34,11 @@ decoded, err := decoder.Decode() 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 @@ decoded, err := decoder.Decode() 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.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 == obj + return casted.IP().Equal(obj) } if err := quick.Check(f, nil); err != nil { t.Fatal(err) diff --git a/py3/keks.py b/py3/keks.py index 7675904f1dbf0cf36a26df38a2bed4fd8fca83ef529c6ad5c81b7f55423bf2d8..cc8e5b19ad9d503a0069b1e44144a33be681722ae8d94e8ed48474a59fc8851b 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 @@ def __repr__(self) -> str: 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 @@ if v is False: 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 @@ if b == TagFalse: 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 @@ 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 115341d262357a0ff1fbbd9f9801b05a0d80d4f2f70a5184f772bd7693c757d0..c5e4b78f34619f98a37461c41447058f41d5a9b4f2703f35e32962ab2b59aa51 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 @@ "empties": [ [], {}, 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 56da544989776ea5c967c47ad3b557d3f62effdbcd79fe39b79ed684caf14251..aca1a74541c48589c33ca2ad207efbf792400e17a03509445f881646b748276a 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 @@ just(-1), 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 rename from py3/tests/test_uuid.py rename to py3/tests/test_hexlet.py index 40bdb6b87089cdc53227ca58a8935b0ec21f3c7580a4176d4274cbb3df4de897..cdb85d2425acdb10f2b1a06a457c584f79d94cbdb309217aa1c6890e447d8662 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 @@ uuid_str: str = "12345678-1234-5678-1234-567812345678" 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 @@ @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 b648fd4964807c2a3dc8862185b354a3e570578d183aec62f3035cdd7c02c28c..7789e1c8a0b24140ee37ec4753a35d8a58de345112a831e603fcfac58e55f520 100755 --- a/py3/tests/textdump-tester +++ b/py3/tests/textdump-tester @@ -12,7 +12,7 @@ # * KEKS HEX(complex-data) -- provides the data for decoding and verifying # * 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 @@ # It is followed by pairs of STR(key) and element's value # * 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 @@ elif v is False: 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 0000000000000000000000000000000000000000..cff07c5045fab02fad509c638b39a16dd499a7faefb856e78a28f74ee3fffbde --- /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 e8ab4b56a582176fcb8ff2164141712282dba3498e68bf356b780ca1eb71bdb0..959897ecda14d83162a64d0eedb4dbf979af3bd93be4c603ec34d0ae24097f60 100644 --- a/spec/encoding/index.texi +++ b/spec/encoding/index.texi @@ -15,7 +15,7 @@ @item 000 @tab 00 @tab @code{00000000} @tab 0 @tab @ref{LIST, EOC} @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 @@ @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 e96f6f1e225a1689dd6428543530f4a8e483d3b16ffff96eef922c3ad2ac8117..04509dddeda2a48b3b4db23c5c87342450818257cf910dec5bc49aa078c21bc9 100644 --- a/spec/encoding/table.texi +++ b/spec/encoding/table.texi @@ -8,7 +8,7 @@ @item 000 @tab 00 @tab @code{00000000} @tab 0 @tab @ref{LIST, EOC} @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 5063883442c7f0557f3d7239dad4f01205e25d0b231e87aa684db36284e73a97..0000000000000000000000000000000000000000 --- 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 c21af7c741711f4a1778c6f92a04ec67893ca37f18733d38acd875cf4420ac43..68cc9cf5ef20093cf9ed8edf1109fa9c07549b0a567a8940ee720461216e4681 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 @@ char $t 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 74b1281699b351be93522278f8eddf7113e203800a408042869709764743f71c..482dc4a5b24b42eab2d52b48ff179f354846a02e244eb69ad0ad708eb26f432e 100644 --- a/tcl/test-vector.tcl +++ b/tcl/test-vector.tcl @@ -56,7 +56,7 @@ empties {LIST { {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 @@ {TAI64 1234567890 456000} {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]