/
core.py
5702 lines (4511 loc) · 168 KB
/
core.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# coding: utf-8
"""
ASN.1 type classes for universal types. Exports the following items:
- load()
- Any()
- Asn1Value()
- BitString()
- BMPString()
- Boolean()
- CharacterString()
- Choice()
- EmbeddedPdv()
- Enumerated()
- GeneralizedTime()
- GeneralString()
- GraphicString()
- IA5String()
- InstanceOf()
- Integer()
- IntegerBitString()
- IntegerOctetString()
- Null()
- NumericString()
- ObjectDescriptor()
- ObjectIdentifier()
- OctetBitString()
- OctetString()
- PrintableString()
- Real()
- RelativeOid()
- Sequence()
- SequenceOf()
- Set()
- SetOf()
- TeletexString()
- UniversalString()
- UTCTime()
- UTF8String()
- VideotexString()
- VisibleString()
- VOID
- Void()
Other type classes are defined that help compose the types listed above.
"""
from __future__ import unicode_literals, division, absolute_import, print_function
from datetime import datetime, timedelta
from fractions import Fraction
import binascii
import copy
import math
import re
import sys
from . import _teletex_codec
from ._errors import unwrap
from ._ordereddict import OrderedDict
from ._types import type_name, str_cls, byte_cls, int_types, chr_cls
from .parser import _parse, _dump_header
from .util import int_to_bytes, int_from_bytes, timezone, extended_datetime, create_timezone, utc_with_dst
if sys.version_info <= (3,):
from cStringIO import StringIO as BytesIO
range = xrange # noqa
_PY2 = True
else:
from io import BytesIO
_PY2 = False
_teletex_codec.register()
CLASS_NUM_TO_NAME_MAP = {
0: 'universal',
1: 'application',
2: 'context',
3: 'private',
}
CLASS_NAME_TO_NUM_MAP = {
'universal': 0,
'application': 1,
'context': 2,
'private': 3,
0: 0,
1: 1,
2: 2,
3: 3,
}
METHOD_NUM_TO_NAME_MAP = {
0: 'primitive',
1: 'constructed',
}
_OID_RE = re.compile(r'^\d+(\.\d+)*$')
# A global tracker to ensure that _setup() is called for every class, even
# if is has been called for a parent class. This allows different _fields
# definitions for child classes. Without such a construct, the child classes
# would just see the parent class attributes and would use them.
_SETUP_CLASSES = {}
def load(encoded_data, strict=False):
"""
Loads a BER/DER-encoded byte string and construct a universal object based
on the tag value:
- 1: Boolean
- 2: Integer
- 3: BitString
- 4: OctetString
- 5: Null
- 6: ObjectIdentifier
- 7: ObjectDescriptor
- 8: InstanceOf
- 9: Real
- 10: Enumerated
- 11: EmbeddedPdv
- 12: UTF8String
- 13: RelativeOid
- 16: Sequence,
- 17: Set
- 18: NumericString
- 19: PrintableString
- 20: TeletexString
- 21: VideotexString
- 22: IA5String
- 23: UTCTime
- 24: GeneralizedTime
- 25: GraphicString
- 26: VisibleString
- 27: GeneralString
- 28: UniversalString
- 29: CharacterString
- 30: BMPString
:param encoded_data:
A byte string of BER or DER-encoded data
:param strict:
A boolean indicating if trailing data should be forbidden - if so, a
ValueError will be raised when trailing data exists
:raises:
ValueError - when strict is True and trailing data is present
ValueError - when the encoded value tag a tag other than listed above
ValueError - when the ASN.1 header length is longer than the data
TypeError - when encoded_data is not a byte string
:return:
An instance of the one of the universal classes
"""
return Asn1Value.load(encoded_data, strict=strict)
def unpickle_helper(asn1crypto_cls, der_bytes):
"""
Helper function to integrate with pickle.
Note that this must be an importable top-level function.
"""
return asn1crypto_cls.load(der_bytes)
class Asn1Value(object):
"""
The basis of all ASN.1 values
"""
# The integer 0 for primitive, 1 for constructed
method = None
# An integer 0 through 3 - see CLASS_NUM_TO_NAME_MAP for value
class_ = None
# An integer 1 or greater indicating the tag number
tag = None
# An alternate tag allowed for this type - used for handling broken
# structures where a string value is encoded using an incorrect tag
_bad_tag = None
# If the value has been implicitly tagged
implicit = False
# If explicitly tagged, a tuple of 2-element tuples containing the
# class int and tag int, from innermost to outermost
explicit = None
# The BER/DER header bytes
_header = None
# Raw encoded value bytes not including class, method, tag, length header
contents = None
# The BER/DER trailer bytes
_trailer = b''
# The native python representation of the value - this is not used by
# some classes since they utilize _bytes or _unicode
_native = None
@classmethod
def load(cls, encoded_data, strict=False, **kwargs):
"""
Loads a BER/DER-encoded byte string using the current class as the spec
:param encoded_data:
A byte string of BER or DER-encoded data
:param strict:
A boolean indicating if trailing data should be forbidden - if so, a
ValueError will be raised when trailing data exists
:return:
An instance of the current class
"""
if not isinstance(encoded_data, byte_cls):
raise TypeError('encoded_data must be a byte string, not %s' % type_name(encoded_data))
spec = None
if cls.tag is not None:
spec = cls
value, _ = _parse_build(encoded_data, spec=spec, spec_params=kwargs, strict=strict)
return value
def __init__(self, explicit=None, implicit=None, no_explicit=False, tag_type=None, class_=None, tag=None,
optional=None, default=None, contents=None, method=None):
"""
The optional parameter is not used, but rather included so we don't
have to delete it from the parameter dictionary when passing as keyword
args
:param explicit:
An int tag number for explicit tagging, or a 2-element tuple of
class and tag.
:param implicit:
An int tag number for implicit tagging, or a 2-element tuple of
class and tag.
:param no_explicit:
If explicit tagging info should be removed from this instance.
Used internally to allow contructing the underlying value that
has been wrapped in an explicit tag.
:param tag_type:
None for normal values, or one of "implicit", "explicit" for tagged
values. Deprecated in favor of explicit and implicit params.
:param class_:
The class for the value - defaults to "universal" if tag_type is
None, otherwise defaults to "context". Valid values include:
- "universal"
- "application"
- "context"
- "private"
Deprecated in favor of explicit and implicit params.
:param tag:
The integer tag to override - usually this is used with tag_type or
class_. Deprecated in favor of explicit and implicit params.
:param optional:
Dummy parameter that allows "optional" key in spec param dicts
:param default:
The default value to use if the value is currently None
:param contents:
A byte string of the encoded contents of the value
:param method:
The method for the value - no default value since this is
normally set on a class. Valid values include:
- "primitive" or 0
- "constructed" or 1
:raises:
ValueError - when implicit, explicit, tag_type, class_ or tag are invalid values
"""
try:
if self.__class__ not in _SETUP_CLASSES:
cls = self.__class__
# Allow explicit to be specified as a simple 2-element tuple
# instead of requiring the user make a nested tuple
if cls.explicit is not None and isinstance(cls.explicit[0], int_types):
cls.explicit = (cls.explicit, )
if hasattr(cls, '_setup'):
self._setup()
_SETUP_CLASSES[cls] = True
# Normalize tagging values
if explicit is not None:
if isinstance(explicit, int_types):
if class_ is None:
class_ = 'context'
explicit = (class_, explicit)
# Prevent both explicit and tag_type == 'explicit'
if tag_type == 'explicit':
tag_type = None
tag = None
if implicit is not None:
if isinstance(implicit, int_types):
if class_ is None:
class_ = 'context'
implicit = (class_, implicit)
# Prevent both implicit and tag_type == 'implicit'
if tag_type == 'implicit':
tag_type = None
tag = None
# Convert old tag_type API to explicit/implicit params
if tag_type is not None:
if class_ is None:
class_ = 'context'
if tag_type == 'explicit':
explicit = (class_, tag)
elif tag_type == 'implicit':
implicit = (class_, tag)
else:
raise ValueError(unwrap(
'''
tag_type must be one of "implicit", "explicit", not %s
''',
repr(tag_type)
))
if explicit is not None:
# Ensure we have a tuple of 2-element tuples
if len(explicit) == 2 and isinstance(explicit[1], int_types):
explicit = (explicit, )
for class_, tag in explicit:
invalid_class = None
if isinstance(class_, int_types):
if class_ not in CLASS_NUM_TO_NAME_MAP:
invalid_class = class_
else:
if class_ not in CLASS_NAME_TO_NUM_MAP:
invalid_class = class_
class_ = CLASS_NAME_TO_NUM_MAP[class_]
if invalid_class is not None:
raise ValueError(unwrap(
'''
explicit class must be one of "universal", "application",
"context", "private", not %s
''',
repr(invalid_class)
))
if tag is not None:
if not isinstance(tag, int_types):
raise TypeError(unwrap(
'''
explicit tag must be an integer, not %s
''',
type_name(tag)
))
if self.explicit is None:
self.explicit = ((class_, tag), )
else:
self.explicit = self.explicit + ((class_, tag), )
elif implicit is not None:
class_, tag = implicit
if class_ not in CLASS_NAME_TO_NUM_MAP:
raise ValueError(unwrap(
'''
implicit class must be one of "universal", "application",
"context", "private", not %s
''',
repr(class_)
))
if tag is not None:
if not isinstance(tag, int_types):
raise TypeError(unwrap(
'''
implicit tag must be an integer, not %s
''',
type_name(tag)
))
self.class_ = CLASS_NAME_TO_NUM_MAP[class_]
self.tag = tag
self.implicit = True
else:
if class_ is not None:
if class_ not in CLASS_NAME_TO_NUM_MAP:
raise ValueError(unwrap(
'''
class_ must be one of "universal", "application",
"context", "private", not %s
''',
repr(class_)
))
self.class_ = CLASS_NAME_TO_NUM_MAP[class_]
if self.class_ is None:
self.class_ = 0
if tag is not None:
self.tag = tag
if method is not None:
if method not in set(["primitive", 0, "constructed", 1]):
raise ValueError(unwrap(
'''
method must be one of "primitive" or "constructed",
not %s
''',
repr(method)
))
if method == "primitive":
method = 0
elif method == "constructed":
method = 1
self.method = method
if no_explicit:
self.explicit = None
if contents is not None:
self.contents = contents
elif default is not None:
self.set(default)
except (ValueError, TypeError) as e:
args = e.args[1:]
e.args = (e.args[0] + '\n while constructing %s' % type_name(self),) + args
raise e
def __str__(self):
"""
Since str is different in Python 2 and 3, this calls the appropriate
method, __unicode__() or __bytes__()
:return:
A unicode string
"""
if _PY2:
return self.__bytes__()
else:
return self.__unicode__()
def __repr__(self):
"""
:return:
A unicode string
"""
if _PY2:
return '<%s %s b%s>' % (type_name(self), id(self), repr(self.dump()))
else:
return '<%s %s %s>' % (type_name(self), id(self), repr(self.dump()))
def __bytes__(self):
"""
A fall-back method for print() in Python 2
:return:
A byte string of the output of repr()
"""
return self.__repr__().encode('utf-8')
def __unicode__(self):
"""
A fall-back method for print() in Python 3
:return:
A unicode string of the output of repr()
"""
return self.__repr__()
def __reduce__(self):
"""
Permits pickling Asn1Value objects using their DER representation.
"""
return unpickle_helper, (self.__class__, self.dump())
def _new_instance(self):
"""
Constructs a new copy of the current object, preserving any tagging
:return:
An Asn1Value object
"""
new_obj = self.__class__()
new_obj.class_ = self.class_
new_obj.tag = self.tag
new_obj.implicit = self.implicit
new_obj.explicit = self.explicit
return new_obj
def __copy__(self):
"""
Implements the copy.copy() interface
:return:
A new shallow copy of the current Asn1Value object
"""
new_obj = self._new_instance()
new_obj._copy(self, copy.copy)
return new_obj
def __deepcopy__(self, memo):
"""
Implements the copy.deepcopy() interface
:param memo:
A dict for memoization
:return:
A new deep copy of the current Asn1Value object
"""
new_obj = self._new_instance()
memo[id(self)] = new_obj
new_obj._copy(self, copy.deepcopy)
return new_obj
def copy(self):
"""
Copies the object, preserving any special tagging from it
:return:
An Asn1Value object
"""
return copy.deepcopy(self)
def retag(self, tagging, tag=None):
"""
Copies the object, applying a new tagging to it
:param tagging:
A dict containing the keys "explicit" and "implicit". Legacy
API allows a unicode string of "implicit" or "explicit".
:param tag:
A integer tag number. Only used when tagging is a unicode string.
:return:
An Asn1Value object
"""
# This is required to preserve the old API
if not isinstance(tagging, dict):
tagging = {tagging: tag}
new_obj = self.__class__(explicit=tagging.get('explicit'), implicit=tagging.get('implicit'))
new_obj._copy(self, copy.deepcopy)
return new_obj
def untag(self):
"""
Copies the object, removing any special tagging from it
:return:
An Asn1Value object
"""
new_obj = self.__class__()
new_obj._copy(self, copy.deepcopy)
return new_obj
def _copy(self, other, copy_func):
"""
Copies the contents of another Asn1Value object to itself
:param object:
Another instance of the same class
:param copy_func:
An reference of copy.copy() or copy.deepcopy() to use when copying
lists, dicts and objects
"""
if self.__class__ != other.__class__:
raise TypeError(unwrap(
'''
Can not copy values from %s object to %s object
''',
type_name(other),
type_name(self)
))
self.contents = other.contents
self._native = copy_func(other._native)
def debug(self, nest_level=1):
"""
Show the binary data and parsed data in a tree structure
"""
prefix = ' ' * nest_level
# This interacts with Any and moves the tag, implicit, explicit, _header,
# contents, _footer to the parsed value so duplicate data isn't present
has_parsed = hasattr(self, 'parsed')
_basic_debug(prefix, self)
if has_parsed:
self.parsed.debug(nest_level + 2)
elif hasattr(self, 'chosen'):
self.chosen.debug(nest_level + 2)
else:
if _PY2 and isinstance(self.native, byte_cls):
print('%s Native: b%s' % (prefix, repr(self.native)))
else:
print('%s Native: %s' % (prefix, self.native))
def dump(self, force=False):
"""
Encodes the value using DER
:param force:
If the encoded contents already exist, clear them and regenerate
to ensure they are in DER format instead of BER format
:return:
A byte string of the DER-encoded value
"""
contents = self.contents
# If the length is indefinite, force the re-encoding
if self._header is not None and self._header[-1:] == b'\x80':
force = True
if self._header is None or force:
if isinstance(self, Constructable) and self._indefinite:
self.method = 0
header = _dump_header(self.class_, self.method, self.tag, self.contents)
if self.explicit is not None:
for class_, tag in self.explicit:
header = _dump_header(class_, 1, tag, header + self.contents) + header
self._header = header
self._trailer = b''
return self._header + contents + self._trailer
class ValueMap():
"""
Basic functionality that allows for mapping values from ints or OIDs to
python unicode strings
"""
# A dict from primitive value (int or OID) to unicode string. This needs
# to be defined in the source code
_map = None
# A dict from unicode string to int/OID. This is automatically generated
# from _map the first time it is needed
_reverse_map = None
def _setup(self):
"""
Generates _reverse_map from _map
"""
cls = self.__class__
if cls._map is None or cls._reverse_map is not None:
return
cls._reverse_map = {}
for key, value in cls._map.items():
cls._reverse_map[value] = key
class Castable(object):
"""
A mixin to handle converting an object between different classes that
represent the same encoded value, but with different rules for converting
to and from native Python values
"""
def cast(self, other_class):
"""
Converts the current object into an object of a different class. The
new class must use the ASN.1 encoding for the value.
:param other_class:
The class to instantiate the new object from
:return:
An instance of the type other_class
"""
if other_class.tag != self.__class__.tag:
raise TypeError(unwrap(
'''
Can not covert a value from %s object to %s object since they
use different tags: %d versus %d
''',
type_name(other_class),
type_name(self),
other_class.tag,
self.__class__.tag
))
new_obj = other_class()
new_obj.class_ = self.class_
new_obj.implicit = self.implicit
new_obj.explicit = self.explicit
new_obj._header = self._header
new_obj.contents = self.contents
new_obj._trailer = self._trailer
if isinstance(self, Constructable):
new_obj.method = self.method
new_obj._indefinite = self._indefinite
return new_obj
class Constructable(object):
"""
A mixin to handle string types that may be constructed from chunks
contained within an indefinite length BER-encoded container
"""
# Instance attribute indicating if an object was indefinite
# length when parsed - affects parsing and dumping
_indefinite = False
def _merge_chunks(self):
"""
:return:
A concatenation of the native values of the contained chunks
"""
if not self._indefinite:
return self._as_chunk()
pointer = 0
contents_len = len(self.contents)
output = None
while pointer < contents_len:
# We pass the current class as the spec so content semantics are preserved
sub_value, pointer = _parse_build(self.contents, pointer, spec=self.__class__)
if output is None:
output = sub_value._merge_chunks()
else:
output += sub_value._merge_chunks()
if output is None:
return self._as_chunk()
return output
def _as_chunk(self):
"""
A method to return a chunk of data that can be combined for
constructed method values
:return:
A native Python value that can be added together. Examples include
byte strings, unicode strings or tuples.
"""
return self.contents
def _setable_native(self):
"""
Returns a native value that can be round-tripped into .set(), to
result in a DER encoding. This differs from .native in that .native
is designed for the end use, and may account for the fact that the
merged value is further parsed as ASN.1, such as in the case of
ParsableOctetString() and ParsableOctetBitString().
:return:
A python value that is valid to pass to .set()
"""
return self.native
def _copy(self, other, copy_func):
"""
Copies the contents of another Constructable object to itself
:param object:
Another instance of the same class
:param copy_func:
An reference of copy.copy() or copy.deepcopy() to use when copying
lists, dicts and objects
"""
super(Constructable, self)._copy(other, copy_func)
# We really don't want to dump BER encodings, so if we see an
# indefinite encoding, let's re-encode it
if other._indefinite:
self.set(other._setable_native())
class Void(Asn1Value):
"""
A representation of an optional value that is not present. Has .native
property and .dump() method to be compatible with other value classes.
"""
contents = b''
def __eq__(self, other):
"""
:param other:
The other Primitive to compare to
:return:
A boolean
"""
return other.__class__ == self.__class__
def __nonzero__(self):
return False
def __len__(self):
return 0
def __iter__(self):
return iter(())
@property
def native(self):
"""
The native Python datatype representation of this value
:return:
None
"""
return None
def dump(self, force=False):
"""
Encodes the value using DER
:param force:
If the encoded contents already exist, clear them and regenerate
to ensure they are in DER format instead of BER format
:return:
A byte string of the DER-encoded value
"""
return b''
VOID = Void()
class Any(Asn1Value):
"""
A value class that can contain any value, and allows for easy parsing of
the underlying encoded value using a spec. This is normally contained in
a Structure that has an ObjectIdentifier field and _oid_pair and _oid_specs
defined.
"""
# The parsed value object
_parsed = None
def __init__(self, value=None, **kwargs):
"""
Sets the value of the object before passing to Asn1Value.__init__()
:param value:
An Asn1Value object that will be set as the parsed value
"""
Asn1Value.__init__(self, **kwargs)
try:
if value is not None:
if not isinstance(value, Asn1Value):
raise TypeError(unwrap(
'''
value must be an instance of Asn1Value, not %s
''',
type_name(value)
))
self._parsed = (value, value.__class__, None)
self.contents = value.dump()
except (ValueError, TypeError) as e:
args = e.args[1:]
e.args = (e.args[0] + '\n while constructing %s' % type_name(self),) + args
raise e
@property
def native(self):
"""
The native Python datatype representation of this value
:return:
The .native value from the parsed value object
"""
if self._parsed is None:
self.parse()
return self._parsed[0].native
@property
def parsed(self):
"""
Returns the parsed object from .parse()
:return:
The object returned by .parse()
"""
if self._parsed is None:
self.parse()
return self._parsed[0]
def parse(self, spec=None, spec_params=None):
"""
Parses the contents generically, or using a spec with optional params
:param spec:
A class derived from Asn1Value that defines what class_ and tag the
value should have, and the semantics of the encoded value. The
return value will be of this type. If omitted, the encoded value
will be decoded using the standard universal tag based on the
encoded tag number.
:param spec_params:
A dict of params to pass to the spec object
:return:
An object of the type spec, or if not present, a child of Asn1Value
"""
if self._parsed is None or self._parsed[1:3] != (spec, spec_params):
try:
passed_params = spec_params or {}
_tag_type_to_explicit_implicit(passed_params)
if self.explicit is not None:
if 'explicit' in passed_params:
passed_params['explicit'] = self.explicit + passed_params['explicit']
else:
passed_params['explicit'] = self.explicit
contents = self._header + self.contents + self._trailer
parsed_value, _ = _parse_build(
contents,
spec=spec,
spec_params=passed_params
)
self._parsed = (parsed_value, spec, spec_params)
# Once we've parsed the Any value, clear any attributes from this object
# since they are now duplicate
self.tag = None
self.explicit = None
self.implicit = False
self._header = b''
self.contents = contents
self._trailer = b''
except (ValueError, TypeError) as e:
args = e.args[1:]
e.args = (e.args[0] + '\n while parsing %s' % type_name(self),) + args
raise e
return self._parsed[0]
def _copy(self, other, copy_func):
"""
Copies the contents of another Any object to itself
:param object:
Another instance of the same class
:param copy_func:
An reference of copy.copy() or copy.deepcopy() to use when copying