diff --git a/csla_binary/__init__.py b/csla_binary/__init__.py index 028474f..92b611c 100644 --- a/csla_binary/__init__.py +++ b/csla_binary/__init__.py @@ -15,3 +15,5 @@ # along with this program. If not, see . from .binary_reader import CslaBinaryReader +from .binary_writer import CslaBinaryWriter +from .serialization_info import ChildData, FieldData, SerializationInfo diff --git a/csla_binary/binary_reader.py b/csla_binary/binary_reader.py index 0406a96..c5031b1 100644 --- a/csla_binary/binary_reader.py +++ b/csla_binary/binary_reader.py @@ -23,7 +23,7 @@ from .serialization_info import ChildData, FieldData, SerializationInfo class CslaBinaryReader: """Reads binary-serialised CSLA data into SerializationInfo objects""" - def __init__(self, stream: io.BytesIO): + def __init__(self, stream: io.BufferedIOBase): self.stream = stream self.keywords_dictionary = {} diff --git a/csla_binary/binary_writer.py b/csla_binary/binary_writer.py new file mode 100644 index 0000000..8ce4815 --- /dev/null +++ b/csla_binary/binary_writer.py @@ -0,0 +1,111 @@ +# pycsla-binary: Python implementation of CSLA .NET binary serialisation +# Copyright (C) 2025 Lee Yingtong Li (RunasSudo) +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import io +import struct +from typing import List + +from .known_types import CslaKnownTypes +from .serialization_info import SerializationInfo + +class CslaBinaryWriter: + """Writes SerializationInfo objects into binary-serialised CSLA data""" + + def __init__(self, stream: io.BufferedIOBase): + self.stream = stream + self.keywords_dictionary = [] + + def write(self, serialisation_infos: List[SerializationInfo]): + # Reverse of CslaBinaryReader.Read + self.write_int32(len(serialisation_infos)) + + for info in serialisation_infos: + # Write ReferenceID + self.write_int32(info.reference_id) + + # Write TypeName + self.write_object_system_string(info.type_name) + + # Write children + self.write_int32(len(info.children)) + for child in info.children: + self.write_object_system_string(child.name) + self.write_object_bool(child.is_dirty) + self.write_object_int32(child.reference_id) + + # Write field values + self.write_int32(len(info.values)) + for value in info.values: + self.write_object_system_string(value.name) + self.write_object_system_string(value.enum_type_name or '') + self.write_object_bool(value.is_dirty) + self.write_object(value.value) + + def write_7bit_encoded_int(self, value): + # BinaryWriter.Write7BitEncodedInt + # "The integer of the value parameter is written out seven bits at a time, starting with the seven least-significant bits. The high bit of a byte indicates whether there are more bytes to be written after this one." + while True: + value_7lsb = value & 0b01111111 + value >>= 7 + + if value == 0: + # Final byte + self.stream.write(bytes([value_7lsb])) + return + else: + # Further bytes remaining + self.stream.write(bytes([value_7lsb | 0b10000000])) + + def write_int32(self, value): + # BinaryWriter.WriteInt32 + self.stream.write(struct.pack('