SegmentSequence([fints.segments.HNHBK3(header=fints.formals.SegmentHeader('HNHBK', 1, 3), message_size='000000000428', hbci_version=300, dialogue_id='430711670077=043999659571CN9D=', message_number=2, reference_message=fints.formals.ReferenceMessage(dialogue_id='430711670077=043999659571CN9D=', message_number=2)), fints.segments.HNVSK3(header=fints.formals.SegmentHeader('HNVSK', 998, 3), security_profile=fints.formals.SecurityProfile(security_method='PIN', security_method_version=1), security_function='998', security_role='1', security_identification_details=fints.formals.SecurityIdentificationDetails(name_party='2', cid=None, identifier_party='oIm3BlHv6mQBAADYgbPpp+kWrAQA'), security_datetime=fints.formals.SecurityDateTime(datetime_type='1'), encryption_algorithm=fints.formals.EncryptionAlgorithm(usage_encryption='2', operation_mode='2', encryption_algorithm='13', algorithm_parameter_value=b'00000000', algorithm_parameter_name='5', algorithm_parameter_iv_name='1'), key_name=fints.formals.KeyName(bank_identifier=fints.formals.BankIdentifier(country_identifier='280', bank_code='15050500'), user_id='hermes', key_type='S', key_number=0, key_version=0), compression_function='0'), fints.segments.HNVSD1(header=fints.formals.SegmentHeader('HNVSD', 999, 1), data=SegmentSequence([fints.segments.HNSHK4(header=fints.formals.SegmentHeader('HNSHK', 2, 4), security_profile=fints.formals.SecurityProfile(security_method='PIN', security_method_version=1), security_function='999', security_reference='9166926', security_application_area='1', security_role='1', security_identification_details=fints.formals.SecurityIdentificationDetails(name_party='2', cid=None, identifier_party='oIm3BlHv6mQBAADYgbPpp+kWrAQA'), security_reference_number=1, security_datetime=fints.formals.SecurityDateTime(datetime_type='1'), hash_algorithm=fints.formals.HashAlgorithm(usage_hash='1', hash_algorithm='999', algorithm_parameter_name='1'), signature_algorithm=fints.formals.SignatureAlgorithm(usage_signature='6', signature_algorithm='10', operation_mode='16'), key_name=fints.formals.KeyName(bank_identifier=fints.formals.BankIdentifier(country_identifier='280', bank_code='15050500'), user_id='hermes', key_type='S', key_number=0, key_version=0)), fints.segments.HIRMG2(header=fints.formals.SegmentHeader('HIRMG', 3, 2), response=[fints.formals.Response(response_code='0010', reference_element=None, response_text='Nachricht entgegengenommen.'), fints.formals.Response(response_code='0100', reference_element=None, response_text='Dialog beendet.')]), fints.segments.HNSHA2(header=fints.formals.SegmentHeader('HNSHA', 4, 2), security_reference='9166926')])), fints.segments.HNHBS1(header=fints.formals.SegmentHeader('HNHBS', 5, 1), message_number=2)])
>>> from fints.parser import FinTS3Serializer
>>> FinTS3Serializer().serialize_message(s)
+ b"HNHBK:1:3+000000000428+300+430711670077=043999659571CN9D=+2+430711670077=043999659571CN9D=:2'HNVSK:998:3+PIN:1+998+1+2::oIm3BlHv6mQBAADYgbPpp?+kWrAQA+1+2:2:13:@8@00000000:5:1+280:15050500:hermes:S:0:0+0'HNVSD:999:1+@195@HNSHK:2:4+PIN:1+999+9166926+1+1+2::oIm3BlHv6mQBAADYgbPpp?+kWrAQA+1+1+1:999:1+6:10:16+280:15050500:hermes:S:0:0'HIRMG:3:2+0010::Nachricht entgegengenommen.+0100::Dialog beendet.'HNSHA:4:2+9166926''HNHBS:5:1+2'"
-## FIXME: Implement :)
FinTS Segment Sequence
----------------------
def render(self, value):
if value is None:
- return ""
+ return None
return self._render_value(value)
return str(value)
class SegmentSequence:
+ """A sequence of FinTS3Segment objects"""
+
def __init__(self, segments = None):
if isinstance(segments, bytes):
from .parser import FinTS3Parser
segments = [parser.parse_segment(segment) for segment in data]
self.segments = segments or []
+ def render_bytes(self) -> bytes:
+ from .parser import FinTS3Serializer
+ return FinTS3Serializer().serialize_message(self)
+
def __repr__(self):
return "{}({!r})".format(self.__class__.__name__, self.segments)
segment.print_nested(stream=stream, level=level+1, indent=indent, prefix=prefix, first_level_indent=True, trailer=",")
stream.write( (prefix + level*indent) + "]){}\n".format(trailer) )
+ def find_segments(self, type=None, version=None, callback=None, recurse=True):
+ """Yields an iterable of all matching segments.
+
+ :param type: Either a str specifying a segment type (such as 'HNHBK'), or a list or tuple of strings.
+ If a list/tuple is specified, segments returning any matching type will be returned.
+ :param version: Either an int specifying a segment version, or a list or tuple of ints.
+ If a list/tuple is specified, segments returning any matching version will be returned.
+ :param callback: A callable that will be given the segment as its sole argument and must return a booleans indicating whether to return this segment.
+ :param recurse: If True (the default), recurse into SegmentSequenceField values, otherwise only look at segments in this SegmentSequence.
+
+ The match results of all given parameters will be AND-combined.
+ """
+
+ def find_segment_first(self, *args, **kwargs):
+ """Finds the first matching segment.
+
+ Same parameters as find_segments(), but only returns the first match, or None if no match is found."""
+
class SegmentSequenceField(DataElementField):
type = 'sf'
else:
return SegmentSequence(value)
+ def _render_value(self, value):
+ return value.render_bytes()
+
class ContainerMeta(type):
@classmethod
yield Token.EOF, b''
class FinTS3Parser:
- def parse_message(self, data):
+ """Parser for FinTS/HBCI 3.0 messages
+ """
+
+ def parse_message(self, data: bytes) -> SegmentSequence:
+ "Takes a FinTS 3.0 message as byte array, and returns a parsed segment sequence"
if isinstance(data, bytes):
data = self.explode_segments(data)
return segments
class FinTS3Serializer:
- def serialize_message(self, message):
+ """Serializer for FinTS/HBCI 3.0 messages
+ """
+
+ def serialize_message(self, message: SegmentSequence) -> bytes:
+ "Serialize a message (as SegmentSequence, list of FinTS3Segment, or FinTS3Segment) into a byte array"
if isinstance(message, FinTS3Segment):
message = [message]
if isinstance(message, (list, tuple, Iterable)):
def serialize_segment(self, segment):
seg = []
- skipping_end = False
+ filler = []
for name,field in segment._fields.items():
repeat = field.count != 1
elif val is None:
empty = True
- if skipping_end and not empty:
- raise ValueError("Inconsistency during serialization: Field {}.{} not empty, but a field before it was".format(segment.__class__.__name__, name))
-
if empty:
- skipping_end = True
+ filler.append(None)
continue
+ else:
+ if filler:
+ seg.extend(filler)
+ filler.clear()
if not constructed:
if repeat:
seg.append( field.render(getattr(segment, name)) )
else:
if repeat:
- inner = []
for val in getattr(segment, name):
- inner.extend( self.serialize_deg(val) )
- seg.append(inner)
+ seg.append( self.serialize_deg(val) )
else:
seg.append( self.serialize_deg(getattr(segment, name)) )
return re.sub(r"([+:'@?])", r"?\1", val).encode('iso-8859-1')
elif isinstance(val, bytes):
return "@{}@".format(len(val)).encode('us-ascii') + val
+ elif val is None:
+ return b''
else:
- raise TypeError("Can only escape str and bytes")
+ raise TypeError("Can only escape str, bytes and None")
from fints.formals import NumericField
import pytest
+from conftest import SIMPLE_EXAMPLE
+
def test_serialize_1():
class ITST1(FinTS3Segment):
a = NumericField(count=3)
assert FinTS3Parser.explode_segments(s) == m
+def test_implode_roundtrip_simple():
+ segments = FinTS3Parser.explode_segments(SIMPLE_EXAMPLE)
+ assert FinTS3Serializer.implode_segments(segments) == SIMPLE_EXAMPLE
+
+ message = FinTS3Parser().parse_message(segments)
+ assert FinTS3Serializer().serialize_message(message) == SIMPLE_EXAMPLE
+
def test_escape():
assert b"a" == FinTS3Serializer.escape_value('a')
with pytest.raises(TypeError):
FinTS3Serializer.escape_value(1)
+
+def test_serialize_2():
+ from fints.formals import SegmentSequence
+ import fints.formals, fints.segments
+ s = SegmentSequence([fints.segments.HNHBK3(header=fints.formals.SegmentHeader('HNHBK', 1, 3), message_size='000000000428', hbci_version=300, dialogue_id='430711670077=043999659571CN9D=', message_number=2, reference_message=fints.formals.ReferenceMessage(dialogue_id='430711670077=043999659571CN9D=', message_number=2)), fints.segments.HNVSK3(header=fints.formals.SegmentHeader('HNVSK', 998, 3), security_profile=fints.formals.SecurityProfile(security_method='PIN', security_method_version=1), security_function='998', security_role='1', security_identification_details=fints.formals.SecurityIdentificationDetails(name_party='2', cid=None, identifier_party='oIm3BlHv6mQBAADYgbPpp+kWrAQA'), security_datetime=fints.formals.SecurityDateTime(datetime_type='1'), encryption_algorithm=fints.formals.EncryptionAlgorithm(usage_encryption='2', operation_mode='2', encryption_algorithm='13', algorithm_parameter_value=b'00000000', algorithm_parameter_name='5', algorithm_parameter_iv_name='1'), key_name=fints.formals.KeyName(bank_identifier=fints.formals.BankIdentifier(country_identifier='280', bank_code='15050500'), user_id='hermes', key_type='S', key_number=0, key_version=0), compression_function='0'), fints.segments.HNVSD1(header=fints.formals.SegmentHeader('HNVSD', 999, 1), data=SegmentSequence([fints.segments.HNSHK4(header=fints.formals.SegmentHeader('HNSHK', 2, 4), security_profile=fints.formals.SecurityProfile(security_method='PIN', security_method_version=1), security_function='999', security_reference='9166926', security_application_area='1', security_role='1', security_identification_details=fints.formals.SecurityIdentificationDetails(name_party='2', cid=None, identifier_party='oIm3BlHv6mQBAADYgbPpp+kWrAQA'), security_reference_number=1, security_datetime=fints.formals.SecurityDateTime(datetime_type='1'), hash_algorithm=fints.formals.HashAlgorithm(usage_hash='1', hash_algorithm='999', algorithm_parameter_name='1'), signature_algorithm=fints.formals.SignatureAlgorithm(usage_signature='6', signature_algorithm='10', operation_mode='16'), key_name=fints.formals.KeyName(bank_identifier=fints.formals.BankIdentifier(country_identifier='280', bank_code='15050500'), user_id='hermes', key_type='S', key_number=0, key_version=0)), fints.segments.HIRMG2(header=fints.formals.SegmentHeader('HIRMG', 3, 2), response=[fints.formals.Response(response_code='0010', reference_element=None, response_text='Nachricht entgegengenommen.'), fints.formals.Response(response_code='0100', reference_element=None, response_text='Dialog beendet.')]), fints.segments.HNSHA2(header=fints.formals.SegmentHeader('HNSHA', 4, 2), security_reference='9166926')])), fints.segments.HNHBS1(header=fints.formals.SegmentHeader('HNHBS', 5, 1), message_number=2)])
+
+ assert FinTS3Serializer().serialize_message(s) == SIMPLE_EXAMPLE