Skip to content

Commit

Permalink
Merge pull request #843 from SmithSamuelM/main
Browse files Browse the repository at this point in the history
Labeler as Matter subclass for compact field map labels that support all sizes   with unit tests
  • Loading branch information
SmithSamuelM authored Aug 12, 2024
2 parents e37f427 + 227f9b0 commit d22dd3d
Show file tree
Hide file tree
Showing 3 changed files with 375 additions and 30 deletions.
200 changes: 178 additions & 22 deletions src/keri/core/coring.py
Original file line number Diff line number Diff line change
Expand Up @@ -550,6 +550,46 @@ def __iter__(self):
TagDex = TagCodex() # Make instance


@dataclass(frozen=True)
class LabelCodex:
"""
LabelCodex is codex of.
Only provide defined codes.
Undefined are left out so that inclusion(exclusion) via 'in' operator works.
"""
Tag1: str = '0J' # 1 B64 char tag with 1 pre pad
Tag2: str = '0K' # 2 B64 char tag
Tag3: str = 'X' # 3 B64 char tag
Tag4: str = '1AAF' # 4 B64 char tag
Tag5: str = '0L' # 5 B64 char tag with 1 pre pad
Tag6: str = '0M' # 6 B64 char tag
Tag7: str = 'Y' # 7 B64 char tag
Tag8: str = '1AAN' # 8 B64 char tag
Tag9: str = '0N' # 9 B64 char tag with 1 pre pad
Tag10: str = '0O' # 10 B64 char tag
StrB64_L0: str = '4A' # String Base64 Only Leader Size 0
StrB64_L1: str = '5A' # String Base64 Only Leader Size 1
StrB64_L2: str = '6A' # String Base64 Only Leader Size 2
StrB64_Big_L0: str = '7AAA' # String Base64 Only Big Leader Size 0
StrB64_Big_L1: str = '8AAA' # String Base64 Only Big Leader Size 1
StrB64_Big_L2: str = '9AAA' # String Base64 Only Big Leader Size 2
Label1: str = 'V' # Label1 1 bytes for label lead size 1
Label2: str = 'W' # Label2 2 bytes for label lead size 0
Bytes_L0: str = '4B' # Byte String lead size 0
Bytes_L1: str = '5B' # Byte String lead size 1
Bytes_L2: str = '6B' # Byte String lead size 2
Bytes_Big_L0: str = '7AAB' # Byte String big lead size 0
Bytes_Big_L1: str = '8AAB' # Byte String big lead size 1
Bytes_Big_L2: str = '9AAB' # Byte String big lead size 2

def __iter__(self):
return iter(astuple(self))


LabelDex = LabelCodex() # Make instance



@dataclass(frozen=True)
class PreCodex:
Expand Down Expand Up @@ -2013,7 +2053,7 @@ class Tagger(Matter):
composable (bool): True when .qb64b and .qb2 are 24 bit aligned and round trip
Properties:
tag (str): B64 primitive without prepad (alias of .soft)
tag (str): B64 .soft portion of code but without prepad
Inherited Hidden: (See Matter)
Expand Down Expand Up @@ -2059,16 +2099,13 @@ def __init__(self, tag='', soft='', code=None, **kwa):
"""
if tag:
if hasattr(tag, "decode"): # make tag str
tag = tag.decode("utf-8")
if not Reb64.match(tag.encode("utf-8")):
if hasattr(tag, "encode"): # make tag bytes for regex
tag = tag.encode("utf-8")

if not Reb64.match(tag):
raise InvalidSoftError(f"Non Base64 chars in {tag=}.")
# TagDex tags appear in order of size 1 to 10, at indices 0 to 9
codes = astuple(TagDex)
l = len(tag) # soft not empty so l > 0
if l > len(codes):
raise InvalidSoftError("Oversized tag={soft}.")
code = codes[l-1] # get code for for tag of len where (index = len - 1)

code = self._codify(tag=tag)
soft = tag


Expand All @@ -2077,6 +2114,27 @@ def __init__(self, tag='', soft='', code=None, **kwa):
if (not self._special(self.code)) or self.code not in TagDex:
raise InvalidCodeError(f"Invalid code={self.code} for Tagger.")


@staticmethod
def _codify(tag):
"""Returns code for tag when tag is appropriately sized Base64
Parameters:
tag (str | bytes): Base64 value
Returns:
code (str): derivation code for tag
"""
# TagDex tags appear in order of size 1 to 10, at indices 0 to 9
codes = astuple(TagDex)
l = len(tag)
if l < 1 or l > len(codes):
raise InvalidSoftError(f"Invalid {tag=} size {l=}, empty or oversized.")
return codes[l-1] # return code at index = len - 1



@property
def tag(self):
"""Returns:
Expand Down Expand Up @@ -2646,7 +2704,8 @@ def __init__(self, raw=None, qb64b=None, qb64=None, qb2=None,
raise ValidationError("Invalid code = {} for Bexter."
"".format(self.code))

def _rawify(self, bext):
@staticmethod
def _rawify(bext):
"""Returns raw value equivalent of Base64 text.
Suitable for variable sized matter
Expand All @@ -2660,14 +2719,15 @@ def _rawify(self, bext):
raw = decodeB64(base)[ls:] # convert and remove leader
return raw # raw binary equivalent of text

@property
def bext(self):
"""
Property bext: Base64 text value portion of qualified b64 str
Returns the value portion of .qb64 with text code and leader removed
@classmethod
def _derawify(cls, raw, code):
"""Returns decoded raw as B64 str aka bext value
Returns:
bext (str): decoded raw as B64 str aka bext value
"""
_, _, _, _, ls = self.Sizes[self.code]
bext = encodeB64(bytes([0] * ls) + self.raw)
_, _, _, _, ls = cls.Sizes[code]
bext = encodeB64(bytes([0] * ls) + raw)
ws = 0
if ls == 0 and bext:
if bext[0] == ord(b'A'): # strip leading 'A' zero pad
Expand All @@ -2677,6 +2737,15 @@ def bext(self):
return bext.decode('utf-8')[ws:]


@property
def bext(self):
"""
Property bext: Base64 text value portion of qualified b64 str
Returns the value portion of .qb64 with text code and leader removed
"""
return self._derawify(raw=self.raw, code=self.code)


class Pather(Bexter):
"""
Pather is a subclass of Bexter that provides SAD Path language specific functionality
Expand Down Expand Up @@ -2909,6 +2978,95 @@ def _resolve(self, val, ptr):
return self._resolve(cur, ptr)


class Labeler(Matter):
"""
Labeler is subclass of Matter for CESR native field map labels and/or generic
textual field values. Labeler auto sizes the instance code to minimize
the total encoded size of associated field label or textual field value.
Attributes:
Inherited Properties:
(See Matter)
Properties:
label (str): base value without encoding
Inherited Hidden:
(See Matter)
Hidden:
Methods:
"""


def __init__(self, label='', raw=None, code=None, soft=None, **kwa):
"""
Inherited Parameters:
(see Matter)
Parameters:
label (str | bytes): base value before encoding
"""
if label:
if hasattr(label, "encode"): # make label bytes
label = label.encode("utf-8")

if Reb64.match(label): # candidate for Base64 compact encoding
try:
code = Tagger._codify(tag=label)
soft = label

except InvalidSoftError as ex: # too big
if label[0] != ord(b'A'): # use Bexter code
code = LabelDex.StrB64_L0
raw = Bexter._rawify(label)

else: # use Texter code since ambiguity if starts with 'A'
code = LabelDex.Bytes_L0
raw = label

else:
if len(label) == 1:
code = LabelDex.Label1

elif len(label) == 2:
code = LabelDex.Label2

else:
code = LabelDex.Bytes_L0

raw = label

super(Labeler, self).__init__(raw=raw, code=code, soft=soft, **kwa)

if self.code not in LabelDex:
raise InvalidCodeError(f"Invalid code={self.code} for Labeler.")



@property
def label(self):
"""Extracts and returns label from .code and .soft or .code and .raw
Returns:
label (str): base value without encoding
"""
if self.code in TagDex: # tag
return self.soft # soft part of code

if self.code in BexDex: # bext
return Bexter._derawify(raw=self.raw, code=self.code) # derawify

return self.raw.decode() # everything else is just raw as str



class Verfer(Matter):
"""
Expand Down Expand Up @@ -3417,13 +3575,11 @@ def _derive(clas, sad: dict, *,
raw, proto, kind, sad, version = sizeify(ked=sad, kind=kind)

ser = dict(sad)
if ignore:
if ignore: # delete ignore fields in said calculation from ser dict
for f in ignore:
del ser[f]

# string now has correct size
# sad as 'v' verision string then use its kind otherwise passed in kind
cpa = clas._serialize(ser, kind=kind) # raw pos arg class
cpa = clas._serialize(ser, kind=kind) # serialize ser
return (Diger._digest(ser=cpa, code=code), sad) # raw digest and sad


Expand Down
4 changes: 2 additions & 2 deletions src/keri/help/helping.py
Original file line number Diff line number Diff line change
Expand Up @@ -264,9 +264,9 @@ def fromIso8601(dts):
B64_CHARS = tuple(B64ChrByIdx.values()) # tuple of characters in Base64


B64REX = rb'^[0-9A-Za-z_-]*\Z' # [A-Za-z0-9\-\_]
B64REX = rb'^[0-9A-Za-z_-]*\Z' # [A-Za-z0-9\-\_] bytes
Reb64 = re.compile(B64REX) # compile is faster
# Usage: if Reb64.match(bext): or if not Reb64.match(bext):
# Usage: if Reb64.match(bext): or if not Reb64.match(bext): bext is bytes


def intToB64(i, l=1):
Expand Down
Loading

0 comments on commit d22dd3d

Please sign in to comment.