├── .dockerignore ├── .github ├── CONTRIBUTORS ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── 0.1-response-codes.md ├── Dockerfile ├── Ethereum-Wire-Protocol-0.1.EBNF ├── Ethereum-Wire-Protocol-0.2.EBNF ├── LICENSE ├── README.md ├── cloudbuild.yaml ├── gossip.md ├── parsers ├── bash │ └── parser.sh ├── build.sh ├── c │ ├── main.c │ ├── makefile │ ├── request.h │ ├── util.h │ └── vector.h ├── clisp │ ├── hobbit.lisp │ ├── init.lisp │ └── setup.lisp ├── cpp │ ├── main.cpp │ ├── makefile │ ├── request.h │ └── util.h ├── d │ ├── ewp.d │ ├── main.d │ └── makefile ├── erlang │ ├── ewp_request.erl │ ├── ewp_response.erl │ └── test ├── go │ ├── parser.go │ └── test.go ├── java │ └── Parser.java ├── js │ └── parser.js ├── perl │ ├── Hobbit.pm │ ├── parser.t │ └── test.pl ├── php │ ├── request.php │ └── test.php ├── python │ ├── parser.py │ └── test.py ├── racket │ ├── hobbit.rkt │ └── test.rkt ├── rs │ └── parser.rs ├── ruby │ └── parser.rb ├── scheme │ ├── hobbit.scm │ └── test.scm └── swift │ ├── parser │ ├── parser.swift │ └── parser.txt ├── protocol.md ├── requirements.txt ├── rlpx.md ├── rpc-messages.md ├── setup.sh ├── test ├── rpc │ ├── main.go │ ├── ping.go │ └── port.go ├── run.py └── tests.yaml └── uri.md /.dockerignore: -------------------------------------------------------------------------------- 1 | /parsers/cpp/test 2 | -------------------------------------------------------------------------------- /.github/CONTRIBUTORS: -------------------------------------------------------------------------------- 1 | Matthew A Elder 2 | - Spec, EBNF, Rust, JS, Test suite 3 | Zak Cole 4 | - Spec, BASH, general arsehole 5 | Nathaniel Blakely 6 | - C, CPP, Common Lisp, D, Erlang, Scheme, Racket, PHP 7 | Daniel Choi 8 | - Python, Go 9 | Antoine Toulme 10 | - Java 11 | - Specs 12 | Dean Eigenmann 13 | - Specs 14 | - Swift 15 | - Response Code Specs 16 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | * **Please check if the PR fulfills these requirements** 2 | - [ ] The commit message follows our guidelines 3 | - [ ] Tests for the changes have been added (for bug fixes / features) 4 | - [ ] Docs have been added / updated (for bug fixes / features) 5 | 6 | 7 | * **What kind of change does this PR introduce?** (Bug fix, feature, docs update, ...) 8 | 9 | 10 | 11 | * **What is the current behavior?** (You can also link to an open issue here) 12 | 13 | 14 | 15 | * **What is the new behavior (if this is a feature change)?** 16 | 17 | 18 | 19 | * **Does this PR introduce a breaking change?** (What changes might users need to make in their application due to this PR?) 20 | 21 | 22 | 23 | * **Other information**: 24 | 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /parsers/cpp/test* 3 | /parsers/go/test 4 | /parsers/rs/parser 5 | *.pyc 6 | /parsers/python/main.py 7 | parsers/c/test 8 | parsers/d/ewp.o 9 | parsers/d/main 10 | parsers/d/main.o 11 | parsers/erlang/ewp_request.beam 12 | parsers/erlang/ewp_response.beam 13 | parsers/racket/compiled/ 14 | parsers/racket/hobbit.rkt~ 15 | parsers/racket/test 16 | parsers/racket/test.rkt~ 17 | parsers/scheme/compiled/ 18 | parsers/scheme/hobbit.scm~ 19 | parsers/scheme/test 20 | parsers/scheme/test.scm~ 21 | *.exe 22 | *.exe~ 23 | *.dll 24 | *.so 25 | *.dylib 26 | *.test 27 | *.out 28 | 29 | -------------------------------------------------------------------------------- /0.1-response-codes.md: -------------------------------------------------------------------------------- 1 | # Response Codes 2 | 3 | The EWP defines various response codes inspired by [HTTP Status codes](https://en.wikipedia.org/wiki/List_of_HTTP_status_codes). 4 | 5 | ## `200` Ok 6 | 7 | This response code is considered the **default** code. It is returned when the request could be answered as is expected. 8 | 9 | ## `400` Bad Request 10 | 11 | This response code is returned when the `message` is malformed e.g. does not contain the required fields. 12 | 13 | ## `403` Forbidden 14 | 15 | This response code is returned when the caller is not allowed to access specific data. 16 | 17 | ## `404` Not Found 18 | 19 | This response code is returned when a lookup or similar function against a node returned empty results. 20 | 21 | ## `406` Request Compression Unsupported 22 | 23 | This response code is returned when the chosen request compression codec is unsupported by the server. 24 | 25 | ## `407` Response Compression Unsupported 26 | 27 | This response code is returned when none of the requested response compression preferences are supported. 28 | 29 | ## `418` Not a Blockchain 30 | 31 | This response code is returned when the node software refuses to cooperate because it is not actually a blockchain. 32 | 33 | ## `500` Internal Server Error 34 | 35 | This response code is returned when a node experiences some form of internal error that causes it to fail handling the request as expected. 36 | 37 | ## `501` Not Implemented 38 | 39 | This response code is returned when the node does not support the functionality required to fulfill the request. This is the appropriate response when the node does not recognize the request command and is not capable of supporting it. -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:18.04 2 | 3 | ENV DEBIAN_FRONTEND noninteractive 4 | RUN apt-get update 5 | RUN apt-get install -y software-properties-common 6 | RUN add-apt-repository ppa:openjdk-r/ppa 7 | RUN apt-get update 8 | RUN apt-get install -y --no-install-recommends \ 9 | build-essential \ 10 | ca-certificates \ 11 | curl \ 12 | golang-go \ 13 | libedit-dev \ 14 | libxml2-dev \ 15 | mit-scheme \ 16 | perl \ 17 | php \ 18 | python \ 19 | python-dev \ 20 | python-yaml \ 21 | racket \ 22 | rustc \ 23 | snapd \ 24 | wget \ 25 | erlang \ 26 | haskell-platform \ 27 | clisp-dev \ 28 | cl-quicklisp \ 29 | openjdk-11-jre 30 | 31 | WORKDIR /tmp 32 | 33 | # install swift 34 | RUN curl -sL https://swift.org/builds/swift-4.2.3-release/ubuntu1804/swift-4.2.3-RELEASE/swift-4.2.3-RELEASE-ubuntu18.04.tar.gz | tar -C / --strip 1 -xvzf - 35 | 36 | #install dlang 37 | RUN wget http://downloads.dlang.org/releases/2.x/2.085.0/dmd_2.085.0-0_amd64.deb && dpkg -i dmd_2.085.0-0_amd64.deb 38 | 39 | WORKDIR /hobbits 40 | 41 | COPY parsers/ parsers/ 42 | COPY test/ test/ 43 | 44 | WORKDIR /hobbits 45 | 46 | RUN cd parsers && ./build.sh 47 | 48 | ENV RUST_BACKTRACE=full 49 | 50 | CMD python test/run.py 51 | 52 | -------------------------------------------------------------------------------- /Ethereum-Wire-Protocol-0.1.EBNF: -------------------------------------------------------------------------------- 1 | request = request-line, lf, headers, body ; 2 | response = response-line, lf, headers, [ body ] ; 3 | 4 | request-line = magic , sp , version , sp , command , sp , compression-preference , sp , compression-preferences , sp , headers-len , sp , body-len , [ sp , head-only-indicator ]; 5 | 6 | response-line = response-status , sp , compression-preference , sp , headers-len , sp , body-len; 7 | 8 | response-status = digit , { digit } ; 9 | magic = 'EWP' ; 10 | version = '0.1' ; 11 | 12 | headers-len = digit , { digit } ; 13 | body-len = digit , { digit } ; 14 | 15 | command = command-character , { command-character } ; 16 | command-character = uppercase-letter | digit | "_" ; 17 | 18 | compression-preferences = compression-preference , { ",", compression-preference } ; 19 | compression-preference = compression-preference-character , { compression-preference-character } ; 20 | compression-preference-character = lowercase-letter | digit | "_" ; 21 | 22 | uppercase-letter = "A" | "B" | "C" | "D" | "E" | "F" | "G" 23 | | "H" | "I" | "J" | "K" | "L" | "M" | "N" 24 | | "O" | "P" | "Q" | "R" | "S" | "T" | "U" 25 | | "V" | "W" | "X" | "Y" | "Z" ; 26 | lowercase-letter = "a" | "b" 27 | | "c" | "d" | "e" | "f" | "g" | "h" | "i" 28 | | "j" | "k" | "l" | "m" | "n" | "o" | "p" 29 | | "q" | "r" | "s" | "t" | "u" | "v" | "w" 30 | | "x" | "y" | "z" ; 31 | digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" ; 32 | sp = " " ; 33 | head-only-indicator = "H" ; 34 | lf = "\n" ; 35 | 36 | headers = ? BSON ? ; 37 | body = ? COMMAND-DEFINED ENTITY ? ; -------------------------------------------------------------------------------- /Ethereum-Wire-Protocol-0.2.EBNF: -------------------------------------------------------------------------------- 1 | envelope = info, lf, headers, body ; 2 | 3 | info = magic , sp , version , sp , envelope-kind , sp , headers-len , sp , body-len; 4 | 5 | magic = 'EWP' ; 6 | version = '0.2' ; 7 | 8 | headers-len = digit , { digit } ; 9 | body-len = digit , { digit } ; 10 | 11 | envelope-kind = envelope-kind-character , { envelope-kind-character } ; 12 | envelope-kind-character = uppercase-letter | digit | "_" ; 13 | 14 | uppercase-letter = "A" | "B" | "C" | "D" | "E" | "F" | "G" 15 | | "H" | "I" | "J" | "K" | "L" | "M" | "N" 16 | | "O" | "P" | "Q" | "R" | "S" | "T" | "U" 17 | | "V" | "W" | "X" | "Y" | "Z" ; 18 | digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" ; 19 | sp = " " ; 20 | lf = "\n" ; 21 | 22 | headers = ? BSON HEADER DATA ? ; 23 | body = ? DATA PAYLOAD DEFINED BY envelope-kind elsewhere ? ; 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Whiteblock 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # THIS REPOSITORY HAS MOVED. 2 | 3 | Hobbits has been adopted by the [DeltaP2P Foundation](https://github.com/deltap2p/hobbits) where it is now being maintained. 4 | 5 | 6 | # Hobbits, or There and Back Again 7 | 8 | A Lightweight, Multiclient Wire Protocol For ETH2.0 Communications 9 | 10 | Hobbits is a modular wire protocol which allows implementers to experiment with various application logic over a practical network in an agnostic and expedient manner. 11 | 12 | ## Specifications 13 | 14 | ### EWP 0.2 15 | - [EBNF Grammar](/Ethereum-Wire-Protocol-0.2.EBNF) 16 | - [Protocol](/protocol.md) 17 | 18 | ### ETH 2.0 Protocol 19 | - [Messages](/rpc-messages.md) 20 | 21 | ## Implementations 22 | 23 | ### Demo Implementations 24 | - [C](/parsers/c) 25 | - [C++](/parsers/cpp) 26 | - [Common Lisp](/parsers/clisp) 27 | - [D](/parsers/d) 28 | - [Erlang](/parsers/erlang) 29 | - [Go](/parsers/go) 30 | - [Java](/parsers/java) 31 | - [Javascript](/parsers/js) 32 | - [Perl](/parsers/perl) 33 | - [PHP](/parsers/php) 34 | - [Python](/parsers/python) 35 | - [Racket](/parsers/racket) 36 | - [Rust](/parsers/rs) 37 | - [Scheme](/parsers/scheme) 38 | - [Swift](/parsers/swift) 39 | - [Bash](/parsers/bash) 40 | - [Ruby](/parsers/ruby) 41 | 42 | ### Full Implementations 43 | - [Java](https://github.com/pegasyseng/artemis) 44 | - [Swift](https://github.com/yeeth/Hobbits.swift) 45 | 46 | ### Missing languages 47 | * brainfuck 48 | * x86 asm 49 | * ada 50 | * css3 51 | 52 | ## Setup 53 | 54 | ```bash 55 | sh setup.sh 56 | ``` 57 | 58 | ## Running Tests 59 | 60 | ``` 61 | python test/run.py 62 | ``` 63 | > Note: This chicken shit python test runner is written in python 2. 64 | 65 | 66 | Basic [benchmark results](https://gist.github.com/prestonvanloon/6663510164f967fa05553ead157cd5c1) against Protobuf. 67 | 68 | ## Contributing 69 | 70 | Please create issues to document your critiques, suggestions, improvements, etc. We will use it as a discussion funnel to refine this specification and move forward towards a stable 1.0 71 | 72 | STEAL THIS CODE 73 | -------------------------------------------------------------------------------- /cloudbuild.yaml: -------------------------------------------------------------------------------- 1 | # https://cloud.google.com/cloud-build/docs/speeding-up-builds 2 | # https://cloud.google.com/cloud-build/docs/configuring-builds/substitute-variable-values 3 | substitutions: 4 | _IMAGE: 'gcr.io/whiteblock/hobbits' 5 | timeout: '30m' 6 | steps: 7 | # allow these steps to fail, they try to pull cache first 8 | - name: 'gcr.io/cloud-builders/docker' 9 | entrypoint: 'bash' 10 | args: ['-c', 'docker pull $_IMAGE:$BRANCH_NAME || true' ] 11 | # build final docker image 12 | - name: 'gcr.io/cloud-builders/docker' 13 | args: [ 14 | 'build', 15 | '-t', '$_IMAGE:$BRANCH_NAME', 16 | '-t', '$_IMAGE:$COMMIT_SHA', 17 | '--cache-from', '$_IMAGE:$BRANCH_NAME', 18 | '.' 19 | ] 20 | # push docker image tag(s) one branch, one immutable 21 | - name: 'gcr.io/cloud-builders/docker' 22 | args: [ 'push', '$_IMAGE:$COMMIT_SHA' ] 23 | - name: 'gcr.io/cloud-builders/docker' 24 | args: [ 'push', '$_IMAGE:$BRANCH_NAME' ] 25 | options: 26 | machineType: 'N1_HIGHCPU_8' 27 | -------------------------------------------------------------------------------- /gossip.md: -------------------------------------------------------------------------------- 1 | # Messages 2 | These messages are used to define a gossip protocol for Ethereum 2.0. These messages define a gossip protocol for clients to replicate information with each other. 3 | 4 | # Envelope 5 | All messages follow the envelope standard to Hobbits as described in [protocol.md]. 6 | 7 | This application protocol is classified under the `GOSSIP` command. 8 | 9 | The message must contain the following headers: 10 | 11 | | Header name | Type | Notes | 12 | |-------------|------|-------| 13 | | method_id | uint8| the method used in this exchange, as described below | 14 | | message_type | uint8| the type of message being exachanged, as described below | 15 | | message_hash | bytes32 | a hash uniquely representing the message contents, with a hash function up to the application | 16 | | hash_signature | bytes32 | a signature of the message hash with a public key identifying the node sending data | 17 | 18 | Example (showing the bson snappy data as json): 19 | 20 | ```java 21 | EWP 0.2 GOSSIP 24 0 22 | { 23 | "method_id": 3, 24 | "message_type": 0, 25 | "message_hash": "0x9D686F6262697473206172652074776F20616E6420666F75722066656574", 26 | "hash_signature": "0x0000000009A4672656E63682070656F706C6520617265207468652062657374" 27 | } 28 | ``` 29 | 30 | # Methods 31 | 32 | ## 0x00 GOSSIP 33 | 34 | Nodes use `GOSSIP` methods to send data to other nodes in the network. 35 | 36 | The body of a `GOSSIP` method consists in the data being gossiped. 37 | 38 | The `message_hash` header value must match the hash of the contents of the body according to a predefined hash function defined by the application. 39 | 40 | ## 0x01 PRUNE 41 | 42 | Nodes use `PRUNE` messages to inform other nodes that they are removed from the list of peers that will receive data from them. 43 | Instead of sending data, nodes will send attestations as `IHAVE` messages. 44 | 45 | The header may contain the `message_hash` of a message that triggered the pruning. 46 | 47 | ## 0x02 GRAFT 48 | 49 | Nodes use `PRUNE` messages to inform other nodes that they are added to the list of peers that will receive data from them. 50 | Instead of sending attestations as `IHAVE` messages, nodes will send data as `GOSSIP` messages. 51 | 52 | No body is present in `GRAFT` messages. 53 | 54 | The header may contain the `message_hash` of a message triggered the graft. 55 | 56 | Targets should reply with a `GOSSIP` message sending the message matching the hash. 57 | 58 | ## 0x03 IHAVE 59 | 60 | Nodes use `IHAVE` messages to inform other nodes that they are in possession of data that matches the signature they are sending. 61 | 62 | No body is present in `IHAVE` messages. 63 | 64 | The header must contain the `message_hash` with the value of the hash of the data attested by the peer. 65 | 66 | # Message Types 67 | 68 | ## 0x00 BLOCK 69 | [Block](https://github.com/ethereum/eth2.0-specs/blob/v0.5.0/specs/core/0_beacon-chain.md#beaconblock) - from v0.5.1 of the BeaconChain spec 70 | ## 0x01 ATTESTATION 71 | [Attestation](https://github.com/ethereum/eth2.0-specs/blob/v0.5.0/specs/core/0_beacon-chain.md#attestation) - from v0.5.1 of the BeaconChain spec 72 | -------------------------------------------------------------------------------- /parsers/bash/parser.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | if [ $# -lt 2 ];then 3 | echo "ERROR: incorrect number of arguments!" 4 | exit 1 5 | elif [ ! -p /dev/stdin ]; then 6 | echo "No data piped into script" 7 | exit 1 8 | fi 9 | INPUT=$(cat |tr '\0' '|'; echo x) ; INPUT=${INPUT%?} 10 | INFO=($(echo ${INPUT/\\n*/})) 11 | PROTO=${INFO[0]} 12 | VERSION=${INFO[1]} 13 | COMMAND=${INFO[2]} 14 | HEADERBODY=${INPUT/*$COMMAND/} 15 | HEADERBODY=${HEADERBODY//|/\\x00} 16 | HEADER=($(echo ${HEADER/\\n*/})) 17 | BODY=${HEADERBODY/#$HEADER} 18 | printf "$PROTO $VERSION $COMMAND$HEADER$BODY" 19 | -------------------------------------------------------------------------------- /parsers/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -xeu 2 | 3 | pushd c 4 | make re 5 | popd 6 | 7 | pushd clisp 8 | clisp setup.lisp || true 9 | clisp -i "~/quicklisp/setup.lisp" -i init.lisp -c hobbit.lisp || true 10 | popd 11 | 12 | pushd cpp 13 | make 14 | popd 15 | 16 | pushd d 17 | make 18 | popd 19 | 20 | pushd erlang 21 | erl -compile ewp_request.erl ewp_response.erl 22 | popd 23 | 24 | pushd go 25 | go get || true 26 | go build 27 | mv ./go ./test 28 | popd 29 | 30 | # N/A 31 | #pushd js 32 | #popd 33 | 34 | pushd perl 35 | prove -v parser.t 36 | popd 37 | 38 | # N/A 39 | #pushd php 40 | #popd 41 | 42 | # N/A 43 | #pushd python 44 | #popd 45 | 46 | pushd racket 47 | raco exe test.rkt 48 | popd 49 | 50 | pushd rs 51 | rustc parser.rs 52 | popd 53 | 54 | pushd scheme 55 | raco exe test.scm 56 | popd 57 | 58 | #pushd swift 59 | # swiftc parser.swift 60 | #popd 61 | -------------------------------------------------------------------------------- /parsers/c/main.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "request.h" 8 | 9 | 10 | int main(int argc,char** argv) 11 | { 12 | if(argc != 3){ 13 | perror("Invalid number of arguments\n"); 14 | return EXIT_FAILURE; 15 | } 16 | int size = atoi(argv[2]); 17 | char* buffer = (char*)calloc(size+1,sizeof(char)); 18 | size_t output_size = 0; 19 | if(read(0,buffer,size) == -1){ 20 | perror("recv had an error"); 21 | return EXIT_FAILURE; 22 | } 23 | 24 | if (strcmp(argv[1],"request") == 0){ 25 | struct ewp_request* req = (struct ewp_request*)malloc(sizeof(struct ewp_request)); 26 | if(ewp_request_parse(buffer,req) != 0){ 27 | write(2,"Parse failed\n",13); 28 | return EXIT_FAILURE; 29 | } 30 | char* out = ewp_request_marshal(req,&output_size); 31 | write(1,out,output_size); 32 | }else{ 33 | perror("Invalid first argument\n"); 34 | return EXIT_FAILURE; 35 | } 36 | 37 | 38 | return EXIT_SUCCESS; 39 | } -------------------------------------------------------------------------------- /parsers/c/makefile: -------------------------------------------------------------------------------- 1 | CC=gcc 2 | 3 | INCLUDE=-Ilib/ 4 | VERSION=c11 5 | CFLAGS=-g -std=$(VERSION) -Wno-format -Wall 6 | 7 | FILES=main.c 8 | 9 | 10 | .PHONY: clean fclean re all 11 | 12 | all: $(OBJECTS) test 13 | 14 | test: 15 | $(CC) $(CFLAGS) $(FILES) -o test 16 | 17 | clean: 18 | rm -f $(OBJECTS) $(TEST_OBJ) 19 | 20 | fclean: 21 | rm -f test 22 | 23 | re: fclean all 24 | -------------------------------------------------------------------------------- /parsers/c/request.h: -------------------------------------------------------------------------------- 1 | #ifndef HOBBIT_REQUEST_H 2 | #define HOBBIT_REQUEST_H 3 | /** 4 | * POC DO NOT USE IN PRODUCTION 5 | * UNSAFE UNSAFE UNSAFE 6 | */ 7 | #include 8 | #include 9 | #include "util.h" 10 | 11 | struct ewp_request { 12 | char* proto; 13 | char* version; 14 | char* command; 15 | size_t header_len; 16 | size_t body_len; 17 | char* header; 18 | char* body; 19 | }; 20 | 21 | int ewp_request_parse(char* in,struct ewp_request* req) 22 | { 23 | int index = index_of_char(in,'\n'); 24 | char* request_line = substring(in,0,index); 25 | 26 | vector request = explode(" ",request_line); 27 | //printf("%d\n",vector_length(request)); 28 | if(vector_length(request) < 5){ 29 | vector_free(request); 30 | return 1; 31 | //Not enough parameters 32 | } 33 | 34 | char* tmp = (char*)vector_get(request,0); 35 | req->proto = (char*)calloc(strlen(tmp)+1,sizeof(char)); 36 | strcpy(req->proto,tmp); 37 | 38 | tmp = (char*)vector_get(request,1); 39 | req->version = (char*)calloc(strlen(tmp)+1,sizeof(char)); 40 | strcpy(req->version,tmp); 41 | 42 | tmp = (char*)vector_get(request,2); 43 | req->command = (char*)calloc(strlen(tmp)+1,sizeof(char)); 44 | strcpy(req->command,tmp); 45 | 46 | if(index != -1){ 47 | char* request_body = substring(in,index+1,strlen(in)); 48 | req->header_len = atoi((char*)vector_get(request,3)); 49 | req->body_len = atoi((char*)vector_get(request,4)); 50 | req->header = substring(request_body,0,req->header_len); 51 | req->body = substring(request_body,req->header_len,req->body_len); 52 | free(request_body); 53 | } 54 | vector_free(request); 55 | return 0; 56 | 57 | } 58 | 59 | char* ewp_request_marshal(struct ewp_request* req,size_t* size) 60 | { 61 | 62 | char* tmp; 63 | char* out =strappend(' ', req->proto); 64 | *size += strlen(out); 65 | tmp = strappend(' ',req->version); 66 | *size += strlen(tmp); 67 | out = concat(out,tmp, SECOND); 68 | tmp = strappend(' ',req->command); 69 | *size += strlen(tmp); 70 | out = concat(out,tmp,FIRST | SECOND); 71 | 72 | tmp = strappend(' ' ,ltoa(req->header_len)); 73 | *size += strlen(tmp); 74 | out = concat(out,tmp,FIRST | SECOND); 75 | 76 | tmp = ltoa(req->body_len); 77 | *size += strlen(tmp); 78 | out = concat(out,tmp,FIRST); 79 | 80 | tmp = strappend('\n',out); 81 | free(out); 82 | out = tmp; 83 | *size += 1; 84 | 85 | tmp = memsafe_concat(req->header,req->header_len,req->body,req->body_len,0); 86 | 87 | out = memsafe_concat(out,*size,tmp,req->header_len + req->body_len,FIRST|SECOND); 88 | *size += req->header_len + req->body_len; 89 | 90 | return out; 91 | } 92 | 93 | 94 | 95 | 96 | 97 | #endif -------------------------------------------------------------------------------- /parsers/c/util.h: -------------------------------------------------------------------------------- 1 | #ifndef HOBBIT_UTIL_H 2 | #define HOBBIT_UTIL_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "vector.h" 9 | 10 | #define TRUE 1 11 | #define FALSE 0 12 | #define FIRST 1 13 | #define SECOND 2 14 | 15 | char* strappend(char s,const char* str){ 16 | size_t length = strlen(str); 17 | char* out = (char*)malloc(sizeof(char)*(length+2)); 18 | if(str!=NULL){ 19 | strcpy(out,str); 20 | } 21 | out[length + 1] = '\0'; 22 | out[length] = s; 23 | return out; 24 | } 25 | 26 | unsigned int strcompsub(char* str1,char* str2, size_t index, size_t length) 27 | { 28 | int_fast64_t i; 29 | if(length != strlen(str1)){ 30 | return FALSE; 31 | } 32 | for(i = index;i> 1)& 1){ 105 | free(s2); 106 | } 107 | if((mem & 1)){ 108 | free(s1); 109 | } 110 | out[length] = '\0'; 111 | return out; 112 | } 113 | 114 | char* memsafe_concat(char* s1,size_t l1, char* s2,size_t l2, uint8_t mem){ 115 | size_t length = l1 + l2; 116 | char* out = (char*)calloc(length+1,sizeof(char)); 117 | uint_fast64_t i; 118 | for(i = 0;i> 1)& 1){ 125 | free(s2); 126 | } 127 | if((mem & 1)){ 128 | free(s1); 129 | } 130 | out[length] = '\0'; 131 | return out; 132 | } 133 | 134 | char* ltoa(uint64_t num){ 135 | char * characters[10] = {"0","1","2","3","4","5","6","7","8","9"}; 136 | return (num>=10)? concat(ltoa(num/10),characters[num%10],FALSE) : characters[num]; 137 | } 138 | 139 | 140 | char* concat_all(int args,...){ 141 | va_list valist; 142 | int i = 0; 143 | char* output; 144 | va_start(valist, args); 145 | for(i = 0;i 0 ){ 175 | vector_push(&out,subj); 176 | } 177 | 178 | return out; 179 | } 180 | 181 | vector split(char quan,char* subject){ 182 | vector out = NULL; 183 | int index = index_of_char(subject,quan); 184 | vector_push(&out,substring(subject,0,index)); 185 | vector_push(&out,substr(subject,index+1)); 186 | return out; 187 | } 188 | 189 | #endif -------------------------------------------------------------------------------- /parsers/c/vector.h: -------------------------------------------------------------------------------- 1 | #ifndef vector_H 2 | #define vector_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | typedef struct vect* vector; 9 | struct vect{ 10 | vector next; 11 | vector prev; 12 | void* data; 13 | }; 14 | 15 | static vector create_vector(){ 16 | vector out = (vector) malloc(sizeof(struct vect)); 17 | out->next = NULL; 18 | out->prev = NULL; 19 | out->data = NULL; 20 | return out; 21 | } 22 | 23 | size_t vector_length(vector v){ 24 | size_t length = 0; 25 | vector tmp = v; 26 | if(v==NULL){ 27 | return 0; 28 | } 29 | while(tmp != NULL){ 30 | tmp = tmp->next; 31 | length++; 32 | } 33 | return length; 34 | } 35 | 36 | void* vector_get(vector v,size_t index){ 37 | vector tmp = v; 38 | int i = 0; 39 | while(i != index && tmp != NULL){ 40 | tmp = tmp->next; 41 | i++; 42 | } 43 | return tmp->data; 44 | } 45 | 46 | void vector_clean(vector v){ 47 | vector tmp = v; 48 | vector clear = NULL; 49 | if(v==NULL){ 50 | return; 51 | } 52 | 53 | while(tmp != NULL){ 54 | 55 | clear = tmp; 56 | tmp = tmp->next; 57 | clear->next = NULL; 58 | clear->prev = NULL; 59 | if(clear->data) 60 | free(clear->data); 61 | free(clear); 62 | } 63 | } 64 | 65 | void* vector_get_f(vector v,size_t index){ 66 | char * out = vector_get(v,index); 67 | vector_clean(v); 68 | return out; 69 | } 70 | 71 | void vector_pop(vector* v, size_t index){ 72 | if(v==NULL || *v == NULL){ 73 | return; 74 | } 75 | vector tmp = *v; 76 | vector p; 77 | int i = 0; 78 | if(index == 0){ 79 | *v = tmp->next; 80 | free(tmp); 81 | if(*v != NULL){ 82 | (*v)->prev = NULL; 83 | } 84 | return; 85 | } 86 | while(i != index && tmp != NULL){ 87 | tmp = tmp->next; 88 | i++; 89 | } 90 | p = tmp->prev; 91 | p->next = tmp->next; 92 | tmp->next->prev = p; 93 | free(tmp); 94 | } 95 | 96 | 97 | 98 | void vector_push(vector * v,void* data){ 99 | vector add = create_vector(); 100 | vector tmp; 101 | add->data = data; 102 | if(v==NULL||*v==NULL){ 103 | *v = add; 104 | return; 105 | } 106 | 107 | tmp = *v; 108 | while(tmp->next != NULL){ 109 | tmp = tmp->next; 110 | } 111 | tmp->next = add; 112 | add->prev = tmp; 113 | } 114 | 115 | 116 | 117 | void vector_free(vector v){ 118 | vector tmp = v; 119 | vector clear = NULL; 120 | if(v==NULL){ 121 | return; 122 | } 123 | 124 | while(tmp->next != NULL){ 125 | tmp = tmp->next; 126 | clear = tmp->prev; 127 | clear->next = NULL; 128 | clear->prev = NULL; 129 | clear->data = NULL; 130 | free(clear); 131 | } 132 | free(tmp); 133 | } 134 | 135 | vector vector_merge(vector * v1, vector * v2){ 136 | int i; 137 | for(i = 0;idata)); 151 | v = v->next; 152 | } 153 | puts("done"); 154 | } 155 | 156 | void ** vector_to_array(vector v){ 157 | size_t size = vector_length(v); 158 | void ** out = (void**)calloc(sizeof(void*),size+1); 159 | out[size] = NULL; 160 | int32_t i = 0; 161 | for(i = 0;i 62 | ; '("EWP" "0.1" "PING" "none" ("none" "non") #f "55" "555") 63 | 64 | (defun parse-request (request) 65 | (let ((req-split (cl-ppcre:split #\linefeed request ))) 66 | 67 | (let ((req-line (parse-request-line (car req-split)))) 68 | (let ((payload (if (equal (length req-split) 1) "" (arr-to-string (cdr req-split) "\n"))) ) 69 | ;(print req-line) 70 | (let ((body-len (parse-integer (nth 6 req-line)))) 71 | 72 | (let ((header-len (parse-integer (nth 5 req-line)))) 73 | ;(print header-len) 74 | (append 75 | (reverse 76 | (if (equal 8 (length req-line)) (cdddr (reverse req-line)) (cddr (reverse req-line)))) 77 | (list 78 | (and 79 | (equal 8 (length req-line)) 80 | (string= "H" (nth 7 req-line ))) 81 | (substring payload 0 header-len) 82 | (substring payload header-len (+ header-len body-len))))))))) 83 | ) 84 | 85 | (defun marshal-request (request) 86 | ;(print request) 87 | (concatenate 'string 88 | (arr-merge request " " 0 3) 89 | " " 90 | (arr-to-string (nth 4 request) ",") 91 | " " 92 | (write-to-string (length (nth 6 request))) 93 | " " 94 | (write-to-string (length (nth 7 request))) 95 | (if (nth 5 request) " H" "" ) 96 | '(#\linefeed) 97 | (nth 6 request) (nth 7 request)) 98 | ) 99 | ;(parse-request "EWP 0.1 PING none none,non 2 3\n55555") 100 | ;(marshal-request (parse-request "EWP 0.1 PING none none,non 2 3\n55555")) 101 | 102 | (defun parse-response-line (rl) 103 | (let ((arr (cl-ppcre:split " " rl))) 104 | ;(print arr) 105 | (append 106 | (list 107 | (parse-integer (car arr)) 108 | (cadr arr) 109 | (parse-integer (caddr arr))) 110 | (if (equal (length arr) 4) (list (parse-integer (nth 3 arr))) '()))) 111 | ) 112 | ; (parse-response "200 none 5 5\n1234512345") 113 | ; (parse-response "200 none 5\n1234512345") 114 | (defun parse-response (response) 115 | (let ((res-line (parse-response-line (car (cl-ppcre:split #\linefeed response ))))) 116 | ;(print (cl-ppcre:split #\linefeed response )) 117 | (let ((payload (arr-to-string (cdr (cl-ppcre:split #\linefeed response :limit 2)) #\linefeed ) )) 118 | ;(print payload) 119 | (append (list (car res-line) (cadr res-line )) 120 | (append (list (substring payload 0 (nth 2 res-line))) 121 | (if (equal (length res-line) 3) '() 122 | (list (substring payload (nth 2 res-line) (+ (nth 2 res-line) (nth 3 res-line))))) 123 | )))) 124 | ) 125 | 126 | (defun marshal-response (response) 127 | (concatenate 'string 128 | (write-to-string (car response)) 129 | " " 130 | (cadr response) 131 | " " 132 | (write-to-string (length (nth 2 response))) 133 | 134 | (if (equal (length response) 3) "" " ") 135 | 136 | (if (equal (length response) 3) "" (write-to-string (length (cadddr response)))) 137 | '(#\linefeed) 138 | (caddr response) 139 | (cadddr response) 140 | )) 141 | 142 | 143 | ;(write-string (marshal-response (parse-response (format nil "200 none 5 5~C1234567890" #\linefeed)))) 144 | 145 | 146 | ;(write-string (marshal-request (parse-request (format nil "EWP 0.1 PING none none 2 3~C55555" #\linefeed)))) 147 | 148 | (defun read-x (chars) 149 | (if (equal chars 0) 150 | "" 151 | (let ((chr (read-char))) 152 | (concatenate 'string (string chr) (read-x (- chars 1)))) 153 | ) 154 | ) 155 | 156 | ;(setf (readtable-case *readtable*) :preserve) 157 | 158 | (let ( 159 | (input-type (car *ARGS*)) 160 | (input-length (parse-integer (cadr *ARGS*))) 161 | 162 | ) 163 | ;(print input-length) 164 | (let ((usr-input (read-x input-length))) 165 | (if (string= input-type "response") 166 | (write-string (marshal-response (parse-response usr-input))) 167 | (if (string= input-type "request") 168 | (write-string (marshal-request (parse-request usr-input))) 169 | ))) 170 | ) 171 | -------------------------------------------------------------------------------- /parsers/clisp/init.lisp: -------------------------------------------------------------------------------- 1 | (ql:quickload "cl-ppcre" :silent t) -------------------------------------------------------------------------------- /parsers/clisp/setup.lisp: -------------------------------------------------------------------------------- 1 | (load "/usr/share/cl-quicklisp/quicklisp.lisp") 2 | (quicklisp-quickstart:install) -------------------------------------------------------------------------------- /parsers/cpp/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "request.h" 7 | 8 | using namespace std; 9 | 10 | string parse_input(size_t size){ 11 | const size_t buffer_size = 100; 12 | size_t len = 0; 13 | string out; 14 | char buffer[buffer_size]; 15 | 16 | while(len < size){ 17 | size_t max_to_read = std::min(buffer_size - 1,size - len); 18 | 19 | cin.read(buffer,max_to_read); 20 | out.append(buffer,max_to_read); 21 | len += max_to_read; 22 | } 23 | return out; 24 | } 25 | 26 | int main(int argc,char** argv) 27 | { 28 | 29 | if(argc == 1){ 30 | hobbit::ewp_request req(string("EWP 0.2 PING 0 5\n12345")); 31 | 32 | cout< 5 | #include 6 | #include 7 | #include 8 | #include "util.h" 9 | 10 | namespace hobbit{ 11 | 12 | struct ewp_request { 13 | std::string proto; 14 | std::string version; 15 | std::string command; 16 | std::string header; 17 | std::string body; 18 | 19 | 20 | ewp_request(const std::string& in) 21 | { 22 | this->parse(in); 23 | } 24 | 25 | 26 | void parse(const std::string& in) 27 | { 28 | size_t index = in.find('\n'); 29 | const auto request_line = in.substr(0,index); 30 | 31 | 32 | auto request = explode(request_line,' '); 33 | if(request.size() < 5){ 34 | throw std::invalid_argument("Not enough parameters"); 35 | //Not enough parameters 36 | } 37 | 38 | this->proto = request[0]; 39 | this->version = request[1]; 40 | this->command = request[2]; 41 | if(index != std::string::npos){ 42 | const auto request_body = in.substr(index+1); 43 | this->header = request_body.substr(0,std::stoi(request[3])); 44 | this->body = request_body.substr(std::stoi(request[3]),std::stoi(request[4])); 45 | } 46 | 47 | } 48 | 49 | std::string marshal() const noexcept 50 | { 51 | std::string out = this->proto; 52 | out += " " + this->version; 53 | out += " " + this->command; 54 | out += " " + std::to_string(this->header.size()); 55 | out += " " + std::to_string(this->body.size()); 56 | out += "\n"; 57 | out += this->header + this->body; 58 | return out; 59 | } 60 | }; 61 | } 62 | 63 | 64 | 65 | #endif -------------------------------------------------------------------------------- /parsers/cpp/util.h: -------------------------------------------------------------------------------- 1 | #ifndef HOBBIT_UTIL_H 2 | #define HOBBIT_UTIL_H 3 | 4 | #include 5 | 6 | namespace hobbit{ 7 | template 8 | std::vector explode(const T& subj,char quan) 9 | { 10 | std::vector out; 11 | size_t slength = subj.size(); 12 | size_t index = 0; 13 | 14 | for(size_t i = 0;i < slength - 1;i++){ 15 | if(quan == subj.at(i+index)){ 16 | if(i != 0){ 17 | out.emplace_back(subj.substr(index,i)); 18 | } 19 | 20 | index += i+1; 21 | slength -= i+1; 22 | i = 0; 23 | } 24 | } 25 | if(index < subj.size()){ 26 | out.emplace_back(subj.substr(index)); 27 | } 28 | 29 | return out; 30 | } 31 | } 32 | #endif -------------------------------------------------------------------------------- /parsers/d/ewp.d: -------------------------------------------------------------------------------- 1 | import std.stdio; 2 | import std.string; 3 | import std.array; 4 | import std.conv; 5 | 6 | //module ewp; 7 | 8 | class request{ 9 | string _proto; 10 | string _version; 11 | string _command; 12 | string _header; 13 | string _body; 14 | this(string input) 15 | { 16 | this.parse(input); 17 | } 18 | 19 | void parse(string input) 20 | { 21 | ptrdiff_t index = indexOf(input,'\n'); 22 | string request_line = input[0 .. index]; 23 | 24 | auto request = split(request_line,' '); 25 | if(request.length < 5){ 26 | writeln("Not enough parameters"); 27 | } 28 | _proto = request[0]; 29 | _version = request[1]; 30 | _command = request[2]; 31 | if(index != -1){ 32 | string request_body = input[index+1 .. $]; 33 | _header = request_body[0 .. to!int(request[3])]; 34 | _body = request_body[to!int(request[3]) .. to!int(request[3]) + to!int(request[4])]; 35 | } 36 | } 37 | 38 | string marshal() 39 | { 40 | string ret = _proto ~ " " ~ _version ~ " "; 41 | ret = ret ~ _command; 42 | 43 | ret = ret ~ " " ~ to!string(_header.length) ~ " " ~ to!string(_body.length); 44 | ret = ret ~ "\n" ~ _header ~ _body; 45 | return ret; 46 | } 47 | } 48 | 49 | -------------------------------------------------------------------------------- /parsers/d/main.d: -------------------------------------------------------------------------------- 1 | import std.stdio; 2 | import std.conv; 3 | 4 | import ewp; 5 | void main(string[] args){ 6 | 7 | if(args.length != 3){ 8 | writeln("Not enough arguments"); 9 | return; 10 | } 11 | int size = to!int(args[2]); 12 | 13 | char[] buffer = new char[](size); 14 | stdin.rawRead(buffer); 15 | string input = to!string(buffer); 16 | 17 | if (args[1] == "request"){ 18 | ewp.request req = new ewp.request(input); 19 | write(req.marshal()); 20 | } 21 | } -------------------------------------------------------------------------------- /parsers/d/makefile: -------------------------------------------------------------------------------- 1 | CC=dmd 2 | 3 | CFLAGS=-g 4 | 5 | LIB_FILES= ewp.d 6 | LIB_OBJ= $(LIB_FILES:.d=.o) 7 | 8 | FILES= main.d 9 | 10 | OBJS := $(FILES:.d=.o) 11 | .PHONY: clean fclean re all 12 | 13 | all: $(LIB_OBJ) test 14 | 15 | test: % : main.d 16 | $(CC) $(CFLAGS) $< $(LIB_OBJ) 17 | 18 | clean: 19 | rm -f $(OBJECTS) 20 | 21 | fclean: 22 | rm -f test 23 | 24 | re: fclean all 25 | 26 | $(LIB_OBJ): %.o : %.d 27 | $(CC) $(CFLAGS) -c $< $@ -------------------------------------------------------------------------------- /parsers/erlang/ewp_request.erl: -------------------------------------------------------------------------------- 1 | -module(ewp_request). 2 | 3 | -import(string,[str/2,sub_string/3,len/1,tokens/2]). 4 | 5 | -export([parse/1,marshal/1]). 6 | 7 | -record(ewp_request,{ 8 | proto, 9 | version, 10 | envelope_kind, 11 | header, 12 | body 13 | }). 14 | 15 | parse(In) -> 16 | Index = str(In,"\n"), 17 | RequestLine = string:slice(In,0,Index-1), 18 | RequestBody = string:substr(In,Index+1), 19 | Request = string:tokens(RequestLine," "), 20 | if 21 | length(Request) < 5 -> 22 | exit("Not enough items"); 23 | true -> 24 | ok 25 | end, 26 | Proto = lists:nth(1,Request), 27 | Version = lists:nth(2,Request), 28 | EnvelopeKind = lists:nth(3,Request), 29 | Header = string:slice(RequestBody,0,list_to_integer(lists:nth(4,Request))), 30 | if 31 | length(Request) == 8 -> 32 | Body = ""; 33 | true -> 34 | Body = string:slice(RequestBody,list_to_integer(lists:nth(4,Request)),list_to_integer(lists:nth(5,Request))) 35 | 36 | end, 37 | #ewp_request{ 38 | proto = Proto, 39 | version = Version, 40 | envelope_kind = EnvelopeKind, 41 | header = Header, 42 | body = Body 43 | } 44 | . 45 | 46 | %ewp_request:marshal(ewp_request:parse("EWP 0.1 PING none none 2 2 H\ntest")). 47 | %ewp_request:marshal(ewp_request:parse("EWP 0.1 PING none none 2 2\ntest")). 48 | marshal(State)-> 49 | 50 | binary_to_list(erlang:iolist_to_binary(io_lib:format("~s ~s ~s ~p ~p\n~s~s",[ 51 | State#ewp_request.proto, 52 | State#ewp_request.version, 53 | State#ewp_request.envelope_kind, 54 | string:len(State#ewp_request.header), 55 | string:len(State#ewp_request.body), 56 | State#ewp_request.header, 57 | State#ewp_request.body 58 | ]))) 59 | 60 | . -------------------------------------------------------------------------------- /parsers/erlang/ewp_response.erl: -------------------------------------------------------------------------------- 1 | -module(ewp_response). 2 | 3 | -import(string,[str/2,sub_string/3,len/1,tokens/2]). 4 | 5 | -export([parse/1,marshal/1]). 6 | 7 | -record(ewp_response,{ 8 | response_status, 9 | compression, 10 | header, 11 | body, 12 | has_body = false 13 | }). 14 | 15 | %ewp_response:parse("200 none 9 10\n987654321\n\n\n\n\n\n\n\n\n\n"). 16 | parse(In) -> 17 | Index = str(In,"\n"), 18 | ResponseLine = string:slice(In,0,Index-1), 19 | ResponseBody = string:substr(In,Index+1), 20 | Response = string:tokens(ResponseLine," "), 21 | if 22 | length(Response) < 3 -> 23 | exit("Not enough items"); 24 | true -> 25 | ok 26 | end, 27 | 28 | ResponseStatus = list_to_integer(lists:nth(1,Response)), 29 | Compression = lists:nth(2,Response), 30 | Header = string:slice(ResponseBody,0,list_to_integer(lists:nth(3,Response))), 31 | 32 | %1HasBody = false, 33 | 34 | if 35 | length(Response) == 4 -> 36 | Body = string:slice(ResponseBody,list_to_integer(lists:nth(3,Response)),list_to_integer(lists:nth(4,Response))); 37 | true -> 38 | Body = "" 39 | end, 40 | 41 | #ewp_response{ 42 | response_status=ResponseStatus, 43 | compression=Compression, 44 | header=Header, 45 | body=Body, 46 | has_body=(length(Response) == 4) 47 | } 48 | . 49 | 50 | %ewp_response:marshal(ewp_response:parse("200 none 9 10\n987654321\n\n\n\n\n\n\n\n\n\n")). 51 | marshal(State) -> 52 | if 53 | State#ewp_response.has_body -> 54 | binary_to_list(erlang:iolist_to_binary(io_lib:format("~p ~s ~p ~p\n~s~s",[ 55 | State#ewp_response.response_status, 56 | State#ewp_response.compression, 57 | string:len(State#ewp_response.header), 58 | string:len(State#ewp_response.body), 59 | State#ewp_response.header, 60 | State#ewp_response.body]))); 61 | true -> 62 | binary_to_list(erlang:iolist_to_binary(io_lib:format("~p ~s ~p\n~s",[ 63 | State#ewp_response.response_status, 64 | State#ewp_response.compression, 65 | string:len(State#ewp_response.header), 66 | State#ewp_response.header]))) 67 | end 68 | . -------------------------------------------------------------------------------- /parsers/erlang/test: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env escript 2 | %% -*- erlang -*- 3 | %%! -pa ./parsers/erlang/ 4 | 5 | main([Type,Length]) -> 6 | Given = io:get_chars("", list_to_integer(Length)), 7 | case Type of 8 | "response" -> 9 | io:fwrite("~s",[ewp_response:marshal(ewp_response:parse(Given))]); 10 | "request" -> 11 | io:fwrite("~s",[ewp_request:marshal(ewp_request:parse(Given))]); 12 | _Else -> 13 | io:fwrite("Invalid argument ~p~n",[Type]) 14 | end 15 | 16 | ; 17 | 18 | main(_) -> 19 | io:fwrite("Usage ./test \n"). 20 | 21 | 22 | -------------------------------------------------------------------------------- /parsers/go/parser.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "strings" 7 | ) 8 | 9 | type Request struct { 10 | proto string 11 | version string 12 | command string 13 | headers []byte 14 | body []byte 15 | } 16 | 17 | func reqParse(req string) Request { 18 | res := strings.Split(req, "\n") 19 | 20 | reqLine := res[0] 21 | payload := strings.Join(res[1:], "\n") 22 | r := strings.Split(reqLine, " ") 23 | if len(r) < 8 { 24 | r = append(r, " ") 25 | } 26 | headersLen, _ := strconv.Atoi(r[3]) 27 | bodyLen, _ := strconv.Atoi(r[4]) 28 | headers := payload[0:headersLen] 29 | body := payload[headersLen : headersLen+bodyLen] 30 | 31 | request := Request{ 32 | proto: r[0], 33 | version: r[1], 34 | command: r[2], 35 | headers: []byte(headers), 36 | body: []byte(body), 37 | } 38 | return request 39 | } 40 | 41 | func reqMarshal(req Request) string { 42 | requestLine := fmt.Sprintf("%s %s %s %d %d", 43 | req.proto, 44 | req.version, 45 | req.command, 46 | len(req.headers), 47 | len(req.body)) 48 | 49 | r := fmt.Sprintf("%s\n%s%s", requestLine, string(req.headers), string(req.body)) 50 | return r 51 | } 52 | -------------------------------------------------------------------------------- /parsers/go/test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "os" 7 | "strconv" 8 | ) 9 | 10 | func main() { 11 | reqres := os.Args[1] 12 | len, err := strconv.Atoi(os.Args[2]) 13 | if err != nil { 14 | panic(err) 15 | } 16 | 17 | reader := bufio.NewReader(os.Stdin) 18 | buffer := make([]byte, len) 19 | reader.Read(buffer) 20 | if reqres == "request" { 21 | fmt.Printf(reqMarshal(reqParse(string(buffer)))) 22 | } else { 23 | fmt.Println("invalid request given") 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /parsers/java/Parser.java: -------------------------------------------------------------------------------- 1 | //import java.net.http.HttpResponse.BodySubscriber; 2 | import java.nio.charset.StandardCharsets; 3 | /** 4 | * Copyright Whiteblock, Inc. and others 2019 5 | * 6 | * Licensed under MIT license. 7 | */ 8 | import java.util.Arrays; 9 | import java.io.IOException; 10 | 11 | /** 12 | * Example parser in Java for EWP. 13 | */ 14 | public final class Parser { 15 | 16 | public static final class Request { 17 | 18 | private final String protocol; 19 | private final String version; 20 | private final String command; 21 | private final String headers; 22 | private final String body; 23 | 24 | 25 | Request(String protocol, 26 | String version, 27 | String command, 28 | String headers, 29 | String body) { 30 | this.protocol = protocol; 31 | this.command = command; 32 | this.version = version; 33 | this.headers = headers; 34 | this.body = body; 35 | } 36 | 37 | public String toString() { 38 | StringBuilder builder = new StringBuilder(); 39 | builder.append(protocol + " " + 40 | version + " " + 41 | command); 42 | if (headers == null) { 43 | builder.append(" 0"); 44 | } else { 45 | builder.append(" ").append(headers.length()); 46 | } 47 | if (body == null) { 48 | builder.append(" 0"); 49 | } else { 50 | builder.append(" ").append(body.length()); 51 | } 52 | builder.append("\n"); 53 | if (headers != null) { 54 | builder.append(headers); 55 | } 56 | if (body != null) { 57 | builder.append(body); 58 | } 59 | return builder.toString(); 60 | } 61 | } 62 | 63 | /** 64 | * Parses a string into a EWP request 65 | * @param str the string to parse 66 | * @return the request 67 | * @throws IllegalArgumentException if the string doesn't match the EWP spec. 68 | */ 69 | public static Request parseRequest(String str) { 70 | final int newlineIdx = str.indexOf('\n'); 71 | if (newlineIdx < 0) { 72 | throw new IllegalArgumentException("No new line found"); 73 | } 74 | String reqLine = str.substring(0, newlineIdx); 75 | String[] requestArguments = reqLine.split(" "); 76 | if (requestArguments.length < 5) { 77 | throw new IllegalArgumentException("Not enough elements in request line"); 78 | } 79 | final int startHeaders = newlineIdx + 1; 80 | final int endHeaders; 81 | final int endBody; 82 | try { 83 | int headerLength = Integer.parseUnsignedInt(requestArguments[3]); 84 | int bodyLength = Integer.parseUnsignedInt(requestArguments[4]); 85 | endHeaders = startHeaders + headerLength; 86 | endBody = endHeaders + bodyLength; 87 | } catch (NumberFormatException e) { 88 | throw new IllegalArgumentException(e); 89 | } 90 | if (str.length() < endBody) { 91 | throw new IllegalArgumentException("Invalid length encoding"); 92 | } 93 | return new Request( 94 | requestArguments[0], 95 | requestArguments[1], 96 | requestArguments[2], 97 | str.substring(startHeaders, endHeaders), // headers 98 | str.substring(endHeaders, endBody) // body 99 | ); 100 | } 101 | 102 | public static void main(String[] args) throws IOException { 103 | String reqres = args[0]; 104 | int length = Integer.parseInt(args[1]); 105 | byte[] input = new byte[length]; 106 | System.in.read(input, 0, length); 107 | String toRead = new String(input, StandardCharsets.UTF_8); 108 | if ("request".equals(reqres)) { 109 | Request msg = parseRequest(toRead); 110 | System.out.print(msg.toString()); 111 | } else { 112 | throw new IllegalArgumentException("invalid request response given " + reqres); 113 | } 114 | } 115 | } -------------------------------------------------------------------------------- /parsers/js/parser.js: -------------------------------------------------------------------------------- 1 | const example_ping_req = "EWP 0.2 PING none none 0 5\n12345"; 2 | 3 | // req is a string 4 | function parse(req) { 5 | let [ reqLine, payload ] = req.split('\n') 6 | let r = reqLine.split(' ') 7 | let headersLen = parseInt(r[3]) 8 | let bodyLen = parseInt(r[4]) 9 | let headers = payload.slice(0, headersLen) 10 | let body = payload.slice(headersLen, bodyLen) 11 | 12 | return { 13 | proto: r[0], 14 | version: r[1], 15 | command: r[2], 16 | headers, 17 | body 18 | } 19 | } 20 | 21 | function test() { 22 | console.log(parse(example_ping_req)) 23 | } 24 | test() 25 | -------------------------------------------------------------------------------- /parsers/perl/Hobbit.pm: -------------------------------------------------------------------------------- 1 | package Hobbit; 2 | use strict; 3 | use warnings; 4 | 5 | sub new { 6 | my ($class, $opts) = @_; 7 | $opts ||= {}; 8 | my $self = { %$opts }; 9 | bless($self, $class); 10 | } 11 | 12 | sub parse_request { 13 | my ($self, $req) = @_; 14 | my ($req_line, $payload) = ($req =~ /(.*?)\n(.*)/s); 15 | my @r = split(' ', $req_line); 16 | my $headers_len = int $r[3]; 17 | my $body_len = int $r[4]; 18 | my $headers = substr $payload, 0, $headers_len; 19 | my $body = substr $payload, $headers_len, $body_len; 20 | return { 21 | proto => $r[0], 22 | version => $r[1], 23 | command => $r[2], 24 | headers => $headers, 25 | body => $body, 26 | } 27 | } 28 | 29 | sub marshal_request { 30 | my ($self, $req) = @_; 31 | my $request_line = sprintf( 32 | "%s %s %s %d %d%s", 33 | $req->{proto}, 34 | $req->{version}, 35 | $req->{command}, 36 | length($req->{headers}), 37 | length($req->{body})); 38 | my $r = sprintf("%s\n%s%s", $request_line, $req->{headers}, $req->{body}); 39 | return $r; 40 | } 41 | 42 | 1; 43 | 44 | __END__ 45 | 46 | =head1 NAME 47 | 48 | Hobbit - a parser for the Hobbit protocol 49 | 50 | =head1 SYNOPSIS 51 | 52 | my $hobbit = Hobbit->new(); 53 | my $request = $hobbit->parse_request("EWP 0.1 PING none none 0 5\n12345"); 54 | 55 | =head1 DESCRIPTION 56 | 57 | This is a Perl implementation of the Hobbit protocol which is 58 | a lightweight, multiclient wire protocol for ETH2.0 communications. 59 | 60 | =head1 API 61 | 62 | =head2 new(%opts) 63 | 64 | =head2 parse_request($request_string) 65 | 66 | =head2 marshal_request($request_hashref) 67 | 68 | =head1 SEE ALSO 69 | 70 | =over 4 71 | 72 | =item https://github.com/Whiteblock/hobbits 73 | 74 | =back 75 | 76 | =head1 AUTHOR 77 | 78 | John Beppu (john.beppu@gmail.com) 79 | 80 | =cut 81 | -------------------------------------------------------------------------------- /parsers/perl/parser.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | use strict; 4 | use warnings; 5 | use Test::More tests => 7; 6 | 7 | require_ok('./Hobbit.pm'); 8 | 9 | my $hobbit = Hobbit->new(); 10 | ok($hobbit->isa('Hobbit'), "Hobbit->new() returned a Hobbit parser instance"); 11 | 12 | my $request = $hobbit->parse_request("EWP 0.2 PING 0 5\n12345"); 13 | is($request->{proto}, 'EWP', "proto should be 'EWP'"); 14 | is($request->{version}, '0.2', "version should be '0.2'"); 15 | is($request->{command}, 'PING', "command should be 'PING'"); 16 | is($request->{headers}, '', "headers should be an empty string"); 17 | is($request->{body}, '12345', "body should be '12345'"); 18 | 19 | -------------------------------------------------------------------------------- /parsers/perl/test.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | use strict; 4 | use warnings; 5 | use lib './'; 6 | require('Hobbit.pm'); 7 | 8 | my $hobbit = Hobbit->new(); 9 | 10 | my $reqres = shift; 11 | if ($reqres eq 'request') { 12 | my $request = do { local $/; }; 13 | print $hobbit->marshal_request($hobbit->parse_request($request)) 14 | } else { 15 | print "invalid request response given\n"; 16 | } 17 | -------------------------------------------------------------------------------- /parsers/php/request.php: -------------------------------------------------------------------------------- 1 | proto = $given_request[0]; 14 | $this->version = $given_request[1]; 15 | $this->command = $given_request[2]; 16 | $this->header = substr($req_split[1],0,intval($given_request[3])); 17 | $this->body = substr($req_split[1],intval($given_request[3]),intval($given_request[4])); 18 | } 19 | 20 | public function marshal(){ 21 | $out = $this->proto . " ". $this->version . " " . $this->command; 22 | $out .= " ".strlen($this->header). " ".strlen($this->body); 23 | $out .= "\n".$this->header . $this->body; 24 | return $out; 25 | } 26 | } 27 | 28 | //Uncomment and run for example 29 | /* 30 | $test = new ewp_request; 31 | $test->parse("EWP 0.2 PING 0 5\n12345"); 32 | 33 | print_r($test); 34 | 35 | echo $test->marshal()."\n";*/ 36 | ?> -------------------------------------------------------------------------------- /parsers/php/test.php: -------------------------------------------------------------------------------- 1 | parse($ewp); 21 | echo $test->marshal(); 22 | } 23 | ?> -------------------------------------------------------------------------------- /parsers/python/parser.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | class Request: 4 | def __init__(self, proto, version, command, headers, body): 5 | self.proto = proto 6 | self.version = version 7 | self.command = command 8 | self.headers = headers 9 | self.body = body 10 | 11 | def req_parse(req): 12 | res = req.split("\n",1) 13 | reqLine = res[0] 14 | tmp = res 15 | for i in range (0,len(res)): 16 | if tmp[i] == "": 17 | tmp[i] = "\n" 18 | payload = "".join(tmp[1:]) 19 | r = str.split(reqLine, " ") 20 | if len(r) < 8: 21 | r.append("") 22 | headersLen = int(r[3]) 23 | bodyLen = int(r[4]) 24 | headers = payload[0:headersLen] 25 | body = payload[headersLen:bodyLen+headersLen] 26 | 27 | request = Request(r[0],r[1],r[2],headers,body) 28 | return request.__dict__ 29 | 30 | def req_marshal(req): 31 | if isinstance(req,dict): 32 | proto = req.get("proto") 33 | version = req.get("version") 34 | command = req.get("command") 35 | headerLen = len(req.get("headers")) 36 | bodyLen = len(req.get("body")) 37 | headers = (req.get("headers"), "")[headerLen == 0] 38 | body = (req.get("body"), "")[bodyLen == 0] 39 | return ("{} {} {} {} {}\n{}{}").format(proto, version, command, headerLen, bodyLen, headers, body) 40 | -------------------------------------------------------------------------------- /parsers/python/test.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | 3 | import sys 4 | 5 | from parser import ( 6 | req_parse, 7 | req_marshal, 8 | ) 9 | 10 | reqres = sys.argv[1] 11 | chars = int(sys.argv[2]) 12 | 13 | stdin = sys.stdin.read(chars) 14 | 15 | if reqres == "request": 16 | print(req_marshal(req_parse(stdin)),end='') 17 | else: 18 | print("invalid request input") 19 | -------------------------------------------------------------------------------- /parsers/racket/hobbit.rkt: -------------------------------------------------------------------------------- 1 | (module ewp racket 2 | 3 | (provide marshal-request parse-request) 4 | (define (parse-request-line rl) 5 | (let ([arr (string-split rl " ")]) 6 | (list (list-ref arr 0) (list-ref arr 1) (list-ref arr 2) (string->number (list-ref arr 3)) (string->number (list-ref arr 4)))) 7 | ) 8 | (define list-index 9 | (lambda (e lst) 10 | (if (null? lst) 11 | -1 12 | (if (eq? (car lst) e) 13 | 0 14 | (if (= (list-index e (cdr lst)) -1) 15 | -1 16 | (+ 1 (list-index e (cdr lst)))))))) 17 | 18 | (define (explode lst delimit) 19 | (if (empty? lst) '() 20 | (let ([i (list-index delimit lst )]) 21 | (if (< i 0) (list (list->string lst)) 22 | (if (= i 0) (explode (cdr lst) delimit) 23 | (append (list (list->string (reverse (list-tail (reverse lst ) (- (length lst) i 0))))) 24 | (explode (list-tail lst (+ i 1)) delimit) ))))) 25 | ) 26 | 27 | 28 | 29 | (define (arr-to-string lst delim) 30 | (match (length lst) 31 | [0 ""] 32 | [1 (car lst)] 33 | [ _ 34 | (string-append (string-append (car lst) delim) (arr-to-string (cdr lst) delim))] 35 | ) 36 | ) 37 | 38 | (define (arr-merge arr spacer start end) 39 | (if (equal? start end) (car arr) 40 | (string-append (string-append (car arr) spacer) (arr-merge (cdr arr) spacer start (- end 1))))) 41 | 42 | ;Parse the request into a tuple of the important parts 43 | ;(parse-request "EWP 0.2 PING 2 3\n55555") -> 44 | ; '("EWP" "0.2" "PING" #f "55" "555") 45 | 46 | (define (parse-request request) 47 | (let ([req-split (string-split request "\n" #:trim? #f #:repeat? #f)]) 48 | (let ([req-line (parse-request-line (car req-split))]) 49 | (let ([payload (if (equal? (length req-split) 1) "" (arr-to-string (cdr req-split) "\n")) ]) 50 | (let ([body-len (list-ref req-line 4)]) 51 | (let ([header-len (list-ref req-line 3)]) 52 | (append 53 | (reverse (cddr (reverse req-line))) 54 | (list 55 | (substring payload 0 header-len) 56 | (substring payload header-len (+ header-len body-len))) 57 | )))))) 58 | ) 59 | 60 | (define (marshal-request request) 61 | (string-append (arr-merge request " " 0 2) 62 | (string-append " " 63 | (string-append (number->string (string-length (list-ref request 3))) 64 | (string-append " " 65 | (string-append (number->string (string-length (list-ref request 4))) 66 | (string-append "\n" 67 | (string-append (list-ref request 3) (list-ref request 4))))))))) 68 | 69 | ) 70 | ;(parse-request "EWP 0.2 PING 2 3\n55555") 71 | ;(marshal-request (parse-request "EWP 0.2 PING 2 3\n55555")) -------------------------------------------------------------------------------- /parsers/racket/test.rkt: -------------------------------------------------------------------------------- 1 | #lang racket 2 | (require "hobbit.rkt") 3 | 4 | (let ([args (current-command-line-arguments)]) 5 | (if (not (equal? (vector-length args) 2)) (display "No arguments given") 6 | (let ([size (string->number (vector-ref args 1))]) 7 | (let ([usr-input (read-string size)]) 8 | (match (vector-ref args 0) 9 | 10 | ["request" 11 | (display (marshal-request (parse-request usr-input))) 12 | ] 13 | 14 | [_ (display "Can only be request")] 15 | ) 16 | )) 17 | ) 18 | ) -------------------------------------------------------------------------------- /parsers/rs/parser.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::io::{self, Read}; 3 | 4 | #[derive(Clone,Debug)] 5 | pub struct EWPRequest { 6 | pub version: String, 7 | pub command: String, 8 | pub headers: Vec, 9 | pub body: Vec 10 | } 11 | 12 | 13 | impl EWPRequest { 14 | pub fn parse(req: &[u8]) -> Result { 15 | let req_line_end_idx = req.iter().position(|b| *b == ('\n' as u8)) 16 | .ok_or_else(|| 17 | std::io::Error::new(std::io::ErrorKind::Other, "request newline terminator missing") 18 | ) 19 | ?; 20 | let req_line_raw = &req[..req_line_end_idx]; 21 | let payload = &req[(req_line_end_idx + 1)..]; 22 | let req_line: String = String::from_utf8(req_line_raw.to_vec()).map_err(|err| std::io::Error::new(std::io::ErrorKind::Other, err))?; 23 | 24 | let r: Vec = req_line.splitn(8, |chr| chr == ' ').map(|s| s.to_string()).collect(); 25 | assert!(&r[0] == "EWP"); 26 | assert!(&r[1] == "0.2"); 27 | let headers_len: usize = r[3].parse().map_err(|err| std::io::Error::new(std::io::ErrorKind::Other, err))?; 28 | let body_len: usize = r[4].parse().map_err(|err| std::io::Error::new(std::io::ErrorKind::Other, err))?; 29 | 30 | Ok(Self { 31 | version: "0.2".to_string(), 32 | command: r[2].clone(), 33 | headers: payload[0..headers_len].to_vec(), 34 | body: payload[headers_len..(headers_len + body_len)].to_vec(), 35 | }) 36 | } 37 | pub fn marshal(&self) -> Vec { 38 | let headers_len = self.headers.len().to_string(); 39 | let body_len = self.body.len().to_string(); 40 | 41 | let parts: Vec<&str> = vec![ 42 | "EWP", 43 | &self.version, 44 | &self.command, 45 | &headers_len, 46 | &body_len 47 | ]; 48 | 49 | let req_line = parts.join(" ") + "\n"; 50 | 51 | let mut rval = req_line.into_bytes(); 52 | rval.extend(&self.headers); 53 | rval.extend(&self.body); 54 | 55 | rval 56 | } 57 | } 58 | 59 | fn main() { 60 | let args: Vec = env::args().collect(); 61 | let mode = &args[1]; 62 | let payload_len: usize = args[2].parse().expect("payload length is not an integer"); 63 | let mut buffer = vec![0u8; payload_len]; 64 | 65 | io::stdin().read_exact(&mut buffer).expect("could not read stdin"); 66 | 67 | match mode.as_str() { 68 | "request" => { 69 | let req = EWPRequest::parse(&buffer).expect("parse request failed"); 70 | let marshalled = req.marshal(); 71 | let marshalled_str = unsafe { String::from_utf8_unchecked(marshalled) }; 72 | print!("{}", marshalled_str); 73 | }, 74 | _ => panic!("invalid serialization type"), 75 | }; 76 | } 77 | -------------------------------------------------------------------------------- /parsers/ruby/parser.rb: -------------------------------------------------------------------------------- 1 | class Request 2 | 3 | attr_reader :protocol, :version, :compression, :encoding, :headers, :body 4 | 5 | def initialize(protocol, version, compression, encoding, headers, body) 6 | @protocol = protocol 7 | @version = version 8 | @compression = compression 9 | @encoding = encoding 10 | #@head_only_indicator = head_only_indicator 11 | @headers = headers 12 | @body = body 13 | end 14 | 15 | end 16 | 17 | class Parser 18 | 19 | def self.parse_request(request) 20 | lines = request.split("\n") 21 | parts = lines[0].split(" ") 22 | protocol = parts[0] 23 | version = parts[1] 24 | compression = parts[3] 25 | encoding = parts[4] 26 | #head_only_indicator = " " 27 | headers = "" 28 | if parts[5].to_i > 0 29 | headers = lines[1][0..(parts[5].to_i-1)] 30 | end 31 | body = lines[1][parts[5].to_i..(parts[6].to_i-1)] 32 | 33 | Request.new(protocol, version, compression, encoding, headers, body) 34 | end 35 | 36 | end 37 | -------------------------------------------------------------------------------- /parsers/scheme/hobbit.scm: -------------------------------------------------------------------------------- 1 | (module ewp scheme 2 | (provide marshal-request parse-request) 3 | (define (parse-request-line rl) 4 | (let ([arr (string-split rl " ")]) 5 | (list (list-ref arr 0) (list-ref arr 1) (list-ref arr 2) (string->number (list-ref arr 3)) (string->number (list-ref arr 4)))) 6 | ) 7 | (define list-index 8 | (lambda (e lst) 9 | (if (null? lst) 10 | -1 11 | (if (eq? (car lst) e) 12 | 0 13 | (if (= (list-index e (cdr lst)) -1) 14 | -1 15 | (+ 1 (list-index e (cdr lst)))))))) 16 | 17 | (define (explode lst delimit) 18 | (if (empty? lst) '() 19 | (let ([i (list-index delimit lst )]) 20 | (if (< i 0) (list (list->string lst)) 21 | (if (= i 0) (explode (cdr lst) delimit) 22 | (append (list (list->string (reverse (list-tail (reverse lst ) (- (length lst) i 0))))) 23 | (explode (list-tail lst (+ i 1)) delimit) ))))) 24 | ) 25 | 26 | 27 | 28 | (define (arr-to-string lst delim) 29 | (match (length lst) 30 | [0 ""] 31 | [1 (car lst)] 32 | [ _ 33 | (string-append (string-append (car lst) delim) (arr-to-string (cdr lst) delim))] 34 | ) 35 | ) 36 | 37 | (define (arr-merge arr spacer start end) 38 | (if (equal? start end) (car arr) 39 | (string-append (string-append (car arr) spacer) (arr-merge (cdr arr) spacer start (- end 1))))) 40 | 41 | ;Parse the request into a tuple of the important parts 42 | ;(parse-request "EWP 0.2 PING 2 3\n55555") -> 43 | ; '("EWP" "0.2" "PING" #f "55" "555") 44 | 45 | (define (parse-request request) 46 | (let ([req-split (string-split request "\n" #:trim? #f #:repeat? #f)]) 47 | (let ([req-line (parse-request-line (car req-split))]) 48 | (let ([payload (if (equal? (length req-split) 1) "" (arr-to-string (cdr req-split) "\n")) ]) 49 | (let ([body-len (list-ref req-line 4)]) 50 | (let ([header-len (list-ref req-line 3)]) 51 | (append 52 | (reverse (cddr (reverse req-line))) 53 | (list 54 | (substring payload 0 header-len) 55 | (substring payload header-len (+ header-len body-len))) 56 | )))))) 57 | ) 58 | 59 | (define (marshal-request request) 60 | (string-append (arr-merge request " " 0 2) 61 | (string-append " " 62 | (string-append (number->string (string-length (list-ref request 3))) 63 | (string-append " " 64 | (string-append (number->string (string-length (list-ref request 4))) 65 | (string-append "\n" 66 | (string-append (list-ref request 3) (list-ref request 4))))))))) 67 | 68 | ) -------------------------------------------------------------------------------- /parsers/scheme/test.scm: -------------------------------------------------------------------------------- 1 | #lang scheme 2 | (require "hobbit.scm") 3 | (let ([args (current-command-line-arguments)]) 4 | (if (not (equal? (vector-length args) 2)) (display "No arguments given") 5 | (let ([size (string->number (vector-ref args 1))]) 6 | (let ([usr-input (read-string size)]) 7 | (match (vector-ref args 0) 8 | 9 | ["request" 10 | (display (marshal-request (parse-request usr-input))) 11 | ] 12 | 13 | [_ (display "Can only be request")] 14 | ) 15 | )) 16 | ) 17 | ) -------------------------------------------------------------------------------- /parsers/swift/parser: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whiteblock/hobbits/5d45e8d784f9416724f171c6936962c75fed3e5e/parsers/swift/parser -------------------------------------------------------------------------------- /parsers/swift/parser.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | struct Message { 4 | 5 | let version: String // @todo Version type 6 | let proto: String 7 | let compression: String 8 | let encoding: String 9 | let headers: [UInt8] 10 | let body: [UInt8] 11 | } 12 | 13 | extension Message: CustomStringConvertible { 14 | 15 | public var description: String { 16 | return "EWP " + version 17 | + " " + proto.rawValue 18 | + " " + compression 19 | + " " + encoding 20 | + " " + String(headers.count) 21 | + " " + String(body.count) 22 | + "\n" + String(bytes: headers, encoding: .utf8)! 23 | + String(bytes: body, encoding: .utf8)! 24 | } 25 | } 26 | 27 | extension Message { 28 | 29 | init(serializedData input: String) { 30 | var lines = input.split(separator: "\n") 31 | if lines.count == 1 { 32 | lines.append("") 33 | } 34 | 35 | let payload = lines[1] 36 | 37 | var request = lines[0].split(separator: " ") 38 | assert(request.count >= 7) // @todo exception handling 39 | let headersLength = Int(request[5])! 40 | let bodyLength = Int(request[6])! 41 | 42 | version = String(request[1]) 43 | proto = String(request[2]) 44 | compression = String(request[3]) 45 | encoding = String(request[4]) 46 | headers = payload.substring(start: 0, end: headersLength).bytes 47 | body = payload.substring(start: headersLength, end: bodyLength).bytes 48 | } 49 | } 50 | 51 | print(Message(serializedData: "EWP 0.2 RPC none json 0 25\n{'id':1,'method_id':0x00}")) 52 | -------------------------------------------------------------------------------- /parsers/swift/parser.txt: -------------------------------------------------------------------------------- 1 | Request(proto: "EWP", version: "0.2", command: "PING", headers: [], body: [49, 50, 51, 52, 53]) 2 | -------------------------------------------------------------------------------- /protocol.md: -------------------------------------------------------------------------------- 1 | # EWP (Ethereum Wire Protocol) 2 | 3 | ## Messages 4 | 5 | The message type both defines the `request` and `response` as they are identical. The `message` format is the following: 6 | 7 | ``` 8 | EWP 9 |
10 | ``` 11 | 12 | A parsed message would look like this: 13 | 14 | ```python 15 | { 16 | 'version': 'string', 17 | 'protocol': 'string' 18 | 'compression': 'string', 19 | 'encoding': 'string', 20 | 'headers': 'bytes', 21 | 'body': 'bytes' 22 | } 23 | ``` 24 | 25 | ### Fields 26 | 27 | | Field | Definition | Validity | 28 | |:------:|----------|:----:| 29 | | `version` | Defines the EWP version number e.g. `0.1`. | `(\d+\.)(\d+)` | 30 | | `protocol` | Defines the communication protocol. | `(RPC\|GOSSIP)` | 31 | | `compression` | Defines the compression codec of the `header` and `body`, none can be specified. | `[a-z0-9_]+` | 32 | | `encoding` | Defines the encoding of the `header` and `body`. | `[a-z0-9_]+` | 33 | | `header` | Defines the header which is a `BSON` payload, it is seperately encoded and compressed. | `BSON` payload | 34 | | `body` | Defines the body which is a `BSON` payload, it is seperately encoded and compressed. | `BSON` payload | 35 | 36 | ### Examples 37 | 38 | example of a wire protocol message 39 | 40 | #### RPC call example with ping 41 | ``` 42 | # Request (RPC call with a body of a RPC ping call) 43 | EWP 0.2 RPC none json 0 25 44 | {"id":1,"method_id":0x00} 45 | 46 | # Response 47 | EWP 0.2 RPC none json 0 25 48 | {"id":1,"method_id":0x01} 49 | ``` 50 | 51 | #### RPC call with payload 52 | ``` 53 | # Request (empty headers, bson body) 54 | EWP 0.2 RPC deflate bson 0 1234 55 | <1234 bytes of deflate compressed binary bson body data> 56 | # Response 57 | EWP 0.2 RPC gzip bson 321 1234 58 | <321 bytes of gzip compressed binary bson header data> 59 | <1234 bytes of gzip compressed binary bson body data> 60 | 61 | # Request (no compression, bson headers, bson body) 62 | EWP 0.1 RPC none none 321 1234 63 | <321 bytes of binary bson header data> 64 | <1234 bytes of binary bson body data> 65 | # Response 66 | 200 none 0 0\n 67 | ``` 68 | 69 | #### Gossip 70 | ``` 71 | # Request (Gossip call with a header with a message hash) 72 | EWP 0.2 GOSSIP none json 33 0 73 | "001322323232232932232322232327f" 74 | 75 | # Request (Gossip call with a full block) 76 | EWP 0.2 GOSSIP snappy bson 25 1234 77 | <25 bytes of snappy compressed binary bson header data> 78 | <1234 bytes of snappy compressed binary bson body data> 79 | ``` 80 | 81 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pyyaml 2 | 3 | -------------------------------------------------------------------------------- /rlpx.md: -------------------------------------------------------------------------------- 1 | # RLPx connection 2 | 3 | Hobbits allows to transmit messages over RLPx, by declaring a subprotocol in the RLPx protocol. 4 | 5 | ## The 'hob' subprotocol. 6 | 7 | The Hobbits subprotocol is named 'hob' with version '1'. 8 | 9 | ### 0x01 Hobbits message 10 | 11 | The hobbits subprotocol defines one message. 12 | 13 | The payload of the message is interpreted as a complete Hobbits message. -------------------------------------------------------------------------------- /rpc-messages.md: -------------------------------------------------------------------------------- 1 | # Messages 2 | 3 | These messages are used to define an application protocol for Ethereum 2.0. 4 | These messages define a RPC protocol for clients to interact with each other. 5 | 6 | # Envelope 7 | 8 | All messages follow the envelope standard to Hobbits as described in the [protocol definition](/protocol.md). 9 | 10 | This application protocol is classified under the `RPC` command. 11 | 12 | The body of the RPC calls must conform to: 13 | ``` 14 | { 15 | method_id: uint16 // byte representing the method 16 | id: uint64 // id of the request 17 | body: // body of the request itself 18 | } 19 | ``` 20 | 21 | Example (showing the bson snappy data as json): 22 | ``` 23 | EWP 0.2 RPC snappy bson 0 12 24 | { 25 | "method_id": 0x00, 26 | "id": 1, 27 | "body": { 28 | "reason": 42 29 | } 30 | } 31 | ``` 32 | 33 | ## `0x00` HELLO 34 | 35 | Nodes can send `HELLO` messages to each other to exchange information on their status. 36 | 37 | 38 | ```java 39 | { 40 | 'network_id': 'uint8' // the ID of the network (1 for mainnet, and some predefined number for a testnet) 41 | 'chain_id': 'uint8' // the ID of the chain (1 for ETH) 42 | 'latest_finalized_root': 'bytes32' // the hash of the latest finalized root 43 | 'latest_finalized_epoch': 'uint64' // the number of the latest finalized epoch 44 | 'best_root': 'bytes32' // the hash of the best root this node can offer 45 | 'best_slot': 'uint64' // the number of the best slot this node can offer 46 | } 47 | ``` 48 | 49 | ## `0x01` GOODBYE 50 | 51 | Nodes may signal to other nodes that they are going away by sending a `GOODBYE` message. 52 | The reason given is optional. Reason codes are up to each client and should not be trusted. 53 | 54 | ```java 55 | { 56 | 'reason': 'uint64' // an optional reason code up to the client 57 | } 58 | 59 | ``` 60 | 61 | ## `0x02` GET_STATUS 62 | 63 | Nodes may exchange metadata information using a `GET_STATUS` message. 64 | 65 | This information is useful to identify other nodes and clients and report that information to statistics services. 66 | 67 | ```java 68 | { 69 | 'sha': 'bytes32' // the commit hash of the node 70 | 'user_agent': 'bytes' // the human readable name of the client, optionally with its version and other metadata 71 | 'timestamp': 'uint64' // the current time of the node in milliseconds since epoch 72 | } 73 | ``` 74 | 75 | ## `0x0A` GET_BLOCK_ROOTS 76 | 77 | Nodes may request block roots from other nodes using the `GET_BLOCK_ROOTS` message. 78 | 79 | ```java 80 | { 81 | 'start_root': 'bytes32' // the root hash to start querying from 82 | 'start_slot': 'uint64' // the slot number to start querying from 83 | 'max': 'uint64' // the max number of elements to return 84 | 'skip': 'uint64' // the number of elements apart to pick from 85 | 'direction': 'uint8' // 0x01 is ascending, 0x00 is descending direction to query elements 86 | } 87 | ``` 88 | 89 | ## `0x0B` BLOCK_ROOTS 90 | 91 | Nodes may provide block roots to other nodes using the `BLOCK_ROOTS` message, usually in response to a `GET_BLOCK_ROOTS` message. 92 | 93 | ```java 94 | [ 95 | { 96 | 'block_root': 'bytes32', 97 | 'slot': 'uint64' 98 | }, 99 | ... 100 | ] 101 | ``` 102 | 103 | 104 | ## `0x0C` GET_BLOCK_HEADERS 105 | 106 | Nodes may request block headers from other nodes using the `GET_BLOCK_HEADERS` message. 107 | 108 | ```java 109 | { 110 | 'start_root': 'bytes32' // the root hash to start querying from 111 | 'start_slot': 'uint64' // the slot number to start querying from 112 | 'max': 'uint64' // the max number of elements to return 113 | 'skip': 'uint64' // the number of elements apart to pick from 114 | 'direction': 'uint8' // 0x01 is ascending, 0x00 is descending direction to query elements 115 | } 116 | ``` 117 | 118 | ## `0x0D` BLOCK_HEADERS 119 | 120 | Nodes may provide block roots to other nodes using the `BLOCK_HEADERS` message, usually in response to a `GET_BLOCK_HEADERS` message. 121 | 122 | ```java 123 | 'headers': '[]BeaconBlockHeader' 124 | ``` 125 | 126 | ## `0x0E` GET_BLOCK_BODIES 127 | 128 | Nodes may request block bodies from other nodes using the `GET_BLOCK_BODIES` message. 129 | 130 | ```java 131 | { 132 | 'start_root': 'bytes32' // the root hash to start querying from 133 | 'start_slot': 'uint64' // the slot number to start querying from 134 | 'max': 'uint64' // the max number of elements to return 135 | 'skip': 'uint64' // the number of elements apart to pick from 136 | 'direction': 'uint8' // 0x01 is ascending, 0x00 is descending direction to query elements 137 | } 138 | ``` 139 | 140 | ## `0x0F` BLOCK_BODIES 141 | 142 | Nodes may provide block roots to other nodes using the `BLOCK_BODIES` message, usually in response to a `GET_BLOCK_BODIES` message. 143 | 144 | ```java 145 | [ 146 | { 147 | 'randao_reveal': 'bytes96', 148 | 'eth1_data': Eth1Data, 149 | 'proposer_slashings': [ProposerSlashing], 150 | 'attester_slashings': [AttesterSlashing], 151 | 'attestations': [Attestation], 152 | 'deposits': [Deposit], 153 | 'voluntary_exits': [VoluntaryExit], 154 | 'transfers': [Transfer], 155 | 'header_signature:' 'bytes96' 156 | } 157 | ] 158 | ``` 159 | 160 | # Lifecycle and message exchanges 161 | 162 | ## Initial hello 163 | 164 | Upon discovering each other, nodes may exchange `HELLO` messages. 165 | 166 | Nodes may send `HELLO` to other peers when they exchange messages for the first time or when their state changes to let them know new blocks are available. 167 | 168 | Upon receiving a `HELLO` message, the node may reply with a `HELLO` message. 169 | 170 | ## Status messages 171 | 172 | Any peer may provide information about their status and metadata to any other peer. Other peers may respond on a best effort basis, if at all. 173 | 174 | ## Block and header messages 175 | 176 | Peers may request blocks and headers from other peers. 177 | 178 | Other peers may respond on a best effort basis with header and block data. 179 | 180 | There is no SLA for responding. Peers may request blocks repeatidly from the same peers. 181 | 182 | #### The following definitions are aligned to v0.5.0 of the Beacon Chain Spec: 183 | 184 | - [ProposerSlashing](https://github.com/ethereum/eth2.0-specs/blob/v0.5.0/specs/core/0_beacon-chain.md#proposerslashing) 185 | - [AttesterSlashing](https://github.com/ethereum/eth2.0-specs/blob/v0.5.0/specs/core/0_beacon-chain.md#attesterslashing) 186 | - [Attestation](https://github.com/ethereum/eth2.0-specs/blob/v0.5.0/specs/core/0_beacon-chain.md#attestation) 187 | - [Deposit](https://github.com/ethereum/eth2.0-specs/blob/v0.5.0/specs/core/0_beacon-chain.md#deposit) 188 | - [VoluntaryExit](https://github.com/ethereum/eth2.0-specs/blob/v0.5.0/specs/core/0_beacon-chain.md#voluntaryexit) 189 | - [Transfer](https://github.com/ethereum/eth2.0-specs/blob/v0.5.0/specs/core/0_beacon-chain.md#transfer) 190 | - [Eth1Data](https://github.com/ethereum/eth2.0-specs/blob/v0.5.0/specs/core/0_beacon-chain.md#eth1data) 191 | 192 | 193 | -------------------------------------------------------------------------------- /setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if ! [ -x "$(command -v pip)" ]; then 4 | echo 'Error: pip is not installed' 5 | echo 'Install it by running sudo easy_install pip' 6 | exit 1 7 | else 8 | pip install -r requirements.txt 9 | fi 10 | 11 | 12 | -------------------------------------------------------------------------------- /test/rpc/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/spf13/pflag" 7 | ) 8 | 9 | var ( 10 | ip string 11 | port string 12 | ) 13 | 14 | func main() { 15 | pflag.StringVarP(&ip, "ip", "i", "10.1.0.2", "ip address") 16 | pflag.StringVarP(&port, "port", "p", "9000", "port") 17 | 18 | pflag.Parse() 19 | 20 | pinger() 21 | fmt.Println("Ping test passed. Testing port...") 22 | porter() 23 | fmt.Printf("Port test passed. Testing message delivery...") 24 | 25 | } 26 | -------------------------------------------------------------------------------- /test/rpc/ping.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/sparrc/go-ping" 7 | ) 8 | 9 | func pinger() { 10 | 11 | pinger, err := ping.NewPinger(ip) 12 | if err != nil { 13 | fmt.Printf("ERROR: %s\n", err.Error()) 14 | return 15 | } 16 | pinger.SetPrivileged(true) 17 | fmt.Println("Starting Ping Test....") 18 | 19 | pinger.OnRecv = func(pkt *ping.Packet) { 20 | pinger.Count = 5 21 | fmt.Printf("%d bytes from %s: icmp_seq=%d time=%v\n", 22 | pkt.Nbytes, pkt.IPAddr, pkt.Seq, pkt.Rtt) 23 | } 24 | fmt.Println("IP is valid. Pinging...") 25 | 26 | pinger.OnFinish = func(stats *ping.Statistics) { 27 | fmt.Printf("\n--- %s ping statistics ---\n", stats.Addr) 28 | fmt.Printf("%d packets transmitted, %d packets received, %v%% packet loss\n", 29 | stats.PacketsSent, stats.PacketsRecv, stats.PacketLoss) 30 | fmt.Printf("round-trip min/avg/max/stddev = %v/%v/%v/%v\n", 31 | stats.MinRtt, stats.AvgRtt, stats.MaxRtt, stats.StdDevRtt) 32 | 33 | } 34 | 35 | fmt.Printf("PING %s (%s):\n", pinger.Addr(), pinger.IPAddr()) 36 | pinger.Run() 37 | 38 | } 39 | -------------------------------------------------------------------------------- /test/rpc/port.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "os" 7 | ) 8 | 9 | func porter() { 10 | ln, err := net.Dial("tcp", ip+":"+port) 11 | 12 | if err != nil { 13 | fmt.Fprintf(os.Stderr, "Test failed. Port %q: %s unavailable.", port, err) 14 | os.Exit(1) 15 | } 16 | 17 | err = ln.Close() 18 | if err != nil { 19 | fmt.Fprintf(os.Stderr, "Couldn't close port %q: %s", port, err) 20 | os.Exit(1) 21 | } 22 | 23 | fmt.Printf("TCP Port %q is available", port) 24 | } 25 | -------------------------------------------------------------------------------- /test/run.py: -------------------------------------------------------------------------------- 1 | # This is the test runner; it runs all parser/marshal implementations against the same suite of marshalled messages. 2 | # To pass the test the implementation must parse the message from stdin (request or response variant) and then marshal 3 | # it and print to stdout. 4 | # The interface the binary or script produced must adhere to is as follows so it is compatible with this test suite: 5 | # 6 | # - request|response determines whether or not we are testing the request or response message type 7 | # - payload-size is the byte length of the message 8 | # - STDIN must be read to obtain the marshalled test data; read bytes only to obtain test data bytes 9 | 10 | import re 11 | from subprocess import Popen, PIPE 12 | import unittest 13 | import yaml 14 | 15 | VERSION = '0.2' 16 | FIELDS = [ 17 | "version", "command", "compression", 18 | "head-only", "headers", "body" 19 | ] 20 | 21 | LANGS = { 22 | 'bash':['bash','./parsers/bash/parser.sh'] 23 | 'c': ['./parsers/c/test'], 24 | 'cpp': [ './parsers/cpp/test' ], 25 | 'd': ['./parsers/d/main'], 26 | 'erlang': ['./parsers/erlang/test'], 27 | 'java':['java','./parsers/java/Parser.java'], 28 | 'php': ['php','./parsers/php/test.php'], 29 | 'rs': [ './parsers/rs/parser' ], 30 | 'racket': ['./parsers/racket/test'], 31 | 'scheme':['./parsers/scheme/test'], 32 | 'python':['python', './parsers/python/test.py'], 33 | 'perl': ['perl', '-I' './parsers/perl', './parsers/perl/test.pl'], 34 | 'go':['go','run','./parsers/go/test.go', './parsers/go/parser.go'], 35 | } 36 | 37 | 38 | class DynamicTest(unittest.TestCase): 39 | longMessage = True 40 | 41 | def quote(token): 42 | return '"' + token + '"' 43 | 44 | def bracket(token): 45 | return '[' + token + ']' 46 | 47 | def load_yaml_file(path): 48 | with open(path, 'r') as stream: 49 | return yaml.load(stream) 50 | 51 | def run_impl(args, stdindata): 52 | proc = Popen(args, stdin=PIPE, stdout=PIPE, stderr=PIPE) 53 | stdout, stderr = proc.communicate(stdindata) 54 | print stderr 55 | return stdout 56 | 57 | if __name__ == '__main__': 58 | suites = load_yaml_file('test/tests.yaml') 59 | 60 | for lang, lang_args in LANGS.iteritems(): 61 | for suite in suites: 62 | suite_name = re.sub('[ -]', '_', suite['suite']).lower() 63 | 64 | for request in suite['requests']: 65 | desc = re.sub('[ -]', '_', request['desc']).lower() 66 | test_name = "_".join([ 'test', lang, suite_name, 'request', desc ]) 67 | 68 | args = [] 69 | args.extend(lang_args) 70 | args.extend([ 'request', str(len(request['marshalled'])) ]) 71 | expected = request['marshalled'] 72 | 73 | def test_func(self, args=args, expected=expected): 74 | actual = run_impl(args, expected) 75 | self.assertEqual(actual, expected) 76 | 77 | setattr(DynamicTest, test_name, test_func) 78 | 79 | unittest.main(verbosity=3) 80 | -------------------------------------------------------------------------------- /test/tests.yaml: -------------------------------------------------------------------------------- 1 | - suite: 'Basic Sanity' 2 | requests: 3 | - desc: 'no body' 4 | marshalled: "EWP 0.2 PING 0 0\n" 5 | - desc: '10 byte body' 6 | marshalled: "EWP 0.2 PING 0 10\n0123456789" 7 | - desc: '10 byte header' 8 | marshalled: "EWP 0.2 PING 10 0\n0123456789" 9 | - desc: '9 byte header, 10 byte body' 10 | marshalled: "EWP 0.2 PING 9 10\n9876543210123456789" 11 | - desc: '9 byte header, 10 byte body, extra newlines' 12 | marshalled: "EWP 0.2 PING 9 10\n\n876543210123456\n89" 13 | - desc: '9 byte header, 10 byte body, extra extra newlines' 14 | marshalled: "EWP 0.2 PING 9 10\n\n87654321\n\n\n\n\n\n\n\n\n\n" 15 | - desc: '9 byte header, 10 byte body, control character montage' 16 | marshalled: "EWP 0.2 PING 9 10\n\n87654321\n\0\a\b\f\n\r\t\v\\" 17 | - suite: 'test different commands' 18 | requests: 19 | - desc: 'PING' 20 | marshalled: "EWP 0.2 PING 0 0\n" 21 | - desc: 'FOO' 22 | marshalled: "EWP 0.2 FOO 0 0\n" 23 | - desc: 'BAR' 24 | marshalled: "EWP 0.2 BAR 0 0\n" 25 | - desc: 'PONG' 26 | marshalled: "EWP 0.2 PONG 0 0\n" 27 | responses: [] 28 | -------------------------------------------------------------------------------- /uri.md: -------------------------------------------------------------------------------- 1 | # URI 2 | 3 | General form: 4 | 5 | `://@:` 6 | 7 | ## Scheme 8 | 9 | We use the following URI schemes for hobbits URIs: 10 | 11 | | Secure connection | Type of connection | Scheme | 12 | | ----------------- | ------------------ | --------- | 13 | | Insecure | Persistent TCP | hob+tcp | 14 | | Insecure | UDP | hob+udp | 15 | | TLS | TCP | hob+tls | 16 | | TLS | UDP | hobs+dtls | 17 | | RLPx | Persistent TCP | hob+rlpx | 18 | | Insecure | HTTP | hob+http | 19 | | TLS | HTTPs | hob+https | 20 | | TLS | Web socket | hob+ws | 21 | | Secure Scuttlebutt| Persistent TCP | hob+ssb | 22 | 23 | Example: 24 | 25 | ``` 26 | hob+tcp://10.0.0.1:9000 // Insecure persistent TCP 27 | hob+udp://10.0.0.1:9000 // Insecure UDP 28 | 29 | hob+tls://10.0.0.1:9000 // TLS TCP 30 | hob+dtls://10.0.0.1:9000 // TLS UDP 31 | ``` 32 | 33 | ## Identity 34 | 35 | The user information part of the URI is used to store the public key of the peer if the connection is made over RLPx or Secure Scuttlebutt. 36 | 37 | With RLPx, the user information is a 64 bytes long hexadecimal string representing the SECP256K1 public key. 38 | 39 | With Secure Scuttlebutt, the user information is the base64-encoded representation of the Ed25519 public key of the peer. 40 | 41 | ## Host 42 | 43 | The host to connect to - either a DNS-resolved hostname or an IP. 44 | 45 | ## Port 46 | 47 | An integer between 1 and 65535. --------------------------------------------------------------------------------