Skip to content

Commit

Permalink
Add networking tests; fix issue with server socket
Browse files Browse the repository at this point in the history
  • Loading branch information
nbbeeken committed Aug 1, 2020
1 parent e5542c0 commit b6943d7
Show file tree
Hide file tree
Showing 11 changed files with 240 additions and 164 deletions.
5 changes: 5 additions & 0 deletions dashmips/debugger.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,3 +101,8 @@ def checkfile(f: str) -> bool:
local_breakpoints.append(-1)

return remote_breakpoints, local_breakpoints


def debug_error(program: MipsProgram, params):
"""Raise an error on the interpreter's end."""
raise MipsException("".join(params))
83 changes: 65 additions & 18 deletions dashmips/debuggerserver.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,79 @@
import logging as log
import signal
import socketserver
from socket import socket
import re

from .utils import MipsException
from .models import MipsProgram


HEADER_REGEX = re.compile(r'{\s*"size"\s*:\s*\d+\s*}')


class ProgramExit(Exception):
"""Program exited normally."""

pass


def client_loop(message: str, commands: dict):
def receive_dashmips_message(client: socket) -> dict:
r"""Receive a dashmips debugger message.
Dashmips communicates in a modified JSON-RPC format.
An example message looks like (in regex form):
{"size": \d+}{"method": "\w+"}
Two concatenated JSON objects, the first reporting the size of the second object.
"""
message = client.recv(30).decode("utf8") # { "size": 9007199254740991 } <- largest message with some padding

if message == "":
# Client is disconnected! maybe we should just exit?
return {"method": "stop"}

header_search = HEADER_REGEX.match(message)
if header_search:
header = header_search[0]
else:
raise MipsException(f"Header not included in message: {message}")

msg_size = json.loads(header)["size"] # must be valid json {size: \d+}
command = message[len(header) :]

remaining_bytes = msg_size - len(command)
if remaining_bytes > 0:
command += client.recv(remaining_bytes).decode("utf8")

return json.loads(command)


def send_dashmips_message(client: socket, data: str):
r"""Send a dashmips debugger message.
Size is calculated from the utf8 encoding of data.
"""
data_encoded = bytes(data, "utf8")
size = len(data_encoded)
size_header = bytes(json.dumps({"size": size}), "utf8")
out_message = size_header + data_encoded
client.sendall(out_message)


def run_method(request: dict, commands: dict):
"""Message loop handler."""
log.info(f"Recv `{message}`")
log.info(f"Recv `{request}`")

if "method" not in request:
raise MipsException("Must specify 'method' in debug messages.")

method = request["method"]
params = request.get("params", [])

request = json.loads(message)
if method not in commands:
raise MipsException(f"Unsupported method '{method}'.")

command = commands[request["method"]]
result = command(params=request["params"])
command = commands[method]
result = command(params=params)

response = json.dumps({"method": request["method"], "result": result})

Expand Down Expand Up @@ -65,28 +120,20 @@ def setup(self):

def handle(self):
# self.request is the TCP socket connected to the client
while True:
header = b""
while True:
header += self.request.recv(1)
if header and chr(header[-1]) == "}":
break
if len(header) >= 1000:
log.error("Communication error between client and server")
break
client: socket = self.request

msg_size = int(header[8:-1])
command = self.request.recv(msg_size)
while True: # Enter the loop that will continuously chat with the debugger client
command = receive_dashmips_message(client)

log.info(f"{self.client_address[0]} wrote: {command}")

try:
response = client_loop(command, self.commands)
response = run_method(command, self.commands)
except ProgramExit:
log.info("Program exited normally")
break

self.request.sendall(bytes(json.dumps({"size": len(response)}), "ascii") + bytes(response, "ascii"))
send_dashmips_message(client, response)

# Allows server to reuse address to prevent crash
socketserver.TCPServer.allow_reuse_address = True
Expand Down
61 changes: 38 additions & 23 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions poetry.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
[virtualenvs]
create = true
in-project = true
path = "./.venv"
95 changes: 48 additions & 47 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,47 +1,48 @@
[tool.poetry]
name = "dashmips"
version = "0.1.6"
description = "Mips Interpreter"
authors = ["Neal Beeken <[email protected]>"]
license = "MIT"
readme = "README.md"
repository = "https://github.com/nbbeeken/dashmips"
classifiers = [
"Development Status :: 4 - Beta",
"Intended Audience :: Education",
"Topic :: Software Development :: Assemblers",
"Programming Language :: Assembly",
"License :: OSI Approved :: MIT License"
]

[tool.poetry.scripts]
dashmips = "dashmips.__main__:main"

[tool.poetry.dependencies]
"python" = "^3.8"
"dataclasses" = { version = "^0.6", python = "3.6" }
"typing-extensions" = "3.7.4.2"

[tool.poetry.dev-dependencies]
"pip" = "^20.1.1"
"wheel" = "^0.34.2"
"mypy" = "^0.782"
"mypy-extensions" = "^0.4.1"
"pycodestyle" = "^2.5.0"
"pydocstyle" = "^5.0.2"
"black" = "^19.10b0"
"pytest" = "^5.0.1"
"pytest-pycodestyle" = "^2.0.1"
"pytest-pydocstyle" = "^2.0.1"
"pytest-mypy" = "^0.6.2"
"pytest-black" = "^0.3.9"

[tool.black]
"line-length" = 160

[metadata]
license_files = "LICENSE"

[build-system]
requires = ["poetry>=0.12"]
build-backend = "poetry.masonry.api"
[tool.poetry]
name = "dashmips"
version = "0.1.6"
description = "Mips Interpreter"
authors = ["Neal Beeken <[email protected]>"]
license = "MIT"
readme = "README.md"
repository = "https://github.com/nbbeeken/dashmips"
classifiers = [
"Development Status :: 4 - Beta",
"Intended Audience :: Education",
"Topic :: Software Development :: Assemblers",
"Programming Language :: Assembly",
"License :: OSI Approved :: MIT License"
]

[tool.poetry.scripts]
dashmips = "dashmips.__main__:main"

[tool.poetry.dependencies]
"python" = "^3.8"
"dataclasses" = { version = "^0.6", python = "3.6" }
"typing-extensions" = "3.7.4.2"

[tool.poetry.dev-dependencies]
"pip" = "^20.1.1"
"wheel" = "^0.34.2"
"mypy" = "^0.782"
"mypy-extensions" = "^0.4.1"
"pycodestyle" = "^2.5.0"
"pydocstyle" = "^5.0.2"
"black" = "^19.10b0"
"pytest" = "^5.0.1"
"pytest-pycodestyle" = "^2.0.1"
"pytest-pydocstyle" = "^2.0.1"
"pytest-mypy" = "^0.6.2"
"pytest-black" = "^0.3.9"
"rope" = "^0.17.0"

[tool.black]
"line-length" = 160

[metadata]
license_files = "LICENSE"

[build-system]
requires = ["poetry>=0.12"]
build-backend = "poetry.masonry.api"
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ docstyle_ignore = D202,D213

[pycodestyle]
max-line-length = 160
ignore = E251,E701,E203,E731
ignore = E251,E701,E203,E731,E203
statistics = True

[pydocstyle]
Expand Down
75 changes: 0 additions & 75 deletions tests/test_debugger/debugserver_tests.py

This file was deleted.

Loading

0 comments on commit b6943d7

Please sign in to comment.