From bcb6f1790e70205698c490752f7b3deba3c71f5a Mon Sep 17 00:00:00 2001 From: Samuel M Smith Date: Mon, 25 Mar 2024 20:48:46 -0600 Subject: [PATCH] more prelim work to support CESR native message --- src/keri/core/coring.py | 23 --- src/keri/core/counting.py | 56 ++++-- src/keri/core/serdering.py | 391 +++++++++++++++++++++++------------- src/keri/kering.py | 101 ++++++++-- tests/core/test_coring.py | 26 +-- tests/core/test_counting.py | 42 +++- tests/test_kering.py | 241 +++++++++++++++++++--- 7 files changed, 629 insertions(+), 251 deletions(-) diff --git a/src/keri/core/coring.py b/src/keri/core/coring.py index 7b55182e8..9d1a09094 100644 --- a/src/keri/core/coring.py +++ b/src/keri/core/coring.py @@ -4696,27 +4696,6 @@ def __iter__(self): CtrDex = CounterCodex() -@dataclass(frozen=True) -class GenusCodex: - """GenusCodex is codex of protocol genera for code table. - - Only provide defined codes. - Undefined are left out so that inclusion(exclusion) via 'in' operator works. - """ - KERI_ACDC_SPAC: str = '--AAA' # KERI, ACDC, and SPAC Protocol Stacks share the same tables - KERI: str = '--AAA' # KERI and ACDC Protocol Stacks share the same tables - ACDC: str = '--AAA' # KERI and ACDC Protocol Stacks share the same tables - SPAC: str = '--AAA' # KERI and ACDC Protocol Stacks share the same tables - - - def __iter__(self): - return iter(astuple(self)) # enables inclusion test with "in" - # duplicate values above just result in multiple entries in tuple so - # in inclusion still works - -GenDex = GenusCodex() # Make instance - - class Counter: """ Counter is fully qualified cryptographic material primitive base class for @@ -5186,8 +5165,6 @@ def _inhale(self, raw): if version != Version: raise VersionError("Unsupported version = {}.{}, expected {}." "".format(version.major, version.minor, Version)) - #if len(raw) < size: - #raise ShortageError("Need more bytes.") ked = loads(raw=raw, size=size, kind=kind) diff --git a/src/keri/core/counting.py b/src/keri/core/counting.py index c000f1693..8e162e2f9 100644 --- a/src/keri/core/counting.py +++ b/src/keri/core/counting.py @@ -19,6 +19,29 @@ from ..core.coring import Sizage + +@dataclass(frozen=True) +class GenusCodex: + """GenusCodex is codex of protocol genera for code table. + + Only provide defined codes. + Undefined are left out so that inclusion(exclusion) via 'in' operator works. + """ + KERI_ACDC_SPAC: str = '--AAA' # KERI, ACDC, and SPAC Protocol Stacks share the same tables + KERI: str = '--AAA' # KERI and ACDC Protocol Stacks share the same tables + ACDC: str = '--AAA' # KERI and ACDC Protocol Stacks share the same tables + SPAC: str = '--AAA' # KERI and ACDC Protocol Stacks share the same tables + + + def __iter__(self): + return iter(astuple(self)) # enables inclusion test with "in" + # duplicate values above just result in multiple entries in tuple so + # in inclusion still works + +GenDex = GenusCodex() # Make instance + + + @dataclass class MapDom: """Base class for dataclasses that support map syntax @@ -173,25 +196,6 @@ def __iter__(self): CtrDex_2_0 = CounterCodex_2_0() - -@dataclass(frozen=True) -class GenusCodex(MapCodex): - """GenusCodex is codex of protocol genera for code table. - - Only provide defined codes. - Undefined are left out so that inclusion(exclusion) via 'in' operator works. - """ - KERI: str = '--AAA' # KERI and ACDC Protocol Stacks share the same tables - ACDC: str = '--AAA' # KERI and ACDC Protocol Stacks share the same tables - - - def __iter__(self): - return iter(astuple(self)) # enables value not key inclusion test with "in" - # duplicate values above just result in multiple entries in tuple so - # in inclusion still works - -GenDex = GenusCodex() # Make instance - # keys and values as strings of keys Codict1 = asdict(CtrDex_1_0) Tagage_1_0 = namedtuple("Tagage_1_0", list(Codict1), defaults=list(Codict1)) @@ -581,10 +585,24 @@ def count(self): """ Returns ._count Makes ._count read only + + number of quadlets of b64 chars or triplets of b2 bytes of material + framed by counter """ return self._count + @property + def fullSize(self): + """ + Returns full size of counter in bytes + + """ + _, _, fs, _ = self.sizes[self.code] # get from sizes table + + return fs + + @property def qb64b(self): """ diff --git a/src/keri/core/serdering.py b/src/keri/core/serdering.py index f3351cc23..a6fb9b8cb 100644 --- a/src/keri/core/serdering.py +++ b/src/keri/core/serdering.py @@ -33,7 +33,7 @@ from .coring import Matter, Saider, Verfer, Diger, Number, Tholder from ..core import counting -from ..core.counting import AllTags, Counter +from ..core.counting import GenDex, AllTags, Counter from .. import help from ..help import helping @@ -112,12 +112,80 @@ class FieldDom: def __iter__(self): return iter(asdict(self)) +"""Design Notes: + +Problems is that sniff only determines if counter not which type of +counter. Then smell does regex lookahead to find out which serialization +when not count code but extractor does not look ahead but strips from +stream. So when possibility that CESR message is next either need to +not strip from stream when extracting or if counter is message +then grab rest of frame and reattach so raw in Serder includes the +message counter. Latter is better since always keep counter around +until later. So need to check counter type and if message then +extract rest of counter frame (message) and reattach counter raw. +Then can call Serder with raw and smellage that indicates CESR kind + +But this does not solve the problem of using the Serder subclass +for the given protocol. Merely knowing is a CESR message is not +enough also have to know the protocol which comes in the version +field (not version string). + +One solution is to modify smell so that it also can lookahead and +see the version field. Or lookahead and see the version field with +count codes in front. Problem is that the Regexes don't separate +cleanly. + +Another solution is to use distinct function for cesr native called +snuff like smell but regex only for CESR native. Reap can be told which +because sniff tells which it is. +So question for snuff is should it be searching over the counter or should it +start at version field. This changes regex so forced start at front of raw. +so if reattach counter but use skip then can snuff at start of string. +Begine regex with b'^' or b'\A' to match at start of string. + +So change Smellage to return extra field that has gvrsn when used by snuff +so can use Smellage for both smell and snuff in both reap and inhale +where smellage is used. Change egacy uses of smell to ignore extra value. + + + +Lets try that as it works the best. + +while True: # extract, deserialize, and strip message from ims + try: + serder = serdery.reap(ims=ims) # can set version here + except kering.ShortageError as ex: # need more bytes + yield + else: # extracted and stripped successfully + break # break out of while loop + +ctr = yield from self._extractor(ims=ims, klas=Counter, cold=cold) +if ctr.code == CtrDex.AttachmentGroup: # pipeline ctr? + pipelined = True + +@staticmethod +def _extractor(ims, klas, cold=Colds.txt, abort=False): + while True: + try: + if cold == Colds.txt: + return klas(qb64b=ims, strip=True) + elif cold == Colds.bny: + return klas(qb2=ims, strip=True) + else: + raise kering.ColdStartError("Invalid stream state cold={}.".format(cold)) + except kering.ShortageError as ex: + if abort: # pipelined pre-collects full frame before extracting + raise # bad pipelined frame so abort by raising error + yield + + + +""" + class Serdery: """Serder factory class for generating serder instances by protocol type from an incoming message stream. - - """ def __init__(self, *pa, **kwa): @@ -129,7 +197,7 @@ def __init__(self, *pa, **kwa): pass - def reap(self, ims): + def reap(self, ims, skip=0, native=False): """Extract and return Serder subclass based on protocol type reaped from version string inside serialized raw of Serder. @@ -140,12 +208,16 @@ def reap(self, ims): Parameters: ims (bytearray) of serialized incoming message stream. Assumes start of stream is raw Serder. - version (Versionage | None): instance supported protocol version - None means do not enforce a supported version - """ - #version = version if version is not None else self.version + skip (int): bytes to skip at front of ims. Useful when CESR native + serialization where skip is size of the message counter so smell + does need to see counter + native (bool): True means sniff determined may be CESR native message + so snuff instead of smell. + False means sniff determined not CESR native i.e + JSON, CBOR, MGPK field map. so use smell. Default False - smellage = smell(ims) + """ + smellage = smell(memoryview(ims)[skip:]) # does not copy to skip if smellage.protocol == Protocols.keri: return SerderKERI(raw=ims, strip=True, smellage=smellage) @@ -201,7 +273,8 @@ class Serder: Properties: raw (bytes): of serialized event only sad (dict): self addressed data dict - cvrsn (Versionage): CESR code table version (Major, Minor) + genus (str): CESR genus code + gvrsn (Versionage): instance CESR genus code table version (Major, Minor) proto (str): Protocolage value as protocol identifier such as KERI, ACDC alias of .protocol protocol (str): Protocolage value as protocol identifier such as KERI, ACDC @@ -252,8 +325,6 @@ class Serder: saids (dict): strict (bool): - - """ Dummy = "#" # dummy spaceholder char for SAID. Must not be a valid Base64 char @@ -447,7 +518,7 @@ class Serder: def __init__(self, *, raw=b'', sad=None, strip=False, smellage=None, - cvrsn=None, verify=True, makify=False, + genus=GenDex.KERI, gvrsn=Vrsn_2_0, verify=True, makify=False, proto=None, vrsn=None, kind=None, ilk=None, saids=None): """Deserialize raw if provided. Update properties from deserialized raw. Verifies said(s) embedded in sad as given by labels. @@ -459,7 +530,7 @@ def __init__(self, *, raw=b'', sad=None, strip=False, smellage=None, given by label(s) according to proto and ilk and code. Parameters: - raw (bytes): serialized event + raw (bytes | bytearray): serialized event sad (dict): serializable saidified field map of message. Ignored if raw provided strip (bool): True means strip (delete) raw from input stream @@ -469,8 +540,11 @@ def __init__(self, *, raw=b'', sad=None, strip=False, smellage=None, version string elements. If none or empty ignore otherwise assume that raw already had its version string extracted (reaped) into the elements of smellage. - cvrsn (Versionage | None): instance desired CESR code table version - If None then its extracted from raw or uses default .CVrsn + genus (str): CESR genus str. Either provided by parser from stream + or generated by Serder to stream + gvrsn (Versionage): instance CESR genus code table version + Either provided by parser from stream genus version or desired when + generating Serder instance to stream verify (bool): True means verify said(s) of given raw or sad. Raises ValidationError if verification fails Ignore when raw not provided or when raw and saidify is True @@ -495,11 +569,12 @@ def __init__(self, *, raw=b'', sad=None, strip=False, smellage=None, """ - cvrsn = cvrsn if cvrsn is not None else self.CVrsn + self._gvrsn = gvrsn + self._genus = genus if raw: # deserialize raw using property setter - self._inhale(raw=raw, smellage=smellage, cvrsn=cvrsn) - # ._inhale updates ._raw, ._sad, ._cvrsn, ._proto, ._vrsn, ._kind, ._size + self._inhale(raw=raw, smellage=smellage) + # ._inhale updates ._raw, ._sad, ._proto, ._vrsn, ._kind, ._size # primary said field label try: @@ -516,7 +591,7 @@ def __init__(self, *, raw=b'', sad=None, strip=False, smellage=None, except TypeError: pass # ignore if bytes - if verify: # verify the said(s) provided in raw + if verify: # verify fields including the said(s) provided in raw try: self._verify() # raises exception when not verify except Exception as ex: @@ -528,13 +603,13 @@ def __init__(self, *, raw=b'', sad=None, strip=False, smellage=None, elif sad or makify: # serialize sad into raw or make sad if makify: # recompute properties and said(s) and reset sad # makify resets sad, raw, proto, vrsn, kind, ilk, and size - self.makify(sad=sad, cvrsn=cvrsn, proto=proto, vrsn=vrsn, kind=kind, + self.makify(sad=sad, proto=proto, vrsn=vrsn, kind=kind, ilk=ilk, saids=saids) - # .makify updates ._raw, ._sad, ._cvrsn, ._proto, ._vrsn, ._kind, ._size + # .makify updates ._raw, ._sad, ._proto, ._vrsn, ._kind, ._size else: - self._exhale(sad=sad, cvrsn=cvrsn) - # .exhale updates ._raw, ._sad, ._cvrsn, ._proto, ._vrsn, ._kind, ._size + self._exhale(sad=sad) + # .exhale updates ._raw, ._sad, ._proto, ._vrsn, ._kind, ._size # primary said field label try: @@ -545,7 +620,7 @@ def __init__(self, *, raw=b'', sad=None, strip=False, smellage=None, except Exception: self._said = None # no saidive field - if verify: # verify the said(s) provided in sad + if verify: # verify fields including the said(s) provided in sad try: self._verify() # raises exception when not verify except Exception as ex: @@ -588,17 +663,34 @@ def _verify(self): Raises a ValidationError (or subclass) if any verification fails """ - if self.Protocol and self.proto != self.Protocol: - raise ValidationError(f"Expected protocol = {self.Protocol}, got " + if self.Protocol and self.proto != self.Protocol: # class required + raise ValidationError(f"Required protocol = {self.Protocol}, got " f"{self.proto} instead.") if self.proto not in self.Fields: raise ValidationError(f"Invalid protocol type = {self.proto}.") + if self.genus not in GenDex: # ensures self.genus != None + raise SerializeError(f"Invalid genus={self.genus}.") + + if getattr(GenDex, self.proto, None) != self.genus: + raise SerializeError(f"Incompatible protocol={self.proto} with " + f"genus={self.genus}.") + + if self.vrsn.major > self.gvrsn.major: + raise SerializeError(f"Incompatible major protocol version={self.vrsn}" + f" with major genus version={self.gvrsn}.") + + if self.vrsn not in self.Fields[self.proto]: + raise SerializeError(f"Invalid version={self.vrsn} for " + f"protocol={self.proto}.") + if self.ilk not in self.Fields[self.proto][self.vrsn]: raise ValidationError(f"Invalid packet type (ilk) = {self.ilk} for" f"protocol = {self.proto}.") + + fields = self.Fields[self.proto][self.vrsn][self.ilk] # get labelage alls = fields.alls # faster local reference @@ -649,9 +741,8 @@ def _verify(self): if saids[label] in DigDex: # if digestive then replace with dummy sad[label] = self.Dummy * len(sad[label]) - + # compute saidive digestive field values using raw from sized dummied sad raw = self.dumps(sad, kind=self.kind) # serialize dummied sad copy - for label, code in saids.items(): if code in DigDex: # subclass override if non digestive allowed klas, size, length = self.Digests[code] # digest algo size & length @@ -667,14 +758,37 @@ def _verify(self): f" = {self._sad}, should be {dig}.") sad[label] = dig - raw = self.dumps(sad, kind=self.kind) + raw = self.dumps(sad, kind=self.kind) # compute final raw + if raw != self.raw: raise ValidationError(f"Invalid round trip of {sad} != \n" f"{self.sad}.") + + if "v" not in sad: + raise ValidationError(f"Missing version string field in {sad}.") + + # extract version string elements to verify consistency with attributes + proto, vrsn, kind, size = deversify(sad["v"]) + if self.proto != proto: + raise ValidationError(f"Inconsistent protocol={self.proto} in {sad}.") + + if self.vrsn != vrsn: + raise ValidationError(f"Inconsistent version={self.vrsn} in {sad}.") + + if self.kind != kind: + raise ValidationError(f"Inconsistent kind={self.kind} in {sad}.") + + if self.kind in (Serials.json, Serials.cbor, Serials.mgpk): + if size != self.size != len(raw): + raise ValidationError(f"Inconsistent size={self.size} in {sad}.") + else: # size is not set in version string when kind is CESR + if self.size != len(raw): + raise ValidationError(f"Inconsistent size={self.size} in {sad}.") + # verified successfully since no exception - def makify(self, sad, *, cvrsn=None, proto=None, vrsn=None, kind=None, + def makify(self, sad, *, proto=None, vrsn=None, kind=None, ilk=None, saids=None): """Makify given sad dict makes the versions string and computes the said field values and sets associated properties: @@ -693,8 +807,6 @@ def makify(self, sad, *, cvrsn=None, proto=None, vrsn=None, kind=None, Parameters: sad (dict): serializable saidified field map of message. Ignored if raw provided - cvrsn (Versionage | None): instance desired CESR code table version - If None then its extracted from raw or uses default .CVrsn proto (str | None): desired protocol type str value of Protocols If None then its extracted from sad or uses default .Proto vrsn (Versionage | None): instance desired protocol version @@ -721,19 +833,32 @@ def makify(self, sad, *, cvrsn=None, proto=None, vrsn=None, kind=None, else: silk = sad.get('t') # if 't' not in sad .get returns None which may be valid - cvrsn = cvrsn if cvrsn is not None else self.CVrsn - if proto is None: proto = sproto if sproto is not None else self.Proto if proto not in self.Fields: - raise SerializeError(f"Invalid protocol type = {proto}.") + raise SerializeError(f"Invalid protocol={proto}.") + + if self.Protocol and proto != self.Protocol: # required by class + raise SerializeError(f"Required protocol={self.Protocol}, got " + f"protocol={proto} instead.") + + if self.genus not in GenDex: # ensures self.genus != None + raise SerializeError(f"Invalid genus={self.genus}.") + + if getattr(GenDex, proto, None) != self.genus: + raise SerializeError(f"Incompatible protocol={proto} with " + f"genus={self.genus}.") if vrsn is None: vrsn = svrsn if svrsn is not None else self.Vrsn if vrsn not in self.Fields[proto]: - raise SerializeError(f"Invalid version = {vrsn} for protocol = {proto}.") + raise SerializeError(f"Invalid version={vrsn} for protocol={proto}.") + + if vrsn.major > self.gvrsn.major: + raise SerializeError(f"Incompatible major protocol version={vrsn} " + f"with major genus version={self.gvrsn}.") if kind is None: kind = skind if skind is not None else self.Kind @@ -742,23 +867,15 @@ def makify(self, sad, *, cvrsn=None, proto=None, vrsn=None, kind=None, ilk = (silk if silk is not None else list(self.Fields[proto][vrsn])[0]) # list(dict) gives list of keys - if self.Protocol and proto != self.Protocol: - raise SerializeError(f"Expected protocol = {self.Protocol}, got " - f"{proto} instead.") - - if kind not in Serials: raise SerializeError(f"Invalid serialization kind = {kind}") - if ilk not in self.Fields[proto][vrsn]: raise SerializeError(f"Invalid packet type (ilk) = {ilk} for" f"protocol = {proto}.") fields = self.Fields[proto][vrsn][ilk] # get FieldDom of fields - - alls = fields.alls # faster local reference oalls = oset(alls) # ordereset of field labels oopts = oset(fields.opts) # ordereset of field labels @@ -838,21 +955,22 @@ def makify(self, sad, *, cvrsn=None, proto=None, vrsn=None, kind=None, raise SerializeError(f"Missing requires version string field 'v'" f" in sad = {sad}.") - # this size of sad needs to be computed based on actual version string span - # since not same for all versions - sad['v'] = self.Dummy * self.Spans[vrsn] # ensure span of vs is dummied MAXVERFULLSPAN + if kind in (Serials.json, Serials.cbor, Serials.mgpk): + # this size of sad needs to be computed based on actual version string span + # since not same for all versions + sad['v'] = self.Dummy * self.Spans[vrsn] # ensure span of vs is dummied MAXVERFULLSPAN - raw = self.dumps(sad, kind) # get size of sad with fully dummied vs and saids - size = len(raw) + raw = self.dumps(sad, kind) # get size of sad with fully dummied vs and saids + size = len(raw) - # generate new version string with correct size - vs = versify(protocol=proto, version=vrsn, kind=kind, size=size) - sad["v"] = vs # update version string in sad + # generate new version string with correct size + vs = versify(protocol=proto, version=vrsn, kind=kind, size=size) + sad["v"] = vs # update version string in sad + # now have correctly sized version string in sad - # now have correctly sized version string in sad - # now compute saidive digestive field values using sized dummied sad - raw = self.dumps(sad, kind=kind) # serialize sized dummied sad + # compute saidive digestive field values using raw from sized dummied sad + raw = self.dumps(sad, kind=kind) # serialize sized dummied sad for label, code in _saids.items(): if code in DigDex: # subclass override if non digestive allowed klas, dsize, dlen = self.Digests[code] # digest algo size & length @@ -866,17 +984,18 @@ def makify(self, sad, *, cvrsn=None, proto=None, vrsn=None, kind=None, sad[label] = dig raw = self.dumps(sad, kind=kind) # compute final raw + if kind == Serials.cesr:# cesr kind version string does not set size + size = len(raw) # size of whole message self._raw = raw self._sad = sad - self._cvrsn = cvrsn self._proto = proto self._vrsn = vrsn self._kind = kind self._size = size - def _inhale(self, raw, *, smellage=None, cvrsn=None): + def _inhale(self, raw, *, smellage=None): """Deserializes raw. Parses serilized event ser of serialization kind and assigns to instance attributes and returns tuple of associated elements. @@ -899,12 +1018,8 @@ def _inhale(self, raw, *, smellage=None, cvrsn=None): elements. If none or empty ignore otherwise assume that raw already had its version string extracted (reaped) into the elements of smellage. - cvrsn (Versionage | None): instance desired CESR code table version - If None then its extracted from raw or uses default .CVrsn - Note: - loads and jumps of json use str whereas cbor and msgpack use bytes - Assumes only supports Version + """ if smellage: # passed in so don't need to smell raw again @@ -913,15 +1028,14 @@ def _inhale(self, raw, *, smellage=None, cvrsn=None): proto, vrsn, kind, size = smell(raw) sad = self.loads(raw=raw, size=size, kind=kind) - - cvrsn = cvrsn if cvrsn is not None else self.CVrsn + # ._gvrsn may be set in loads when CESR native deserialization provides _gvrsn if "v" not in sad: # Regex does not check for version string label itself raise FieldError(f"Missing version string field in {sad}.") - self._raw = bytes(raw[:size]) # crypto ops require bytes not bytearray + # cypto opts want bytes not bytearray + self._raw = bytes(raw[:size]) # make copy so strip not affect self._sad = sad - self._cvrsn = cvrsn self._proto = proto self._vrsn = vrsn self._kind = kind @@ -930,18 +1044,23 @@ def _inhale(self, raw, *, smellage=None, cvrsn=None): def loads(self, raw, size=None, kind=Serials.json): - """Utility static method to handle deserialization by kind + """method to handle deserialization by kind + assumes already sniffed and smelled to determine + serialization size and kind Returns: sad (dict | list): deserialized dict or list. Assumes attribute dict of saidified data. Parameters: - raw (bytes |bytearray): raw serialization to deserialze as dict + raw (bytes | bytearray): raw serialization to deserialze as dict size (int): number of bytes to consume for the deserialization. If None then consume all bytes in raw kind (str): value of Serials (Serialage) serialization kind "JSON", "MGPK", "CBOR" + + Notes: + loads of json uses str whereas loads of cbor and msgpack use bytes """ if kind == Serials.json: try: @@ -982,30 +1101,17 @@ def _loads(self, raw, size=None): size (int): number of bytes to consume for the deserialization. If None then consume all bytes in raw """ + # ._gvrsn may be set in loads when CESR native deserialization provides _gvrsn pass - def _exhale(self, sad, cvrsn=None): - """Serializes sad and sets the serialized size in its version string. - - As classmethod enables bootstrap of valid sad dict that has correct size - in version string. This obviates sizeify. This can be called on self as - well because it only ever accesses clas attributes not instance attributes. - - Returns tuple of (raw, proto, kind, sad, vrsn) where: - raw (str): serialized event as bytes of kind - proto (str): protocol type as value of Protocolage - kind (str): serialzation kind as value of Serialage - sad (dict): modified serializable attribute dict of saidified data - vrsn (Versionage): tuple value (major, minor) + def _exhale(self, sad): + """Serializes sad and assigns attributes. + Asssumes all field values in sad are valid. + Call .verify to otherwise Parameters: - clas (Serder): class reference sad (dict): serializable attribute dict of saidified data - cvrsn (Versionage | None): instance desired CESR code table version - If None then its extracted from raw or uses default .CVrsn - - """ if "v" not in sad: raise SerializeError(f"Missing version string field in {sad}.") @@ -1014,28 +1120,13 @@ def _exhale(self, sad, cvrsn=None): proto, vrsn, kind, size = deversify(sad["v"]) raw = self.dumps(sad, kind) - size = len(raw) - - # generate new version string with correct size - vs = versify(protocol=proto, version=vrsn, kind=kind, size=size) - - cvrsn = cvrsn if cvrsn is not None else self.CVrsn - # find location of old version string inside raw - match = Rever.search(raw) # Rever's regex takes bytes - if not match or match.start() > 12: - raise SerializeError(f"Invalid version string in raw = {raw}.") - fore, back = match.span() # start and end positions of version string + if kind in (Serials.cesr): # cesr kind version string does not set size + size = len(raw) # size of whole message - # replace old version string in raw with new one - raw = b'%b%b%b' % (raw[:fore], vs.encode("utf-8"), raw[back:]) - if size != len(raw): # substitution messed up - raise SerializeError(f"Malformed size of raw in version string == {vs}") - sad["v"] = vs # update sad - - self._raw = raw + # must call .verify to ensure these are compatible + self._raw = raw # crypto opts want bytes not bytearray self._sad = sad - self._cvrsn = cvrsn self._proto = proto self._vrsn = vrsn self._kind = kind @@ -1043,7 +1134,8 @@ def _exhale(self, sad, cvrsn=None): def dumps(self, sad, kind=Serials.json): - """Utility static method to handle serialization by kind + """Method to handle serialization by kind + Assumes sad fields are properly filled out for serialization kind. Returns: raw (bytes): serialization of sad dict using serialization kind @@ -1051,7 +1143,11 @@ def dumps(self, sad, kind=Serials.json): Parameters: sad (dict | list)): serializable dict or list to serialize kind (str): value of Serials (Serialage) serialization kind - "JSON", "MGPK", "CBOR" + "JSON", "MGPK", "CBOR", "CSER" + + Notes: + dumps of json uses str whereas dumps of cbor and msgpack use bytes + crypto opts want bytes not bytearray """ if kind == Serials.json: raw = json.dumps(sad, separators=(",", ":"), @@ -1062,54 +1158,61 @@ def dumps(self, sad, kind=Serials.json): elif kind == Serials.cbor: raw = cbor.dumps(sad) + + elif kind == Serials.cser: + raw = self._dumps(sad) + else: raise SerializeError(f"Invalid serialization kind = {kind}") return raw - def _dumps(self, sad, protocol, version, cversion=Vrsn_2_0): + def _dumps(self, sad): """CESR native serialization of sad Returns: raw (bytes): CESR native serialization of sad dict Parameters: - clas (Serder): class reference sad (dict | list)): serializable dict or list to serialize - protocol (str): message protocol - version (Versionage): message protocol version - cversion (Versionage): CESR code table genus version Versioning: - CESR native serialization includes message protocol, protocol version - and CESR (genus) version. Assumes genus is compatible with message - protocol so genus is not needed. Protects from malleability attack + CESR native serialization includes in its fixed version field + a version primitive that includes message protocol+protocol version + +genus version: 0NPPPPMmmMmm (12 B64 characters) + + This assumes that genus is compatible with message + protocol so genus is not needed. This protects from malleability attack and ensure compatible cesr codes especially count (group) codes. Primitive codes are less problematic since so far all primitive codes tables are backwards compatible across major versions. - 0NPPPPMmmMmm (12 B64 characters) + """ + if (self.gvrsn.major < Vrsn_2_0.major or + self.vrsn.major < Vrsn_2_0.major): + raise SerializeError(f"Invalid major genus version={self.gvrsn}" + f"or Invalid major protocol version={self.vrsn}" + f" for native CESR serialization.") + if self.genus not in GenDex: # ensures self.genus != None + raise SerializeError(f"Invalid genus={self.genus}.") + + if getattr(GenDex, self.proto, None) != self.genus: + raise SerializeError(f"Incompatible protocol={self.proto} with " + f"genus={self.genus}.") - """ fixed = True # True = use fixed field, False= use field map raw = bytearray() - - if protocol not in Protocols: - raise SerializeError(f"Invalid {protocol=}.") - - if version not in self.Fields[protocol]: - raise SerializeError(f"Invalid {version=} for {protocol=}.") - - ilks = self.Fields[protocol][version] # get fields keyed by ilk + ilks = self.Fields[self.proto][self.vrsn] # get fields keyed by ilk ilk = sad.get('t') # returns None if missing message type (ilk) if ilk not in ilks: # raise SerializeError(f"Missing message type field " - f"'t' for {protocol=} {version=} with {sad=}.") + f"'t' for protocol={self.proto} " + f"version={self.vrsn} with {sad=}.") fields = ilks[ilk] # FieldDom for given protocol and ilk @@ -1121,7 +1224,7 @@ def _dumps(self, sad, protocol, version, cversion=Vrsn_2_0): # so can serialize in order to compute saidive fields # need to fix ._verify and .makify to account for CESR native serialization - if protocol == Protocols.keri: + if self.proto == Protocols.keri: for l, v in sad.items(): # assumes valid field order & presence if not fixed: # prepend label pass @@ -1129,8 +1232,9 @@ def _dumps(self, sad, protocol, version, cversion=Vrsn_2_0): # should dispatch or use match instead of big if else match l: # label case "v": # protocol+version - val = (MtrDex.Tag7 + protocol + - Counter.verToB64(version)).encode("utf-8") + val = (MtrDex.Tag10 + self.proto + + Counter.verToB64(self.vrsn) + + Counter.verToB64(self.gvrsn)).encode("utf-8") case "t": # message type val = (MtrDex.Tag3 + ilk).encode("utf-8") # add code @@ -1151,7 +1255,7 @@ def _dumps(self, sad, protocol, version, cversion=Vrsn_2_0): val = bytearray(Counter(tag=AllTags.GenericListGroup, count=len(frame) % 4, - version=cversion).qb64b) + version=self.gvrsn).qb64b) val.extend(frame) case "c": # list of config traits strings @@ -1162,7 +1266,7 @@ def _dumps(self, sad, protocol, version, cversion=Vrsn_2_0): val = bytearray(Counter(tag=AllTags.GenericListGroup, count=len(frame) % 4, - version=cversion).qb64b) + version=self.gvrsn).qb64b) val.extend(frame) case "a": # list of seals or field map of attributes @@ -1192,7 +1296,7 @@ def _dumps(self, sad, protocol, version, cversion=Vrsn_2_0): raw.extend(val) - elif protocol == Protocols.acdc: + elif self.protocol == Protocols.acdc: for l, val in sad.items(): # assumes valid field order & presence if not fixed: pass # prepend label @@ -1200,7 +1304,7 @@ def _dumps(self, sad, protocol, version, cversion=Vrsn_2_0): else: - raise SerializeError(f"Unsupported {protocol=}.") + raise SerializeError(f"Unsupported protocol={self.protocol}.") # prepend count code for message @@ -1274,14 +1378,25 @@ def sad(self): """ return dict(self._sad) # return copy + + @property + def genus(self): + """genus (CESR genu code) property getter + + Returns: + genus (stre): CESR genus code for this Serder + """ + return self._genus + + @property - def cvrsn(self): - """cvrsn (version) property getter + def gvrsn(self): + """gvrsn (CESR genus version) property getter Returns: - cvrsn (Versionage): instance of CESR code table version for this Serder + gvrsn (Versionage): instance, CESR genus code table version for this Serder """ - return self._cvrsn + return self._gvrsn @property diff --git a/src/keri/kering.py b/src/keri/kering.py index 7ed23ef36..338f87a39 100644 --- a/src/keri/kering.py +++ b/src/keri/kering.py @@ -36,32 +36,41 @@ #VEREX0 = b'(?P[A-Z]{4})(?P[0-9a-f])(?P[0-9a-f])(?P[A-Z]{4})(?P[0-9a-f]{6})_' #Rever = re.compile(VEREX0) # compile is faster +# version string in JSON, CBOR, or MGPK field map serialization version 1 VER1FULLSPAN = 17 # number of characters in full version string VER1TERM = b'_' VEREX1 = b'(?P[A-Z]{4})(?P[0-9a-f])(?P[0-9a-f])(?P[A-Z]{4})(?P[0-9a-f]{6})_' +# version string in JSON, CBOR, or MGPK field map serialization version 2 VER2FULLSPAN = 16 # number of characters in full version string VER2TERM = b'.' -VEREX2 = b'(?P[A-Z]{4})(?P[0-9A-Za-z_-])(?P[0-9A-Za-z_-]{2})(?P[A-Z]{4})(?P[0-9A-Za-z_-]{4}).' +VEREX2 = b'(?P[A-Z]{4})(?P[0-9A-Za-z_-])(?P[0-9A-Za-z_-]{2})(?P[A-Z]{4})(?P[0-9A-Za-z_-]{4})\.' VEREX = VEREX2 + b'|' + VEREX1 -MAXVERFULLSPAN = max(VER1FULLSPAN, VER2FULLSPAN) # max number of characters in full version string +# max number of characters in full version string +MAXVERFULLSPAN = max(VER2FULLSPAN, VER1FULLSPAN) Rever = re.compile(VEREX) # compile is faster - MAXVSOFFSET = 12 SMELLSIZE = MAXVSOFFSET + MAXVERFULLSPAN # min buffer size to inhale +# version field in CESR native serialization +VFFULLSPAN = 12 # number of characters in full version string +VFREX = b'0N(?P[A-Z]{4})(?P[0-9A-Za-z_-])(?P[0-9A-Za-z_-]{2})(?P[0-9A-Za-z_-])(?P[0-9A-Za-z_-]{2})' + +Revfer = re.compile(VFREX) # compile is faster + """ Smellage (results of smelling a version string such as in a Serder) protocol (str): protocol type value of Protocols examples 'KERI', 'ACDC' - version (Versionage): named tuple (major, minor) ints of major minor version + version (Versionage): protocol version namedtuple (major, minor) of ints kind (str): serialization value of Serials examples 'JSON', 'CBOR', 'MGPK' - size (str): int size of raw serialization + size (int | Versionage): int size of raw serialization or + genus version namedtuple (major, minor) of ints """ Smellage = namedtuple("Smellage", "protocol version kind size") @@ -75,7 +84,9 @@ def rematch(match): Parameters: match (re.Match): instance of Match class - + Notes: + regular expressions work with memoryview objects not just bytes or + bytearrays """ full = match.group() # full matched version string if len(full) == VER2FULLSPAN and full[-1] == ord(VER2TERM): @@ -90,11 +101,6 @@ def rematch(match): vrsn = Versionage(major=b64ToInt(major), minor=b64ToInt(minor)) if vrsn.major < 2: # version2 vs but major < 2 raise VersionError(f"Incompatible {vrsn=} with version string.") - #if version is not None: # compatible version with vrsn - #if (vrsn.major > version.major or - #(vrsn.major == version.major and vrsn.minor > version.minor)): - #raise VersionError(f"Incompatible {version=}, with " - #f"{vrsn=}.") kind = kind.decode("utf-8") if kind not in Serials: @@ -113,19 +119,16 @@ def rematch(match): vrsn = Versionage(major=int(major, 16), minor=int(minor, 16)) if vrsn.major > 1: # version1 vs but major > 1 raise VersionError(f"Incompatible {vrsn=} with version string.") - #if version is not None: # compatible version with vrsn - #if (vrsn.major > version.major or - #(vrsn.major == version.major and vrsn.minor > version.minor)): - #raise VersionError(f"Incompatible {version=}, with " - #f"{vrsn=}.") kind = kind.decode("utf-8") if kind not in Serials: raise KindError(f"Invalid serialization kind = {kind}.") size = int(size, 16) - return Smellage(protocol=protocol, version=vrsn, kind=kind, size=size) + else: + raise VersionError(f"Bad rematch.") + return Smellage(protocol=protocol, version=vrsn, kind=kind, size=size) def versify(protocol=Protocols.keri, version=Version, kind=Serials.json, size=0): @@ -151,7 +154,6 @@ def versify(protocol=Protocols.keri, version=Version, kind=Serials.json, size=0) f"{intToB64(version.minor, l=2)}{kind}{intToB64(size, l=4)}.") - def deversify(vs): """ Returns: tuple(proto, kind, version, size) Where: @@ -182,8 +184,6 @@ def deversify(vs): return rematch(match) - - def smell(raw): """Extract and return instance of Smellage from version string inside raw serialization. @@ -196,6 +196,7 @@ def smell(raw): of stream is JSON, CBOR, or MGPK field map with first field is labeled 'v' and value is version string. + """ if len(raw) < SMELLSIZE: raise ShortageError(f"Need more raw bytes to smell full version string.") @@ -207,7 +208,67 @@ def smell(raw): return rematch(match) +def snatch(match, size=0): + """ Returns: + smellage (Smellage): named tuple extracted from version string regex match + (protocol, version, kind, size) + + Parameters: + match (re.Match): instance of Match class + size (int): provided size to substitute when missing + + Notes: + regular expressions work with memoryview objects not just bytes or + bytearrays + """ + if len(full) == VER0FULLSPAN: + proto, major, minor, gmajor, gminor = match.group("proto0", + "major0", + "minor0", + "gmajor0", + "gminor0") + protocol = proto.decode("utf-8") + if protocol not in Protocols: + raise ProtocolError(f"Invalid protocol type = {protocol}.") + vrsn = Versionage(major=b64ToInt(major), minor=b64ToInt(minor)) + if vrsn.major < 2: # version2 vs but major < 2 + raise VersionError(f"Incompatible {vrsn=} with version string.") + + gvrsn = Versionage(major=b64ToInt(gmajor), minor=b64ToInt(gminor)) + if gvrsn.major < 2: # version2 vs but major < 2 + raise VersionError(f"Incompatible {gvrsn=} with CESR native version" + f"field.") + kind = Serials.cesr + size = size + else: + raise VersionError(f"Bad snatch.") + + return Smellage(protocol=protocol, version=vrsn, kind=kind, size=size) + + +def snuff(raw, size=0): + """Extract and return instance of Smellage from version string inside + raw serialization. + + Returns: + smellage (Smellage): named Tuple of (protocol, version, kind, size) + + Parameters: + raw (bytearray) of serialized incoming message stream. Assumes start + of stream is JSON, CBOR, or MGPK field map with first field + is labeled 'v' and value is version string. + size (int): provided size to substitute when missing + + """ + if len(raw) < SMELLSIZE: + raise ShortageError(f"Need more raw bytes to smell full version string.") + + match = Rever.search(raw) # Rever regex takes bytes/bytearray not str + if not match or match.start() > MAXVSOFFSET: + raise VersionError(f"Invalid version string from smelled raw = " + f"{raw[: SMELLSIZE]}.") + return snatch(match, size=size) @dataclass(frozen=True) diff --git a/tests/core/test_coring.py b/tests/core/test_coring.py index 4687c0dd7..98970e84d 100644 --- a/tests/core/test_coring.py +++ b/tests/core/test_coring.py @@ -31,7 +31,7 @@ from keri.core.coring import Serialage, Serials, Tiers from keri.core.coring import (Sizage, MtrDex, Matter, Xizage, IdrDex, IdxSigDex, IdxCrtSigDex, IdxBthSigDex, Indexer, - CtrDex, Counter, GenDex) + CtrDex, Counter) from keri.core.coring import (Verfer, Cigar, Signer, Salter, Saider, DigDex, Diger, Prefixer, Cipher, Encrypter, Decrypter) from keri.core.coring import versify, deversify, Rever, MAXVERFULLSPAN @@ -46,30 +46,6 @@ from keri.kering import Version, Versionage, VersionError -def test_genus_codex(): - """ - Test protocol genera in GenDex as instance of GenusCodex - - """ - - assert dataclasses.asdict(GenDex) == \ - { - 'KERI_ACDC_SPAC': '--AAA', - 'KERI': '--AAA', - 'ACDC': '--AAA', - 'SPAC': '--AAA' - } - - assert '--AAA' in GenDex - assert GenDex.KERI == "--AAA" - assert GenDex.ACDC == "--AAA" - assert GenDex.SPAC == "--AAA" - assert GenDex.KERI_ACDC_SPAC == "--AAA" - assert GenDex.KERI == GenDex.ACDC - - """End Test""" - - def test_matter(): """ diff --git a/tests/core/test_counting.py b/tests/core/test_counting.py index 521d13b65..d50a3fe4e 100644 --- a/tests/core/test_counting.py +++ b/tests/core/test_counting.py @@ -19,10 +19,41 @@ nabSextets) from keri.core import counting -from keri.core.counting import Sizage, MapDom, MapCodex, Counter +from keri.core.counting import GenDex, Sizage, MapDom, MapCodex, Counter from keri.core.counting import Versionage, Vrsn_1_0, Vrsn_2_0, AllTags + +def test_genus_codex(): + """ + Test protocol genera in GenDex as instance of GenusCodex + + """ + + assert asdict(GenDex) == \ + { + 'KERI_ACDC_SPAC': '--AAA', + 'KERI': '--AAA', + 'ACDC': '--AAA', + 'SPAC': '--AAA' + } + + assert '--AAA' in GenDex + assert GenDex.KERI == "--AAA" + assert GenDex.ACDC == "--AAA" + assert GenDex.SPAC == "--AAA" + assert GenDex.KERI_ACDC_SPAC == "--AAA" + assert GenDex.KERI == GenDex.ACDC + + assert hasattr(GenDex, "KERI") + assert hasattr(GenDex, "ACDC") + assert hasattr(GenDex, "SPAC") + assert hasattr(GenDex, "KERI_ACDC_SPAC") + + """End Test""" + + + def test_mapdom(): """Test MapDom base dataclass""" @@ -166,6 +197,7 @@ def __iter__(self): # so value in dataclass not key in dataclass """End Test""" + def test_codexes_tags(): """ Test supporting module attributes @@ -619,6 +651,7 @@ def test_counter_v1(): assert counter.qb64 == qsc assert counter.qb2 == qscb2 assert counter.version == Vrsn_1_0 + assert counter.fullSize == 4 counter = Counter(tag=AllTags.ControllerIdxSigs, count=count, version=Vrsn_1_0) assert counter.code == CtrDex.ControllerIdxSigs @@ -865,6 +898,7 @@ def test_counter_v1(): assert counter.qb64 == qsc assert counter.qb2 == qscb2 assert counter.version == Vrsn_1_0 + assert counter.fullSize == 8 assert not ims ims = bytearray(qscb2) @@ -875,6 +909,7 @@ def test_counter_v1(): assert counter.qb64 == qsc assert counter.qb2 == qscb2 assert counter.version == Vrsn_1_0 + assert counter.fullSize == 8 assert not ims # test protocol genus with CESR protocol genus version @@ -940,6 +975,7 @@ def test_counter_v2(): assert counter.qb64 == qsc assert counter.qb2 == qscb2 assert counter.version == Vrsn_2_0 + assert counter.fullSize == 4 # default count = 1 counter = Counter(code=CtrDex.ControllerIdxSigs, version=Vrsn_2_0) @@ -1223,6 +1259,7 @@ def test_counter_v2(): assert counter.qb64 == qsc assert counter.qb2 == qscb2 assert counter.version == Vrsn_2_0 + assert counter.fullSize == 8 counter = Counter(qb64b=qscb, version=Vrsn_2_0) # test with bytes not str assert counter.code == CtrDex.BigGenericGroup @@ -1263,6 +1300,7 @@ def test_counter_v2(): assert counter.qb64 == qsc assert counter.qb2 == qscb2 assert counter.version == Vrsn_2_0 + assert counter.fullSize == 8 assert not ims ims = bytearray(qscb2) @@ -1319,6 +1357,7 @@ def test_counter_v2(): assert counter.qb64 == qsc assert counter.qb2 == qscb2 assert counter.version == Vrsn_2_0 + assert counter.fullSize == 8 counter = Counter(code=CtrDex.KERIACDCGenusVersion, countB64=genver, @@ -1336,6 +1375,7 @@ def test_counter_v2(): if __name__ == "__main__": + test_genus_codex() test_mapdom() test_mapcodex() test_codexes_tags() diff --git a/tests/test_kering.py b/tests/test_kering.py index 80d0a070b..039e75d3e 100644 --- a/tests/test_kering.py +++ b/tests/test_kering.py @@ -16,19 +16,17 @@ from keri.kering import Serialage, Serials from keri.kering import Ilkage, Ilks from keri.kering import (Versionage, Version, MAXVERFULLSPAN, - versify, deversify, Rever) -from keri.kering import (VER1FULLSPAN, VER1TERM, VEREX1, - VER2FULLSPAN, VER2TERM, VEREX2, VEREX) + versify, deversify, Rever, Smellage, smell, + VER1FULLSPAN, VER1TERM, VEREX1, + VER2FULLSPAN, VER2TERM, VEREX2, + VEREX) +from keri.kering import VFFULLSPAN, VFREX, Revfer from keri.kering import VersionError, ProtocolError, KindError from keri.help.helping import (intToB64, intToB64b, b64ToInt, B64_CHARS, codeB64ToB2, codeB2ToB64, Reb64, nabSextets) - - - - def test_protos(): """ Test protocols namedtuple instance Protocols @@ -49,18 +47,20 @@ def test_protos(): def test_version_regex(): """ Test version string regexing - """ - #VER1FULLSPAN = 17 # number of characters in full version string - #VER1TERM = b'_' - #VEREX1 = b'(?P[A-Z]{4})(?P[0-9a-f])(?P[0-9a-f])(?P[A-Z]{4})(?P[0-9a-f]{6})_' + VER1FULLSPAN = 17 # number of characters in full version string + VER1TERM = b'_' + VEREX1 = b'(?P[A-Z]{4})(?P[0-9a-f])(?P[0-9a-f])(?P[A-Z]{4})(?P[0-9a-f]{6})_' + + VER2FULLSPAN = 16 # number of characters in full version string + VER2TERM = b'.' + VEREX2 = b'(?P[A-Z]{4})(?P[0-9A-Za-z_-])(?P[0-9A-Za-z_-]{2})(?P[A-Z]{4})(?P[0-9A-Za-z_-]{4}).' - #VER2FULLSPAN = 16 # number of characters in full version string - #VER2TERM = b'.' - #VEREX2 = b'(?P[A-Z]{4})(?P[0-9A-Za-z_-])(?P[0-9A-Za-z_-]{2})(?P[A-Z]{4})(?P[0-9A-Za-z_-]{4}).' + VEREX = VEREX2 + b'|' + VEREX1 + """ - #VEREX = VEREX2 + b'|' + VEREX1 + # Test VEREX2 by itself pattern = re.compile(VEREX2) # compile is faster vs = b'KERICAAJSONAAAB.' @@ -83,7 +83,7 @@ def test_version_regex(): assert groups == (b'KERI', b'C', b'AA', b'JSON', b'AAAB') - + # Test VEREX with combined VEREXes pattern = re.compile(VEREX) # compile is faster vs = b'KERICAAJSONAAAB.' @@ -146,7 +146,6 @@ def test_version_regex(): assert groups == (b'KERI', b'C', b'AA', b'JSON', b'AAAB') - raw = b'{"vs":"KERI10JSON000002_","pre":"AaU6JR2nmwyZ-i0d8JZAoTNZH3ULvYAfSVPzhzS6b5CM"}' match = pattern.search(raw) @@ -169,6 +168,203 @@ def test_version_regex(): """End Test""" +def test_smell(): + """ + Test smell function to parse into Serializations + """ + + raw = b'{"vs":"KERICAAJSONAAAB.","t":"ixn","d":"EPTgL0UEOa8xUWBqghryJYMLOd2eYjmclndQN4bArjSf"}' + assert smell(raw) == Smellage(protocol='KERI', + version=Versionage(major=2, minor=0), + kind='JSON', + size=1) + + raw = b'{"vs":"KERI10JSON000002_","t":"ixn","d":"EPTgL0UEOa8xUWBqghryJYMLOd2eYjmclndQN4bArjSf"}' + assert smell(raw) == Smellage(protocol='KERI', + version=Versionage(major=1, minor=0), + kind='JSON', + size=2) + + raw = b'{"vs":"KERICAAJSONAAABX.","t":"ixn","d":"EPTgL0UEOa8xUWBqghryJYMLOd2eYjmclndQN4bArjSf"}' + with pytest.raises(ProtocolError): + smell(raw) + + raw = b'{"vs":"KERI1XJSON000002_","t":"ixn","d":"EPTgL0UEOa8xUWBqghryJYMLOd2eYjmclndQN4bArjSf"}' + with pytest.raises(VersionError): + smell(raw) + + """End Test""" + + +def test_snuff(): + """ + Test snuff for looking ahead at CESR native messages from stream + + VER0FULLSPAN = 12 # number of characters in full version string + VEREX0 = b'0N(?P[A-Z]{4})(?P[0-9A-Za-z_-])(?P[0-9A-Za-z_-]{2})(?P[0-9A-Za-z_-])(?P[0-9A-Za-z_-]{2})' + + + """ + #pattern = re.compile(VFREX) # compile is faster + pattern = Revfer + + vv = b'0NKERICAACAB' + + match = pattern.match(vv) + assert match + + full = match.group() # not group args so returns full match + assert full == vv + span = len(full) + assert span == VFFULLSPAN + + groups = match.group("proto0", + "major0", + "minor0", + "gmajor0", + "gminor0") + + assert groups == (b'KERI', b'C', b'AA', b'C', b'AB') + + raw = b'-FAM' + vv + assert raw == b'-FAM0NKERICAACAB' + + match = pattern.search(raw) + assert match + + full = match.group() # not group args so returns full match + assert full == vv + span = len(full) + assert span == VFFULLSPAN + + groups = match.group("proto0", + "major0", + "minor0", + "gmajor0", + "gminor0") + + assert groups == (b'KERI', b'C', b'AA', b'C', b'AB') + + raw = b'-0FAAAAM' + vv + assert raw == b'-0FAAAAM0NKERICAACAB' + + match = pattern.search(raw) + assert match + + full = match.group() # not group args so returns full match + assert full == vv + span = len(full) + assert span == VFFULLSPAN + + groups = match.group("proto0", + "major0", + "minor0", + "gmajor0", + "gminor0") + + assert groups == (b'KERI', b'C', b'AA', b'C', b'AB') + + + vv = b'0NKERICAACAB' + + match = pattern.match(vv) + assert match + + full = match.group() # not group args so returns full match + assert full == vv + span = len(full) + assert span == VFFULLSPAN + + groups = match.group("proto0", + "major0", + "minor0", + "gmajor0", + "gminor0") + + assert groups == (b'KERI', b'C', b'AA', b'C', b'AB') + + raw = b'-FAM' + vv + assert raw == b'-FAM0NKERICAACAB' + + match = pattern.search(raw) + assert match + + full = match.group() # not group args so returns full match + assert full == vv + span = len(full) + assert span == VFFULLSPAN + + groups = match.group("proto0", + "major0", + "minor0", + "gmajor0", + "gminor0") + + assert groups == (b'KERI', b'C', b'AA', b'C', b'AB') + + raw = b'-0FAAAAM' + vv + assert raw == b'-0FAAAAM0NKERICAACAB' + + match = pattern.search(raw) + assert match + + full = match.group() # not group args so returns full match + assert full == vv + span = len(full) + assert span == VFFULLSPAN + + groups = match.group("proto0", + "major0", + "minor0", + "gmajor0", + "gminor0") + + assert groups == (b'KERI', b'C', b'AA', b'C', b'AB') + + vv = b'0NKERI______' + raw = b'-0FAAAAM' + vv + assert raw == b'-0FAAAAM0NKERI______' + + match = pattern.search(raw) + assert match + + full = match.group() # not group args so returns full match + assert full == vv + span = len(full) + assert span == VFFULLSPAN + + groups = match.group("proto0", + "major0", + "minor0", + "gmajor0", + "gminor0") + + assert groups == (b'KERI', b'_', b'__', b'_', b'__') + + + + #raw = b'-FAM0NKERICAACABXicpEPTgL0UEOa8xUWBqghryJYMLOd2eYjmclndQN4bArjSf' + #assert smell(raw) == Smellage(protocol='KERI', + #version=Versionage(major=2, minor=0), + #kind='CESR', + #size=0) + + #raw =b'-0FAAAAM0NKERICAACABXrotEPTgL0UEOa8xUWBqghryJYMLOd2eYjmclndQN4bArjSf' + #assert smell(raw, size=4096) == Smellage(protocol='KERI', + #version=Versionage(major=2, minor=0), + #kind='CESR', + #size=4096) + + #raw =b'-0FAAAAM0MKERICAACABXrotEPTgL0UEOa8xUWBqghryJYMLOd2eYjmclndQN4bArjSf' + #with pytest.raises(VersionError): + #smell(raw) + + #raw =b'-0FAAAAMNKERICAACABXrotEPTgL0UEOa8xUWBqghryJYMLOd2eYjmclndQN4bArjSf' + #with pytest.raises(VersionError): + #smell(raw) + + + def test_serials(): """ Test Serializations namedtuple instance Serials @@ -573,20 +769,15 @@ def test_ilks(): """End Test """ -def test_smell(): - """ - Test smell function to parse into Serializations - """ - pass - - """End Test""" if __name__ == "__main__": test_protos() test_version_regex() + test_smell() + test_snuff() test_serials() test_versify_v1() test_versify_v2() test_ilks() - test_smell() +