From f2ceb9912635bbb6e8999a257bcc5fdb473df01c Mon Sep 17 00:00:00 2001 From: Sergey Matveev Date: Mon, 2 Oct 2017 16:05:21 +0300 Subject: [PATCH] Various additions to documentation --- MANIFEST.in | 1 - NEWS | 1 - THANKS | 2 + pyderasn.py | 202 ++++++++++++++++++++++++++++++++++++++++++++++++--- pyderasn.pyi | 28 +++++++ 5 files changed, 221 insertions(+), 13 deletions(-) delete mode 100644 NEWS create mode 100644 THANKS diff --git a/MANIFEST.in b/MANIFEST.in index 2c591b2..d6f4e89 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -2,7 +2,6 @@ include .coveragerc include AUTHORS include COPYING* include INSTALL -include NEWS include nose.cfg include pip-requirements* include PUBKEY.asc diff --git a/NEWS b/NEWS deleted file mode 100644 index 1333ed7..0000000 --- a/NEWS +++ /dev/null @@ -1 +0,0 @@ -TODO diff --git a/THANKS b/THANKS new file mode 100644 index 0000000..670e586 --- /dev/null +++ b/THANKS @@ -0,0 +1,2 @@ +* pyasn1 (http://pyasn1.sourceforge.net/) project +* Go encoding/asn1 (https://golang.org/pkg/encoding/asn1/) library diff --git a/pyderasn.py b/pyderasn.py index b1f029c..4ae1eb0 100755 --- a/pyderasn.py +++ b/pyderasn.py @@ -298,6 +298,17 @@ SetOf _____ .. autoclass:: pyderasn.SetOf :members: __init__ + +Various +------- + +.. autofunction:: pyderasn.hexenc +.. autofunction:: pyderasn.hexdec +.. autofunction:: pyderasn.tag_encode +.. autofunction:: pyderasn.tag_decode +.. autofunction:: pyderasn.tag_ctxp +.. autofunction:: pyderasn.tag_ctxc +.. autoclass:: pyderasn.Obj """ from codecs import getdecoder @@ -392,6 +403,16 @@ TagClassReprs = { class DecodeError(Exception): def __init__(self, msg="", klass=None, decode_path=(), offset=0): + """ + :param str msg: reason of decode failing + :param klass: optional exact DecodeError inherited class (like + :py:exc:`NotEnoughData`, :py:exc:`TagMismatch`, + :py:exc:`InvalidLength`) + :param decode_path: tuple of strings. It contains human + readable names of the fields through which + decoding process has passed + :param int offset: binary offset where failure happened + """ super(DecodeError, self).__init__() self.msg = msg self.klass = klass @@ -496,10 +517,14 @@ _hexencoder = getencoder("hex") def hexdec(data): + """Binary data to hexadecimal string convert + """ return _hexdecoder(data)[0] def hexenc(data): + """Hexadecimal string to binary data convert + """ return _hexencoder(data)[0].decode("ascii") @@ -523,6 +548,16 @@ def zero_ended_encode(num): def tag_encode(num, klass=TagClassUniversal, form=TagFormPrimitive): + """Encode tag to binary form + + :param int num: tag's number + :param int klass: tag's class (:py:data:`pyderasn.TagClassUniversal`, + :py:data:`pyderasn.TagClassContext`, + :py:data:`pyderasn.TagClassApplication`, + :py:data:`pyderasn.TagClassPrivate`) + :param int form: tag's form (:py:data:`pyderasn.TagFormPrimitive`, + :py:data:`pyderasn.TagFormConstructed`) + """ if num < 31: # [XX|X|.....] return int2byte(klass | form | num) @@ -531,8 +566,14 @@ def tag_encode(num, klass=TagClassUniversal, form=TagFormPrimitive): def tag_decode(tag): - """ - assume that data is validated + """Decode tag from binary form + + .. warning:: + + No validation is performed, assuming that it has already passed. + + It returns tuple with three integers, as + :py:func:`pyderasn.tag_encode` accepts. """ first_octet = byte2int(tag) klass = first_octet & 0xC0 @@ -547,10 +588,14 @@ def tag_decode(tag): def tag_ctxp(num): + """Create CONTEXT PRIMITIVE tag + """ return tag_encode(num=num, klass=TagClassContext, form=TagFormPrimitive) def tag_ctxc(num): + """Create CONTEXT CONSTRUCTED tag + """ return tag_encode(num=num, klass=TagClassContext, form=TagFormConstructed) @@ -612,6 +657,9 @@ def len_decode(data): class Obj(object): """Common ASN.1 object class + + All ASN.1 types are inherited from it. It has metaclass that + automatically adds ``__slots__`` to all inherited classes. """ __slots__ = ( "tag", @@ -649,6 +697,8 @@ class Obj(object): @property def ready(self): # pragma: no cover + """Is object ready to be encoded? + """ raise NotImplementedError() def _assert_ready(self): @@ -657,9 +707,13 @@ class Obj(object): @property def decoded(self): + """Is object decoded? + """ return self.llen > 0 def copy(self): # pragma: no cover + """Make a copy of object, safe to be mutated + """ raise NotImplementedError() @property @@ -686,6 +740,14 @@ class Obj(object): return b"".join((self._expl, len_encode(len(raw)), raw)) def decode(self, data, offset=0, leavemm=False, decode_path=()): + """Decode the data + + :param data: either binary or memoryview + :param int offset: initial data's offset + :param bool leavemm: do we need to leave memoryview of remaining + data as is, or convert it to bytes otherwise + :returns: (Obj, remaining data) + """ tlv = memoryview(data) if self._expl is None: obj, tail = self._decode( @@ -888,6 +950,15 @@ def pp_console_blob(pp): def pprint(obj, oids=None, big_blobs=False): + """Pretty print object + + :param Obj obj: object you want to pretty print + :param oids: ``OID <-> humand readable string`` dictionary. When OID + from it is met, then its humand readable form is printed + :param big_blobs: if large binary objects are met (like OctetString + values), do we need to print them too, on separate + lines + """ def _pprint_pps(pps): for pp in pps: if hasattr(pp, "_fields"): @@ -921,10 +992,6 @@ class Boolean(Obj): True >>> bool(b) True - >>> Boolean(optional=True) - BOOLEAN OPTIONAL - >>> Boolean(impl=tag_ctxp(1), default=False) - [1] BOOLEAN False OPTIONAL DEFAULT """ __slots__ = () tag_default = tag_encode(1) @@ -1119,10 +1186,6 @@ class Integer(Obj): True >>> int(b) -123 - >>> Integer(optional=True) - INTEGER OPTIONAL - >>> Integer(impl=tag_ctxp(1), default=123) - [1] INTEGER 123 OPTIONAL DEFAULT >>> Integer(2, bounds=(1, 3)) INTEGER 2 @@ -3275,6 +3338,86 @@ class Any(Obj): ######################################################################## class Sequence(Obj): + """``SEQUENCE`` structure type + + You have to make specification of sequence:: + + class Extension(Sequence): + __slots__ = () + schema = ( + ("extnID", ObjectIdentifier()), + ("critical", Boolean(default=False)), + ("extnValue", OctetString()), + ) + + Then, you can work with it as with dictionary. + + >>> ext = Extension() + >>> Extension().specs + OrderedDict([ + ('extnID', OBJECT IDENTIFIER), + ('critical', BOOLEAN False OPTIONAL DEFAULT), + ('extnValue', OCTET STRING), + ]) + >>> ext["extnID"] = "1.2.3" + Traceback (most recent call last): + pyderasn.InvalidValueType: invalid value type, expected: + >>> ext["extnID"] = ObjectIdentifier("1.2.3") + + You can know if sequence is ready to be encoded: + + >>> ext.ready + False + >>> ext.encode() + Traceback (most recent call last): + pyderasn.ObjNotReady: object is not ready: extnValue + >>> ext["extnValue"] = OctetString(b"foobar") + >>> ext.ready + True + + Value you want to assign, must have the same **type** as in + corresponding specification, but it can have different tags, + optional/default attributes -- they will be taken from specification + automatically:: + + class TBSCertificate(Sequence): + schema = ( + ("version", Version(expl=tag_ctxc(0), default="v1")), + [...] + + >>> tbs = TBSCertificate() + >>> tbs["version"] = Version("v2") # no need to explicitly add ``expl`` + + You can know if value exists/set in the sequence and take its value: + + >>> "extnID" in ext, "extnValue" in ext, "critical" in ext + (True, True, False) + >>> ext["extnID"] + OBJECT IDENTIFIER 1.2.3 + + But pay attention that if value has default, then it won't be (not + in) in the sequence (because ``DEFAULT`` must not be encoded in + DER), but you can read its value: + + >>> "critical" in ext, ext["critical"] + (False, BOOLEAN False) + >>> ext["critical"] = Boolean(True) + >>> "critical" in ext, ext["critical"] + (True, BOOLEAN True) + + All defaulted values are always optional. + + .. warning:: + + When decoded DER contains defaulted value inside, then + technically this is not valid DER encoding. But we allow + and pass it. Of course reencoding of that kind of DER will + result in different binary representation (validly without + defaulted value inside). + + Two sequences are equal if they have equal specification (schema), + implicit/explicit tagging and the same values. + """ __slots__ = ("specs",) tag_default = tag_encode(form=TagFormConstructed, num=16) asn1_type_name = "SEQUENCE" @@ -3518,6 +3661,10 @@ class Sequence(Obj): class Set(Sequence): + """``SET`` structure type + + Its usage is identical to :py:class:`pyderasn.Sequence`. + """ __slots__ = () tag_default = tag_encode(form=TagFormConstructed, num=17) asn1_type_name = "SET" @@ -3601,6 +3748,35 @@ class Set(Sequence): class SequenceOf(Obj): + """``SEQUENCE OF`` sequence type + + For that kind of type you must specify the object it will carry on + (bounds are for example here, not required):: + + class Ints(SequenceOf): + schema = Integer() + bounds = (0, 2) + + >>> ints = Ints() + >>> ints.append(Integer(123)) + >>> ints.append(Integer(234)) + >>> ints + Ints SEQUENCE OF[INTEGER 123, INTEGER 234] + >>> [int(i) for i in ints] + [123, 234] + >>> ints.append(Integer(345)) + Traceback (most recent call last): + pyderasn.BoundsError: unsatisfied bounds: 0 <= 3 <= 2 + >>> ints[1] + INTEGER 234 + >>> ints[1] = Integer(345) + >>> ints + Ints SEQUENCE OF[INTEGER 123, INTEGER 345] + + Also you can initialize sequence with preinitialized values: + + >>> ints = Ints([Integer(123), Integer(234)]) + """ __slots__ = ("spec", "_bound_min", "_bound_max") tag_default = tag_encode(form=TagFormConstructed, num=16) asn1_type_name = "SEQUENCE OF" @@ -3841,6 +4017,10 @@ class SequenceOf(Obj): class SetOf(SequenceOf): + """``SET OF`` sequence type + + Its usage is identical to :py:class:`pyderasn.SequenceOf`. + """ __slots__ = () tag_default = tag_encode(form=TagFormConstructed, num=17) asn1_type_name = "SET OF" @@ -3884,7 +4064,7 @@ def main(): # pragma: no cover parser.add_argument( "DERFile", type=argparse.FileType("rb"), - help="Python path to schema definition to use", + help="Path to DER file you want to decode", ) args = parser.parse_args() der = memoryview(args.DERFile.read()) diff --git a/pyderasn.pyi b/pyderasn.pyi index 83f0032..702a115 100644 --- a/pyderasn.pyi +++ b/pyderasn.pyi @@ -183,6 +183,7 @@ class Boolean(Obj): tag_default = ... # type: bytes asn1_type_name = ... # type: str default = ... # type: "Boolean" + optional = ... # type: bool def __init__( self, @@ -215,6 +216,7 @@ class Integer(Obj): asn1_type_name = ... # type: str specs = ... # type: Dict[str, int] default = ... # type: "Integer" + optional = ... # type: bool def __init__( self, @@ -254,6 +256,7 @@ class BitString(Obj): asn1_type_name = ... # type: str specs = ... # type: Dict[str, int] default = ... # type: "BitString" + optional = ... # type: bool def __init__( self, @@ -293,6 +296,7 @@ class OctetString(Obj): tag_default = ... # type: bytes asn1_type_name = ... # type: str default = ... # type: "OctetString" + optional = ... # type: bool def __init__( self, @@ -328,6 +332,7 @@ class Null(Obj): tag_default = ... # type: bytes asn1_type_name = ... # type: str default = ... # type: "Null" + optional = ... # type: bool def __init__( self, @@ -355,6 +360,7 @@ class ObjectIdentifier(Obj): tag_default = ... # type: bytes asn1_type_name = ... # type: str default = ... # type: "ObjectIdentifier" + optional = ... # type: bool def __init__( self, @@ -391,6 +397,7 @@ class Enumerated(Integer): tag_default = ... # type: bytes asn1_type_name = ... # type: str default = ... # type: "Enumerated" + optional = ... # type: bool def __init__( self, @@ -426,6 +433,7 @@ class UTF8String(CommonString): encoding = ... # type: str asn1_type_name = ... # type: str default = ... # type: "UTF8String" + optional = ... # type: bool def __init__( self, @@ -445,6 +453,7 @@ class NumericString(CommonString): encoding = ... # type: str asn1_type_name = ... # type: str default = ... # type: "NumericString" + optional = ... # type: bool def __init__( self, @@ -462,6 +471,7 @@ class PrintableString(CommonString): encoding = ... # type: str asn1_type_name = ... # type: str default = ... # type: "PrintableString" + optional = ... # type: bool def __init__( self, @@ -479,6 +489,7 @@ class TeletexString(CommonString): encoding = ... # type: str asn1_type_name = ... # type: str default = ... # type: "TeletexString" + optional = ... # type: bool def __init__( self, @@ -494,6 +505,7 @@ class TeletexString(CommonString): class T61String(TeletexString): asn1_type_name = ... # type: str default = ... # type: "T61String" + optional = ... # type: bool class VideotexString(CommonString): @@ -501,6 +513,7 @@ class VideotexString(CommonString): encoding = ... # type: str asn1_type_name = ... # type: str default = ... # type: "VideotexString" + optional = ... # type: bool def __init__( self, @@ -518,6 +531,7 @@ class IA5String(CommonString): encoding = ... # type: str asn1_type_name = ... # type: str default = ... # type: "IA5String" + optional = ... # type: bool def __init__( self, @@ -535,6 +549,7 @@ class UTCTime(CommonString): encoding = ... # type: str asn1_type_name = ... # type: str default = ... # type: "UTCTime" + optional = ... # type: bool def __init__( self, @@ -554,6 +569,7 @@ class GeneralizedTime(UTCTime): tag_default = ... # type: bytes asn1_type_name = ... # type: str default = ... # type: "GeneralizedTime" + optional = ... # type: bool def todatetime(self) -> datetime: ... @@ -563,6 +579,7 @@ class GraphicString(CommonString): encoding = ... # type: str asn1_type_name = ... # type: str default = ... # type: "GraphicString" + optional = ... # type: bool def __init__( self, @@ -580,6 +597,7 @@ class VisibleString(CommonString): encoding = ... # type: str asn1_type_name = ... # type: str default = ... # type: "VisibleString" + optional = ... # type: bool def __init__( self, @@ -595,6 +613,7 @@ class VisibleString(CommonString): class ISO646String(VisibleString): asn1_type_name = ... # type: str default = ... # type: "ISO646String" + optional = ... # type: bool class GeneralString(CommonString): @@ -602,6 +621,7 @@ class GeneralString(CommonString): encoding = ... # type: str asn1_type_name = ... # type: str default = ... # type: "GeneralString" + optional = ... # type: bool def __init__( self, @@ -619,6 +639,7 @@ class UniversalString(CommonString): encoding = ... # type: str asn1_type_name = ... # type: str default = ... # type: "UniversalString" + optional = ... # type: bool def __init__( self, @@ -636,6 +657,7 @@ class BMPString(CommonString): encoding = ... # type: str asn1_type_name = ... # type: str default = ... # type: "BMPString" + optional = ... # type: bool def __init__( self, @@ -653,6 +675,7 @@ class Choice(Obj): asn1_type_name = ... # type: str specs = ... # type: Dict[str, Obj] default = ... # type: "Choice" + optional = ... # type: bool def __init__( self, @@ -703,6 +726,7 @@ class Any(Obj): tag_default = ... # type: bytes asn1_type_name = ... # type: str default = ... # type: "Any" + optional = ... # type: bool def __init__( self, @@ -736,6 +760,7 @@ class Sequence(Obj): asn1_type_name = ... # type: str specs = ... # type: Dict[str, Obj] default = ... # type: "Sequence" + optional = ... # type: bool def __init__( self, @@ -771,6 +796,7 @@ class Set(Sequence): tag_default = ... # type: bytes asn1_type_name = ... # type: str default = ... # type: "Set" + optional = ... # type: bool class SequenceOf(Obj): @@ -778,6 +804,7 @@ class SequenceOf(Obj): asn1_type_name = ... # type: str spec = ... # type: Obj default = ... # type: "SequenceOf" + optional = ... # type: bool def __init__( self, @@ -817,6 +844,7 @@ class SetOf(SequenceOf): tag_default = ... # type: bytes asn1_type_name = ... # type: str default = ... # type: "SetOf" + optional = ... # type: bool def obj_by_path(pypath: str) -> TAny: ... -- 2.44.0