From patchwork Sat Jan 28 19:52:02 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: [esnacc-dev,1/2] python: Introduce Python library X-Patchwork-Submitter: Aaron Conole X-Patchwork-Id: 8 Message-Id: <1485633123-4103-2-git-send-email-aconole@bytheb.org> To: dev@lists.esnacc.org Date: Sat, 28 Jan 2017 14:52:02 -0500 From: Aaron Conole List-Id: eSNACC Development discussion This commit introduces ASN.1 primitive types for ASN.1 Integer, Enumerated, Octet-String, RelativeOID, OID, Bit-String, and the various collected string types. Additionally, a tiny buffer system is added. Signed-off-by: Aaron Conole --- NEWS | 1 + py-lib/.gitignore | 1 + py-lib/esnacc/__init__.py | 7 + py-lib/esnacc/asn_base.py | 189 +++++++++++++++++++ py-lib/esnacc/asn_bool.py | 53 ++++++ py-lib/esnacc/asn_buffer.py | 124 +++++++++++++ py-lib/esnacc/asn_ints.py | 356 ++++++++++++++++++++++++++++++++++++ py-lib/esnacc/asn_list.py | 98 ++++++++++ py-lib/esnacc/asn_octs.py | 112 ++++++++++++ py-lib/esnacc/asn_useful.py | 35 ++++ py-lib/esnacctests/__init__.py | 7 + py-lib/esnacctests/asn_ints_test.py | 192 +++++++++++++++++++ 12 files changed, 1175 insertions(+) create mode 100644 py-lib/.gitignore create mode 100644 py-lib/esnacc/__init__.py create mode 100644 py-lib/esnacc/asn_base.py create mode 100644 py-lib/esnacc/asn_bool.py create mode 100644 py-lib/esnacc/asn_buffer.py create mode 100644 py-lib/esnacc/asn_ints.py create mode 100644 py-lib/esnacc/asn_list.py create mode 100644 py-lib/esnacc/asn_octs.py create mode 100644 py-lib/esnacc/asn_useful.py create mode 100644 py-lib/esnacctests/__init__.py create mode 100755 py-lib/esnacctests/asn_ints_test.py diff --git a/NEWS b/NEWS index 4f4ad5d..5e1c2dd 100644 --- a/NEWS +++ b/NEWS @@ -6,6 +6,7 @@ Future * c++-lib: IOManipulator based encoding/decoding * documentation: Developer's guides * esnacc-logo: A mascot (and CC-BY license) was added for esnacc +* py-lib: Introduce a first cut at a python back-end 1.80 diff --git a/py-lib/.gitignore b/py-lib/.gitignore new file mode 100644 index 0000000..0d20b64 --- /dev/null +++ b/py-lib/.gitignore @@ -0,0 +1 @@ +*.pyc diff --git a/py-lib/esnacc/__init__.py b/py-lib/esnacc/__init__.py new file mode 100644 index 0000000..c274353 --- /dev/null +++ b/py-lib/esnacc/__init__.py @@ -0,0 +1,7 @@ +# Copyright (C) 2016, Aaron Conole +# +# Licensed under the terms of the GNU General Public License +# agreement (version 2 or greater). Alternately, may be licensed +# under the terms of the ESNACC Public License. +# +# This file intentionally blank... diff --git a/py-lib/esnacc/asn_base.py b/py-lib/esnacc/asn_base.py new file mode 100644 index 0000000..5e88247 --- /dev/null +++ b/py-lib/esnacc/asn_base.py @@ -0,0 +1,189 @@ +# Copyright (C) 2016, Aaron Conole +# +# Licensed under the terms of the GNU General Public License +# agreement (version 2 or greater). Alternately, may be licensed +# under the terms of the ESNACC Public License. +# +# ASN.1 Base type for eSNACC + +from asn_buffer import AsnBuf +from asn_buffer import BDecDefLen +from asn_buffer import BEncDefLen + +class Constraint(object): + def __init__(self): + pass + + def withinConstraint(self, value): + raise NotImplementedError + + +class BERConsts(object): + BER_ANY_CLASS = -2 + BER_NULL_CLASS = -1 + BER_UNIVERSAL_CLASS = 0 + BER_APPLICATION_CLASS = 1 << 6 + BER_CONTEXTUAL_CLASS = 2 << 6 + BER_PRIVATE_CLASS = 3 << 6 + + UNIV = BER_UNIVERSAL_CLASS + APPL = BER_APPLICATION_CLASS + CNTX = BER_CONTEXTUAL_CLASS + PRIV = BER_PRIVATE_CLASS + + BER_ANY_FORM = -2 + BER_NULL_FORM = -1 + BER_PRIMITIVE_FORM = 0 + BER_CONSTRUCTED_FORM = 1 << 5 + + PRIM = BER_PRIMITIVE_FORM + CONS = BER_CONSTRUCTED_FORM + + @staticmethod + def MakeTag(cls, frm, tag): + fnl = cls & 0xff + fnl |= (frm & 0xff) + + def intcnv(f): + return chr(int(f)) + + if tag < 31: + fnl |= tag & 0xff + fnl = chr(fnl) + else: + tg = intcnv(tag & 0x7f) + tag >>= 7 + while tag: + tg = intcnv(0x80|(tag & 0x7f)) + tg + tag >>= 7 + fnl = intcnv(fnl|0x1f) + tg + + return int(fnl.encode('hex'), 16) + + NO_TAG_CODE = 0 + BOOLEAN_TAG_CODE = 1 + INTEGER_TAG_CODE = 2 + BITSTRING_TAG_CODE = 3 + OCTETSTRING_TAG_CODE = 4 + NULLTYPE_TAG_CODE = 5 + OID_TAG_CODE = 6 + OD_TAG_CODE = 7 + EXTERNAL_TAG_CODE = 8 + REAL_TAG_CODE = 9 + ENUM_TAG_CODE = 10 + UTF8STRING_TAG_CODE = 12 + RELATIVE_OID_TAG_CODE = 13 + SEQ_TAG_CODE = 16 + SET_TAG_CODE = 17 + NUMERICSTRING_TAG_CODE = 18 + PRINTABLESTRING_TAG_CODE = 19 + TELETEXSTRING_TAG_CODE = 20 + VIDEOTEXSTRING_TAG_CODE = 21 + IA5STRING_TAG_CODE = 22 + UTCTIME_TAG_CODE = 23 + GENERALIZEDTIME_TAG_CODE = 24 + GRAPHICSTRING_TAG_CODE = 25 + VISIBLESTRING_TAG_CODE = 26 + GENERALSTRING_TAG_CODE = 27 + UNIVERSALSTRING_TAG_CODE = 28 + BMPSTRING_TAG_CODE = 30 + + +class AsnBase(object): + + BER_CLASS = BERConsts.BER_UNIVERSAL_CLASS + BER_FORM = BERConsts.BER_PRIMITIVE_FORM + + def __init__(self): + pass + + def typename(self): + raise NotImplementedError + + def passesAllConstraints(self, constraints): + return True + + def addConstraint(self, constraint): + try: + self.constraints.append(constraint) + except AttributeError: + self.constraints = [] + self.constraints.append(constraint) + + def BEncContent(self, asnbuf): + raise NotImplementedError + + def BDecContent(self, asnbuf, contentlen): + raise NotImplementedError + + def BEnc(self, asnbuf): + try: + if len(self.constraints) > 0 and \ + not self.passesAllConstraints(self.constraints): + raise ValueError("Not all constraints passed") + except AttributeError: + pass + except TypeError: + pass + + newbuf = AsnBuf() + asnlen = self.BEncContent(newbuf) + asnlen += BEncDefLen(newbuf, asnlen) + TAG_CODE = BERConsts.MakeTag(self.BER_CLASS & 0xff, + self.BER_FORM & 0xff, + self.BER_TAG & 0xff) + asnlen += newbuf.PutBufReverse(TAG_CODE) + asnbuf.PutBuf(newbuf.buf) + return asnlen + + def BDecTag(self, asnbuf, asnlen): + al = 1 + bufTag = ord(asnbuf.Buffer()[0]) + lastTag = bufTag + + if (lastTag & 0x1f) != 0x1f: + return bufTag, al + + lastTag = ord(asnbuf.Buffer()[al]) + while lastTag & 0x80: + lastTag = ord(asnbuf.Buffer()[al]) + bufTag <<= 8 + bufTag |= lastTag + al += 1 + return bufTag, al + + def BDec(self, asnbuf, asnlen): + bufTag, al = self.BDecTag(asnbuf, asnlen) + + PRIM_TAG = BERConsts.MakeTag(self.BER_CLASS & 0xff, + BERConsts.BER_PRIMITIVE_FORM & 0xff, + self.BER_TAG & 0xff) + CONS_TAG = BERConsts.MakeTag(self.BER_CLASS & 0xff, + BERConsts.BER_CONSTRUCTED_FORM & 0xff, + self.BER_TAG & 0xff) + + if bufTag != PRIM_TAG and bufTag != CONS_TAG: + raise IOError("Invalid bytes 0x%x expected [0x%x|0x%x]" % + (bufTag, PRIM_TAG, CONS_TAG)) + + asnbuf.swallow(al) + asnlen += al + tag_total_len, totalBytesLength = BDecDefLen(asnbuf) + asnlen += tag_total_len + asnlen += totalBytesLength + self.BDecContent(asnbuf, totalBytesLength) + return asnlen + + def BEncPdu(self, asnbuf, asnlen): + try: + asnlen = self.BEnc(asnbuf) + except Exception: + return False + return True, asnlen + + def BDecPdu(self, asnbuf, asnlen): + try: + asnlen = self.BDec(asnbuf, asnlen) + except Exception: + return False + return True, asnlen diff --git a/py-lib/esnacc/asn_bool.py b/py-lib/esnacc/asn_bool.py new file mode 100644 index 0000000..5646e5a --- /dev/null +++ b/py-lib/esnacc/asn_bool.py @@ -0,0 +1,53 @@ +# Copyright (C) 2016, Aaron Conole +# +# Licensed under the terms of the GNU General Public License +# agreement (version 2 or greater). Alternately, may be licensed +# under the terms of the ESNACC Public License. +# +# ASN.1 Boolean for eSNACC + +from asn_base import BERConsts +from asn_ints import AsnInt + +def convert_to_bool(value): + if value is None: + return False + + if isinstance(value, int) or isinstance(value, float): + if value == 0: + return False + return True + + if isinstance(value, basestring): + if value.lower() != "true": + return False + return True + + return False + +class AsnBool(AsnInt): + + BER_TAG = BERConsts.BOOLEAN_TAG_CODE + + def __init__(self, value=None): + self.value(value) + + def value(self, newval=None): + if convert_to_bool(newval): + self.intVal = 0xff + else: + self.intVal = 0 + return convert_to_bool(newval) + + def typename(self): + return "AsnBool" + + def __int__(self): + if convert_to_bool(self.value()): + return 0xff + return 0 + + def __str__(self): + if convert_to_bool(self.value()): + return 'True' + return 'False' diff --git a/py-lib/esnacc/asn_buffer.py b/py-lib/esnacc/asn_buffer.py new file mode 100644 index 0000000..553968a --- /dev/null +++ b/py-lib/esnacc/asn_buffer.py @@ -0,0 +1,124 @@ +# Copyright (C) 2016, Aaron Conole +# +# Licensed under the terms of the GNU General Public License +# agreement (version 2 or greater). Alternately, may be licensed +# under the terms of the ESNACC Public License. +# +# ASN.1 Buffer library for eSNACC + + +def ints2octs(listOfInts): + return [chr(int(x)) for x in listOfInts] + + +def BEncDefLen(asnbuf, length): + if length < 128: + asnbuf.PutBufReverse(chr(length & 0xff)) + return 1 + elif length < 256: + asnbuf.PutBufReverse(chr(length & 0xff)) + asnbuf.PutBufReverse(chr(0x81)) + return 2 + elif length < 65536: + asnbuf.PutBufReverse(chr(length & 0xff)) + asnbuf.PutBufReverse(chr((length & 0xff00) >> 8)) + asnbuf.PutBufReverse(chr(0x82)) + return 3 + elif length < 16777126: + asnbuf.PutBufReverse(chr(length & 0xff)) + asnbuf.PutBufReverse(chr((length & 0xff00) >> 8)) + asnbuf.PutBufReverse(chr((length & 0xff0000) >> 16)) + asnbuf.PutBufReverse(chr(0x83)) + return 4 + else: + asnbuf.PutBufReverse(chr(length & 0xff)) + asnbuf.PutBufReverse(chr((length & 0xff00) >> 8)) + asnbuf.PutBufReverse(chr((length & 0xff0000) >> 16)) + asnbuf.PutBufReverse(chr((length & 0xff000000) >> 24)) + asnbuf.PutBufReverse(chr(0x84)) + return 5 + + +def BDecDefLen(asnbuf): + lenByte = ord(asnbuf.GetByte()) + if lenByte < 128: + return 1, lenByte + elif lenByte == 0x80: + raise IOError("INDEFINITE length not supported") + + bytesTotal = lenByte & 0x7f + lenByte = 0 + for b in range(bytesTotal): + lenByte = lenByte << 8 + lenByte += ord(asnbuf.GetByte()) + return bytesTotal, lenByte + + +class AsnBuf(object): + + def __init__(self, bufbytes=None): + if bufbytes is None: + self.buf = [] + else: + if isinstance(bufbytes, basestring): + self.buf = list(bufbytes) + elif isinstance(bufbytes, file): + self.buf = list(bufbytes.read()) + else: + self.buf = bufbytes + + def __len__(self): + return len(self.buf) + + def PutBufReverse(self, listOf): + length = 0 + if isinstance(listOf, list): + for x in reversed(listOf): + length += self.PutBufReverse(x) + else: + self.buf.insert(0, listOf) + length = 1 + return length + + def PutBufIntsReverse(self, listOfInts): + return self.PutBufReverse(ints2octs(listOfInts)) + + def PutBuf(self, listOf): + length = 0 + if isinstance(listOf, list): + for x in listOf: + length += self.PutBuf(x) + else: + self.buf.append(listOf) + length = 1 + return length + + def PutBufInts(self, listOfInts): + return self.PutBuf(ints2octs(listOfInts)) + + def Buffer(self): + return self.buf + + def swallow(self, num): + if len(self.buf) < num: + raise ValueError("Too many bytes to swallow") + + for x in range(num): + del self.buf[0] + + def GetByte(self): + ret = self.buf[0] + self.swallow(1) + return ret + + def GetSeg(self, length): + ret = [] + + if len(self.buf) < length: + raise ValueError("Too many bytes specified") + + for x in range(length): + ret.append(self.buf[0]) + del self.buf[0] + + return ret diff --git a/py-lib/esnacc/asn_ints.py b/py-lib/esnacc/asn_ints.py new file mode 100644 index 0000000..db1b77e --- /dev/null +++ b/py-lib/esnacc/asn_ints.py @@ -0,0 +1,356 @@ +# Copyright (C) 2016, Aaron Conole +# +# Licensed under the terms of the GNU General Public License +# agreement (version 2 or greater). Alternately, may be licensed +# under the terms of the ESNACC Public License. +# +# ASN.1 Integer Types for eSNACC + +from asn_base import AsnBase +from asn_base import Constraint +from asn_base import BERConsts +from asn_buffer import AsnBuf +import math + + +class IntegerConstraint(Constraint): + def __init__(self, lowerBound=None, upperBound=None): + self.lower = lowerBound + self.upper = upperBound + + def withinConstraint(self, value): + if self.lower is not None and value is not None and value < self.lower: + return False + if self.upper is not None and value is not None and value > self.upper: + return False + return True + + +def isfundamental_int_type(value): + if isinstance(value, int) or isinstance(value, float) or \ + isinstance(value, basestring): + return True + return False + + +class AsnInt(AsnBase): + + BER_TAG = BERConsts.INTEGER_TAG_CODE + + def __init__(self, value=None): + self.constraints = None + if value is None: + self.intVal = 0 + elif isfundamental_int_type(value): + self.intVal = int(value) + elif isinstance(value, AsnInt): + self.intVal = value.intVal + else: + raise ValueError("AsnInt - bad value") + + def __bool__(self): + if self.intVal == 0: + return False + return True + + __nonzero__=__bool__ + + def __cmp__(self, obj): + cmpValA = self.intVal + if isinstance(obj, int): + cmpValB = obj + elif isinstance(obj, AsnInt): + cmpValB = obj.intVal + elif isinstance(obj, basestring) or isinstance(obj, float): + cmpValB = int(obj) + else: + raise TypeError("Compare with int, or AsnInt") + return cmp(cmpValA, cmpValB) + + def value(self, newval=None): + if newval is not None: + if isinstance(newval, int) or isinstance(newval, float) or \ + isinstance(newval, basestring): + self.intVal = int(newval) + elif isinstance(newval, AsnInt): + self.intVal = newval.intVal + return self.intVal + + def __int__(self): + return self.value() + + def __float__(self): + return float(self.intVal) + + def __add__(self, obj): + if isinstance(obj, AsnInt): + return self.intVal + obj.intVal + return self.intVal + int(obj) + + def __iadd__(self, obj): + if isinstance(obj, AsnInt): + self.intVal += obj.intVal + else: + self.intVal += int(obj) + return self + + def __sub__(self, obj): + if isinstance(obj, AsnInt): + return self.intVal - obj.intVal + return self.intVal - int(obj) + + def __isub__(self, obj): + if isinstance(obj, AsnInt): + self.intVal -= obj.intVal + else: + self.intVal -= int(obj) + return self + + def __mul__(self, obj): + if isinstance(obj, AsnInt): + return self.intVal * obj.intVal + return self.intVal * obj + + def __imul__(self, obj): + if isinstance(obj, AsnInt): + self.intVal *= obj.intVal + else: + self.intVal *= obj + return self + + def __str__(self): + return str(self.value()) + + def typename(self): + return "AsnInt" + + def passesAllConstraints(self, constraints): + if constraints is None: + return True + if isinstance(constraints, IntegerConstraint): + return constraints.withinConstraint(self.intVal) + elif isinstance(constraints, list): + for x in constraints: + if not self.passesAllConstraints(x): + return False + return True + + def BEncContent(self, asnbuf): + if not isinstance(asnbuf, AsnBuf): + raise ValueError("Buffer must be esnacc.asn_buf.AsnBuf") + + octets = [] + value = int(self) + while 1: + octets.insert(0, value & 0xff) + if value == 0 or value == -1: + break + value = value >> 8 + if value == 0 and octets[0] & 0x80: + octets.insert(0, 0) + while len(octets) > 1 and \ + (octets[0] == 0 and octets[1] & 0x80 == 0 or + octets[0] == 0xff and octets[1] & 0x80 != 0): + del octets[0] + + asnbuf.PutBufIntsReverse(octets) + return len(octets) + + def BDecContent(self, asnbuf, intlen): + buf = ord(asnbuf.Buffer()[0]) + result = 0 + if buf & 0x80: + result = -1 + + for x in range(intlen): + result <<= 8 + result |= ord(asnbuf.GetByte()) + self.intVal = result + return intlen + + +class EnumeratedConstraint(Constraint): + def __init__(self, listOfEnumerations): + self.enums = listOfEnumerations + + def withinConstraint(self, value): + if value not in self.enums: + return False + return True + + +class AsnEnum(AsnInt): + BER_TAG = BERConsts.ENUM_TAG_CODE + + def __init__(self, value=None): + self.constraints = None + if value is not None: + self.intVal = value + + def typename(self): + return "AsnEnum" + + def passesAllConstraints(self, constraints): + if constraints is None: + return True + if isinstance(constraints, IntegerConstraint) \ + or isinstance(constraints, EnumeratedConstraint): + return constraints.withinConstraint(self.intVal) + elif isinstance(constraints, list): + for x in constraints: + if not self.passesAllConstraints(x): + return False + return True + + +class AsnReal(AsnBase): + + BER_TAG = BERConsts.REAL_TAG_CODE + + def __bool__(self): + if self.floatVal == 0: + return False + return True + + def __int__(self): + return int(self.value()) + + def __float__(self): + return self.value() + + def __add__(self, obj): + if isinstance(obj, AsnReal): + return self.floatVal + obj.floatVal + return self.floatVal + float(obj) + + def __iadd__(self, obj): + if isinstance(obj, AsnReal): + self.floatVal += obj.floatVal + else: + self.floatVal += float(obj) + return self + + def __sub__(self, obj): + if isinstance(obj, AsnReal): + return self.floatVal - obj.floatVal + return self.floatVal - float(obj) + + def __isub__(self, obj): + if isinstance(obj, AsnReal): + self.floatVal -= obj.floatVal + else: + self.floatVal -= float(obj) + return self + + def __mul__(self, obj): + if isinstance(obj, AsnReal): + return self.floatVal * obj.floatVal + return self.floatVal * obj + + def __imul__(self, obj): + if isinstance(obj, AsnReal): + self.floatVal *= obj.floatVal + else: + self.floatVal *= obj + return self + + def __str__(self): + return str(self.value()) + + __nonzero__=__bool__ + + def __cmp__(self, obj): + cmpValA = self.floatVal + if isinstance(obj, int): + cmpValB = obj + elif isinstance(obj, AsnReal): + cmpValB = obj.floatVal + elif isinstance(obj, basestring) or isinstance(obj, int): + cmpValB = int(obj) + else: + raise TypeError("Compare with float, or AsnReal") + return cmp(cmpValA, cmpValB) + + def typename(self): + return "AsnReal" + + def value(self, newval=None): + if newval is not None: + if isinstance(newval, int) or isinstance(newval, float) or \ + isinstance(newval, basestring): + self.floatVal = float(newval) + elif isinstance(newval, AsnReal): + self.floatVal = newval.floatVal + return self.floatVal + + PLUS_INF = float('+inf') + MINUS_INF = float('-inf') + NOT_A_NUM = float('nan') + + def BEncContent(self, asnbuf): + if not isinstance(asnbuf, AsnBuf): + raise ValueError("Buffer must be esnacc.asn_buf.AsnBuf") + + if self.floatVal is 0.0: + return 0 + + if self.floatVal is AsnReal.PLUS_INF: + asnbuf.PutBuf(chr(0x40)) + return 1 + elif self.floatVal is AsnReal.MINUS_INF: + asnbuf.PutBuf(chr(0x41)) + return 1 + + def intcnv(f): + return chr(int(f)) + + m, e = math.frexp(self.floatVal) + # Time to normalize for base2 encoding + + encF = 0x80 + if m < 0: + encF |= 0x40 + + while m & 0x1 == 0: + m >>= 1 + e += 1 + + scaleF = 0 + while m & 0x1 == 0: + m >>= 1 + scaleF += 1 + + if scaleF > 3: + raise ValueError('Scale Factor overflow') + + encE = intcnv(0 & 0xff) + encF |= scaleF << 2 + if e in (0, -1): + encE = intcnv(e & 0xff) + else: + while e not in (0, -1): + encE = intcnv(e&0xff) + encE + e >>= 8 + if e == 0 and encE[0] != 0 and int(encE[0]) & 0x80: + encE = intcnv(0) + encE + if e == -1 and encE[0] != 0 and int(encE[0]) & 0x80: + encE = intcnv(0xff) + encE + ln = len(encE) + if ln > 0xff: + raise ValueError('Overflow of real value') + if ln > 1: + encF |= ln - 1 + if ln > 3: + encE = intcnv(ln & 0xff) + encE + + encV = intcnv(0) + while m: + encV = intcnv(m & 0xff) + encV + m >>= 8 + + octs = intcnv(encF) + encE + encV + asnbuf.PutBufReverse(octs) + return len(octs) + + def BDecContent(self, asnbuf, length): + raise NotImplementedError('Not ready yet') diff --git a/py-lib/esnacc/asn_list.py b/py-lib/esnacc/asn_list.py new file mode 100644 index 0000000..a77012b --- /dev/null +++ b/py-lib/esnacc/asn_list.py @@ -0,0 +1,98 @@ +# Copyright (C) 2016, Aaron Conole +# +# Licensed under the terms of the GNU General Public License +# agreement (version 2 or greater). Alternately, may be licensed +# under the terms of the ESNACC Public License. +# +# ASN.1 Integer Types for eSNACC + +from asn_base import AsnBase +from asn_base import BERConsts + + +class AsnSetOf(AsnBase): + + BER_TAG = BERConsts.SET_TAG_CODE + BER_FORM = BERConsts.BER_CONSTRUCTED_FORM + + def __init__(self, elemts=None, expected=None): + self.constraints = None + self.elemts = [] + self.expected = expected + if expected is None: + raise ValueError("Cannot decode a bare typed list: specify a type") + if elemts is not None: + self.elemts = elemts + + def __getitem__(self, idx): + if self.elemts is None or idx > len(self.elemts): + raise IndexError("Octet value out of range") + return self.elemts[idx] + + def __contains__(self, a): + if self.elemts is not None: + return a in self.elemts + return None + + def __len__(self): + if self.elemts is not None: + return len(self.elemts) + return 0 + + def __delitem__(self, idx): + if self.elemts is not None: + del self.elemts[idx] + raise IndexError("Out of range") + + def append(self, item): + if isinstance(item, self.expected): + self.elemts.append(item) + else: + raise TypeError("Invalid type, expected: %s" % (self.expected)) + + def __str__(self): + s = "%s" % self.typename() + s += " {" + s += ','.join(str(x) for x in self.elemts) + s += "}" + return s + + def typename(self): + return "AsnSetOf" + + def BEncContent(self, asnbuf): + enclength = 0 + for x in reversed(self.elemts): + enclength += x.BEnc(asnbuf) + + return enclength + + def BDecContent(self, asnbuf, contentlen): + while contentlen > 0: + t = self.expected() + contentlen -= t.BDec(asnbuf, contentlen) + + +class AsnSequenceOf(AsnSetOf): + + BER_TAG = BERConsts.SEQ_TAG_CODE + + def __init__(self, elemts=None, expected=None): + AsnSetOf.__init__(self, elemts, expected) + + def typename(self): + return "AsnSequenceOf" + + def BEncContent(self, asnbuf): + self.elemts.sort() + return AsnSetOf.BEncContent(self, asnbuf) + + def BDecContent(self, asnbuf, contentlen): + ret = AsnSetOf.BDecContent(self, asnbuf, contentlen) + # Generally, an ASN list is unsorted, but a SEQ implies sorted + # - however -, if a remote end decides to give us an unsorted + # list, we'll just do the sort here. + self.elemts.sort() + if ret is None: + ret = 0 + return ret diff --git a/py-lib/esnacc/asn_octs.py b/py-lib/esnacc/asn_octs.py new file mode 100644 index 0000000..f4fec16 --- /dev/null +++ b/py-lib/esnacc/asn_octs.py @@ -0,0 +1,112 @@ +# Copyright (C) 2016, Aaron Conole +# +# Licensed under the terms of the GNU General Public License +# agreement (version 2 or greater). Alternately, may be licensed +# under the terms of the ESNACC Public License. +# +# ASN.1 Integer Types for eSNACC + +from asn_base import AsnBase +from asn_base import BERConsts +from asn_base import Constraint + + +class CharacterValueConstraint(Constraint): + def __init__(self, lowerBound=None, upperBound=None, charList=None): + self.lower = lowerBound + self.upper = upperBound + self.chars = charList + + def withinConstraint(self, value): + if isinstance(value, list) or isinstance(value, basestring): + for x in value: + if self.withinConstraint(x): + return False + if self.lower is not None and value is not None and \ + value < self.lower: + return False + if self.upper is not None and value is not None and \ + value > self.upper: + return False + if self.chars is not None and value is not None and \ + value not in self.chars: + return False + return True + + +class OctetLengthConstraint(Constraint): + def __init__(self, length=None): + self.length = length + + def withinConstraint(self, value): + if isinstance(value, list) or isinstance(value, basestring): + if len(value) <= self.length: + return True + return False + + +class AsnOcts(AsnBase): + BER_TAG = BERConsts.OCTETSTRING_TAG_CODE + + def __init__(self, value=None): + self.constraints = None + self.octs = None + if value is not None: + self.setData(value) + + def __getitem__(self, idx): + if self.octs is None or idx > len(self.octs): + raise IndexError("Octet value out of range") + return self.octs[idx] + + def __contains__(self, a): + if self.octs is not None: + return a in self.octs + return None + + def __len__(self): + if self.octs is not None: + return len(self.octs) + return 0 + + def __delitem__(self, idx): + if self.octs is not None: + del self.octs[idx] + raise IndexError("Out of range") + + def __str__(self): + s = "[" + if self.octs is not None: + s += ' '.join(["%02X" % ord(x) for x in self.octs]) + s += "]" + return s + + def typename(self): + return "AsnOcts" + + def octs(self): + return self.octs + + def setData(self, octets): + if isinstance(octets, list): + self.octs = octets + elif isinstance(octets, basestring): + self.octs = [] + for x in octets: + self.octs.append(chr(ord(x))) + + def BEncContent(self, asnbuf): + asnbuf.PutBufReverse(self.octs) + return len(self.octs) + + def BDecContent(self, asnbuf, contentlen): + self.octs = asnbuf.GetSeg(contentlen) + return len(self.octs) + + +class AsnString(AsnOcts): + def __init__(self, value=None): + self.contains = None + if value is None: + value = "" + self.setData(value) diff --git a/py-lib/esnacc/asn_useful.py b/py-lib/esnacc/asn_useful.py new file mode 100644 index 0000000..b818d4c --- /dev/null +++ b/py-lib/esnacc/asn_useful.py @@ -0,0 +1,35 @@ +# Copyright (C) 2016, Aaron Conole +# +# Licensed under the terms of the GNU General Public License +# agreement (version 2 or greater). Alternately, may be licensed +# under the terms of the ESNACC Public License. +# +# ASN.1 'Useful' Types for eSNACC +# Normally, this should be auto-generated. For now, we'll handcode it +# (because insanity) + +from asn_base import BERConsts +from asn_octs import AsnOcts +from asn_octs import CharacterValueConstraint + + +NumericStrConstraint = \ + CharacterValueConstraint(charList=['0', '1', '2', '3', '4', '5', + '6', '7', '8', '9', ' ']) + + +class NumericString(AsnOcts): + + BER_TAG = BERConsts.NUMERICSTRING_TAG_CODE + + def __init__(self, value=None): + self.constraints = [NumericStrConstraint] + self.octs = value + if self.octs is not None: + self.setData(value) + +class IA5String(AsnOcts): + BER_TAG = BERConsts.IA5STRING_TAG_CODE + + def __init__(self, value=None): + AsnOcts.__init__(self, value) diff --git a/py-lib/esnacctests/__init__.py b/py-lib/esnacctests/__init__.py new file mode 100644 index 0000000..c274353 --- /dev/null +++ b/py-lib/esnacctests/__init__.py @@ -0,0 +1,7 @@ +# Copyright (C) 2016, Aaron Conole +# +# Licensed under the terms of the GNU General Public License +# agreement (version 2 or greater). Alternately, may be licensed +# under the terms of the ESNACC Public License. +# +# This file intentionally blank... diff --git a/py-lib/esnacctests/asn_ints_test.py b/py-lib/esnacctests/asn_ints_test.py new file mode 100755 index 0000000..ea4193f --- /dev/null +++ b/py-lib/esnacctests/asn_ints_test.py @@ -0,0 +1,192 @@ +#!/usr/bin/env python +# Copyright (C) 2016, Aaron Conole +# +# Licensed under the terms of the GNU General Public License +# agreement (version 2 or greater). Alternately, may be licensed +# under the terms of the ESNACC Public License. +# +# ESNACC ASN.1 integer tests + +import unittest +if __package__ is None: + import sys + from os import path + sys.path.append( path.dirname( path.dirname( path.abspath(__file__) ) ) ) + from esnacc import asn_ints + from esnacc import asn_buffer +else: + from ..esnacc import asn_ints + from ..esnacc import asn_buffer + +class TestAsnInts(unittest.TestCase): + def test_operations_default(self): + asninteger = asn_ints.AsnInt(1) + + self.assertTrue(asninteger == 1) + self.assertTrue(asninteger > 0) + self.assertTrue(asninteger < 2) + + asninteger += 1 + + self.assertTrue(asninteger == 2) + self.assertTrue((asninteger * 1) == 2) + self.assertTrue((asninteger - 1) == 1) + + def test_ber_encoder_simple(self): + class testVec(unittest.TestCase): + def __init__(self, number, length, byteCodes): + self.nm = number + self.ln = length + self.bc = byteCodes + + def run(self): + asninteger = asn_ints.AsnInt(self.nm) + asnbuf = asn_buffer.AsnBuf() + + self.assertTrue(asninteger.BEnc(asnbuf) == self.ln) + byteCodes = asnbuf.Buffer() + self.assertTrue(len(byteCodes) == len(self.bc)) + for x in range(len(byteCodes)): + self.assertTrue(self.bc[x] == byteCodes[x]) + return True + + tests = [] + tvn65535codes = [chr(0x2), chr(0x3), chr(0xff), chr(0x0), chr(0x1)] + tests.append(testVec(-65535, 5, tvn65535codes)) + + tvn10codes = [chr(0x2), chr(0x1), chr(0xf6)] + tests.append(testVec(-10, 3, tvn10codes)) + + tv0codes = [chr(0x2), chr(0x1), chr(0x0)] + tests.append(testVec(0, 3, tv0codes)) + + tv1codes = [chr(0x2), chr(0x1), chr(0x1)] + tests.append(testVec(1, 3, tv1codes)) + + tv2codes = [chr(0x2), chr(0x2), chr(0x3f), chr(0xc3)] + tests.append(testVec(16323, 4, tv2codes)) + + tv3codes = [chr(0x2), chr(02), chr(0x7f), chr(0xff)] + tests.append(testVec(32767, 4, tv3codes)) + + tv4codes = [chr(0x2), chr(0x3), chr(0x00), chr(0x80), chr(0x00)] + tests.append(testVec(32768, 5, tv4codes)) + + tv5codes = [chr(0x2), chr(0x3), chr(0x20), chr(0x00), chr(0x00)] + tests.append(testVec(2097152, 5, tv5codes)) + + tv6codes = [chr(0x2), chr(0x4), chr(0x1), chr(0x0), chr(0x0), chr(0x0)] + tests.append(testVec(16777216, 6, tv6codes)) + + for x in tests: + self.assertTrue(x.run()) + + def test_ber_decoder_simple(self): + class testVec(unittest.TestCase): + def __init__(self, number, length, byteCodes): + self.nm = number + self.ln = length + self.bc = byteCodes + + def run(self): + asnbuf = asn_buffer.AsnBuf(self.bc) + asninteger = asn_ints.AsnInt() + self.assertTrue(asninteger.BDec(asnbuf, 0) == self.ln) + self.assertTrue(self.nm == int(asninteger)) + return True + + tests = [] + tvn65535codes = [chr(0x2), chr(0x3), chr(0xff), chr(0x0), chr(0x1)] + tests.append(testVec(-65535, 5, tvn65535codes)) + + tvn10codes = [chr(0x2), chr(0x1), chr(0xf6)] + tests.append(testVec(-10, 3, tvn10codes)) + + tv0codes = [chr(0x2), chr(0x1), chr(0x0)] + tests.append(testVec(0, 3, tv0codes)) + + tv1codes = [chr(0x2), chr(0x1), chr(0x1)] + tests.append(testVec(1, 3, tv1codes)) + + tv2codes = [chr(0x2), chr(0x2), chr(0x3f), chr(0xc3)] + tests.append(testVec(16323, 4, tv2codes)) + + tv3codes = [chr(0x2), chr(02), chr(0x7f), chr(0xff)] + tests.append(testVec(32767, 4, tv3codes)) + + tv4codes = [chr(0x2), chr(0x3), chr(0x00), chr(0x80), chr(0x00)] + tests.append(testVec(32768, 5, tv4codes)) + + tv5codes = [chr(0x2), chr(0x3), chr(0x20), chr(0x00), chr(0x00)] + tests.append(testVec(2097152, 5, tv5codes)) + + tv6codes = [chr(0x2), chr(0x4), chr(0x1), chr(0x0), chr(0x0), chr(0x0)] + tests.append(testVec(16777216, 6, tv6codes)) + + for x in tests: + self.assertTrue(x.run()) + + + def test_constraints(self): + class testVec(unittest.TestCase): + def __init__(self, number): + self.nm = number + + def run(self): + cnstraint_good = asn_ints.IntegerConstraint(self.nm-1, self.nm+1) + asninteger = asn_ints.AsnInt(self.nm) + asninteger.addConstraint(cnstraint_good) + + buf = asn_buffer.AsnBuf() + self.assertTrue(asninteger.BEnc(buf) != 0) + + cnstraint_bad = asn_ints.IntegerConstraint(self.nm+1, self.nm+2) + asninteger.addConstraint(cnstraint_bad) + with self.assertRaises(ValueError): + asninteger.BEnc(buf) + return True + + tests = [] + tests.append(testVec(-65535)) + tests.append(testVec(-10)) + tests.append(testVec(0)) + tests.append(testVec(1)) + tests.append(testVec(16323)) + tests.append(testVec(32767)) + tests.append(testVec(32768)) + tests.append(testVec(2097152)) + tests.append(testVec(16777216)) + + for x in tests: + self.assertTrue(x.run()) + +class TestAsnEnums(unittest.TestCase): + def test_enumeration_encoding(self): + class testVec(unittest.TestCase): + def __init__(self, number, length, byteCodes): + self.nm = number + self.ln = length + self.bc = byteCodes + + def run(self): + asnenum = asn_ints.AsnEnum(self.nm) + asnbuf = asn_buffer.AsnBuf() + + self.assertTrue(asnenum.BEnc(asnbuf) == self.ln) + byteCodes = asnbuf.Buffer() + self.assertTrue(len(byteCodes) == len(self.bc)) + for x in range(len(byteCodes)): + self.assertTrue(self.bc[x] == byteCodes[x]) + return True + + tests = [] + tv0codes = [chr(0xa), chr(0x1), chr(0x0)] + tests.append(testVec(0, 3, tv0codes)) + + for x in tests: + self.assertTrue(x.run()) + + +if __name__ == '__main__': + + unittest.main()