#!/usr/bin/env python
# coding: utf-8
+# cython: language_level=3
# PyDERASN -- Python ASN.1 DER/BER codec with abstract structures
# Copyright (C) 2017-2020 Sergey Matveev <stargrave@stargrave.org>
#
EOC_LEN = len(EOC)
LENINDEF = b"\x80" # length indefinite mark
LENINDEF_PP_CHAR = "I" if PY2 else "∞"
+NAMEDTUPLE_KWARGS = {} if PY2 else {"module": __name__}
+SET01 = frozenset(("0", "1"))
+DECIMAL_SIGNS = ".,"
+
+
+def pureint(value):
+ i = int(value)
+ if (value[0] in "+- ") or (value[-1] == " "):
+ raise ValueError("non-pure integer")
+ return i
+
+def fractions2float(fractions_raw):
+ pureint(fractions_raw)
+ return float("0." + fractions_raw)
########################################################################
"lenindef",
"ber_encoded",
"bered",
-))
+), **NAMEDTUPLE_KWARGS)
def _pp(
"expl_lenindef",
"lenindef",
"ber_encoded",
-))
+), **NAMEDTUPLE_KWARGS)
class Boolean(Obj):
"expl_lenindef",
"lenindef",
"ber_encoded",
-))
+), **NAMEDTUPLE_KWARGS)
class Integer(Obj):
yield pp
-SET01 = frozenset(("0", "1"))
BitStringState = namedtuple("BitStringState", (
"version",
"specs",
"ber_encoded",
"tag_constructed",
"defined",
-))
+), **NAMEDTUPLE_KWARGS)
class BitString(Obj):
"ber_encoded",
"tag_constructed",
"defined",
-))
+), **NAMEDTUPLE_KWARGS)
class OctetString(Obj):
"expl_lenindef",
"lenindef",
"ber_encoded",
-))
+), **NAMEDTUPLE_KWARGS)
class Null(Obj):
"lenindef",
"ber_encoded",
"defines",
-))
-
-
-def pureint(value):
- i = int(value)
- if (value[0] in "+- ") or (value[-1] == " "):
- raise ValueError("non-pure integer")
- return i
+), **NAMEDTUPLE_KWARGS)
class ObjectIdentifier(Obj):
def escape_control_unicode(c):
- if unicat(c).startswith("C"):
+ if unicat(c)[0] == "C":
c = repr(c).lstrip("u").strip("'")
return c
PrintableStringState = namedtuple(
"PrintableStringState",
OctetStringState._fields + ("allowable_chars",),
+ **NAMEDTUPLE_KWARGS
)
LEN_YYYYMMDDHHMMSSZ = len("YYYYMMDDHHMMSSZ")
-def fractions2float(fractions_raw):
- pureint(fractions_raw)
- return float("0." + fractions_raw)
-
-
class VisibleString(CommonString):
__slots__ = ()
tag_default = tag_encode(26)
asn1_type_name = "VisibleString"
-UTCTimeState = namedtuple("UTCTimeState", OctetStringState._fields + ("ber_raw",))
+UTCTimeState = namedtuple(
+ "UTCTimeState",
+ OctetStringState._fields + ("ber_raw",),
+ **NAMEDTUPLE_KWARGS
+)
+
+
+def str_to_time_fractions(value):
+ v = pureint(value)
+ year, v = (v // 10**10), (v % 10**10)
+ month, v = (v // 10**8), (v % 10**8)
+ day, v = (v // 10**6), (v % 10**6)
+ hour, v = (v // 10**4), (v % 10**4)
+ minute, second = (v // 100), (v % 100)
+ return year, month, day, hour, minute, second
class UTCTime(VisibleString):
self.optional = optional
def _strptime_bered(self, value):
- year = pureint(value[:2])
- year += 2000 if year < 50 else 1900
- decoded = datetime(
- year, # %Y
- pureint(value[2:4]), # %m
- pureint(value[4:6]), # %d
- pureint(value[6:8]), # %H
- pureint(value[8:10]), # %M
- )
+ year, month, day, hour, minute, _ = str_to_time_fractions(value[:10] + "00")
value = value[10:]
if len(value) == 0:
raise ValueError("no timezone")
+ year += 2000 if year < 50 else 1900
+ decoded = datetime(year, month, day, hour, minute)
offset = 0
if value[-1] == "Z":
value = value[:-1]
sign = 1
else:
raise ValueError("invalid UTC offset")
- offset = 60 * pureint(value[-2:])
+ v = pureint(value[-4:])
+ offset, v = (60 * (v % 100)), v // 100
if offset >= 3600:
raise ValueError("invalid UTC offset minutes")
- offset += 3600 * pureint(value[-4:-2])
+ offset += 3600 * v
if offset > 14 * 3600:
raise ValueError("too big UTC offset")
offset *= sign
seconds = pureint(value)
if seconds >= 60:
raise ValueError("invalid seconds value")
- decoded += timedelta(seconds=seconds)
- return offset, decoded
+ return offset, decoded + timedelta(seconds=seconds)
def _strptime(self, value):
# datetime.strptime's format: %y%m%d%H%M%SZ
raise ValueError("invalid UTCTime length")
if value[-1] != "Z":
raise ValueError("non UTC timezone")
- year = pureint(value[:2])
+ year, month, day, hour, minute, second = str_to_time_fractions(value[:-1])
year += 2000 if year < 50 else 1900
- return datetime(
- year, # %y
- pureint(value[2:4]), # %m
- pureint(value[4:6]), # %d
- pureint(value[6:8]), # %H
- pureint(value[8:10]), # %M
- pureint(value[10:12]), # %S
- )
+ return datetime(year, month, day, hour, minute, second)
def _dt_sanitize(self, value):
if value.year < 1950 or value.year > 2049:
def _strptime_bered(self, value):
if len(value) < 4 + 3 * 2:
raise ValueError("invalid GeneralizedTime")
- decoded = datetime(
- pureint(value[:4]), # %Y
- pureint(value[4:6]), # %m
- pureint(value[6:8]), # %d
- pureint(value[8:10]), # %H
- )
- value = value[10:]
- offset = 0
+ year, month, day, hour, _, _ = str_to_time_fractions(value[:10] + "0000")
+ decoded = datetime(year, month, day, hour)
+ offset, value = 0, value[10:]
if len(value) == 0:
return offset, decoded
if value[-1] == "Z":
idx = value.rfind(char)
if idx == -1:
continue
- offset_raw = value[idx + 1:].replace(":", "")
- if len(offset_raw) not in (2, 4):
+ offset_raw, value = value[idx + 1:].replace(":", ""), value[:idx]
+ v = pureint(offset_raw)
+ if len(offset_raw) == 4:
+ offset, v = (60 * (v % 100)), v // 100
+ if offset >= 3600:
+ raise ValueError("invalid UTC offset minutes")
+ elif len(offset_raw) == 2:
+ pass
+ else:
raise ValueError("invalid UTC offset")
- value = value[:idx]
- offset = 60 * pureint(offset_raw[2:] or "0")
- if offset >= 3600:
- raise ValueError("invalid UTC offset minutes")
- offset += 3600 * pureint(offset_raw[:2])
+ offset += 3600 * v
if offset > 14 * 3600:
raise ValueError("too big UTC offset")
offset *= sign
break
if len(value) == 0:
return offset, decoded
- decimal_signs = ".,"
- if value[0] in decimal_signs:
+ if value[0] in DECIMAL_SIGNS:
return offset, (
decoded + timedelta(seconds=3600 * fractions2float(value[1:]))
)
value = value[2:]
if len(value) == 0:
return offset, decoded
- if value[0] in decimal_signs:
+ if value[0] in DECIMAL_SIGNS:
return offset, (
decoded + timedelta(seconds=60 * fractions2float(value[1:]))
)
value = value[2:]
if len(value) == 0:
return offset, decoded
- if value[0] not in decimal_signs:
+ if value[0] not in DECIMAL_SIGNS:
raise ValueError("invalid format after seconds")
return offset, (
decoded + timedelta(microseconds=10**6 * fractions2float(value[1:]))
# datetime.strptime's format: %Y%m%d%H%M%SZ
if value[-1] != "Z":
raise ValueError("non UTC timezone")
- return datetime(
- pureint(value[:4]), # %Y
- pureint(value[4:6]), # %m
- pureint(value[6:8]), # %d
- pureint(value[8:10]), # %H
- pureint(value[10:12]), # %M
- pureint(value[12:14]), # %S
- )
+ return datetime(*str_to_time_fractions(value[:-1]))
if l >= LEN_YYYYMMDDHHMMSSDMZ:
# datetime.strptime's format: %Y%m%d%H%M%S.%fZ
if value[-1] != "Z":
if us_len > 6:
raise ValueError("only microsecond fractions are supported")
us = pureint(us + ("0" * (6 - us_len)))
- decoded = datetime(
- pureint(value[:4]), # %Y
- pureint(value[4:6]), # %m
- pureint(value[6:8]), # %d
- pureint(value[8:10]), # %H
- pureint(value[10:12]), # %M
- pureint(value[12:14]), # %S
- us, # %f
- )
- return decoded
+ year, month, day, hour, minute, second = str_to_time_fractions(value[:14])
+ return datetime(year, month, day, hour, minute, second, us)
raise ValueError("invalid GeneralizedTime length")
def _encode_time(self):
"expl_lenindef",
"lenindef",
"ber_encoded",
-))
+), **NAMEDTUPLE_KWARGS)
class Choice(Obj):
"lenindef",
"ber_encoded",
"defined",
-))
+), **NAMEDTUPLE_KWARGS)
class Any(Obj):
"expl_lenindef",
"lenindef",
"ber_encoded",
-))
+), **NAMEDTUPLE_KWARGS)
class Sequence(Obj):
"expl_lenindef",
"lenindef",
"ber_encoded",
-))
+), **NAMEDTUPLE_KWARGS)
class SequenceOf(Obj):