"unicode/utf8"
"unsafe"
- "github.com/google/uuid"
"go.cypherpunks.su/keks/be"
"go.cypherpunks.su/keks/types"
"go.cypherpunks.su/tai64n/v4"
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:
"io"
"math/big"
- "github.com/google/uuid"
"go.cypherpunks.su/keks/be"
"go.cypherpunks.su/tai64n/v4"
)
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.
_ = x[AtomNIL-1]
_ = x[AtomFalse-2]
_ = x[AtomTrue-3]
- _ = x[AtomUUID-4]
+ _ = x[AtomHexlet-4]
_ = x[AtomList-8]
_ = x[AtomMap-9]
_ = x[AtomBLOB-11]
}
const (
- _AtomType_name_0 = "AtomEOCAtomNILAtomFalseAtomTrueAtomUUID"
+ _AtomType_name_0 = "AtomEOCAtomNILAtomFalseAtomTrueAtomHexlet"
_AtomType_name_1 = "AtomListAtomMap"
_AtomType_name_2 = "AtomBLOBAtomPIntAtomNInt"
_AtomType_name_3 = "AtomFloat16AtomFloat32AtomFloat64AtomFloat128AtomFloat256"
)
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}
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")
}
} 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)
} 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:
"fmt"
"log"
"math/big"
+ "net"
"strings"
"time"
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)
"fmt"
"io"
"math/big"
+ "net"
"time"
"github.com/google/uuid"
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)))
{
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"))
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")...,
"strings"
"time"
- "github.com/google/uuid"
"go.cypherpunks.su/keks"
"go.cypherpunks.su/tai64n/v4"
)
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)
"io"
"math/big"
- "github.com/google/uuid"
"go.cypherpunks.su/keks/types"
"go.cypherpunks.su/tai64n/v4"
)
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
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,
"fmt"
"io"
"math/big"
+ "net"
"reflect"
"sort"
"strings"
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:
--- /dev/null
+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[:])
+}
import (
"bytes"
"io"
+ "net"
"testing"
"testing/quick"
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) {
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)
import (
"math/big"
- "github.com/google/uuid"
"go.cypherpunks.su/keks/types"
"go.cypherpunks.su/tai64n/v4"
)
tai64ns int
tai64s int
uints int
- uuids int
+ hexlets int
}
func (ctx *Decoder) Iter() *Iterator {
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:
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 {
AtomNIL AtomType = 0x01
AtomFalse AtomType = 0x02
AtomTrue AtomType = 0x03
- AtomUUID AtomType = 0x04
+ AtomHexlet AtomType = 0x04
AtomList AtomType = 0x08
AtomMap AtomType = 0x09
AtomBLOB AtomType = 0x0B
EOC
NIL
Bool
- UUID
+ Hexlet
UInt
Int
BigInt
_ = x[EOC-1]
_ = x[NIL-2]
_ = x[Bool-3]
- _ = x[UUID-4]
+ _ = x[Hexlet-4]
_ = x[UInt-5]
_ = x[Int-6]
_ = x[BigInt-7]
_ = 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) {
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:
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.
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
TagNIL = 0x01
TagFalse = 0x02
TagTrue = 0x03
-TagUUID = 0x04
+TagHexlet = 0x04
TagList = 0x08
TagMap = 0x09
TagBlob = 0x0B
TagNILb = _byte(TagNIL)
TagFalseb = _byte(TagFalse)
TagTrueb = _byte(TagTrue)
-TagUUIDb = _byte(TagUUID)
+TagHexletb = _byte(TagHexlet)
TagListb = _byte(TagList)
TagMapb = _byte(TagMap)
TagBlobb = _byte(TagBlob)
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"))
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):
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:
if __name__ == "__main__":
from argparse import ArgumentParser
- from argparse import FileType
parser = ArgumentParser(description="Decode KEKS file")
parser.add_argument(
"--nosets", action="store_true",
from datetime import datetime
from datetime import timedelta
+from ipaddress import IPv6Address
from uuid import UUID
import keks
[],
{},
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)),
from keks import _byte
from keks import Blob
+from keks import Hexlet
from keks import Magic
from keks import Raw
from keks import TagTAI64NA
binary(max_size=32),
text_st,
none(),
- uuids(),
+ uuids().map(Hexlet),
datetimes(),
blobs_st,
tai64na_st,
# 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
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:
@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)
# * 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):
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):
--- /dev/null
+@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
@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}
@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
@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
+++ /dev/null
-@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
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]
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
{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 {
{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]