Skip to content

Commit

Permalink
Add jdwp streams implemented abstract class
Browse files Browse the repository at this point in the history
Codegen dataclassess (#79)

* Wrote test to validate primitive type mapping

* Added test for type alias definition

* Built dataclasses code generator

* Built test for dataclassess code generator

* Changed to unittest

* Updated struct dataclass function to support all types

* feat: built dataclass generator

* Added copyright

* Added nested struct support

* Added test for nested struct

* Feat: update functions and put it in a class.

* chore: update tests

* Casted types to pass pyre check

* Updated types

* chore: Refactor typing.cast code

* Added copyright

* refactor: update tests to reflect new schema changes

* fix: Fix pyre typing errors

[jdwp] do not emit array length and union tag fields

[jdwp] map union tags and array lengths to int

[jdwp] fix type for union fields

[jdwp] fix names of fields containing spaces

[jdwp] generate projects.jdwp.runtime.structs

[jdwp] add tests importing components of the runtime library

implement async abstract methods, add TCP conn

update files

update conn and streams

add buck deps

add jvm connection and implement jvm streams

add a base class for jdwp structs

[jdwp] generate projects.jdwp.runtime.structs

[jdwp] add tests importing components of the runtime library

fix fail build
  • Loading branch information
michalgr authored and wekesa360 committed Dec 7, 2023
1 parent ad008aa commit 6beef3d
Show file tree
Hide file tree
Showing 5 changed files with 294 additions and 10 deletions.
3 changes: 2 additions & 1 deletion projects/jdwp/BUCK
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@
python_binary(
name = "main",
main = "main.py",
deps = [],
deps = [":lib"],
)


python_library(
name = "lib",
srcs = glob(["**/*.py"]),
visibility = ["PUBLIC"],
deps = [ "//projects/jdwp/runtime:runtime"]
)
18 changes: 15 additions & 3 deletions projects/jdwp/main.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,21 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.

import asyncio
from projects.jdwp.runtime.jvm_connection import JVMConnection

def main():
return None

async def main():
host = "localhost"
port = 8880

connection = JVMConnection(host, port)

await connection.connect()

await connection.handshake()

await connection.close()


if __name__ == "__main__":
main()
asyncio.run(main())
12 changes: 6 additions & 6 deletions projects/jdwp/runtime/async_streams.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,15 +96,15 @@ async def read_long(
class JDWPOutputStreamBase(abc.ABC):
# Methods for OpaqueType
@abc.abstractmethod
def write_boolean(self, value: bool) -> None:
async def write_boolean(self, value: bool) -> None:
pass

@abc.abstractmethod
def write_location(self, value: typing.Any) -> None:
async def write_location(self, value: typing.Any) -> None:
pass

@abc.abstractmethod
def write_string(self, value: str) -> None:
async def write_string(self, value: str) -> None:
pass

# Methods for IdType
Expand Down Expand Up @@ -166,15 +166,15 @@ def write_frame_id(self, value: FrameIDType) -> None:

# Methods for IntegralType
@abc.abstractmethod
def write_byte(self, value: int) -> None:
async def write_byte(self, value: int) -> None:
pass

@abc.abstractmethod
def write_int(self, value: int) -> None:
async def write_int(self, value: int) -> None:
pass

@abc.abstractmethod
def write_long(
async def write_long(
self,
value: int,
) -> None:
Expand Down
181 changes: 181 additions & 0 deletions projects/jdwp/runtime/jdwp_streams.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.

import struct
import typing
import asyncio
from projects.jdwp.defs.schema import IdType
from projects.jdwp.runtime.type_aliases import *
from projects.jdwp.runtime.async_streams import (
JDWPInputStreamBase,
JDWPOutputStreamBase,
)


class JDWPInputStream(JDWPInputStreamBase):
__tcp_connection: asyncio.StreamReader

def __init__(self, stream_reader: asyncio.StreamReader):
super().__init__()
self.__tcp_connection = stream_reader

async def read_boolean(self) -> bool:
data = await self._read_bytes(1)
return bool(data[0])

async def read_location(self) -> typing.Any:
pass

async def read_string(self) -> str:
length = await self.read_int()
string_data = await self._read_bytes(length)
return string_data.decode("utf-8")

async def read_object_id(self) -> ObjectIDType:
return await self._read_id(IdType.OBJECT_ID)

async def read_thread_id(self) -> ThreadIDType:
return await self._read_id(IdType.THREAD_ID)

async def read_thread_group_id(self) -> ThreadGroupIDType:
return await self._read_id(IdType.THREAD_GROUP_ID)

async def read_string_id(self) -> StringIDType:
return await self._read_id(IdType.STRING_ID)

async def read_class_loader_id(self) -> ClassLoaderIDType:
return await self._read_id(IdType.CLASS_LOADER_ID)

async def read_class_object_id(self) -> ClassObjectIDType:
return await self._read_id(IdType.CLASS_OBJECT_ID)

async def read_array_id(self) -> ArrayIDType:
return await self._read_id(IdType.ARRAY_ID)

async def read_reference_type_id(self) -> ReferenceTypeIDType:
return await self._read_id(IdType.REFERENCE_TYPE_ID)

async def read_class_id(self) -> ClassIDType:
return await self._read_id(IdType.CLASS_ID)

async def read_interface_id(self) -> InterfaceIDType:
return await self._read_id(IdType.INTERFACE_ID)

async def read_array_type_id(self) -> ArrayTypeIDType:
return await self._read_id(IdType.ARRAY_TYPE_ID)

async def read_method_id(self) -> MethodIDType:
return await self._read_id(IdType.METHOD_ID)

async def read_field_id(self) -> FieldIDType:
return await self._read_id(IdType.FIELD_ID)

async def read_frame_id(self) -> FrameIDType:
return await self._read_id(IdType.FRAME_ID)

async def read_byte(self) -> int:
data = await self._read_bytes(1)
return int.from_bytes(data, byteorder="big")

async def read_int(self) -> int:
data = await self._read_bytes(4)
return struct.unpack("!I", data)[0]

async def read_long(self) -> int:
data = await self._read_bytes(8)
return struct.unpack("!Q", data)[0]

async def _read_bytes(self, size: int) -> bytes:
try:
return await self.__tcp_connection.readexactly(size)
except Exception as e:
print(f"Error during data receiving: {e}")
return b""

async def _read_id(self, id_type: IdType) -> typing.Any:
pass


class JDWPOutputStream(JDWPOutputStreamBase):
__tcp_connection: asyncio.StreamWriter

def __init__(self, socket_connection: asyncio.StreamWriter):
super().__init__()
self.__tcp_connection = socket_connection

async def write_boolean(self, value: bool) -> None:
await self._write_bytes(struct.pack("!B", int(value)))

async def write_int(self, value: int) -> None:
await self._write_bytes(struct.pack("!I", value))

async def write_array_id(self, value: ArrayIDType) -> None:
await self._write_id(value)

async def write_array_type_id(self, value: ArrayTypeIDType) -> None:
await self._write_id(value)

async def write_byte(self, value: int) -> None:
await self._write_bytes(struct.pack("!B", value))

async def write_class_id(self, value: ClassIDType) -> None:
await self._write_id(value)

async def write_class_loader_id(self, value: ClassLoaderIDType) -> None:
await self._write_id(value)

async def write_class_object_id(self, value: ClassObjectIDType) -> None:
await self._write_id(value)

async def write_field_id(self, value: FieldIDType) -> None:
await self._write_id(value)

async def write_frame_id(self, value: FrameIDType) -> None:
await self._write_id(value)

async def write_interface_id(self, value: InterfaceIDType) -> None:
await self._write_id(value)

async def write_location(self, value: typing.Any) -> None:
pass

async def write_method_id(self, value: MethodIDType) -> None:
await self._write_id(value)

async def write_string_id(self, value: StringIDType) -> None:
await self._write_id(value)

async def write_thread_group_id(self, value: ThreadGroupIDType) -> None:
await self._write_id(value)

async def write_long(self, value: int) -> None:
await self._write_bytes(struct.pack("!Q", value))

async def write_object_id(self, value: typing.Any) -> None:
await self._write_id(value)

async def write_thread_id(self, value: typing.Any) -> None:
await self._write_id(value)

async def write_string(self, value: str) -> None:
value_bytes = value.encode("utf-8")
length = len(value_bytes)

await self.write_int(length)

await self._write_bytes(value_bytes)

async def write_reference_type_id(self, value: ReferenceTypeIDType) -> None:
await self._write_id(value)

async def _write_bytes(self, data: bytes) -> None:
try:
self.__tcp_connection.write(data)
await self.__tcp_connection.drain()
except Exception as e:
print(f"Error during data sending: {e}")
await self.__tcp_connection.drain()

async def _write_id(self, value: typing.Any) -> None:
size = min(value.bit_length() // 8 + 1, 8)
await self._write_bytes(struct.pack("B", size))
await self._write_bytes(value.to_bytes(size, byteorder="big"))
90 changes: 90 additions & 0 deletions projects/jdwp/runtime/jvm_connection.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.

import asyncio
import typing
import struct
from projects.jdwp.runtime.jdwp_streams import JDWPInputStream, JDWPOutputStream
from projects.jdwp.runtime.jdwpstruct import JDWPStruct


class JVMConnection:
next_packet_id: int = 0

def __init__(self, host: str, port: int):
self.host: str = host
self.port: int = port
self.__reader: typing.Optional[asyncio.StreamReader] = None
self.__writer: typing.Optional[asyncio.StreamWriter] = None
self.__input_stream: typing.Optional[JDWPInputStream] = None
self.__output_stream: typing.Optional[JDWPOutputStream] = None

async def connect(self) -> None:
try:
self.__reader, self.__writer = await asyncio.open_connection(
self.host, self.port
)
if self.__writer is None:
raise Exception("Writer not initialized")
self.__output_stream = JDWPOutputStream(self.__writer)
except Exception as e:
print(f"Error during connection: {e}")
await self.close()

async def close(self) -> None:
for stream in (self.__reader, self.__writer):
if stream is not None:
if isinstance(stream, asyncio.streams.StreamWriter):
stream.close()
self.__input_stream, self.__output_stream = None, None

async def handshake(self) -> None:
try:
handshake_bytes = b"JDWP-Handshake"
if self.__output_stream is not None:
await self.__output_stream._write_bytes(handshake_bytes)

if self.__input_stream is not None and hasattr(
self.__input_stream, "read_packet_data"
):
response_bytes = await self.__input_stream._read_bytes(
len(handshake_bytes)
)

if response_bytes != handshake_bytes:
raise Exception("Invalid handshake response")

print(f"Handshake successful")
except Exception as e:
print(f"Error during handshake: {e}")
await self.close()

async def read_packet_header(self) -> typing.Tuple[int, int, bytes, int, int]:
if self.__input_stream is None:
raise Exception("Input stream not initialized")
header_data = await self.__input_stream._read_bytes(10)
return struct.unpack("!IIcBB", header_data)

async def read_packet_data(self, length: int) -> bytes:
if self.__input_stream is None:
raise Exception("Input stream not initialized")
return await self.__input_stream._read_bytes(length)

async def write_packet_header(
self, length: int, packet_id: int, flags: bytes, command_set: int, command: int
) -> None:
header_data = struct.pack(
"!IIcBB", length, packet_id, flags, command_set, command
)
if self.__output_stream is None:
raise Exception("Output stream not initialized")
await self.__output_stream._write_bytes(header_data)

async def write_packet_data(self, data: bytes) -> None:
if self.__output_stream is None:
raise Exception("Output stream not initialized")
await self.__output_stream._write_bytes(data)

@staticmethod
def get_next_packet_id() -> int:
JVMConnection.next_packet_id += 1
return JVMConnection.next_packet_id

0 comments on commit 6beef3d

Please sign in to comment.