From 3524a6e001d350296a6d44609262e4a6f3c24da5 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Tue, 16 Jan 2018 22:56:55 +0000 Subject: [PATCH 1/5] Clarify what start_server() and start_client are for. --- ptvsd/wrapper.py | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/ptvsd/wrapper.py b/ptvsd/wrapper.py index e99a97eb2..70f3e75fb 100644 --- a/ptvsd/wrapper.py +++ b/ptvsd/wrapper.py @@ -172,14 +172,22 @@ def shutdown(self, mode): pass def recv(self, count): - # TODO: docstring + """Return the requested number of bytes. + + This is where the "socket" sends requests to pydevd. The data + must follow the pydevd line protocol. + """ data = os.read(self.pipe_r, count) #self.log.write('>>>[' + data.decode('utf8') + ']\n\n') #self.log.flush() return data def send(self, data): - # TODO: docstring + """Handle the given bytes. + + This is where pydevd sends responses and events. The data will + follow the pydevd line protocol. + """ result = len(data) data = unquote(data.decode('utf8')) #self.log.write('<<<[' + data + ']\n\n') @@ -754,7 +762,13 @@ def on_pydevd_send_curr_exception_trace_proceeded(self, seq, args): def start_server(port): - # TODO: docstring + """Return a socket to a (new) local pydevd-handling daemon. + + The daemon supports the pydevd client wire protocol, sending + requests and handling responses (and events). + + This is a replacement fori _pydevd_bundle.pydevd_comm.start_server. + """ server = socket.socket(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP) @@ -777,7 +791,13 @@ def start_server(port): def start_client(host, port): - # TODO: docstring + """Return a socket to an existing "remote" pydevd-handling daemon. + + The daemon supports the pydevd client wire protocol, sending + requests and handling responses (and events). + + This is a replacement fori _pydevd_bundle.pydevd_comm.start_client. + """ client = socket.socket(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP) From a29b4ac76a0d7226b35e3209fe5ab2e33fe0793a Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Tue, 6 Feb 2018 16:24:36 +0000 Subject: [PATCH 2/5] Fix the Stub.add_call_exact() signature. --- tests/helpers/stub.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/helpers/stub.py b/tests/helpers/stub.py index c51eff905..122338fd3 100644 --- a/tests/helpers/stub.py +++ b/tests/helpers/stub.py @@ -13,7 +13,7 @@ def set_exceptions(self, *exceptions): def add_call(self, name, *args, **kwargs): self.add_call_exact(name, args, kwargs) - def add_call_exact(self, name, args, kwargs): + def add_call_exact(self, name, args=None, kwargs=None): self.calls.append((name, args, kwargs)) def maybe_raise(self): From 18943f410ba598a25ce27c01a6a710aae0f6c335 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Tue, 6 Feb 2018 17:37:02 +0000 Subject: [PATCH 3/5] Expose the wire-format helpers. --- debugger_protocol/messages/{_io.py => wireformat.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename debugger_protocol/messages/{_io.py => wireformat.py} (100%) diff --git a/debugger_protocol/messages/_io.py b/debugger_protocol/messages/wireformat.py similarity index 100% rename from debugger_protocol/messages/_io.py rename to debugger_protocol/messages/wireformat.py From cabbc318f130fe38e6ac3b070521de161eb872c7 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Tue, 6 Feb 2018 18:30:24 +0000 Subject: [PATCH 4/5] Add a README about the debugger protocol. --- debugger_protocol/README.md | 114 ++++++++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 debugger_protocol/README.md diff --git a/debugger_protocol/README.md b/debugger_protocol/README.md new file mode 100644 index 000000000..06a032e46 --- /dev/null +++ b/debugger_protocol/README.md @@ -0,0 +1,114 @@ +# VSC Debugger Protocol + +[Visual Studio Code](https://code.visualstudio.com/) defines several +protocols that extensions may leverage to fully integrate with VSC +features. For ptvad the most notable of those is the debugger protocol. +When VSC handles debugger-related input via the UI, it delegates the +underlying behavior to an extension's debug adapter (e.g. ptvsd) via +the protocol. The +[debugger_protocol](https://github.com/ericsnowcurrently/ptvsd/blob/master/debugger_protocol) +package (at which you are looking) provides resources for understanding +and using the protocol. + +For more high-level info see: + +* [the VSC debug protocol page](https://code.visualstudio.com/docs/extensionAPI/api-debugging) +* [the example extension page](https://code.visualstudio.com/docs/extensions/example-debuggers) + + +## Protocol Definition + +The VSC debugger protocol has [a schema](https://github.com/Microsoft/vscode-debugadapter-node/blob/master/debugProtocol.json) +which defines its messages. The wire format is HTTP messages with JSON +bodies. Note that the schema does not define the semantics of the +protocol, though a large portion is elaborated in the "description" +fields in +[the schema document](https://github.com/Microsoft/vscode-debugadapter-node/blob/master/debugProtocol.json). + +[//]: # (TODO: Add a link to where the wire format is defined.) + + +## Components + +### Participants + +The VSC debugger protocol involves 2 participants: the `client` and the +`debug adapter`, AKA `server`. VSC is an example of a `client`. ptvsd +is an example of a `debug adapter`. VSC extensions are responsible for +providing the `debug adapter`, declaring it to VSC and connecting the +adapter to VSC when desired. + +### Communication + +Messages are sent back and forth over a socket. The messages are +JSON-encoded and sent as the body of an HTTP message. + +Flow: + + + +### Message Types + +All messages specify their `type` and a globally-unique +monotonically-increasing ID (`seq`). + +The protocol consists for 3 types of messages: + +* event +* request +* response + +An `event` is a message by which the `debug adapter` reports to the +`client` that something happened. Only the `debug adapter` sends +`event`s. An `event` may be sent at any time, so the `client` may get +one after sending a `request` but before receiving the corresponding +`response`. + +A `request` is a message by which the `client` requests something from +the `debug adapter` over the connection. That "something" may be data +corresponding to the state of the debugger or it may be an action that +should be performed. Note that the protocol dictates that the `debug +adapter` may also send `request`s to the `client`, but currently there +aren't any such `request` types. + +Each `request` type has a corresponding `response` type; and for each +`request` sent by the `client`, the `debug adapter` sends back the +corresponding `response`. `response` messages include a `request_seq` +field that matches the `seq` field of the corresponding `request`. + + +## Protocol-related Tools + +Tools related to the schema, as well as +[a vendored copy](https://github.com/ericsnowcurrently/ptvsd/blob/master/debugger_protocol/schema/debugProtocol.json) +of the schema file itself, are found in +[debugger_protocol/schema](https://github.com/Microsoft/ptvsd/tree/master/debugger_protocol/schema). +Python bindings for the messages are found in +[debugger_protocol/messages](https://github.com/ericsnowcurrently/ptvsd/blob/master/debugger_protocol/messages). +Tools for handling the wire format are found in +[debugger_protocol/messages/wireformat.py](https://github.com/ericsnowcurrently/ptvsd/blob/master/debugger_protocol/messages/wireformat.py). + +### Using the Python-implemented Message Types + +The Python implementations of the schema-defined messages all share a +[ProtocolMessage](https://github.com/ericsnowcurrently/ptvsd/blob/master/debugger_protocol/messages/message.py#L27) +base class. The 3 message types each have their own base class. Every +message class has the following methods to aid with serialization: + +* a `from_data(**raw)` factory method +* a `as_data()` method + +These methods are used by +[the wireformat helpers](https://github.com/ericsnowcurrently/ptvsd/blob/master/debugger_protocol/messages/wireformat.py). + + +## Other Resources + +* https://github.com/Microsoft/vscode-mock-debug +* https://github.com/Microsoft/vscode-debugadapter-node/tree/master/testSupport +* https://github.com/Microsoft/vscode-debugadapter-node/blob/master/protocol/src/debugProtocol.ts +* https://github.com/Microsoft/vscode-mono-debug + +* http://json-schema.org/latest/json-schema-core.html +* https://python-jsonschema.readthedocs.io/ +* http://python-jsonschema-objects.readthedocs.io/ From 8912574f74df7e1927f4e3bb5da0ab4de37cc39a Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Wed, 7 Feb 2018 01:48:56 +0000 Subject: [PATCH 5/5] Add debugger_protocol.messages.look_up(). --- debugger_protocol/messages/__init__.py | 9 +++++++++ debugger_protocol/messages/wireformat.py | 14 ++++++++------ 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/debugger_protocol/messages/__init__.py b/debugger_protocol/messages/__init__.py index a6dfa91dd..ccc56c04c 100644 --- a/debugger_protocol/messages/__init__.py +++ b/debugger_protocol/messages/__init__.py @@ -46,6 +46,14 @@ def register(cls, msgtype=None, typekey=None, key=None): return cls +def look_up(data): + """Return the message class for the given raw message data.""" + msgtype = data['type'] + typekey = MESSAGE_TYPE_KEYS[msgtype] + key = data[typekey] + return MESSAGE_TYPES[msgtype][key] + + class Message(object): """The API for register-able message types.""" @@ -63,5 +71,6 @@ def as_data(self): # Force registration. +from .message import ProtocolMessage, Request, Response, Event from .requests import * # noqa from .events import * # noqa diff --git a/debugger_protocol/messages/wireformat.py b/debugger_protocol/messages/wireformat.py index c58778efc..a31a2e822 100644 --- a/debugger_protocol/messages/wireformat.py +++ b/debugger_protocol/messages/wireformat.py @@ -1,6 +1,6 @@ import json -from . import MESSAGE_TYPES, MESSAGE_TYPE_KEYS +from . import look_up def read(stream): @@ -22,14 +22,16 @@ def read(stream): data = json.loads(body.decode('utf-8')) - msgtype = data['type'] - typekey = MESSAGE_TYPE_KEYS[msgtype] - key = data[typekey] - cls = MESSAGE_TYPES[msgtype][key] - + cls = look_up(data) return cls.from_data(**data) +def write(stream, msg): + """Serialize the message and write it to the stream.""" + raw = as_bytes(msg) + stream.write(raw) + + def as_bytes(msg): """Return the raw bytes for the message.""" headers, body = _as_http_data(msg)