From 6ba9d85fde0eb07f49f452b44c221438ba63fb71 Mon Sep 17 00:00:00 2001 From: "Michael A. Wekesa" Date: Fri, 20 Oct 2023 19:45:26 +0300 Subject: [PATCH 1/9] add jdwp serializer classes --- projects/jdwp/serialization/serializer.py | 28 +++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 projects/jdwp/serialization/serializer.py diff --git a/projects/jdwp/serialization/serializer.py b/projects/jdwp/serialization/serializer.py new file mode 100644 index 0000000..0f99c8c --- /dev/null +++ b/projects/jdwp/serialization/serializer.py @@ -0,0 +1,28 @@ +""" JDWP serializer classes. """ + +class JDWPPacketHeaders: + def __init__(self, length, id, flags, command_set, command): + self.length = length + self.id = id + self.flags = flags + self.command_set = command_set + self.command = command + + def serialize(self): + length_bytes = self.length.to_bytes(4, byteorder='big') + id_bytes = self.id.to_bytes(4, byteorder='big') + flags_bytes = self.flags.to_bytes(1, byteorder='big') + command_set_bytes = self.command_set.to_bytes(1, byteorder='big') + command_bytes = self.command.to_bytes(1, byteorder='big') + return length_bytes + id_bytes + flags_bytes + command_set_bytes + command_bytes + + +class JDWPPacket: + def __init__(self, header, payload): + self.header = header + self.payload = payload + + def serialize(self): + header_bytes = self.header.serialize() + return header_bytes + self.payload + From 88a417b5b5b7c2ba3789a3fdc7e015d489e73527 Mon Sep 17 00:00:00 2001 From: "Michael A. Wekesa" Date: Fri, 20 Oct 2023 19:50:28 +0300 Subject: [PATCH 2/9] add type annotation --- projects/jdwp/serialization/serializer.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/projects/jdwp/serialization/serializer.py b/projects/jdwp/serialization/serializer.py index 0f99c8c..c293e10 100644 --- a/projects/jdwp/serialization/serializer.py +++ b/projects/jdwp/serialization/serializer.py @@ -1,14 +1,14 @@ """ JDWP serializer classes. """ -class JDWPPacketHeaders: - def __init__(self, length, id, flags, command_set, command): +class JDWPPacketHeader: + def __init__(self, length: int, id: int, flags: int, command_set: int, command: int): self.length = length self.id = id self.flags = flags self.command_set = command_set self.command = command - def serialize(self): + def serialize(self) -> bytes: length_bytes = self.length.to_bytes(4, byteorder='big') id_bytes = self.id.to_bytes(4, byteorder='big') flags_bytes = self.flags.to_bytes(1, byteorder='big') @@ -18,11 +18,11 @@ def serialize(self): class JDWPPacket: - def __init__(self, header, payload): + def __init__(self, header: JDWPPacketHeader, payload: bytes): self.header = header self.payload = payload - def serialize(self): + def serialize(self) -> bytes: header_bytes = self.header.serialize() return header_bytes + self.payload From ac87a473e1e6eaeb436cb4155dd56e4030daadab Mon Sep 17 00:00:00 2001 From: "Michael A. Wekesa" Date: Fri, 20 Oct 2023 19:52:31 +0300 Subject: [PATCH 3/9] add utility function --- projects/jdwp/serialization/serializer.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/projects/jdwp/serialization/serializer.py b/projects/jdwp/serialization/serializer.py index c293e10..5a0c011 100644 --- a/projects/jdwp/serialization/serializer.py +++ b/projects/jdwp/serialization/serializer.py @@ -1,5 +1,6 @@ """ JDWP serializer classes. """ + class JDWPPacketHeader: def __init__(self, length: int, id: int, flags: int, command_set: int, command: int): self.length = length @@ -26,3 +27,5 @@ def serialize(self) -> bytes: header_bytes = self.header.serialize() return header_bytes + self.payload +def serialize_jdwp_packet(packet: JDWPPacket) -> bytes: + return packet.serialize() \ No newline at end of file From 073d53543f1881ff926a10436b8a3352d442afdd Mon Sep 17 00:00:00 2001 From: "Michael A. Wekesa" Date: Fri, 20 Oct 2023 20:16:11 +0300 Subject: [PATCH 4/9] add test for serializer --- projects/jdwp/__init__.py | 0 projects/jdwp/serialization/__init__.py | 0 projects/jdwp/tests/__init__.py | 0 projects/jdwp/tests/test_jdwp_serialization.py | 17 +++++++++++++++++ 4 files changed, 17 insertions(+) create mode 100644 projects/jdwp/__init__.py create mode 100644 projects/jdwp/serialization/__init__.py create mode 100644 projects/jdwp/tests/__init__.py create mode 100644 projects/jdwp/tests/test_jdwp_serialization.py diff --git a/projects/jdwp/__init__.py b/projects/jdwp/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/projects/jdwp/serialization/__init__.py b/projects/jdwp/serialization/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/projects/jdwp/tests/__init__.py b/projects/jdwp/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/projects/jdwp/tests/test_jdwp_serialization.py b/projects/jdwp/tests/test_jdwp_serialization.py new file mode 100644 index 0000000..11541cb --- /dev/null +++ b/projects/jdwp/tests/test_jdwp_serialization.py @@ -0,0 +1,17 @@ +import unittest +from jdwp.serialization.serializer import JDWPPacketHeader, JDWPPacket, serialize_jdwp_packet + + +class TestJDWPPacketSerialization(unittest.TestCase): + def test_jdwp_packet_serialization(self): + header = JDWPPacketHeader(15, 1, 0x80, 2, 3) + + payload = b"Sample Payload" + packet = JDWPPacket(header, payload) + + serialized_packet = serialize_jdwp_packet(packet) + self.assertEqual(len(serialized_packet), 15) + + +if __name__ == '__main__': + unittest.main() From 71b4a049d15c53b7f5343ed64c678226f91b6bc3 Mon Sep 17 00:00:00 2001 From: "Michael A. Wekesa" Date: Wed, 25 Oct 2023 15:29:23 +0300 Subject: [PATCH 5/9] rename file --- projects/jdwp/{serialization => serializers}/__init__.py | 0 .../serializer.py => serializers/jdwp_packet_erializer.py} | 0 projects/jdwp/serializers/serializer_codegen.py | 0 projects/jdwp/tests/test_jdwp_serialization.py | 2 +- 4 files changed, 1 insertion(+), 1 deletion(-) rename projects/jdwp/{serialization => serializers}/__init__.py (100%) rename projects/jdwp/{serialization/serializer.py => serializers/jdwp_packet_erializer.py} (100%) create mode 100644 projects/jdwp/serializers/serializer_codegen.py diff --git a/projects/jdwp/serialization/__init__.py b/projects/jdwp/serializers/__init__.py similarity index 100% rename from projects/jdwp/serialization/__init__.py rename to projects/jdwp/serializers/__init__.py diff --git a/projects/jdwp/serialization/serializer.py b/projects/jdwp/serializers/jdwp_packet_erializer.py similarity index 100% rename from projects/jdwp/serialization/serializer.py rename to projects/jdwp/serializers/jdwp_packet_erializer.py diff --git a/projects/jdwp/serializers/serializer_codegen.py b/projects/jdwp/serializers/serializer_codegen.py new file mode 100644 index 0000000..e69de29 diff --git a/projects/jdwp/tests/test_jdwp_serialization.py b/projects/jdwp/tests/test_jdwp_serialization.py index 11541cb..616b78e 100644 --- a/projects/jdwp/tests/test_jdwp_serialization.py +++ b/projects/jdwp/tests/test_jdwp_serialization.py @@ -1,5 +1,5 @@ import unittest -from jdwp.serialization.serializer import JDWPPacketHeader, JDWPPacket, serialize_jdwp_packet +from projects.jdwp.serializers.jdwp_packet_erializer import JDWPPacketHeader, JDWPPacket, serialize_jdwp_packet class TestJDWPPacketSerialization(unittest.TestCase): From 10983c2ae2b855b481f047d00c72d3b3556dd541 Mon Sep 17 00:00:00 2001 From: "Michael A. Wekesa" Date: Fri, 27 Oct 2023 20:31:09 +0300 Subject: [PATCH 6/9] add codegen for serializer --- .../jdwp/serializers/serializer_codegen.py | 98 +++++++++++++++++++ 1 file changed, 98 insertions(+) diff --git a/projects/jdwp/serializers/serializer_codegen.py b/projects/jdwp/serializers/serializer_codegen.py index e69de29..0254db3 100644 --- a/projects/jdwp/serializers/serializer_codegen.py +++ b/projects/jdwp/serializers/serializer_codegen.py @@ -0,0 +1,98 @@ +import re +import os +from projects.jdwp.defs.schema import Type + + +class CommandSerializerGenerator: + def __init__(self): + self.serializer_code = "" + + def generate_field_serializer(self, field): + serializer_code = "" + if isinstance(field, Struct): + for subfield in field.fields: + serializer_code += self.generate_field_serializer(subfield) + elif field.type == Type.INT: + serializer_code = ( + f" serialized_data += command.{field.name}.to_bytes(4, 'big')" + ) + elif field.type == Type.STRING: + serializer_code = ( + f" serialized_data += command.{field.name}.encode('utf-8')" + ) + elif field.type == Type.OBJECT_ID: + serializer_code = ( + f" serialized_data += command.{field.name}.to_bytes(8, 'big')" + ) + elif field.type == Type.REFERENCE_TYPE_ID: + serializer_code = ( + f" serialized_data += command.{field.name}.to_bytes(8, 'big')" + ) + return serializer_code + + def generate_field_deserializer(self, field): + deserializer_code = "" + if isinstance(field, Struct): + for subfield in field.fields: + deserializer_code += self.generate_field_deserializer(subfield) + elif field.type == Type.INT: + deserializer_code = f"command.{field.name} = int.from_bytes(data[:4], 'big')\n data = data[4:]" + elif field.type == Type.STRING: + deserializer_code = f"null_terminator = data.index(0)\n command.{field.name} = data[:null_terminator].decode('utf-8')\n\t\tdata = data[null_terminator + 1:]" + elif field.type == Type.OBJECT_ID: + deserializer_code = f" command.{field.name} = int.from_bytes(data[:8], 'big')\n data = data[8:]" + elif field.type == Type.REFERENCE_TYPE_ID: + deserializer_code = f" command.{field.name} = int.from_bytes(data[:8], 'big')\n data = data[8:]" + return deserializer_code + + def generate_command_serializer(self, command): + serializer_code = f""" +class {command.name}Command: + @staticmethod + def serialize(command): + serialized_data = bytearray() +{self.generate_field_serializer(command.out)} + return serialized_data +""" + return serializer_code + + def generate_command_deserializer(self, command): + deserializer_code = f""" + @staticmethod + def deserialize(data): + command = {command.name}() +{"".join(self.generate_field_deserializer(field) for field in command.out.fields)} + return command, data +""" + return deserializer_code + + def _convert_to_snake_case(name): + name = re.sub("([a-z0-9])([A-Z])", r"\1_\2", name).lower() + return name + + def generate_command_set_file(self, command_set): + self.serializer_code = f"""\"\"\"Command Set: {command_set.name} \"\"\" + +from projects.jdwp.defs.command_sets.{self._convert_to_snake_case(command_set.name)} import {", ".join([command.name for command in command_set.commands])} + +{"".join(self.generate_command_serializer(command) for command in command_set.commands)} + +{"".join(self.generate_command_deserializer(command) for command in command_set.commands)} +""" + return self.serializer_code + + def generate_serializer_file(self, command_set): + command_set_name = command_set.name + serializer_file_name = ( + f"{self._convert_to_snake_case(command_set_name)}_serializer.py" + ) + command_set_code = self.generate_command_set_file(command_set) + output_dir = os.path.dirname(os.path.realpath(__file__)) + file_path = os.path.join(output_dir, serializer_file_name) + with open(file_path, "w") as output_file: + output_file.write(command_set_code) + print(f"Generated serializer code saved to {serializer_file_name}") + + def _convert_to_snake_case(self, name): + name = re.sub("([a-z0-9])([A-Z])", r"\1_\2", name).lower() + return name From 430d8c40b9325bcfd5e8191ae90b1e8d79f4d6e1 Mon Sep 17 00:00:00 2001 From: "Michael A. Wekesa" Date: Fri, 27 Oct 2023 20:33:59 +0300 Subject: [PATCH 7/9] add type annotation --- .../jdwp/serializers/serializer_codegen.py | 42 +++++++++---------- 1 file changed, 19 insertions(+), 23 deletions(-) diff --git a/projects/jdwp/serializers/serializer_codegen.py b/projects/jdwp/serializers/serializer_codegen.py index 0254db3..4f0455f 100644 --- a/projects/jdwp/serializers/serializer_codegen.py +++ b/projects/jdwp/serializers/serializer_codegen.py @@ -1,14 +1,14 @@ import re import os -from projects.jdwp.defs.schema import Type +from projects.jdwp.defs.schema import Type, Struct, CommandSet, Command, Field class CommandSerializerGenerator: def __init__(self): - self.serializer_code = "" + self.serializer_code: str = "" - def generate_field_serializer(self, field): - serializer_code = "" + def generate_field_serializer(self, field: Field) -> str: + serializer_code: str = "" if isinstance(field, Struct): for subfield in field.fields: serializer_code += self.generate_field_serializer(subfield) @@ -30,8 +30,8 @@ def generate_field_serializer(self, field): ) return serializer_code - def generate_field_deserializer(self, field): - deserializer_code = "" + def generate_field_deserializer(self, field: Field) -> str: + deserializer_code: str = "" if isinstance(field, Struct): for subfield in field.fields: deserializer_code += self.generate_field_deserializer(subfield) @@ -40,13 +40,13 @@ def generate_field_deserializer(self, field): elif field.type == Type.STRING: deserializer_code = f"null_terminator = data.index(0)\n command.{field.name} = data[:null_terminator].decode('utf-8')\n\t\tdata = data[null_terminator + 1:]" elif field.type == Type.OBJECT_ID: - deserializer_code = f" command.{field.name} = int.from_bytes(data[:8], 'big')\n data = data[8:]" + deserializer_code = f"command.{field.name} = int.from_bytes(data[:8], 'big')\n data = data[8:]" elif field.type == Type.REFERENCE_TYPE_ID: - deserializer_code = f" command.{field.name} = int.from_bytes(data[:8], 'big')\n data = data[8:]" + deserializer_code = f"command.{field.name} = int.from_bytes(data[:8], 'big')\n data = data[8:]" return deserializer_code - def generate_command_serializer(self, command): - serializer_code = f""" + def generate_command_serializer(self, command: Command) -> str: + serializer_code: str = f""" class {command.name}Command: @staticmethod def serialize(command): @@ -56,8 +56,8 @@ def serialize(command): """ return serializer_code - def generate_command_deserializer(self, command): - deserializer_code = f""" + def generate_command_deserializer(self, command: Command) -> str: + deserializer_code: str = f""" @staticmethod def deserialize(data): command = {command.name}() @@ -66,11 +66,11 @@ def deserialize(data): """ return deserializer_code - def _convert_to_snake_case(name): + def _convert_to_snake_case(self, name: str) -> str: name = re.sub("([a-z0-9])([A-Z])", r"\1_\2", name).lower() return name - def generate_command_set_file(self, command_set): + def generate_command_set_file(self, command_set: CommandSet) -> str: self.serializer_code = f"""\"\"\"Command Set: {command_set.name} \"\"\" from projects.jdwp.defs.command_sets.{self._convert_to_snake_case(command_set.name)} import {", ".join([command.name for command in command_set.commands])} @@ -81,18 +81,14 @@ def generate_command_set_file(self, command_set): """ return self.serializer_code - def generate_serializer_file(self, command_set): + def generate_serializer_file(self, command_set: CommandSet) -> None: command_set_name = command_set.name - serializer_file_name = ( + serializer_file_name: str = ( f"{self._convert_to_snake_case(command_set_name)}_serializer.py" ) - command_set_code = self.generate_command_set_file(command_set) - output_dir = os.path.dirname(os.path.realpath(__file__)) - file_path = os.path.join(output_dir, serializer_file_name) + command_set_code: str = self.generate_command_set_file(command_set) + output_dir: str = os.path.dirname(os.path.realpath(__file__)) + file_path: str = os.path.join(output_dir, serializer_file_name) with open(file_path, "w") as output_file: output_file.write(command_set_code) print(f"Generated serializer code saved to {serializer_file_name}") - - def _convert_to_snake_case(self, name): - name = re.sub("([a-z0-9])([A-Z])", r"\1_\2", name).lower() - return name From 6a4ed6af43fe8afa25b7913d07a9d38f5b227da0 Mon Sep 17 00:00:00 2001 From: "Michael A. Wekesa" Date: Fri, 27 Oct 2023 20:46:38 +0300 Subject: [PATCH 8/9] fix generated code indentation --- projects/jdwp/serializers/serializer_codegen.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/projects/jdwp/serializers/serializer_codegen.py b/projects/jdwp/serializers/serializer_codegen.py index 4f0455f..d564ca3 100644 --- a/projects/jdwp/serializers/serializer_codegen.py +++ b/projects/jdwp/serializers/serializer_codegen.py @@ -1,6 +1,7 @@ import re import os from projects.jdwp.defs.schema import Type, Struct, CommandSet, Command, Field +from projects.jdwp.defs.command_sets.reference_type import ReferenceType class CommandSerializerGenerator: @@ -36,13 +37,13 @@ def generate_field_deserializer(self, field: Field) -> str: for subfield in field.fields: deserializer_code += self.generate_field_deserializer(subfield) elif field.type == Type.INT: - deserializer_code = f"command.{field.name} = int.from_bytes(data[:4], 'big')\n data = data[4:]" + deserializer_code = f" command.{field.name} = int.from_bytes(data[:4], 'big')\n data = data[4:]" elif field.type == Type.STRING: - deserializer_code = f"null_terminator = data.index(0)\n command.{field.name} = data[:null_terminator].decode('utf-8')\n\t\tdata = data[null_terminator + 1:]" + deserializer_code = f" null_terminator = data.index(0)\n command.{field.name} = data[:null_terminator].decode('utf-8')\n\t\tdata = data[null_terminator + 1:]" elif field.type == Type.OBJECT_ID: - deserializer_code = f"command.{field.name} = int.from_bytes(data[:8], 'big')\n data = data[8:]" + deserializer_code = f" command.{field.name} = int.from_bytes(data[:8], 'big')\n data = data[8:]" elif field.type == Type.REFERENCE_TYPE_ID: - deserializer_code = f"command.{field.name} = int.from_bytes(data[:8], 'big')\n data = data[8:]" + deserializer_code = f" command.{field.name} = int.from_bytes(data[:8], 'big')\n data = data[8:]" return deserializer_code def generate_command_serializer(self, command: Command) -> str: @@ -92,3 +93,7 @@ def generate_serializer_file(self, command_set: CommandSet) -> None: with open(file_path, "w") as output_file: output_file.write(command_set_code) print(f"Generated serializer code saved to {serializer_file_name}") + + +serializer_generator = CommandSerializerGenerator() +gen_file = serializer_generator.generate_serializer_file(ReferenceType) \ No newline at end of file From 7efa84bbdb2cdadb58f08ada3aa47e45749961a3 Mon Sep 17 00:00:00 2001 From: "Michael A. Wekesa" Date: Fri, 27 Oct 2023 20:48:57 +0300 Subject: [PATCH 9/9] reformat files --- .../serializers/reference_type_serializer.py | 21 +++++++++++++++++++ .../jdwp/serializers/serializer_codegen.py | 4 ++-- 2 files changed, 23 insertions(+), 2 deletions(-) create mode 100644 projects/jdwp/serializers/reference_type_serializer.py diff --git a/projects/jdwp/serializers/reference_type_serializer.py b/projects/jdwp/serializers/reference_type_serializer.py new file mode 100644 index 0000000..1f1d9d0 --- /dev/null +++ b/projects/jdwp/serializers/reference_type_serializer.py @@ -0,0 +1,21 @@ +"""Command Set: ReferenceType """ + +from projects.jdwp.defs.command_sets.reference_type import Signature + + +class SignatureCommand: + @staticmethod + def serialize(command): + serialized_data = bytearray() + serialized_data += command.refType.to_bytes(8, 'big') + return serialized_data + + + + @staticmethod + def deserialize(data): + command = Signature() + command.refType = int.from_bytes(data[:8], 'big') + data = data[8:] + return command, data + diff --git a/projects/jdwp/serializers/serializer_codegen.py b/projects/jdwp/serializers/serializer_codegen.py index d564ca3..908ed21 100644 --- a/projects/jdwp/serializers/serializer_codegen.py +++ b/projects/jdwp/serializers/serializer_codegen.py @@ -95,5 +95,5 @@ def generate_serializer_file(self, command_set: CommandSet) -> None: print(f"Generated serializer code saved to {serializer_file_name}") -serializer_generator = CommandSerializerGenerator() -gen_file = serializer_generator.generate_serializer_file(ReferenceType) \ No newline at end of file +serializer_generator = CommandSerializerGenerator() +gen_file = serializer_generator.generate_serializer_file(ReferenceType)