diff --git a/pdd/PDD-multistream.md b/pdd/PDD-multistream.md new file mode 100644 index 000000000..df7fac795 --- /dev/null +++ b/pdd/PDD-multistream.md @@ -0,0 +1,139 @@ +# PDD for multistream (example/use case) + +## Protocol + +The motivation and base of the protocol can be found here: + +- https://github.com/ipfs/specs/blob/wire/protocol/network/wire.md#multistream---self-describing-protocol-stream +- https://github.com/ipfs/specs/blob/wire/protocol/network/wire.md#multistream-selector---self-describing-protocol-stream-selector + +The multistream protocol does not cover: + +- discovering participants, selecting transports, and establishing connections +- managing the state of the connection + +multistream enables several types of streams to be used over one single stream, like an intelligent message broker that offers the ability to negotiate the protocol and version that is going to be used. To simplify, a visual representation can be: + +``` +┌ ─ ─ ─ ─ ─ ─ ┌ ─ ─ ─ ─ ─ ─ ┐┌ ─ ┐ + dht-id/1.0.1│ bitswap/1.2.3 ... +└ ─ ─ ─ ─ ─ ─ └ ─ ─ ─ ─ ─ ─ ┘└ ─ ┘ +┌────────────────────────────────┐ +│ multistream-select │ +└────────────────────────────────┘ +┌────────────────────────────────┐ +│transport │ +│ [TCP, UDP, uTP, ...] │ +└────────────────────────────────┘ +``` + +multistream doesn't cover stream multiplexing over the same connection, however, we can achieve this by leveraging functionality offered by SPDY or HTTP/2. + +``` +┌─── ───┐┌─── ───┐┌ ┌─── ───┐┌─── ───┐┌┐ + a/1.0.0 b/1.0.0 │ a/1.0.0 b/1.0.0 ││ +└─── ───┘└─── ───┘└ └─── ───┘└─── ───┘ ┘ +┌─── ──── ──── ──── ┌─── ──── ──── ──── ┌─── +│multistream-select││multistream-select││...│ +└ ──── ──── ──── ──┘└ ──── ──── ──── ──┘└ ──┘ +┌─── ──── ──── ──── ──── ──── ──── ──── ──── ┌─── +│stream multiplexing ││...│ +│ [HTTP/2, SPDY] ││ │ +└─ ──── ──── ──── ──── ──── ──── ──── ──── ─┘└─ ─┘ +┌────────────────────────────────────────────────┐ +│ multistream-select │ +└────────────────────────────────────────────────┘ +┌────────────────────────────────────────────────┐ +│transport │ +│ [TCP, UDP, uTP, ...] │ +└────────────────────────────────────────────────┘ +``` + +The multistream messages (such as 'ls', 'na', 'protocol/version') have the following format: + +``` +┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ + ┌───────────────┐┌───────┐│ +││varint with ││message│ + │ message length││ ││ +│└───────────────┘└───────┘ + ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ +``` + +However, for readability reasons, we will omit the `varint` part on the Compliance Spec. + + +Other reference material and discussion: +- https://github.com/ipfs/node-ipfs/issues/13#issuecomment-109802818 + +## Protocol Compliance Tests Spec + +Given the protocol spec, an implementation of the multistream-select protocol has to comply with the following scenarios: + +#### 1 - push-stream + +In a push-stream example (one-way stream), we have two agents: + +- 'broadcast' - where messages are emited +- 'silent' - listener to the messages emited by the broadcast counterparty + +Compliance test 1 (human readable format, without varint): +``` +# With a connection established between silent - broadcast +< /multistream/1.0.0 # multistream header + < /bird/3.2.1 # Protocol header + < hey, how is it going? # First protocol message +``` + +#### 2 - duplex-stream + +In a duplex-stream example (interactive conversation), we have two agents: + +- 'select' - waiting for connections, hosts several protocols from where a client can pick from +- 'interactive' - connects to a select agent and queries that agent for a specific protocol + +Compliance test 2 (human readable format): +``` +# With a connection established between interactive - select +< /multistream/1.0.0 + > /multistream/1.0.0 + > ls + < ["/dogs/0.1.0","/cats/1.2.11"] + > /mouse/1.1.0 + < na + > /dogs/0.1.0 + < /dogs/0.1.0 + > hey +``` + +## Wire out + +Since this protocol is not fully plaintext, we have to capture the messages/packets that transmited by one of the agents to make sure we get the transformations right (and therefore doing development driven by what is going on in the wire, which is defined by the Protocol (PDD ftw!)) + +With a first implementation written, we can capture the messages switched on the wire, so that later, we can require other implementations to conform. For the multistream scenario, tests are organized by the following: + +``` +tests +├── comp # Where compliance tests live +│   ├── compliance-test.js +├── impl # Where specific implementation tests live, where we can get code coverage and all that good stuff +│   ├── interactive-test.js +│   └── one-way-test.js +└── spec # Spec tests are the tests were what is passed on the wire is captured, so it can be used in the compliance tests for all the implementations + ├── capture.js + ├── interactive-test.js + ├── one-way-test.js + └── pristine # The pristine folder were those captures live + ├── broadcast.in + ├── broadcast.out # A broadcast.out is the same as a silent.in, since there are only two agents in this exchange, + ├── interactive.in # the reason both files exist is to avoid mind bending when it is time to use the "in/out", it could get confusing + ├── interactive.out + ├── select.in + ├── select.out + ├── silent.in + └── silent.out +``` + +## Protocol Compliance Test Suite + +The protocol compliance test suit for multistream-select can be found on `tests/comp/compliance-test.js`, each agent is tested alone with the input we have prepared on the previous step for it, once that agent replies to all the messages, we compare (diff) both the output generated and its "pristine" counterpart, expecting to get 0 differences. diff --git a/pdd/README.md b/pdd/README.md new file mode 100644 index 000000000..d21cf7d0e --- /dev/null +++ b/pdd/README.md @@ -0,0 +1,82 @@ +RFC {protocol hash} - Protocol Driven Development +================================================= + +# Abstract + +# Introduction + +Cross compatibility through several implementations and runtimes is historically an hard goal to achieve. Each framework/language offers different testing suits and implement a different flavour of testing (BDD, TDD, RDD, etc). We need a better way to test compatibility across different implementations. + +Instead of the common API tests, we can achieve cross implementation testing by leveraging interfaces offered through the network and defined by the Protocol. We call this Protocol Driven Development. + +In order for a code artefact to be PDD compatible +- Expose a connection (duplex stream) interface, may be synchronous (online, interactive) or asynchronous. +- Implement a well defined Protocol spec + +## Objectives + +The objectives for Protocol Driven Development are: +- Well defined process to test Protocol Spec implementations +- Standard definition of implementation requirements to comply with a certain protocol +- Automate cross implementation tests +- Have a general purpose proxy for packet/message capture + +# Process + +In order to achieve compliance, we have to follow four main steps: + +1 - Define the desired Protocol Spec that is going to be implemented +2 - Design the compliance tests that prove that a certain implementation conforms with the spec +3 - Once an implementation is done, capture the messages traded on the wire using that implementation, so that the behaviour of both participants can be replicated without the agent +4 - Create the Protocol Compliance Tests (consisting on injecting the packets/messages generated in the last step in the other implementations and comparing outputs) + +## Protocol Spec + +Should define the goals, motivation, messages traded between participants and some use cases. It should not cover language or framework specifics. + +## Protocol Compliance Tests Spec + +Defines what are the necessary “use stories” in which the Protocol implementation must be tested to assure it complies with the Protocol Spec. For e.g: + +``` +# Protocol that should always ACK messages of type A and not messages of type B +> A + {< ACK} +> B +> B +> B +``` + +**Message Flow DSL:** +- Indentation to communicate a dependency (a ACK of A can only come after A is sent for e.g) +- [ ] for messages that might or not appear (e.g heartbeats should be passed on the wire from time to time, we know we should get some, but not sure how much and specifically when). +- { } for messages that will arrive, we just can't make sure if before of the following messages described + +A test would pass if the messages transmitted by an implementation follow the expected format and order, defined by the message flow DSL. The test would fail if format and order are not respected, plus if any extra message is transmitted that is was not defined. + +Tests should be deterministic, so that different implementations produce the same results: +``` +┌─────────┐ ┌─────────┐ ┌───────────────┐ +│input.txt│──┬─▶│go-impl │───▶│ output.go.txt │ +└─────────┘ │ └─────────┘ └───────────────┘ + │ ┌─────────┐ ┌───────────────┐ + └─▶│node-impl├───▶│output.node.txt│ + └─────────┘ └───────────────┘ +``` + +So that a diff between two results should yield 0 results + +``` +$ diff output.go.txt output.node.txt +$ +``` + +## Interchange Packet/Message Capture + +Since most of these protocols define leverage some type of encoded format for messages, we have to replicate the transformations applied to those messages before being sent. The other option is capturing the messages being sent by one of the implementations, which should suffice the majority of the scenarios. + +## Protocol Compliance Tests Suite + +These tests offer the last step to test different implementations independently. By sending the packets/messages and evaluating their responses and comparing across different implementations, we can infer that in fact they are compatible + +#### [Example use case - go-multistream and node-multistream tests](/PDD-multistream.md) diff --git a/pdd/figs/multistream-1.monopic b/pdd/figs/multistream-1.monopic new file mode 100644 index 000000000..389835839 Binary files /dev/null and b/pdd/figs/multistream-1.monopic differ diff --git a/pdd/figs/multistream-1.txt b/pdd/figs/multistream-1.txt new file mode 100644 index 000000000..92d36c00e --- /dev/null +++ b/pdd/figs/multistream-1.txt @@ -0,0 +1,10 @@ +┌ ─ ─ ─ ─ ─ ─ ┌ ─ ─ ─ ─ ─ ─ ┐┌ ─ ┐ + dht-id/1.0.1│ bitswap/1.2.3 ... +└ ─ ─ ─ ─ ─ ─ └ ─ ─ ─ ─ ─ ─ ┘└ ─ ┘ +┌────────────────────────────────┐ +│ multistream-select │ +└────────────────────────────────┘ +┌────────────────────────────────┐ +│transport │ +│ [TCP, UDP, uTP, ...] │ +└────────────────────────────────┘ \ No newline at end of file diff --git a/pdd/figs/multistream-2.monopic b/pdd/figs/multistream-2.monopic new file mode 100644 index 000000000..f56fe26d7 Binary files /dev/null and b/pdd/figs/multistream-2.monopic differ diff --git a/pdd/figs/multistream-2.txt b/pdd/figs/multistream-2.txt new file mode 100644 index 000000000..71e043a0a --- /dev/null +++ b/pdd/figs/multistream-2.txt @@ -0,0 +1,17 @@ +┌─── ───┐┌─── ───┐┌ ┌─── ───┐┌─── ───┐┌┐ + a/1.0.0 b/1.0.0 │ a/1.0.0 b/1.0.0 ││ +└─── ───┘└─── ───┘└ └─── ───┘└─── ───┘ ┘ +┌─── ──── ──── ──── ┌─── ──── ──── ──── ┌─── +│multistream-select││multistream-select││...│ +└ ──── ──── ──── ──┘└ ──── ──── ──── ──┘└ ──┘ +┌─── ──── ──── ──── ──── ──── ──── ──── ──── ┌─── +│stream multiplexing ││...│ +│ [HTTP/2, SPDY] ││ │ +└─ ──── ──── ──── ──── ──── ──── ──── ──── ─┘└─ ─┘ +┌────────────────────────────────────────────────┐ +│ multistream-select │ +└────────────────────────────────────────────────┘ +┌────────────────────────────────────────────────┐ +│transport │ +│ [TCP, UDP, uTP, ...] │ +└────────────────────────────────────────────────┘ \ No newline at end of file diff --git a/pdd/figs/multistream-3.monopic b/pdd/figs/multistream-3.monopic new file mode 100644 index 000000000..88e24dd7f Binary files /dev/null and b/pdd/figs/multistream-3.monopic differ diff --git a/pdd/figs/multistream-3.txt b/pdd/figs/multistream-3.txt new file mode 100644 index 000000000..8c639ceaf --- /dev/null +++ b/pdd/figs/multistream-3.txt @@ -0,0 +1,6 @@ +┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ + ┌───────────────┐┌───────┐│ +││varint with ││message│ + │ message length││ ││ +│└───────────────┘└───────┘ + ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ \ No newline at end of file