]> Cypherpunks repositories - keks.git/commitdiff
HEXLET instead of UUID
authorSergey Matveev <stargrave@stargrave.org>
Thu, 6 Mar 2025 09:30:43 +0000 (12:30 +0300)
committerSergey Matveev <stargrave@stargrave.org>
Thu, 6 Mar 2025 09:30:44 +0000 (12:30 +0300)
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.

28 files changed:
go/atom-decode.go
go/atom-encode.go
go/atomtype_string.go
go/cm/cmd/enctool/main.go
go/cmd/pp/main.go
go/cmd/test-vector-anys/main.go
go/cmd/test-vector-manual/main.go
go/cmd/textdump-tester/main.go
go/ctx.go
go/encode.go
go/hexlet.go [new file with mode: 0644]
go/hexlet_test.go [moved from go/uuid_test.go with 75% similarity]
go/iter.go
go/type.go
go/types/type.go
go/types/type_string.go
go/unmarshal.go
py3/keks.py
py3/test-vector.py
py3/tests/strategies.py
py3/tests/test_hexlet.py [moved from py3/tests/test_uuid.py with 84% similarity]
py3/tests/textdump-tester
spec/encoding/hexlet.texi [new file with mode: 0644]
spec/encoding/index.texi
spec/encoding/table.texi
spec/encoding/uuid.texi [deleted file]
tcl/keks.tcl
tcl/test-vector.tcl

index 87b9580dce8b30627b71e95ecee30c36ad920c89d52d1ef2b5267139517d4c64..efd9d03f44b541fe55b46d71939e4a7931d0322a577a6c985db52c75e1aa4b2b 100644 (file)
@@ -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:
index 59206b26e7a025f66879ff639a993579c8635b62d1dad3314284d36efee114c7..11283d0d5fe71751684b78a8e97616d1e4155dfe8ff5fc03651d8151b1fdf9fc 100644 (file)
@@ -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.
index dfb719f03b6cfbca34d7432a1884a22543422dd77020ff686e2259d21d3cf988..303c569e31f3a17b15722ed11548f52c293a09630af58382e3b44d77d4d08aea 100644 (file)
@@ -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}
index 0b554505381858bcff62308b6a2256b8088ba77692a27e2dd2c6c3633886c4ad..2b4b16c1d90dbece14d46616aa0e032caf8c99db186647b921f1aaf4ba9c156e 100644 (file)
@@ -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)
index a53f397e7ac61e65b95e29c96919cc476557f2c7ff558bca3cadb31e1f2223dc..cedfd4207d6c84f7fae2a5fdca4e951618e91c1d70e4ff2f108309933283e344 100644 (file)
@@ -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:
index dced9365d022c53972323cfde9952153f0f21f79140f2591e2bfdb33565fbdc2..f85c83e96ab889fa86d98ca6929d13d85d7906c0e8617f69f604d1dbe693e542 100644 (file)
@@ -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)
index 0f1c04344fcae93616ad12643bd7d21a45b583fe72a3da8d89f43c17e277e69b..d8d667959d1e88ac220519873f306c839812aef543f0b539a4ccde7ca17fda63 100644 (file)
@@ -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")...,
index 77f84f8760791b4a7faba946763db73c0413ec99fe3d6be1e559b741156501e4..d9076bb239c508db529c19cd9483b12311bdf1fd599a02c61af0085bc1bf3d8e 100644 (file)
@@ -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)
index 01e053be49d4015372aa314a250f7c4758f1b9d83d39dcffc53203af2cd326bb..3fabcb6029f47336d283a3d3f2fb70e394d32dd04eadee568eb9b29a67e8221d 100644 (file)
--- 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,
index 5fd2324af95d7aa3e8589154956a58895dedf153b7e08d4323eace25cbbc53b7..fd065ab3e064b85ea57694311d6bf461531dd7a466fa2aa96751310a12f0ad86 100644 (file)
@@ -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 (file)
index 0000000..ed15291
--- /dev/null
@@ -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[:])
+}
similarity index 75%
rename from go/uuid_test.go
rename to go/hexlet_test.go
index fc14c915c8b860d663dcbb43a767e626ef65a348b112dfca68d335725ae9d18d..8e0844ad7b8d8b2f173f436fbff52046ff154a2f33e78de01efbb3ba4089c6df 100644 (file)
@@ -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)
index d7cd6a1bbb44bcc526c557a2ddfad83acfc896bc5e1e772d46a132640d271e39..4df258898c57536fe7d5961f95b2920531ce1b102912e3b8411f19bdd1723367 100644 (file)
@@ -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 {
index 26bee98174807ccd55988c82002d5aa336897db04ddc6b3557e46fb470475d8f..ecc8d3a3193b4ed78685c1b50b90b577f7291c696e4a813461a4ab554f20baff 100644 (file)
@@ -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
index 6fd03eb395532ebca4d86d8c616b311e5251fdbf38e9d755fca43a7e493224ff..cb3065862408106e8bb356ed9c7571d4f9f5c0d7bdc05f53ab53613c9f372a1f 100644 (file)
@@ -8,7 +8,7 @@ const (
        EOC
        NIL
        Bool
-       UUID
+       Hexlet
        UInt
        Int
        BigInt
index ab7ab4d7e497139b534714ad61c8be1d6dc939bbedacff441bd165b19d984c74..e7f353fae9f1cf062a647c91b21fa04454a8cd2b3db91d6eb6cf2f2024558b0c 100644 (file)
@@ -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) {
index 9b00943a9580478f262620662c4453be6175e8b7ff0d8f77c52f29703230d905..8ff2c0ff748067cfd260d72a222d8228f9c32d5492c1165c6bd7f5f4abc2b86f 100644 (file)
@@ -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:
index 7675904f1dbf0cf36a26df38a2bed4fd8fca83ef529c6ad5c81b7f55423bf2d8..cc8e5b19ad9d503a0069b1e44144a33be681722ae8d94e8ed48474a59fc8851b 100755 (executable)
@@ -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",
index 115341d262357a0ff1fbbd9f9801b05a0d80d4f2f70a5184f772bd7693c757d0..c5e4b78f34619f98a37461c41447058f41d5a9b4f2703f35e32962ab2b59aa51 100644 (file)
@@ -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)),
index 56da544989776ea5c967c47ad3b557d3f62effdbcd79fe39b79ed684caf14251..aca1a74541c48589c33ca2ad207efbf792400e17a03509445f881646b748276a 100644 (file)
@@ -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,
similarity index 84%
rename from py3/tests/test_uuid.py
rename to py3/tests/test_hexlet.py
index 40bdb6b87089cdc53227ca58a8935b0ec21f3c7580a4176d4274cbb3df4de897..cdb85d2425acdb10f2b1a06a457c584f79d94cbdb309217aa1c6890e447d8662 100644 (file)
 # You should have received a copy of the GNU Lesser General Public
 # License along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
+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)
index b648fd4964807c2a3dc8862185b354a3e570578d183aec62f3035cdd7c02c28c..7789e1c8a0b24140ee37ec4753a35d8a58de345112a831e603fcfac58e55f520 100755 (executable)
@@ -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
 # * 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 (file)
index 0000000..cff07c5
--- /dev/null
@@ -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
index e8ab4b56a582176fcb8ff2164141712282dba3498e68bf356b780ca1eb71bdb0..959897ecda14d83162a64d0eedb4dbf979af3bd93be4c603ec34d0ae24097f60 100644 (file)
@@ -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
index e96f6f1e225a1689dd6428543530f4a8e483d3b16ffff96eef922c3ad2ac8117..04509dddeda2a48b3b4db23c5c87342450818257cf910dec5bc49aa078c21bc9 100644 (file)
@@ -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 (file)
index 5063883..0000000
+++ /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
index c21af7c741711f4a1778c6f92a04ec67893ca37f18733d38acd875cf4420ac43..68cc9cf5ef20093cf9ed8edf1109fa9c07549b0a567a8940ee720461216e4681 100644 (file)
@@ -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
 
index 74b1281699b351be93522278f8eddf7113e203800a408042869709764743f71c..482dc4a5b24b42eab2d52b48ff179f354846a02e244eb69ad0ad708eb26f432e 100644 (file)
@@ -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]