Feature/client binary api update (#306)

* Split text and binary client apis

* Very initial attempt at new serialization

* Defined ser+de for Recipient and ReplySURB

* Response errors

* builds with changes

* Working WS API + moved to separate crate

* updated python examples

* Fixed parsing bug

* Updated go examples

* Updated rust examples

* formatting

* Removed unused imports

* Removed accidentally left panic call
This commit is contained in:
Jędrzej Stuczyński
2020-08-17 10:44:40 +01:00
committed by GitHub
parent eec6034684
commit 7d2d512405
31 changed files with 1900 additions and 987 deletions
Generated
+99 -76
View File
@@ -18,10 +18,19 @@ dependencies = [
]
[[package]]
name = "adler32"
version = "1.0.4"
name = "addr2line"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d2e7343e7fc9de883d1b0341e0b13970f764c14101234857d2ddafa1cb1cac2"
checksum = "1b6a2d3371669ab3ca9797670853d61402b03d0b4b9ebf33d677dfa720203072"
dependencies = [
"gimli",
]
[[package]]
name = "adler"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e"
[[package]]
name = "aes-ctr"
@@ -169,26 +178,18 @@ checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d"
[[package]]
name = "backtrace"
version = "0.3.46"
version = "0.3.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1e692897359247cc6bb902933361652380af0f1b7651ae5c5013407f30e109e"
checksum = "46254cf2fdcdf1badb5934448c1bcbe046a56537b3987d96c51a7afc5d03f293"
dependencies = [
"backtrace-sys",
"addr2line",
"cfg-if",
"libc",
"miniz_oxide",
"object",
"rustc-demangle",
]
[[package]]
name = "backtrace-sys"
version = "0.1.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7de8aba10a69c8e8d7622c5710229485ec32e9d55fdad160ea559c086fdcd118"
dependencies = [
"cc",
"libc",
]
[[package]]
name = "base64"
version = "0.9.3"
@@ -774,9 +775,9 @@ dependencies = [
[[package]]
name = "either"
version = "1.5.3"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3"
checksum = "cd56b59865bce947ac5958779cfa508f6c3b9497cc762b7e24a12d11ccde2c4f"
[[package]]
name = "encoding_rs"
@@ -815,9 +816,9 @@ dependencies = [
[[package]]
name = "error-chain"
version = "0.12.2"
version = "0.12.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d371106cc88ffdfb1eabd7111e432da544f16f3e2d7bf1dfe8bf575f1df045cd"
checksum = "2d2f06b9cac1506ece98fe3231e3cc9c4410ec3d5b1f24ae1c8946f0742cdefc"
dependencies = [
"version_check 0.9.1",
]
@@ -829,9 +830,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe9db393664b0e6c6230a14115e7e798f80b70f54038dc21165db24c6b7f28fc"
dependencies = [
"proc-macro-error",
"proc-macro2 1.0.10",
"proc-macro2 1.0.19",
"quote 1.0.3",
"syn 1.0.17",
"syn 1.0.38",
]
[[package]]
@@ -850,9 +851,9 @@ version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "030a733c8287d6213886dd487564ff5c8f6aae10278b3588ed177f9d18f8d231"
dependencies = [
"proc-macro2 1.0.10",
"proc-macro2 1.0.19",
"quote 1.0.3",
"syn 1.0.17",
"syn 1.0.38",
"synstructure",
]
@@ -864,9 +865,9 @@ checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed"
[[package]]
name = "flate2"
version = "1.0.14"
version = "1.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2cfff41391129e0a856d6d822600b8d71179d46879e310417eb9c762eb178b42"
checksum = "68c90b0fc46cf89d227cc78b40e494ff81287a92dd07631e5af0d06fe3cf885e"
dependencies = [
"cfg-if",
"crc32fast",
@@ -998,9 +999,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a5081aa3de1f7542a794a397cde100ed903b0630152d0973479018fd85423a7"
dependencies = [
"proc-macro-hack",
"proc-macro2 1.0.10",
"proc-macro2 1.0.19",
"quote 1.0.3",
"syn 1.0.17",
"syn 1.0.38",
]
[[package]]
@@ -1102,6 +1103,12 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "gimli"
version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aaf91faf136cb47367fa430cd46e37a788775e7fa104f8b4bcb3861dc389b724"
[[package]]
name = "git2"
version = "0.10.2"
@@ -1467,9 +1474,9 @@ dependencies = [
[[package]]
name = "js-sys"
version = "0.3.37"
version = "0.3.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a27d435371a2fa5b6d2b028a74bbdb1234f308da363226a2854ca3ff8ba7055"
checksum = "85a7e2c92a4804dd459b86c339278d0fe87cf93757fae222c3fa3ae75458bc73"
dependencies = [
"wasm-bindgen",
]
@@ -1658,11 +1665,11 @@ dependencies = [
[[package]]
name = "miniz_oxide"
version = "0.3.6"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa679ff6578b1cddee93d7e82e263b94a575e0bfced07284eb0c037c1d2416a5"
checksum = "be0f75932c1f6cfae3c04000e40114adf955636e19040f9c0a2c380702aa1c7f"
dependencies = [
"adler32",
"adler",
]
[[package]]
@@ -1826,7 +1833,6 @@ dependencies = [
name = "nym-client"
version = "0.8.0-dev"
dependencies = [
"bs58",
"built",
"clap",
"client-core",
@@ -1840,11 +1846,9 @@ dependencies = [
"gateway-requests",
"log 0.4.8",
"nymsphinx",
"pem",
"pemstore",
"pretty_env_logger",
"rand 0.7.3",
"reqwest 0.9.24",
"serde",
"serde_json",
"sled 0.33.0",
@@ -1853,6 +1857,7 @@ dependencies = [
"tokio-tungstenite",
"topology",
"url 2.1.1",
"websocket-requests",
]
[[package]]
@@ -1987,6 +1992,7 @@ version = "0.1.0"
dependencies = [
"crypto",
"nymsphinx-types",
"serde",
]
[[package]]
@@ -1999,6 +2005,7 @@ dependencies = [
"nymsphinx-params",
"nymsphinx-types",
"rand 0.7.3",
"serde",
"topology",
]
@@ -2052,6 +2059,12 @@ dependencies = [
"sphinx",
]
[[package]]
name = "object"
version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ab52be62400ca80aa00285d25253d7f7c437b7375c4de678f5405d3afe82ca5"
[[package]]
name = "opaque-debug"
version = "0.2.3"
@@ -2234,9 +2247,9 @@ checksum = "99b8db626e31e5b81787b9783425769681b347011cc59471e33ea46d2ea0cf55"
dependencies = [
"pest",
"pest_meta",
"proc-macro2 1.0.10",
"proc-macro2 1.0.19",
"quote 1.0.3",
"syn 1.0.17",
"syn 1.0.38",
]
[[package]]
@@ -2304,9 +2317,9 @@ version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "385322a45f2ecf3410c68d2a549a4a2685e8051d0f278e39743ff4e451cb9b3f"
dependencies = [
"proc-macro2 1.0.10",
"proc-macro2 1.0.19",
"quote 1.0.3",
"syn 1.0.17",
"syn 1.0.38",
]
[[package]]
@@ -2360,9 +2373,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "18f33027081eba0a6d8aba6d1b1c3a3be58cbb12106341c2d5759fcd9b5277e7"
dependencies = [
"proc-macro-error-attr",
"proc-macro2 1.0.10",
"proc-macro2 1.0.19",
"quote 1.0.3",
"syn 1.0.17",
"syn 1.0.38",
"version_check 0.9.1",
]
@@ -2372,9 +2385,9 @@ version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a5b4b77fdb63c1eca72173d68d24501c54ab1269409f6b672c85deb18af69de"
dependencies = [
"proc-macro2 1.0.10",
"proc-macro2 1.0.19",
"quote 1.0.3",
"syn 1.0.17",
"syn 1.0.38",
"syn-mid",
"version_check 0.9.1",
]
@@ -2402,9 +2415,9 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.10"
version = "1.0.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df246d292ff63439fea9bc8c0a270bed0e390d5ebd4db4ba15aba81111b5abe3"
checksum = "04f5f085b5d71e2188cb8271e5da0161ad52c3f227a661a3c135fdf28e258b12"
dependencies = [
"unicode-xid 0.2.0",
]
@@ -2468,7 +2481,7 @@ version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2bdc6c187c65bca4260c9011c9e3132efe4909da44726bad24cf7572ae338d7f"
dependencies = [
"proc-macro2 1.0.10",
"proc-macro2 1.0.19",
]
[[package]]
@@ -2881,9 +2894,9 @@ version = "1.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e549e3abf4fb8621bd1609f11dfc9f5e50320802273b12f3811a67e6716ea6c"
dependencies = [
"proc-macro2 1.0.10",
"proc-macro2 1.0.19",
"quote 1.0.3",
"syn 1.0.17",
"syn 1.0.38",
]
[[package]]
@@ -2969,6 +2982,7 @@ dependencies = [
"simple-socks5-requests",
"tokio 0.2.16",
"tokio-tungstenite",
"websocket-requests",
]
[[package]]
@@ -3185,11 +3199,11 @@ dependencies = [
[[package]]
name = "syn"
version = "1.0.17"
version = "1.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0df0eb663f387145cab623dea85b09c2c5b4b0aef44e945d928e682fce71bb03"
checksum = "e69abc24912995b3038597a7a593be5053eb0fb44f3cc5beec0deb421790c1f4"
dependencies = [
"proc-macro2 1.0.10",
"proc-macro2 1.0.19",
"quote 1.0.3",
"unicode-xid 0.2.0",
]
@@ -3200,9 +3214,9 @@ version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7be3539f6c128a931cf19dcee741c1af532c7fd387baa739c03dd2e96479338a"
dependencies = [
"proc-macro2 1.0.10",
"proc-macro2 1.0.19",
"quote 1.0.3",
"syn 1.0.17",
"syn 1.0.38",
]
[[package]]
@@ -3211,9 +3225,9 @@ version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67656ea1dc1b41b1451851562ea232ec2e5a80242139f7e679ceccfb5d61f545"
dependencies = [
"proc-macro2 1.0.10",
"proc-macro2 1.0.19",
"quote 1.0.3",
"syn 1.0.17",
"syn 1.0.38",
"unicode-xid 0.2.0",
]
@@ -3372,9 +3386,9 @@ version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0c3acc6aa564495a0f2e1d59fab677cd7f81a19994cfc7f3ad0e64301560389"
dependencies = [
"proc-macro2 1.0.10",
"proc-macro2 1.0.19",
"quote 1.0.3",
"syn 1.0.17",
"syn 1.0.38",
]
[[package]]
@@ -3750,9 +3764,9 @@ checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
[[package]]
name = "wasm-bindgen"
version = "0.2.60"
version = "0.2.67"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2cc57ce05287f8376e998cbddfb4c8cb43b84a7ec55cf4551d7c00eef317a47f"
checksum = "f0563a9a4b071746dd5aedbc3a28c6fe9be4586fb3fbadb67c400d4f53c6b16c"
dependencies = [
"cfg-if",
"serde",
@@ -3762,16 +3776,16 @@ dependencies = [
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.60"
version = "0.2.67"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d967d37bf6c16cca2973ca3af071d0a2523392e4a594548155d89a678f4237cd"
checksum = "bc71e4c5efa60fb9e74160e89b93353bc24059999c0ae0fb03affc39770310b0"
dependencies = [
"bumpalo",
"lazy_static",
"log 0.4.8",
"proc-macro2 1.0.10",
"proc-macro2 1.0.19",
"quote 1.0.3",
"syn 1.0.17",
"syn 1.0.38",
"wasm-bindgen-shared",
]
@@ -3802,9 +3816,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.60"
version = "0.2.67"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8bd151b63e1ea881bb742cd20e1d6127cef28399558f3b5d415289bc41eee3a4"
checksum = "97c57cefa5fa80e2ba15641578b44d36e7a64279bc5ed43c6dbaf329457a2ed2"
dependencies = [
"quote 1.0.3",
"wasm-bindgen-macro-support",
@@ -3812,22 +3826,22 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.60"
version = "0.2.67"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d68a5b36eef1be7868f668632863292e37739656a80fc4b9acec7b0bd35a4931"
checksum = "841a6d1c35c6f596ccea1f82504a192a60378f64b3bb0261904ad8f2f5657556"
dependencies = [
"proc-macro2 1.0.10",
"proc-macro2 1.0.19",
"quote 1.0.3",
"syn 1.0.17",
"syn 1.0.38",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.60"
version = "0.2.67"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "daf76fe7d25ac79748a37538b7daeed1c7a6867c92d3245c12c6222e4a20d639"
checksum = "93b162580e34310e5931c4b792560108b10fd14d64915d7fff8ff00180e70092"
[[package]]
name = "wasm-bindgen-test"
@@ -3856,14 +3870,23 @@ dependencies = [
[[package]]
name = "web-sys"
version = "0.3.37"
version = "0.3.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d6f51648d8c56c366144378a33290049eafdd784071077f6fe37dae64c1c4cb"
checksum = "dda38f4e5ca63eda02c059d243aa25b5f35ab98451e518c51612cd0f1bd19a47"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "websocket-requests"
version = "0.1.0"
dependencies = [
"nymsphinx",
"serde",
"serde_json",
]
[[package]]
name = "wee_alloc"
version = "0.4.5"
@@ -3964,8 +3987,8 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de251eec69fc7c1bc3923403d18ececb929380e016afe103da75f396704f8ca2"
dependencies = [
"proc-macro2 1.0.10",
"proc-macro2 1.0.19",
"quote 1.0.3",
"syn 1.0.17",
"syn 1.0.38",
"synstructure",
]
+1
View File
@@ -6,6 +6,7 @@ opt-level = "s"
members = [
"clients/native",
"clients/native/websocket-requests",
"clients/socks5",
"clients/webassembly",
"clients/client-core",
+20 -17
View File
@@ -12,22 +12,23 @@ name = "nym_client"
path = "src/lib.rs"
[dependencies]
bs58 = "0.3.0"
clap = "2.33.0"
dirs = "2.0.2"
dotenv = "0.15.0"
futures = "0.3.1"
log = "0.4"
pem = "0.7.0"
pretty_env_logger = "0.3"
rand = { version = "0.7.3", features = ["wasm-bindgen"] }
reqwest = "0.9.22"
serde = { version = "1.0.104", features = ["derive"] }
serde_json = "1.0.44"
sled = "0.33"
tokio = { version = "0.2", features = ["full"] }
tokio-tungstenite = "0.10.1"
url = "2.1"
# dependencies to review:
futures = "0.3" # bunch of futures stuff, however, now that I think about it, it could perhaps be completely removed
# the AsyncRead, AsyncWrite, Stream, Sink, etc. traits could be used from tokio
# channels should really be replaced with crossbeam due to that implementation being more efficient
# and the single instance of abortable we have should really be refactored anyway
url = "2.1" # I think we should just replace it with String
clap = "2.33.0" # for the command line arguments
dirs = "2.0.2" # for determining default store directories in config
dotenv = "0.15.0" # for obtaining environmental variables (only used for RUST_LOG for time being)
log = "0.4" # self explanatory
pretty_env_logger = "0.3" # for formatting log messages
rand = {version = "0.7.3", features = ["wasm-bindgen"]} # rng-related traits + some rng implementation to use
serde = { version = "1.0.104", features = ["derive"] } # for config serialization/deserialization
sled = "0.33" # for storage of replySURB decryption keys
tokio = { version = "0.2", features = ["full"] } # async runtime
tokio-tungstenite = "0.10.1" # websocket
## internal
client-core = { path = "../client-core" }
@@ -39,9 +40,11 @@ gateway-requests = { path = "../../gateway/gateway-requests" }
nymsphinx = { path = "../../common/nymsphinx" }
pemstore = { path = "../../common/pemstore" }
topology = { path = "../../common/topology" }
websocket-requests = { path = "websocket-requests" }
[build-dependencies]
built = "0.3.2"
[dev-dependencies]
tempfile = "3.1.0"
tempfile = "3.1.0"
serde_json = "1.0" # for the "textsend" example
@@ -0,0 +1,218 @@
package main
import (
"encoding/binary"
"fmt"
"io/ioutil"
"github.com/gorilla/websocket"
)
// request tags
const SEND_REQUEST_TAG = 0x00
const REPLY_REQUEST_TAG = 0x01
const SELF_ADDRESS_REQUEST_TAG = 0x02
// response tags
const ERROR_RESPONSE_TAG = 0x00
const RECEIVED_RESPONSE_TAG = 0x01
const SELF_ADDRESS_RESPONSE_TAG = 0x02
func makeSelfAddressRequest() []byte {
return []byte{SELF_ADDRESS_REQUEST_TAG}
}
func parseSelfAddressResponse(rawResponse []byte) []byte {
if len(rawResponse) != 97 || rawResponse[0] != SELF_ADDRESS_RESPONSE_TAG {
panic("Received invalid response")
}
return rawResponse[1:]
}
func makeSendRequest(recipient []byte, message []byte, withReplySurb bool) []byte {
messageLen := make([]byte, 8)
binary.BigEndian.PutUint64(messageLen, uint64(len(message)))
surbByte := byte(0)
if withReplySurb {
surbByte = 1
}
out := []byte{SEND_REQUEST_TAG, surbByte}
out = append(out, recipient...)
out = append(out, messageLen...)
out = append(out, message...)
fmt.Println("first send req: ", out[:10])
return out
}
func makeReplyRequest(message []byte, replySURB []byte) []byte {
messageLen := make([]byte, 8)
binary.BigEndian.PutUint64(messageLen, uint64(len(message)))
surbLen := make([]byte, 8)
binary.BigEndian.PutUint64(surbLen, uint64(len(replySURB)))
out := []byte{REPLY_REQUEST_TAG}
out = append(out, surbLen...)
out = append(out, replySURB...)
out = append(out, messageLen...)
out = append(out, message...)
return out
}
func parseReceived(rawResponse []byte) ([]byte, []byte) {
if rawResponse[0] != RECEIVED_RESPONSE_TAG {
panic("Received invalid response!")
}
hasSurb := false
if rawResponse[1] == 1 {
hasSurb = true
} else if rawResponse[1] == 0 {
hasSurb = false
} else {
panic("malformed received response!")
}
data := rawResponse[2:]
if hasSurb {
surbLen := binary.BigEndian.Uint64(data[:8])
other := data[8:]
surb := other[:surbLen]
msgLen := binary.BigEndian.Uint64(other[surbLen : surbLen+8])
if len(other[surbLen+8:]) != int(msgLen) {
panic("invalid msg len")
}
msg := other[surbLen+8:]
return msg, surb
} else {
msgLen := binary.BigEndian.Uint64(data[:8])
other := data[8:]
if len(other) != int(msgLen) {
panic("invalid msg len")
}
msg := other[:msgLen]
return msg, nil
}
}
func sendWithoutReply() {
uri := "ws://localhost:1977"
conn, _, err := websocket.DefaultDialer.Dial(uri, nil)
if err != nil {
panic(err)
}
defer conn.Close()
selfAddressRequest := makeSelfAddressRequest()
if err = conn.WriteMessage(websocket.BinaryMessage, selfAddressRequest); err != nil {
panic(err)
}
_, receivedResponse, err := conn.ReadMessage()
if err != nil {
panic(err)
}
selfAddress := parseSelfAddressResponse(receivedResponse)
read_data, err := ioutil.ReadFile("dummy_file")
if err != nil {
panic(err)
}
sendRequest := makeSendRequest(selfAddress, read_data, false)
fmt.Printf("sending content of 'dummy file' over the mix network...\n")
if err = conn.WriteMessage(websocket.BinaryMessage, sendRequest); err != nil {
panic(err)
}
fmt.Printf("waiting to receive a message from the mix network...\n")
_, receivedResponse, err = conn.ReadMessage()
if err != nil {
panic(err)
}
fileData, replySURB := parseReceived(receivedResponse)
if replySURB != nil {
panic("did not expect a replySURB!")
}
fmt.Printf("writing the file back to the disk!\n")
ioutil.WriteFile("received_file_noreply", fileData, 0644)
}
func sendWithReply() {
uri := "ws://localhost:1977"
conn, _, err := websocket.DefaultDialer.Dial(uri, nil)
if err != nil {
panic(err)
}
defer conn.Close()
selfAddressRequest := makeSelfAddressRequest()
if err = conn.WriteMessage(websocket.BinaryMessage, selfAddressRequest); err != nil {
panic(err)
}
_, receivedResponse, err := conn.ReadMessage()
if err != nil {
panic(err)
}
selfAddress := parseSelfAddressResponse(receivedResponse)
readData, err := ioutil.ReadFile("dummy_file")
if err != nil {
panic(err)
}
sendRequest := makeSendRequest(selfAddress, readData, true)
fmt.Printf("sending content of 'dummy file' over the mix network...\n")
if err = conn.WriteMessage(websocket.BinaryMessage, sendRequest); err != nil {
panic(err)
}
fmt.Printf("waiting to receive a message from the mix network...\n")
_, receivedResponse, err = conn.ReadMessage()
if err != nil {
panic(err)
}
fileData, replySURB := parseReceived(receivedResponse)
fmt.Printf("writing the file back to the disk!\n")
ioutil.WriteFile("received_file_withreply", fileData, 0644)
replyMessage := []byte("hello from reply SURB! - thanks for sending me the file!")
replyRequest := makeReplyRequest(replyMessage, replySURB)
fmt.Printf("sending '%v' (using reply SURB) over the mix network...\n", string(replyMessage))
if err = conn.WriteMessage(websocket.BinaryMessage, replyRequest); err != nil {
panic(err)
}
fmt.Printf("waiting to receive a message from the mix network...\n")
_, receivedResponse, err = conn.ReadMessage()
if err != nil {
panic(err)
}
receivedMessage, replySURB := parseReceived(receivedResponse)
if replySURB != nil {
panic("did not expect a replySURB!")
}
fmt.Printf("received %v from the mix network!\n", string(receivedMessage))
}
func main() {
// sendWithoutReply()
sendWithReply()
}
@@ -1,177 +0,0 @@
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"strings"
"github.com/btcsuite/btcutil/base58"
"github.com/gorilla/websocket"
)
const WITHOUT_REPLY = 0
const WITH_REPLY = 1
const REPLY = 2
// this is really flaky so ideally will be replaced by some proper serialization
// 16 - length of encryption key
// 32 - length of first hop
// 348 - current sphinx header size
// 192 - size of single payload key
// 4 - number of mix hops (gateway + 3 nodes)
const REPLY_SURB_LEN = 16 + 32 + 348 + 4*192
func getSelfAddress(conn *websocket.Conn) string {
selfAddressRequest, err := json.Marshal(map[string]string{"type": "selfAddress"})
if err != nil {
panic(err)
}
if err = conn.WriteMessage(websocket.TextMessage, []byte(selfAddressRequest)); err != nil {
panic(err)
}
responseJSON := make(map[string]interface{})
err = conn.ReadJSON(&responseJSON)
if err != nil {
panic(err)
}
return responseJSON["address"].(string)
}
func sendWithoutReply() {
uri := "ws://localhost:1977"
conn, _, err := websocket.DefaultDialer.Dial(uri, nil)
if err != nil {
panic(err)
}
defer conn.Close()
selfAddress := getSelfAddress(conn)
fmt.Printf("our address is: %v\n", selfAddress)
// we receive our address in string format of OUR_ID_PUB_KEY . OUR_ENC_PUB_KEY @ OUR_GATE_ID_PUB_KEY
// all keys are 32 bytes and we need to encode them as binary without the '.' or '@' signs
splitAddress := strings.Split(selfAddress, "@")
clientHalf := splitAddress[0]
gatewayHalf := splitAddress[1]
split_client_address := strings.Split(clientHalf, ".")
decodedIdentity := base58.Decode(split_client_address[0])
decodedEncryption := base58.Decode(split_client_address[1])
decodedGateway := base58.Decode(gatewayHalf)
read_data, err := ioutil.ReadFile("dummy_file")
if err != nil {
panic(err)
}
payload := []byte{WITHOUT_REPLY}
payload = append(payload, decodedIdentity[:]...)
payload = append(payload, decodedEncryption[:]...)
payload = append(payload, decodedGateway[:]...)
payload = append(payload, read_data[:]...)
fmt.Printf("sending content of 'dummy file' over the mix network...\n")
if err = conn.WriteMessage(websocket.BinaryMessage, payload); err != nil {
panic(err)
}
fmt.Printf("waiting to receive a message from the mix network...\n")
_, receivedMessage, err := conn.ReadMessage()
if err != nil {
panic(err)
}
if receivedMessage[0] != WITHOUT_REPLY {
panic("incorrect prefix")
}
fmt.Printf("writing the file back to the disk!\n")
ioutil.WriteFile("received_file_noreply", receivedMessage[1:], 0644)
}
func sendWithReply() {
uri := "ws://localhost:1977"
conn, _, err := websocket.DefaultDialer.Dial(uri, nil)
if err != nil {
panic(err)
}
defer conn.Close()
selfAddress := getSelfAddress(conn)
fmt.Printf("our address is: %v\n", selfAddress)
// we receive our address in string format of OUR_ID_PUB_KEY . OUR_ENC_PUB_KEY @ OUR_GATE_ID_PUB_KEY
// all keys are 32 bytes and we need to encode them as binary without the '.' or '@' signs
splitAddress := strings.Split(selfAddress, "@")
clientHalf := splitAddress[0]
gatewayHalf := splitAddress[1]
split_client_address := strings.Split(clientHalf, ".")
decodedIdentity := base58.Decode(split_client_address[0])
decodedEncryption := base58.Decode(split_client_address[1])
decodedGateway := base58.Decode(gatewayHalf)
read_data, err := ioutil.ReadFile("dummy_file")
if err != nil {
panic(err)
}
payload := []byte{WITH_REPLY}
payload = append(payload, decodedIdentity[:]...)
payload = append(payload, decodedEncryption[:]...)
payload = append(payload, decodedGateway[:]...)
payload = append(payload, read_data[:]...)
fmt.Printf("sending content of 'dummy file' over the mix network...\n")
if err = conn.WriteMessage(websocket.BinaryMessage, payload); err != nil {
panic(err)
}
fmt.Printf("waiting to receive a message from the mix network...\n")
_, receivedMessage, err := conn.ReadMessage()
if err != nil {
panic(err)
}
if receivedMessage[0] != WITH_REPLY {
panic("incorrect prefix")
}
replySurb := receivedMessage[1 : 1+REPLY_SURB_LEN]
outputFileData := receivedMessage[1+REPLY_SURB_LEN:]
fmt.Printf("writing the file back to the disk!\n")
ioutil.WriteFile("received_file_withreply", outputFileData, 0644)
replyMessage := []byte("hello from reply SURB! - thanks for sending me the file!")
binaryReply := []byte{REPLY}
binaryReply = append(binaryReply, replySurb[:]...)
binaryReply = append(binaryReply, replyMessage[:]...)
fmt.Printf("sending '%v' (using reply SURB) over the mix network...\n", string(replyMessage))
if err = conn.WriteMessage(websocket.BinaryMessage, binaryReply); err != nil {
panic(err)
}
fmt.Printf("waiting to receive a message from the mix network...\n")
_, receivedReply, err := conn.ReadMessage()
if err != nil {
panic(err)
}
if receivedReply[0] != WITHOUT_REPLY {
panic("incorrect prefix")
}
fmt.Printf("received %v from the mix network!\n", string(receivedReply[1:]))
}
func main() {
// sendWithoutReply()
sendWithReply()
}
@@ -40,9 +40,9 @@ func sendWithoutReply() {
selfAddress := getSelfAddress(conn)
fmt.Printf("our address is: %v\n", selfAddress)
sendRequest, err := json.Marshal(map[string]interface{}{
"type": "send",
"recipient": selfAddress,
"message": message,
"type": "send",
"recipient": selfAddress,
"message": message,
"withReplySurb": false,
})
if err != nil {
@@ -76,9 +76,9 @@ func sendWithReply() {
selfAddress := getSelfAddress(conn)
fmt.Printf("our address is: %v\n", selfAddress)
sendRequest, err := json.Marshal(map[string]interface{}{
"type": "send",
"recipient": selfAddress,
"message": message,
"type": "send",
"recipient": selfAddress,
"message": message,
"withReplySurb": true,
})
if err != nil {
@@ -101,15 +101,15 @@ func sendWithReply() {
if err := json.Unmarshal(receivedMessage, &receivedMessageJson); err != nil {
panic(err)
}
// use the received surb to send an anonymous reply!
replySurb := receivedMessageJson["replySurb"]
replySURB := receivedMessageJson["replySURB"]
replyMessage := "hello from reply SURB!"
reply, err := json.Marshal(map[string]interface{}{
"type": "reply",
"message": replyMessage,
"replySurb": replySurb,
"type": "reply",
"message": replyMessage,
"replySURB": replySURB,
})
if err != nil {
panic(err)
@@ -129,6 +129,6 @@ func sendWithReply() {
}
func main() {
// sendWithoutReply();
sendWithReply();
// sendWithoutReply()
sendWithReply()
}
@@ -0,0 +1,139 @@
import asyncio
import websockets
from pathlib import Path
import struct
# request tags
SEND_REQUEST_TAG = 0x00
REPLY_REQUEST_TAG = 0x01
SELF_ADDRESS_REQUEST_TAG = 0x02
# response tags
ERROR_RESPONSE_TAG = 0x00
RECEIVED_RESPONSE_TAG = 0x01
SELF_ADDRESS_RESPONSE_TAG = 0x02
def make_self_address_request() -> bytes:
return bytes([SELF_ADDRESS_REQUEST_TAG])
def parse_self_address_response(raw_response: bytes) -> bytes:
if len(raw_response) != 97 or raw_response[0] != SELF_ADDRESS_RESPONSE_TAG:
print('Received invalid response!')
raise
return raw_response[1:]
def make_send_request(recipient: bytes, message: bytes, with_reply_surb: bool) -> bytes:
# a big endian uint64
message_len = len(message).to_bytes(length=8, byteorder='big', signed=False)
return bytes([SEND_REQUEST_TAG]) + bytes([with_reply_surb]) + recipient + message_len + message
def make_reply_request(message: bytes, reply_surb: bytes) -> bytes:
message_len = len(message).to_bytes(length=8, byteorder='big', signed=False)
surb_len = len(reply_surb).to_bytes(length=8, byteorder='big', signed=False)
return bytes([REPLY_REQUEST_TAG]) + surb_len + reply_surb + message_len + message
# it should have structure of RECEIVED_RESPONSE_TAG || with_reply || (surb_len || surb) || msg_len || msg
# where surb_len || surb is only present if 'with_reply' is true
def parse_received(raw_response: bytes) -> (bytes, bytes):
if raw_response[0] != RECEIVED_RESPONSE_TAG:
print('Received invalid response!')
raise
if raw_response[1] == 1:
has_surb = True
elif raw_response[1] == 0:
has_surb = False
else:
print("malformed received response!")
raise
data = raw_response[2:]
if has_surb:
(surb_len,), other = struct.unpack(">Q", data[:8]), data[8:]
surb = other[:surb_len]
(msg_len,) = struct.unpack(">Q", other[surb_len:surb_len + 8])
if len(other[surb_len + 8:]) != msg_len:
print("invalid msg len")
raise
msg = other[surb_len + 8:]
return msg, surb
else:
(msg_len,), other = struct.unpack(">Q", data[:8]), data[8:]
if len(other) != msg_len:
print("invalid msg len")
raise
msg = other[:msg_len]
return msg, None
async def send_file_with_reply():
uri = "ws://localhost:1977"
async with websockets.connect(uri) as websocket:
self_address_req = make_self_address_request()
await websocket.send(self_address_req)
self_address = parse_self_address_response(await websocket.recv())
file_data = Path('dummy_file').read_bytes()
send_request = make_send_request(self_address, file_data, True)
print("sending content of 'dummy_file' over the mix network...")
await websocket.send(send_request)
print("waiting to receive the 'dummy_file' from the mix network...")
received_response = await websocket.recv()
received_file, surb = parse_received(received_response)
with open("received_file_withreply", "wb") as output_file:
print("writing the file back to the disk!")
output_file.write(received_file)
reply_message = b"hello from reply SURB! - thanks for sending me the file!"
reply_request = make_reply_request(reply_message, surb)
print("sending '{}' (using reply SURB!) over the mix network...".format(reply_message))
await websocket.send(reply_request)
print("waiting to receive a message from the mix network...")
received_response = await websocket.recv()
received_msg, surb = parse_received(received_response)
assert surb is None # no surbs in replies!
print("received '{}' from the mix network".format(received_msg))
async def send_file_without_reply():
uri = "ws://localhost:1977"
async with websockets.connect(uri) as websocket:
self_address_req = make_self_address_request()
await websocket.send(self_address_req)
self_address = parse_self_address_response(await websocket.recv())
file_data = Path('dummy_file').read_bytes()
send_request = make_send_request(self_address, file_data, False)
print("sending content of 'dummy_file' over the mix network...")
await websocket.send(send_request)
print("waiting to receive the 'dummy_file' from the mix network...")
received_response = await websocket.recv()
received_file, surb = parse_received(received_response)
assert surb is None # we didn't attach a surb so we expect a None here!
with open("received_file_noreply", "wb") as output_file:
print("writing the file back to the disk!")
output_file.write(received_file)
# asyncio.get_event_loop().run_until_complete(send_file_without_reply())
asyncio.get_event_loop().run_until_complete(send_file_with_reply())
@@ -1,111 +0,0 @@
import asyncio
import base58
import json
import websockets
self_address_request = json.dumps({
"type": "selfAddress"
})
WITHOUT_REPLY: int = 0
WITH_REPLY: int = 1
REPLY: int = 2
# this is really flaky so ideally will be replaced by some proper serialization
# 16 - length of encryption key
# 32 - length of first hop
# 348 - current sphinx header size
# 192 - size of single payload key
# 4 - number of mix hops (gateway + 3 nodes)
REPLY_SURB_LEN: int = 16 + 32 + 348 + 4 * 192
async def send_file_with_reply():
uri = "ws://localhost:1977"
async with websockets.connect(uri) as websocket:
await websocket.send(self_address_request)
self_address = json.loads(await websocket.recv())
print("our address is: {}".format(self_address["address"]))
# we receive our address in string format of OUR_ID_PUB_KEY . OUR_ENC_PUB_KEY @ OUR_GATE_ID_PUB_KEY
# all keys are 32 bytes and we need to encode them as binary without the '.' or '@' signs
split_address = self_address["address"].split("@")
client_half = split_address[0]
gateway_half = split_address[1]
# to DH: I think at this point this should be replaced with some sort of protobuf / flatbuffers / Cap'n Proto, etc
split_client_address = client_half.split(".")
bin_payload = bytearray([WITH_REPLY])
bin_payload += base58.b58decode(split_client_address[0])
bin_payload += base58.b58decode(split_client_address[1])
bin_payload += base58.b58decode(gateway_half)
with open("dummy_file", "rb") as input_file:
read_data = input_file.read()
bin_payload += read_data
print("sending content of 'dummy_file' over the mix network...")
await websocket.send(bin_payload)
print("waiting to receive the 'dummy_file' from the mix network...")
received_data = await websocket.recv()
assert received_data[0] == WITH_REPLY
reply_surb = received_data[1:1 +REPLY_SURB_LEN]
output_file_data = received_data[1 + REPLY_SURB_LEN:]
with open("received_file_withreply", "wb") as output_file:
print("writing the file back to the disk!")
output_file.write(output_file_data)
reply_message = b"hello from reply SURB! - thanks for sending me the file!"
binary_reply = bytearray([REPLY])
binary_reply += reply_surb
binary_reply += reply_message
print("sending '{}' (using reply SURB!) over the mix network...".format(reply_message))
await websocket.send(binary_reply)
print("waiting to receive a message from the mix network...")
received_reply = await websocket.recv()
assert received_reply[0] == WITHOUT_REPLY
print("received '{}' from the mix network".format(received_reply[1:]))
async def send_file_without_reply():
uri = "ws://localhost:1977"
async with websockets.connect(uri) as websocket:
await websocket.send(self_address_request)
self_address = json.loads(await websocket.recv())
print("our address is: {}".format(self_address["address"]))
# we receive our address in string format of OUR_ID_PUB_KEY . OUR_ENC_PUB_KEY @ OUR_GATE_ID_PUB_KEY
# all keys are 32 bytes and we need to encode them as binary without the '.' or '@' signs
split_address = self_address["address"].split("@")
client_half = split_address[0]
gateway_half = split_address[1]
# to DH: I think at this point this should be replaced with some sort of protobuf / flatbuffers / Cap'n Proto, etc
split_client_address = client_half.split(".")
bin_payload = bytearray([WITHOUT_REPLY])
bin_payload += base58.b58decode(split_client_address[0])
bin_payload += base58.b58decode(split_client_address[1])
bin_payload += base58.b58decode(gateway_half)
with open("dummy_file", "rb") as input_file:
read_data = input_file.read()
bin_payload += read_data
print("sending content of 'dummy_file' over the mix network...")
await websocket.send(bin_payload)
print("waiting to receive the 'dummy_file' from the mix network...")
received_data = await websocket.recv()
assert received_data[0] == WITHOUT_REPLY
with open("received_file_noreply", "wb") as output_file:
print("writing the file back to the disk!")
output_file.write(received_data[1:])
# asyncio.get_event_loop().run_until_complete(send_file_without_reply())
asyncio.get_event_loop().run_until_complete(send_file_with_reply())
@@ -55,13 +55,13 @@ async def send_text_with_reply():
print("received '{}' from the mix network".format(received_message))
# use the received surb to send an anonymous reply!
reply_surb = received_message["replySurb"]
reply_surb = received_message["replySURB"]
reply_message = "hello from reply SURB!"
reply = json.dumps({
"type": "reply",
"message": reply_message,
"replySurb": reply_surb
"replySURB": reply_surb
})
print("sending '{}' (using reply SURB!) over the mix network...".format(reply_message))
@@ -1,46 +1,30 @@
use futures::{SinkExt, StreamExt};
use nym_client::websocket::{
BinaryClientRequest, BinaryServerResponse, ClientTextRequest, ServerTextResponse,
};
use nymsphinx::addressing::clients::Recipient;
use nymsphinx::receiver::ReconstructedMessage;
use std::convert::TryFrom;
use tokio::net::TcpStream;
use tokio_tungstenite::{connect_async, tungstenite::protocol::Message, WebSocketStream};
use websocket_requests::{requests::ClientRequest, responses::ServerResponse};
// just helpers functions that work in this very particular context because we are sending to ourselves
// and hence will always get a response back (i.e. the message we sent)
async fn send_message_and_get_response<M: Into<Message>>(
async fn send_message_and_get_response(
ws_stream: &mut WebSocketStream<TcpStream>,
req: M,
) -> ReconstructedMessage {
ws_stream.send(req.into()).await.unwrap();
req: Vec<u8>,
) -> ServerResponse {
ws_stream.send(Message::Binary(req)).await.unwrap();
let raw_message = ws_stream.next().await.unwrap().unwrap();
let message = match raw_message {
Message::Binary(bin_payload) => BinaryServerResponse::try_from_bytes(&bin_payload).unwrap(),
match raw_message {
Message::Binary(bin_payload) => ServerResponse::deserialize(&bin_payload).unwrap(),
_ => panic!("received an unexpected response type!"),
};
match message {
BinaryServerResponse::Received(received_message) => received_message,
}
}
async fn get_self_address(ws_stream: &mut WebSocketStream<TcpStream>) -> Recipient {
let self_address_request = ClientTextRequest::SelfAddress;
ws_stream.send(self_address_request.into()).await.unwrap();
let raw_response = ws_stream.next().await.unwrap().unwrap();
// what we received now is just a json, but we know it's exact format
// so might as well use that
let response = match raw_response {
Message::Text(txt_msg) => ServerTextResponse::try_from(txt_msg).unwrap(),
_ => panic!("received an unexpected response type!"),
};
let self_address_request = ClientRequest::SelfAddress.serialize();
let response = send_message_and_get_response(ws_stream, self_address_request).await;
match response {
ServerTextResponse::SelfAddress { address } => Recipient::try_from_string(address).unwrap(),
_ => panic!("received an unexpected response type!"),
ServerResponse::SelfAddress(recipient) => recipient,
_ => panic!("received an unexpected response!"),
}
}
@@ -53,33 +37,42 @@ async fn send_file_with_reply() {
let read_data = std::fs::read("examples/dummy_file").unwrap();
let send_request = BinaryClientRequest::Send {
let send_request = ClientRequest::Send {
recipient,
data: read_data,
message: read_data,
with_reply_surb: true,
};
println!("sending content of 'dummy_file' over the mix network...");
let response = send_message_and_get_response(&mut ws_stream, send_request).await;
let response = send_message_and_get_response(&mut ws_stream, send_request.serialize()).await;
let received = match response {
ServerResponse::Received(received) => received,
_ => panic!("received an unexpected response!"),
};
println!("writing the file back to the disk!");
std::fs::write("examples/received_file_withreply", response.message).unwrap();
std::fs::write("examples/received_file_withreply", received.message).unwrap();
let reply_message = b"hello from reply SURB! - thanks for sending me the file!".to_vec();
let reply_request = BinaryClientRequest::Reply {
let reply_request = ClientRequest::Reply {
message: reply_message.clone(),
reply_surb: response.reply_SURB.unwrap(),
reply_surb: received.reply_SURB.unwrap(),
};
println!(
"sending {:?} (using reply SURB!) over the mix network...",
String::from_utf8(reply_message).unwrap()
);
let response = send_message_and_get_response(&mut ws_stream, reply_request).await;
let response = send_message_and_get_response(&mut ws_stream, reply_request.serialize()).await;
let received = match response {
ServerResponse::Received(received) => received,
_ => panic!("received an unexpected response!"),
};
println!(
"received {:#?} from the mix network!",
String::from_utf8(response.message).unwrap()
String::from_utf8(received.message).unwrap()
);
}
@@ -92,21 +85,33 @@ async fn send_file_without_reply() {
let read_data = std::fs::read("examples/dummy_file").unwrap();
let send_request = BinaryClientRequest::Send {
let send_request = ClientRequest::Send {
recipient,
data: read_data,
message: read_data,
with_reply_surb: false,
};
println!("sending content of 'dummy_file' over the mix network...");
let response = send_message_and_get_response(&mut ws_stream, send_request).await;
let response = send_message_and_get_response(&mut ws_stream, send_request.serialize()).await;
let received = match response {
ServerResponse::Received(received) => received,
_ => panic!("received an unexpected response!"),
};
println!("writing the file back to the disk!");
std::fs::write("examples/received_file_noreply", response.message).unwrap();
std::fs::write("examples/received_file_noreply", received.message).unwrap();
}
#[tokio::main]
async fn main() {
// send_file_without_reply().await;
send_file_with_reply().await
println!("#############################");
println!("Example without using replies");
send_file_without_reply().await;
println!("\n\n#############################");
println!("Example using replies");
send_file_with_reply().await;
}
+49 -50
View File
@@ -1,44 +1,30 @@
use futures::{SinkExt, StreamExt};
use nym_client::websocket::{ClientTextRequest, ReceivedTextMessage, ServerTextResponse};
use std::convert::TryFrom;
use serde_json::json;
use tokio::net::TcpStream;
use tokio_tungstenite::{connect_async, tungstenite::protocol::Message, WebSocketStream};
// PREFACE: in practice I don't see why you would ever want to use text api while in Rust, but example
// is here for the completion sake
// just helpers functions that work in this very particular context because we are sending to ourselves
// and hence will always get a response back (i.e. the message we sent)
async fn send_message_and_get_response<M: Into<Message>>(
async fn send_message_and_get_json_response(
ws_stream: &mut WebSocketStream<TcpStream>,
req: M,
) -> ReceivedTextMessage {
ws_stream.send(req.into()).await.unwrap();
text_req: String,
) -> serde_json::Value {
ws_stream.send(Message::Text(text_req)).await.unwrap();
let raw_message = ws_stream.next().await.unwrap().unwrap();
let message = match raw_message {
Message::Text(txt_msg) => ServerTextResponse::try_from(txt_msg).unwrap(),
_ => panic!("received an unexpected response type!"),
};
match message {
ServerTextResponse::Received(received_message) => received_message,
match raw_message {
Message::Text(txt_msg) => serde_json::from_str(&txt_msg).unwrap(),
_ => panic!("received an unexpected response type!"),
}
}
async fn get_self_address(ws_stream: &mut WebSocketStream<TcpStream>) -> String {
let self_address_request = ClientTextRequest::SelfAddress;
ws_stream.send(self_address_request.into()).await.unwrap();
let self_address_request = json!({ "type": "selfAddress" }).to_string();
let response = send_message_and_get_json_response(ws_stream, self_address_request).await;
let raw_response = ws_stream.next().await.unwrap().unwrap();
// what we received now is just a json, but we know it's exact format
// so might as well use that
let response = match raw_response {
Message::Text(txt_msg) => ServerTextResponse::try_from(txt_msg).unwrap(),
_ => panic!("received an unexpected response type!"),
};
match response {
ServerTextResponse::SelfAddress { address } => address,
_ => panic!("received an unexpected response type!"),
}
response["address"].as_str().unwrap().to_string()
}
async fn send_text_with_reply() {
@@ -50,32 +36,35 @@ async fn send_text_with_reply() {
let recipient = get_self_address(&mut ws_stream).await;
println!("our full address is: {}", recipient.to_string());
let send_request = ClientTextRequest::Send {
message: message.clone(),
recipient,
with_reply_surb: true,
};
let send_request = json!({
"type" : "send",
"recipient": recipient,
"message": message,
"withReplySurb": true,
});
println!(
"sending {:?} (*with* reply SURB) over the mix network...",
message
);
let response = send_message_and_get_response(&mut ws_stream, send_request).await;
println!("received {:#?} from the mix network!", response);
let response =
send_message_and_get_json_response(&mut ws_stream, send_request.to_string()).await;
let reply_message = "hello from reply SURB!";
let reply_request = ClientTextRequest::Reply {
message: reply_message.to_string(),
reply_surb: response.reply_surb.unwrap(),
};
let reply_request = json!({
"type": "reply",
"message": reply_message,
"replySURB": response["replySURB"]
});
println!(
"sending {:?} (using reply SURB!) over the mix network...",
reply_message
);
let response = send_message_and_get_response(&mut ws_stream, reply_request).await;
println!("received {:#?} from the mix network!", response);
let response =
send_message_and_get_json_response(&mut ws_stream, reply_request.to_string()).await;
println!("received {:#?} from the mix network!", response.to_string());
}
async fn send_text_without_reply() {
@@ -87,22 +76,32 @@ async fn send_text_without_reply() {
let recipient = get_self_address(&mut ws_stream).await;
println!("our full address is: {}", recipient.to_string());
let send_request = ClientTextRequest::Send {
message: message.clone(),
recipient,
with_reply_surb: false,
};
let send_request = json!({
"type" : "send",
"recipient": recipient,
"message": message,
"withReplySurb": false,
});
println!(
"sending {:?} (*without reply SURB) over the mix network...",
"sending {:?} (*without* reply SURB) over the mix network...",
message
);
let response = send_message_and_get_response(&mut ws_stream, send_request).await;
let response =
send_message_and_get_json_response(&mut ws_stream, send_request.to_string()).await;
println!("received {:#?} from the mix network!", response);
println!("received {:#?} from the mix network!", response.to_string());
}
#[tokio::main]
async fn main() {
// send_text_without_reply().await;
println!("#############################");
println!("Example without using replies");
send_text_without_reply().await;
println!("\n\n#############################");
println!("Example using replies");
send_text_with_reply().await;
}
+93 -155
View File
@@ -12,8 +12,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use super::types::{BinaryClientRequest, ClientTextRequest, ServerTextResponse};
use crate::websocket::types::ReceivedTextMessage;
use client_core::client::{
inbound_messages::{InputMessage, InputMessageSender},
received_buffer::{
@@ -26,13 +24,13 @@ use log::*;
use nymsphinx::addressing::clients::Recipient;
use nymsphinx::anonymous_replies::ReplySURB;
use nymsphinx::receiver::ReconstructedMessage;
use std::convert::{TryFrom, TryInto};
use tokio::net::TcpStream;
use tokio_tungstenite::{
accept_async,
tungstenite::{protocol::Message, Error as WsError},
tungstenite::{protocol::Message as WsMessage, Error as WsError},
WebSocketStream,
};
use websocket_requests::{requests::ClientRequest, responses::ServerResponse};
enum ReceivedResponseType {
Binary,
@@ -89,189 +87,127 @@ impl Handler {
}
}
fn handle_text_send(
&mut self,
msg: String,
full_recipient_address: String,
with_reply_surb: bool,
) -> Option<ServerTextResponse> {
let message_bytes = msg.into_bytes();
let recipient = match Recipient::try_from_string(full_recipient_address) {
Ok(address) => address,
Err(err) => {
trace!("failed to parse received Recipient: {:?}", err);
return Some(ServerTextResponse::new_error("malformed recipient address"));
}
};
// the ack control is now responsible for chunking, etc.
let input_msg = InputMessage::new_fresh(recipient, message_bytes, with_reply_surb);
self.msg_input.unbounded_send(input_msg).unwrap();
self.received_response_type = ReceivedResponseType::Text;
None
}
fn handle_text_reply(&mut self, msg: String, reply_surb: String) -> Option<ServerTextResponse> {
let message_bytes = msg.into_bytes();
let reply_surb = match ReplySURB::from_base58_string(reply_surb) {
Ok(reply_surb) => reply_surb,
Err(err) => {
trace!("failed to parse received ReplySURB: {:?}", err);
return Some(ServerTextResponse::new_error("malformed reply surb"));
}
};
let input_msg = InputMessage::new_reply(reply_surb, message_bytes);
self.msg_input.unbounded_send(input_msg).unwrap();
self.received_response_type = ReceivedResponseType::Text;
None
}
fn handle_text_self_address(&self) -> ServerTextResponse {
ServerTextResponse::SelfAddress {
address: self.self_full_address.to_string(),
}
}
async fn handle_text_message(&mut self, msg: String) -> Option<Message> {
debug!("Handling text message request");
trace!("Content: {:?}", msg.clone());
match ClientTextRequest::try_from(msg) {
Err(e) => Some(
ServerTextResponse::Error {
message: format!("received invalid request. err: {:?}", e),
}
.into(),
),
Ok(req) => match req {
ClientTextRequest::Send {
message,
recipient,
with_reply_surb,
} => self
.handle_text_send(message, recipient, with_reply_surb)
.map(|resp| resp.into()),
ClientTextRequest::Reply {
message,
reply_surb,
} => self
.handle_text_reply(message, reply_surb)
.map(|resp| resp.into()),
ClientTextRequest::SelfAddress => Some(self.handle_text_self_address().into()),
},
}
}
fn handle_binary_send(
fn handle_send(
&mut self,
recipient: Recipient,
data: Vec<u8>,
message: Vec<u8>,
with_reply_surb: bool,
) -> Option<ServerTextResponse> {
) -> Option<ServerResponse> {
// the ack control is now responsible for chunking, etc.
let input_msg = InputMessage::new_fresh(recipient, data, with_reply_surb);
let input_msg = InputMessage::new_fresh(recipient, message, with_reply_surb);
self.msg_input.unbounded_send(input_msg).unwrap();
self.received_response_type = ReceivedResponseType::Binary;
None
}
fn handle_binary_reply(
&mut self,
reply_surb: ReplySURB,
message: Vec<u8>,
) -> Option<ServerTextResponse> {
fn handle_reply(&mut self, reply_surb: ReplySURB, message: Vec<u8>) -> Option<ServerResponse> {
if message.len() > ReplySURB::max_msg_len(Default::default()) {
return Some(ServerTextResponse::new_error(format!("too long message to put inside a reply SURB. Received: {} bytes and maximum is {} bytes", message.len(), ReplySURB::max_msg_len(Default::default()))));
return Some(ServerResponse::new_error(format!("too long message to put inside a reply SURB. Received: {} bytes and maximum is {} bytes", message.len(), ReplySURB::max_msg_len(Default::default()))));
}
let input_msg = InputMessage::new_reply(reply_surb, message);
self.msg_input.unbounded_send(input_msg).unwrap();
self.received_response_type = ReceivedResponseType::Binary;
None
}
// if it's binary we assume it's a sphinx packet formatted the same way as we'd have sent
// it to the gateway
fn handle_binary_message(&mut self, msg: Vec<u8>) -> Option<Message> {
fn handle_self_address(&self) -> ServerResponse {
ServerResponse::SelfAddress(self.self_full_address.clone())
}
fn handle_request(&mut self, request: ClientRequest) -> Option<ServerResponse> {
match request {
ClientRequest::Send {
recipient,
message,
with_reply_surb,
} => self.handle_send(recipient, message, with_reply_surb),
ClientRequest::Reply {
message,
reply_surb,
} => self.handle_reply(reply_surb, message),
ClientRequest::SelfAddress => Some(self.handle_self_address()),
}
}
fn handle_text_message(&mut self, msg: String) -> Option<WsMessage> {
debug!("Handling text message request");
trace!("Content: {:?}", msg);
self.received_response_type = ReceivedResponseType::Text;
let client_request = ClientRequest::try_from_text(msg);
let response = match client_request {
Err(err) => Some(ServerResponse::Error(err)),
Ok(req) => self.handle_request(req),
};
response.map(|resp| WsMessage::text(resp.into_text()))
}
fn handle_binary_message(&mut self, msg: Vec<u8>) -> Option<WsMessage> {
debug!("Handling binary message request");
self.received_response_type = ReceivedResponseType::Binary;
// make sure it is correctly formatted
let binary_request = BinaryClientRequest::try_from_bytes(&msg);
if binary_request.is_none() {
return Some(ServerTextResponse::new_error("invalid binary request").into());
}
match binary_request.unwrap() {
BinaryClientRequest::Send {
recipient,
data,
with_reply_surb,
} => self.handle_binary_send(recipient, data, with_reply_surb),
BinaryClientRequest::Reply {
message,
reply_surb,
} => self.handle_binary_reply(reply_surb, message),
}
.map(|resp| resp.into())
let client_request = ClientRequest::try_from_binary(msg);
let response = match client_request {
Err(err) => Some(ServerResponse::Error(err)),
Ok(req) => self.handle_request(req),
};
response.map(|resp| WsMessage::Binary(resp.into_binary()))
}
async fn handle_request(&mut self, raw_request: Message) -> Option<Message> {
fn handle_ws_request(&mut self, raw_request: WsMessage) -> Option<WsMessage> {
// apparently tungstenite auto-handles ping/pong/close messages so for now let's ignore
// them and let's test that claim. If that's not the case, just copy code from
// old version of this file.
match raw_request {
Message::Text(text_message) => self.handle_text_message(text_message).await,
Message::Binary(binary_message) => self.handle_binary_message(binary_message),
WsMessage::Text(text_message) => self.handle_text_message(text_message),
WsMessage::Binary(binary_message) => self.handle_binary_message(binary_message),
_ => None,
}
}
// I'm still not entirely sure why `send_all` requires `TryStream` rather than `Stream`, but
// let's just play along for now
fn prepare_reconstructed_binary(
&self,
reconstructed_messages: Vec<ReconstructedMessage>,
) -> Vec<Result<WsMessage, WsError>> {
reconstructed_messages
.into_iter()
.map(ServerResponse::Received)
.map(|resp| Ok(WsMessage::Binary(resp.into_binary())))
.collect()
}
// I'm still not entirely sure why `send_all` requires `TryStream` rather than `Stream`, but
// let's just play along for now
fn prepare_reconstructed_text(
&self,
reconstructed_messages: Vec<ReconstructedMessage>,
) -> Vec<Result<WsMessage, WsError>> {
reconstructed_messages
.into_iter()
.map(ServerResponse::Received)
.map(|resp| Ok(WsMessage::Text(resp.into_text())))
.collect()
}
async fn push_websocket_received_plaintexts(
&mut self,
reconstructed_messages: Vec<ReconstructedMessage>,
) -> Result<(), WsError> {
// TODO: later there might be a flag on the reconstructed message itself
let response_messages: Vec<_> = match self.received_response_type {
ReceivedResponseType::Binary => reconstructed_messages
.into_iter()
.map(|msg| Ok(Message::Binary(msg.into_bytes())))
.collect(),
ReceivedResponseType::Text => {
let mut decoded_messages: Vec<ReceivedTextMessage> = Vec::new();
// either all succeed or all fall back
let mut did_fail = false;
for message in reconstructed_messages.iter() {
match message.try_into() {
Ok(msg) => decoded_messages.push(msg),
Err(err) => {
did_fail = true;
warn!("Invalid UTF-8 sequence in response message - {:?}", err);
break;
}
}
}
if did_fail {
reconstructed_messages
.into_iter()
.map(|msg| Ok(Message::Binary(msg.into_bytes())))
.collect()
} else {
decoded_messages
.into_iter()
.map(|msg| Ok(ServerTextResponse::Received(msg).into()))
.collect()
}
// TODO: later there might be a flag on the reconstructed message itself to tell us
// if it's text or binary, but for time being we use the naive assumption that if
// client is sending Message::Text it expects text back. Same for Message::Binary
let response_messages = match self.received_response_type {
ReceivedResponseType::Binary => {
self.prepare_reconstructed_binary(reconstructed_messages)
}
ReceivedResponseType::Text => self.prepare_reconstructed_text(reconstructed_messages),
};
let mut send_stream = futures::stream::iter(response_messages);
@@ -282,7 +218,7 @@ impl Handler {
.await
}
async fn send_websocket_response(&mut self, msg: Message) -> Result<(), WsError> {
async fn send_websocket_response(&mut self, msg: WsMessage) -> Result<(), WsError> {
match self.socket {
// TODO: more closely investigate difference between `Sink::send` and `Sink::send_all`
// it got something to do with batching and flushing - it might be important if it
@@ -292,7 +228,7 @@ impl Handler {
}
}
async fn next_websocket_request(&mut self) -> Option<Result<Message, WsError>> {
async fn next_websocket_request(&mut self) -> Option<Result<WsMessage, WsError>> {
match self.socket {
Some(ref mut ws_stream) => ws_stream.next().await,
None => None,
@@ -302,6 +238,7 @@ impl Handler {
async fn listen_for_requests(&mut self, mut msg_receiver: ReconstructedMessagesReceiver) {
loop {
tokio::select! {
// we can either get a client request from the websocket
socket_msg = self.next_websocket_request() => {
if socket_msg.is_none() {
break;
@@ -309,7 +246,7 @@ impl Handler {
let socket_msg = match socket_msg.unwrap() {
Ok(socket_msg) => socket_msg,
Err(err) => {
error!("failed to obtain message from websocket stream! stopping connection handler: {}", err);
warn!("failed to obtain message from websocket stream! stopping connection handler: {}", err);
break;
}
};
@@ -318,7 +255,7 @@ impl Handler {
break;
}
if let Some(response) = self.handle_request(socket_msg).await {
if let Some(response) = self.handle_ws_request(socket_msg) {
if let Err(err) = self.send_websocket_response(response).await {
warn!(
"Failed to send message over websocket: {}. Assuming the connection is dead.",
@@ -328,6 +265,7 @@ impl Handler {
}
}
}
// or a reconstructed mix message that we need to push back to the client
mix_messages = msg_receiver.next() => {
let mix_messages = mix_messages.expect(
"mix messages sender was unexpectedly closed! this shouldn't have ever happened!",
-6
View File
@@ -14,12 +14,6 @@
pub(crate) mod handler;
pub(crate) mod listener;
pub(crate) mod types;
pub(crate) use handler::Handler;
pub(crate) use listener::Listener;
pub use types::{
BinaryClientRequest, BinaryServerResponse, ClientTextRequest, ReceivedTextMessage,
ServerTextResponse,
};
-245
View File
@@ -1,245 +0,0 @@
// Copyright 2020 Nym Technologies SA
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use nymsphinx::addressing::clients::Recipient;
use nymsphinx::anonymous_replies::ReplySURB;
use nymsphinx::params::{MessageType, DEFAULT_NUM_MIX_HOPS};
use nymsphinx::receiver::ReconstructedMessage;
use serde::{Deserialize, Serialize};
use std::convert::TryFrom;
use tokio_tungstenite::tungstenite::protocol::Message as WsMessage;
// 'Text'
#[derive(Serialize, Deserialize, Debug)]
#[serde(tag = "type", rename_all = "camelCase")]
pub enum ClientTextRequest {
#[serde(rename_all = "camelCase")]
Send {
message: String,
recipient: String,
// Perhaps we could change it to a number to indicate how many reply_SURBs we want to include?
with_reply_surb: bool,
},
SelfAddress,
#[serde(rename_all = "camelCase")]
Reply {
message: String,
reply_surb: String,
},
}
impl TryFrom<String> for ClientTextRequest {
type Error = serde_json::Error;
fn try_from(msg: String) -> Result<Self, Self::Error> {
serde_json::from_str(&msg)
}
}
impl Into<WsMessage> for ClientTextRequest {
fn into(self) -> WsMessage {
let str_req = serde_json::to_string(&self).unwrap();
WsMessage::Text(str_req)
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct ReceivedTextMessage {
pub message: String,
pub reply_surb: Option<String>,
}
impl<'a> TryFrom<&'a ReconstructedMessage> for ReceivedTextMessage {
type Error = std::str::Utf8Error;
fn try_from(reconstructed_message: &ReconstructedMessage) -> Result<Self, Self::Error> {
Ok(ReceivedTextMessage {
message: std::str::from_utf8(&reconstructed_message.message)?.to_string(),
reply_surb: reconstructed_message
.reply_SURB
.as_ref()
.map(|reply_surb| reply_surb.to_base58_string()),
})
}
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(tag = "type", rename_all = "camelCase")]
pub enum ServerTextResponse {
Received(ReceivedTextMessage),
GetClients { clients: Vec<String> },
SelfAddress { address: String },
Error { message: String },
}
impl ServerTextResponse {
pub fn new_error<S: Into<String>>(msg: S) -> Self {
ServerTextResponse::Error {
message: msg.into(),
}
}
}
impl TryFrom<String> for ServerTextResponse {
type Error = serde_json::Error;
fn try_from(msg: String) -> Result<Self, <ServerTextResponse as TryFrom<String>>::Error> {
serde_json::from_str(&msg)
}
}
impl Into<WsMessage> for ServerTextResponse {
fn into(self) -> WsMessage {
// it should be safe to call `unwrap` here as the message is generated by the server
// so if it fails (and consequently panics) it's a bug that should be resolved
let str_res = serde_json::to_string(&self).unwrap();
WsMessage::Text(str_res)
}
}
// 'Binary'
// Note that control message, like 'SelfAddress', are not allowed as 'BinaryRequests' - but maybe
// they should be?
// Right now you can only send a `Send` or `Reply` binary message
#[allow(non_snake_case)]
#[derive(Debug)]
pub enum BinaryClientRequest {
Send {
recipient: Recipient,
data: Vec<u8>,
with_reply_surb: bool,
},
Reply {
message: Vec<u8>,
reply_surb: ReplySURB,
},
}
impl BinaryClientRequest {
// TODO: I really think this should be done with something like protobuf / flatbuffers / Cap'n Proto,
// especially if people using different languages had to use it
// Another reason for some proper schema: messages pushed back to the client which will require
// extra parsing to determine when the actual message starts and which parts are the reply surb
// TODO2: perhaps do it the proper way and introduce an error type
// TODO3: but if this 'stays' this way, the function could definitely use a clean up
pub fn try_from_bytes(req: &[u8]) -> Option<Self> {
if req.is_empty() {
return None;
}
let with_reply_surb = match req[0] {
n if n == MessageType::WithReplySURB as u8 => true,
n if n == MessageType::WithoutReplySURB as u8 => false,
n if n == MessageType::IsReply as u8 => {
// TODO: this is extremely fragile as only works for the very specific network topology
// and number of hops - another reason for some proper serialization library
let surb_len = ReplySURB::serialized_len(DEFAULT_NUM_MIX_HOPS);
if req.len() < surb_len + 1 {
return None;
}
// note the extra +1 (due to message prefix)
let surb_bytes = &req[1..1 + surb_len];
let reply_surb = ReplySURB::from_bytes(surb_bytes).ok()?;
return Some(BinaryClientRequest::Reply {
message: req[1 + surb_len..].to_vec(),
reply_surb,
});
}
_ => return None, // no other option is valid in this context
};
if req.len() < Recipient::LEN + 1 {
return None;
}
let mut recipient_bytes = [0u8; Recipient::LEN];
recipient_bytes.copy_from_slice(&req[1..Recipient::LEN + 1]);
let recipient = Recipient::try_from_bytes(recipient_bytes).ok()?;
Some(BinaryClientRequest::Send {
recipient,
data: req[1 + Recipient::LEN..].to_vec(),
with_reply_surb,
})
}
pub fn into_bytes(self) -> Vec<u8> {
match self {
// (MessageType::WithReplySURB OR MessageType::WithoutReplySURB) || RECIPIENT || MESSAGE
BinaryClientRequest::Send {
recipient,
data,
with_reply_surb,
} => std::iter::once(if with_reply_surb {
MessageType::WithReplySURB as u8
} else {
MessageType::WithoutReplySURB as u8
})
.chain(recipient.into_bytes().iter().cloned())
.chain(data.into_iter())
.collect(),
// MessageType::IsReply || REPLY_SURB || MESSAGE
// TODO: this is fragile as reply_SURB length CAN BE variable. however temporarily
// we are making 'unsafe' assumption that it will be constant
BinaryClientRequest::Reply {
message,
reply_surb,
} => std::iter::once(MessageType::IsReply as u8)
.chain(reply_surb.to_bytes().into_iter())
.chain(message.into_iter())
.collect(),
}
}
}
impl Into<WsMessage> for BinaryClientRequest {
fn into(self) -> WsMessage {
WsMessage::Binary(self.into_bytes())
}
}
pub enum BinaryServerResponse {
Received(ReconstructedMessage),
}
impl BinaryServerResponse {
pub fn into_bytes(self) -> Vec<u8> {
match self {
// this happens to work because right now there's only a single possible binary response
BinaryServerResponse::Received(reconstructed_message) => {
reconstructed_message.into_bytes()
}
}
}
// TODO: dont be lazy and define error type and change it into Result<Self, Error>
pub fn try_from_bytes(b: &[u8]) -> Option<Self> {
// this happens to work because right now there's only a single possible binary response
Some(BinaryServerResponse::Received(
ReconstructedMessage::try_from_bytes(b).ok()?,
))
}
}
impl Into<WsMessage> for BinaryServerResponse {
fn into(self) -> WsMessage {
WsMessage::Binary(self.into_bytes())
}
}
@@ -0,0 +1,13 @@
[package]
name = "websocket-requests"
version = "0.1.0"
authors = ["Jędrzej Stuczyński <andrew@nymtech.net>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
nymsphinx = { path = "../../../common/nymsphinx" }
@@ -0,0 +1,92 @@
// Copyright 2020 Nym Technologies SA
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use serde::{Deserialize, Serialize};
use std::fmt;
// no need to go fancy here like we've done in other places.
#[derive(PartialEq, Clone, Serialize, Deserialize)]
pub struct Error {
pub kind: ErrorKind,
pub message: String,
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}:{}", self.kind.as_str(), self.message)
}
}
impl fmt::Debug for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self, f)
}
}
impl Error {
pub fn new(kind: ErrorKind, message: String) -> Self {
Error { kind, message }
}
}
#[repr(u8)]
#[derive(PartialEq, Clone, Serialize, Deserialize)]
pub enum ErrorKind {
/// The received request contained no data.
EmptyRequest = 0x01,
/// The received request did not contain enough data to be fully parsed.
TooShortRequest = 0x02,
/// The received request tag is not defined.
UnknownRequest = 0x03,
/// The received request is malformed.
MalformedRequest = 0x04,
// that's an arbitrary division but lets keep 1-127 (hex 0x01 - 0x7F) values request-specific
// and 128-254 (hex 0x80 - 0xFE) for responses
/// The received response contained no data.
EmptyResponse = 0x80,
/// The received response did not contain enough data to be fully parsed.
TooShortResponse = 0x81,
/// The received response tag is not defined.
UnknownResponse = 0x82,
/// The received response is malformed.
MalformedResponse = 0x83,
/// The error is due to something else.
Other = 0xFF,
}
impl ErrorKind {
pub(crate) fn as_str(&self) -> &'static str {
match *self {
ErrorKind::EmptyRequest => "received request contained no data",
ErrorKind::TooShortRequest => "received request did not contain enough data",
ErrorKind::UnknownRequest => "unknown request type",
ErrorKind::MalformedRequest => "malformed request",
ErrorKind::EmptyResponse => "received response contained no data",
ErrorKind::TooShortResponse => "received response did not contain enough data",
ErrorKind::UnknownResponse => "unknown response type",
ErrorKind::MalformedResponse => "malformed response",
ErrorKind::Other => "other",
}
}
}
@@ -12,12 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
// Perhaps this idea could be extended with some binary masking to convey more information
// like to help the receiver to know whether the message is expected to be text-based or binary
#[repr(u8)]
#[derive(Clone, Copy, Debug)]
pub enum MessageType {
WithoutReplySURB = 0,
WithReplySURB = 1,
IsReply = 2,
}
pub mod error;
pub mod requests;
pub mod responses;
mod text;
@@ -0,0 +1,366 @@
// Copyright 2020 Nym Technologies SA
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// all variable size data is always prefixed with u64 length
// tags are u8
use crate::error::{self, ErrorKind};
use crate::text::ClientRequestText;
use nymsphinx::addressing::clients::Recipient;
use nymsphinx::anonymous_replies::ReplySURB;
use std::convert::{TryFrom, TryInto};
use std::mem::size_of;
/// Value tag representing [`Send`] variant of the [`ClientRequest`]
pub const SEND_REQUEST_TAG: u8 = 0x00;
/// Value tag representing [`Reply`] variant of the [`ClientRequest`]
pub const REPLY_REQUEST_TAG: u8 = 0x01;
/// Value tag representing [`SelfAddress`] variant of the [`ClientRequest`]
pub const SELF_ADDRESS_REQUEST_TAG: u8 = 0x02;
#[allow(non_snake_case)]
#[derive(Debug)]
pub enum ClientRequest {
Send {
recipient: Recipient,
message: Vec<u8>,
// Perhaps we could change it to a number to indicate how many reply_SURBs we want to include?
with_reply_surb: bool,
},
Reply {
message: Vec<u8>,
reply_surb: ReplySURB,
},
SelfAddress,
}
// we could have been parsing it directly TryFrom<WsMessage>, but we want to retain
// information about whether it came from binary or text to send appropriate response back
impl ClientRequest {
// SEND_REQUEST_TAG || with_surb || recipient || data_len || data
fn serialize_send(recipient: Recipient, data: Vec<u8>, with_reply_surb: bool) -> Vec<u8> {
let data_len_bytes = (data.len() as u64).to_be_bytes();
std::iter::once(SEND_REQUEST_TAG)
.chain(std::iter::once(with_reply_surb as u8))
.chain(recipient.to_bytes().iter().cloned()) // will not be length prefixed because the length is constant
.chain(data_len_bytes.iter().cloned())
.chain(data.into_iter())
.collect()
}
// SEND_REQUEST_TAG || with_reply || recipient || data_len || data
fn deserialize_send(b: &[u8]) -> Result<Self, error::Error> {
// we need to have at least 1 (tag) + 1 (reply flag) + Recipient::LEN + sizeof<u64> bytes
if b.len() < 2 + Recipient::LEN + size_of::<u64>() {
return Err(error::Error::new(
ErrorKind::TooShortRequest,
"not enough data provided to recover 'send'".to_string(),
));
}
// this MUST match because it was called by 'deserialize'
debug_assert_eq!(b[0], SEND_REQUEST_TAG);
let with_reply_surb = match b[1] {
0 => false,
1 => true,
n => {
return Err(error::Error::new(
ErrorKind::MalformedRequest,
format!("invalid reply surb flag {}", n),
))
}
};
let mut recipient_bytes = [0u8; Recipient::LEN];
recipient_bytes.copy_from_slice(&b[2..2 + Recipient::LEN]);
let recipient = match Recipient::try_from_bytes(recipient_bytes) {
Ok(recipient) => recipient,
Err(err) => {
return Err(error::Error::new(
ErrorKind::MalformedRequest,
format!("malformed recipient: {:?}", err),
))
}
};
let data_len_bytes = &b[2 + Recipient::LEN..2 + Recipient::LEN + size_of::<u64>()];
let data_len = u64::from_be_bytes(data_len_bytes.try_into().unwrap());
let data = &b[2 + Recipient::LEN + size_of::<u64>()..];
if data.len() as u64 != data_len {
return Err(error::Error::new(
ErrorKind::MalformedRequest,
format!(
"data len has inconsistent length. specified: {} got: {}",
data_len,
data.len()
),
));
}
Ok(ClientRequest::Send {
with_reply_surb,
recipient,
message: data.to_vec(),
})
}
// REPLY_REQUEST_TAG || surb_len || surb || message_len || message
fn serialize_reply(message: Vec<u8>, reply_surb: ReplySURB) -> Vec<u8> {
let reply_surb_bytes = reply_surb.to_bytes();
let surb_len_bytes = (reply_surb_bytes.len() as u64).to_be_bytes();
let message_len_bytes = (message.len() as u64).to_be_bytes();
std::iter::once(REPLY_REQUEST_TAG)
.chain(surb_len_bytes.iter().cloned())
.chain(reply_surb_bytes.into_iter())
.chain(message_len_bytes.iter().cloned())
.chain(message.into_iter())
.collect()
}
// REPLY_REQUEST_TAG || surb_len || surb || message_len || message
fn deserialize_reply(b: &[u8]) -> Result<Self, error::Error> {
// we need to have at the very least 2 * sizeof<u64> bytes (in case, for some peculiar reason
// message and reply surb were 0 len - the request would still be malformed, but would in theory
// be parse'able)
if b.len() < 1 + 2 * size_of::<u64>() {
return Err(error::Error::new(
ErrorKind::TooShortRequest,
"not enough data provided to recover 'reply'".to_string(),
));
}
// this MUST match because it was called by 'deserialize'
debug_assert_eq!(b[0], REPLY_REQUEST_TAG);
let reply_surb_len =
u64::from_be_bytes(b[1..1 + size_of::<u64>()].as_ref().try_into().unwrap());
// make sure we won't go out of bounds here
if reply_surb_len > (b.len() - 1 + 2 * size_of::<u64>()) as u64 {
return Err(error::Error::new(
ErrorKind::MalformedRequest,
format!(
"not enough data to recover reply surb with specified length {}",
reply_surb_len
),
));
}
let surb_bound = 1 + size_of::<u64>() + reply_surb_len as usize;
let reply_surb_bytes = &b[1 + size_of::<u64>()..surb_bound];
let reply_surb = match ReplySURB::from_bytes(reply_surb_bytes) {
Ok(reply_surb) => reply_surb,
Err(err) => {
return Err(error::Error::new(
ErrorKind::MalformedRequest,
format!("malformed reply surb: {:?}", err),
))
}
};
let message_len = u64::from_be_bytes(
b[surb_bound..surb_bound + size_of::<u64>()]
.as_ref()
.try_into()
.unwrap(),
);
let message = &b[surb_bound + size_of::<u64>()..];
if message.len() as u64 != message_len {
return Err(error::Error::new(
ErrorKind::MalformedRequest,
format!(
"message len has inconsistent length. specified: {} got: {}",
message_len,
message.len()
),
));
}
// TODO: should this blow HERE, i.e. during deserialization that the data you're trying
// to send via reply is too long?
Ok(ClientRequest::Reply {
reply_surb,
message: message.to_vec(),
})
}
// SELF_ADDRESS_REQUEST_TAG
fn serialize_self_address() -> Vec<u8> {
std::iter::once(SELF_ADDRESS_REQUEST_TAG).collect()
}
// SELF_ADDRESS_REQUEST_TAG
fn deserialize_self_address(b: &[u8]) -> Result<Self, error::Error> {
// this MUST match because it was called by 'deserialize'
debug_assert_eq!(b[0], SELF_ADDRESS_REQUEST_TAG);
Ok(ClientRequest::SelfAddress)
}
pub fn serialize(self) -> Vec<u8> {
match self {
ClientRequest::Send {
recipient,
message,
with_reply_surb,
} => Self::serialize_send(recipient, message, with_reply_surb),
ClientRequest::Reply {
message,
reply_surb,
} => Self::serialize_reply(message, reply_surb),
ClientRequest::SelfAddress => Self::serialize_self_address(),
}
}
pub fn deserialize(b: &[u8]) -> Result<Self, error::Error> {
if b.is_empty() {
// technically I'm not even sure this can ever be returned, because reading empty
// request would imply closed socket, but let's include it for completion sake
return Err(error::Error::new(
ErrorKind::EmptyRequest,
"no data provided".to_string(),
));
}
if b.len() < size_of::<u8>() {
return Err(error::Error::new(
ErrorKind::TooShortRequest,
format!(
"not enough data provided to recover request tag. Provided only {} bytes",
b.len()
),
));
}
let request_tag = b[0];
// determine what kind of request that is and try to deserialize it
match request_tag {
SEND_REQUEST_TAG => Self::deserialize_send(b),
REPLY_REQUEST_TAG => Self::deserialize_reply(b),
SELF_ADDRESS_REQUEST_TAG => Self::deserialize_self_address(b),
n => Err(error::Error::new(
ErrorKind::UnknownRequest,
format!("type {}", n),
)),
}
}
pub fn try_from_binary(raw_req: Vec<u8>) -> Result<Self, error::Error> {
Self::deserialize(&raw_req)
}
pub fn try_from_text(raw_req: String) -> Result<Self, error::Error> {
// use the intermediate string structure and let serde do bunch of work for us
let text_req = ClientRequestText::try_from(raw_req).map_err(|json_err| {
error::Error::new(ErrorKind::MalformedRequest, json_err.to_string())
})?;
text_req.try_into()
}
}
#[cfg(test)]
mod tests {
use super::*;
// very basic tests to check for obvious errors like off by one
#[test]
fn send_request_serialization_works() {
let recipient = Recipient::try_from_base58_string("CytBseW6yFXUMzz4SGAKdNLGR7q3sJLLYxyBGvutNEQV.4QXYyEVc5fUDjmmi8PrHN9tdUFV4PCvSJE1278cHyvoe@4sBbL1ngf1vtNqykydQKTFh26sQCw888GpUqvPvyNB4f").unwrap();
let recipient_string = recipient.to_string();
let send_request_no_surb = ClientRequest::Send {
recipient: recipient.clone(),
message: b"foomp".to_vec(),
with_reply_surb: false,
};
let bytes = send_request_no_surb.serialize();
let recovered = ClientRequest::deserialize(&bytes).unwrap();
match recovered {
ClientRequest::Send {
recipient,
message,
with_reply_surb,
} => {
assert_eq!(recipient.to_string(), recipient_string);
assert_eq!(message, b"foomp".to_vec());
assert!(!with_reply_surb)
}
_ => unreachable!(),
}
let send_request_surb = ClientRequest::Send {
recipient,
message: b"foomp".to_vec(),
with_reply_surb: true,
};
let bytes = send_request_surb.serialize();
let recovered = ClientRequest::deserialize(&bytes).unwrap();
match recovered {
ClientRequest::Send {
recipient,
message,
with_reply_surb,
} => {
assert_eq!(recipient.to_string(), recipient_string);
assert_eq!(message, b"foomp".to_vec());
assert!(with_reply_surb)
}
_ => unreachable!(),
}
}
#[test]
fn reply_request_serialization_works() {
let reply_surb_string = "CjfVbHbfAjbC3W1BvNHGXmM8KNAnDNYGaHMLqVDxRYeo352csAihstup9bvqXam4dTWgfHak6KYwL9STaxWJ47E8XFZbSEvs7hEsfCkxr6K9WJuSBPK84GDDEvad8ZAuMCoaXsAd5S2Lj9a5eYyzG4SL1jHzhSMni55LyJwumxo1ZTGZNXggxw1RREosvyzNrW9Rsi3owyPqLCwXpiei2tHZty8w8midVvg8vDa7ZEJD842CLv8D4ohynSG7gDpqTrhkRaqYAuz7dzqNbMXLJRM7v823Jn16fA1L7YQxmcaUdUigyRSgTdb4i9ebiLGSyJ1iDe6Acz613PQZh6Ua3bZ2zVKq3dSycpDm9ngarRK4zJrAaUxRkdih8YzW3BY4nL9eqkfKA4N1TWCLaRU7zpSaf8yMEwrAZReU3d5zLV8c5KBfa2w8R5anhQeBojduZEGEad8kkHuKU52Zg93FeWHvH1qgZaEJMHH4nN7gKXz9mvWDhYwyF4vt3Uy2NhCHC3N5pL1gMme27YcoPcTEia1fxKZtnt6rtEozzTrAgCJGswigkFbkafiV5QaJwLKTUxtzhkZ57eEuLPte9UvJHzhhXUQ2CV7R2BUkJjYZy3Zsx6YYvdYWiAFFkWUwNEGA4QpShUHciBfsQVHQ7pN41YcyYUhbywQDFnTVgEmdUZ1XCBi3gyK5U3tDQmFzP1u9m3mWrUA8qB9mRDE7ptNDm5c3c1458L6uXLUth7sdMaa1Was5LCmCdmNDtvNpCDAEt1in6q6mrZFR85aCSU9b1baNGwZoCqPpPvydkVe63gXWoi8ebvdyxARrqACFrSB3ZdY3uJBw8CTMNkKK6MvcefMkSVVsbLd36TQAtYSCqrpiMc5dQuKcEu5QfciwvWYXYx8WFNAgKwP2mv49KCTvfozNDUCbjzDwSx92Zv5zjG8HbFpB13bY9UZGeyTPvv7gGxCzjGjJGbW6FRAheRQaaje5fUgCNM95Tv7wBmAMRHHFgWafeK1sdFH7dtCX9u898HucGTaboSKLsVh8J78gbbkHErwjMh7y9YRkceq5TTYS5da4kHnyNKYWSbxgZrmFg44XGKoeYcqoHB3XTZrdsf7F5fFeNwnihkmADvhAcaxXUmVqq4rQFZH84a1iC3WBWXYcqiZH2L7ujGWV7mMDT4HBEerDYjc8rNY4xGTPfivCrBCJW1i14aqW8xRdsdgTM88eTksvC3WPJLJ7iMzfKXeL7fMW1Ek6QGyQtLBW98vEESpdcDg6DeZ5rMz6VqjTGGqcCaFGfHoqtfxMDaBAEsyQ8h7XDX6dg1wq9wH6j4Tw7Tj1MEv1b8uj5NJkozZdzVdYA2QyE2Dp8vuurQG6uVdTDNww2d88RBQ8sVgjxN8gR45y4woJLhFAaNTAtrY6wDTxyXST13ni6oyqdYxjFVk9Am4v3DzH7Y2K8iRVSHfTk4FRbPULyaeK6wt2anvMJH1XdvVRgc14h67MnBxMgMD1UFk8AErN7CDj26fppe3c5G6KozJe4cSqQUGbBjVzBnrHCruqrfZBn5hNZHTV37bQiomqhRQXohxhuKEnNrGbAe1xNvJr9X";
let reply_surb = ReplySURB::from_base58_string(reply_surb_string).unwrap();
let reply_request = ClientRequest::Reply {
message: b"foomp".to_vec(),
reply_surb,
};
let bytes = reply_request.serialize();
let recovered = ClientRequest::deserialize(&bytes).unwrap();
match recovered {
ClientRequest::Reply {
reply_surb,
message,
} => {
assert_eq!(reply_surb.to_base58_string(), reply_surb_string);
assert_eq!(message, b"foomp".to_vec());
}
_ => unreachable!(),
}
}
#[test]
fn self_address_request_serialization_works() {
let self_address_request = ClientRequest::SelfAddress;
let bytes = self_address_request.serialize();
let recovered = ClientRequest::deserialize(&bytes).unwrap();
match recovered {
ClientRequest::SelfAddress => (),
_ => unreachable!(),
}
}
}
@@ -0,0 +1,401 @@
// Copyright 2020 Nym Technologies SA
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// all variable size data is always prefixed with u64 length
// tags are u8
use crate::error::{self, ErrorKind};
use crate::text::ServerResponseText;
use nymsphinx::addressing::clients::Recipient;
use nymsphinx::anonymous_replies::ReplySURB;
use nymsphinx::receiver::ReconstructedMessage;
use std::convert::TryInto;
use std::mem::size_of;
/// Value tag representing [`Error`] variant of the [`ServerResponse`]
pub const ERROR_RESPONSE_TAG: u8 = 0x00;
/// Value tag representing [`Received`] variant of the [`ServerResponse`]
pub const RECEIVED_RESPONSE_TAG: u8 = 0x01;
/// Value tag representing [`SelfAddress`] variant of the [`ServerResponse`]
pub const SELF_ADDRESS_RESPONSE_TAG: u8 = 0x02;
#[derive(Debug)]
pub enum ServerResponse {
Received(ReconstructedMessage),
SelfAddress(Recipient),
Error(error::Error),
}
impl ServerResponse {
pub fn new_error<S: Into<String>>(message: S) -> Self {
ServerResponse::Error(error::Error {
kind: ErrorKind::Other,
message: message.into(),
})
}
// RECEIVED_RESPONSE_TAG || with_reply || (surb_len || surb) || msg_len || msg
fn serialize_received(reconstructed_message: ReconstructedMessage) -> Vec<u8> {
let message_len_bytes = (reconstructed_message.message.len() as u64).to_be_bytes();
if let Some(reply_surb) = reconstructed_message.reply_SURB {
let reply_surb_bytes = reply_surb.to_bytes();
let surb_len_bytes = (reply_surb_bytes.len() as u64).to_be_bytes();
// with_reply || surb_len || surb || msg_len || msg
std::iter::once(RECEIVED_RESPONSE_TAG)
.chain(std::iter::once(true as u8))
.chain(surb_len_bytes.iter().cloned())
.chain(reply_surb_bytes.iter().cloned())
.chain(message_len_bytes.iter().cloned())
.chain(reconstructed_message.message.into_iter())
.collect()
} else {
// without_reply || msg_len || msg
std::iter::once(RECEIVED_RESPONSE_TAG)
.chain(std::iter::once(false as u8))
.chain(message_len_bytes.iter().cloned())
.chain(reconstructed_message.message.into_iter())
.collect()
}
}
// RECEIVED_RESPONSE_TAG || with_reply || (surb_len || surb) || msg_len || msg
fn deserialize_received(b: &[u8]) -> Result<Self, error::Error> {
// this MUST match because it was called by 'deserialize'
debug_assert_eq!(b[0], RECEIVED_RESPONSE_TAG);
// we must be able to read at the very least if it has a reply_surb and length of some field
if b.len() < 2 + size_of::<u64>() {
return Err(error::Error::new(
ErrorKind::TooShortResponse,
"not enough data provided to recover 'received'".to_string(),
));
}
let with_reply_surb = match b[1] {
0 => false,
1 => true,
n => {
return Err(error::Error::new(
ErrorKind::MalformedResponse,
format!("invalid reply flag {}", n),
))
}
};
if with_reply_surb {
let reply_surb_len =
u64::from_be_bytes(b[2..2 + size_of::<u64>()].as_ref().try_into().unwrap());
// make sure we won't go out of bounds here
if reply_surb_len > (b.len() - 2 + 2 * size_of::<u64>()) as u64 {
return Err(error::Error::new(
ErrorKind::MalformedResponse,
"not enough bytes to read reply_surb bytes!".to_string(),
));
}
let surb_bound = 2 + size_of::<u64>() + reply_surb_len as usize;
let reply_surb_bytes = &b[2 + size_of::<u64>()..surb_bound];
let reply_surb = match ReplySURB::from_bytes(reply_surb_bytes) {
Ok(reply_surb) => reply_surb,
Err(err) => {
return Err(error::Error::new(
ErrorKind::MalformedResponse,
format!("malformed reply SURB: {:?}", err),
))
}
};
let message_len = u64::from_be_bytes(
b[surb_bound..surb_bound + size_of::<u64>()]
.as_ref()
.try_into()
.unwrap(),
);
let message = &b[surb_bound + size_of::<u64>()..];
if message.len() as u64 != message_len {
return Err(error::Error::new(
ErrorKind::MalformedResponse,
format!(
"message len has inconsistent length. specified: {} got: {}",
message_len,
message.len()
),
));
}
Ok(ServerResponse::Received(ReconstructedMessage {
message: message.to_vec(),
reply_SURB: Some(reply_surb),
}))
} else {
let message_len =
u64::from_be_bytes(b[2..2 + size_of::<u64>()].as_ref().try_into().unwrap());
let message = &b[2 + size_of::<u64>()..];
if message.len() as u64 != message_len {
return Err(error::Error::new(
ErrorKind::MalformedResponse,
format!(
"message len has inconsistent length. specified: {} got: {}",
message_len,
message.len()
),
));
}
Ok(ServerResponse::Received(ReconstructedMessage {
message: message.to_vec(),
reply_SURB: None,
}))
}
}
// SELF_ADDRESS_RESPONSE_TAG || self_address
fn serialize_self_address(address: Recipient) -> Vec<u8> {
std::iter::once(SELF_ADDRESS_RESPONSE_TAG)
.chain(address.to_bytes().iter().cloned())
.collect()
}
// SELF_ADDRESS_RESPONSE_TAG || self_address
fn deserialize_self_address(b: &[u8]) -> Result<Self, error::Error> {
// this MUST match because it was called by 'deserialize'
debug_assert_eq!(b[0], SELF_ADDRESS_RESPONSE_TAG);
if b.len() != 1 + Recipient::LEN {
return Err(error::Error::new(
ErrorKind::TooShortResponse,
"not enough data provided to recover 'self_address'".to_string(),
));
}
let mut recipient_bytes = [0u8; Recipient::LEN];
recipient_bytes.copy_from_slice(&b[1..1 + Recipient::LEN]);
let recipient = match Recipient::try_from_bytes(recipient_bytes) {
Ok(recipient) => recipient,
Err(err) => {
return Err(error::Error::new(
ErrorKind::MalformedResponse,
format!("malformed Recipient: {:?}", err),
))
}
};
Ok(ServerResponse::SelfAddress(recipient))
}
// ERROR_RESPONSE_TAG || err_code || msg_len || msg
fn serialize_error(error: error::Error) -> Vec<u8> {
let message_len_bytes = (error.message.len() as u64).to_be_bytes();
std::iter::once(ERROR_RESPONSE_TAG)
.chain(std::iter::once(error.kind as u8))
.chain(message_len_bytes.iter().cloned())
.chain(error.message.into_bytes().into_iter())
.collect()
}
// ERROR_RESPONSE_TAG || err_code || msg_len || msg
fn deserialize_error(b: &[u8]) -> Result<Self, error::Error> {
// this MUST match because it was called by 'deserialize'
debug_assert_eq!(b[0], ERROR_RESPONSE_TAG);
if b.len() < size_of::<u8>() + size_of::<u64>() {
return Err(error::Error::new(
ErrorKind::TooShortResponse,
"not enough data provided to recover 'error'".to_string(),
));
}
let error_kind = match b[1] {
_ if b[1] == (ErrorKind::EmptyRequest as u8) => ErrorKind::EmptyRequest,
_ if b[1] == (ErrorKind::TooShortRequest as u8) => ErrorKind::TooShortRequest,
_ if b[1] == (ErrorKind::UnknownRequest as u8) => ErrorKind::UnknownRequest,
_ if b[1] == (ErrorKind::MalformedRequest as u8) => ErrorKind::MalformedRequest,
_ if b[1] == (ErrorKind::EmptyResponse as u8) => ErrorKind::EmptyResponse,
_ if b[1] == (ErrorKind::TooShortResponse as u8) => ErrorKind::TooShortResponse,
_ if b[1] == (ErrorKind::UnknownResponse as u8) => ErrorKind::UnknownResponse,
_ if b[1] == (ErrorKind::MalformedResponse as u8) => ErrorKind::MalformedResponse,
_ if b[1] == (ErrorKind::Other as u8) => ErrorKind::Other,
n => {
return Err(error::Error::new(
ErrorKind::MalformedResponse,
format!("invalid error code {}", n),
))
}
};
let message_len =
u64::from_be_bytes(b[2..2 + size_of::<u64>()].as_ref().try_into().unwrap());
let message = &b[2 + size_of::<u64>()..];
if message.len() as u64 != message_len {
return Err(error::Error::new(
ErrorKind::MalformedResponse,
format!(
"message len has inconsistent length. specified: {} got: {}",
message_len,
message.len()
),
));
}
let err_message = match String::from_utf8(message.to_vec()) {
Ok(msg) => msg,
Err(err) => {
return Err(error::Error::new(
ErrorKind::MalformedResponse,
format!("malformed error message: {:?}", err),
))
}
};
Ok(ServerResponse::Error(error::Error::new(
error_kind,
err_message,
)))
}
pub fn serialize(self) -> Vec<u8> {
match self {
ServerResponse::Received(reconstructed_message) => {
Self::serialize_received(reconstructed_message)
}
ServerResponse::SelfAddress(address) => Self::serialize_self_address(address),
ServerResponse::Error(err) => Self::serialize_error(err),
}
}
pub fn deserialize(b: &[u8]) -> Result<Self, error::Error> {
if b.is_empty() {
// technically I'm not even sure this can ever be returned, because reading empty
// request would imply closed socket, but let's include it for completion sake
return Err(error::Error::new(
ErrorKind::EmptyResponse,
"no data provided".to_string(),
));
}
if b.len() < size_of::<u8>() {
return Err(error::Error::new(
ErrorKind::TooShortResponse,
format!(
"not enough data provided to recover response tag. Provided only {} bytes",
b.len()
),
));
}
let response_tag = b[0];
// determine what kind of response that is and try to deserialize it
match response_tag {
RECEIVED_RESPONSE_TAG => Self::deserialize_received(b),
SELF_ADDRESS_RESPONSE_TAG => Self::deserialize_self_address(b),
ERROR_RESPONSE_TAG => Self::deserialize_error(b),
n => {
return Err(error::Error::new(
ErrorKind::UnknownResponse,
format!("type {}", n),
))
}
}
}
pub fn into_binary(self) -> Vec<u8> {
self.serialize()
}
pub fn into_text(self) -> String {
// use the intermediate string structure and let serde do bunch of work for us
let text_resp = ServerResponseText::from(self);
text_resp.into()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn received_response_serialization_works() {
let reply_surb_string = "CjfVbHbfAjbC3W1BvNHGXmM8KNAnDNYGaHMLqVDxRYeo352csAihstup9bvqXam4dTWgfHak6KYwL9STaxWJ47E8XFZbSEvs7hEsfCkxr6K9WJuSBPK84GDDEvad8ZAuMCoaXsAd5S2Lj9a5eYyzG4SL1jHzhSMni55LyJwumxo1ZTGZNXggxw1RREosvyzNrW9Rsi3owyPqLCwXpiei2tHZty8w8midVvg8vDa7ZEJD842CLv8D4ohynSG7gDpqTrhkRaqYAuz7dzqNbMXLJRM7v823Jn16fA1L7YQxmcaUdUigyRSgTdb4i9ebiLGSyJ1iDe6Acz613PQZh6Ua3bZ2zVKq3dSycpDm9ngarRK4zJrAaUxRkdih8YzW3BY4nL9eqkfKA4N1TWCLaRU7zpSaf8yMEwrAZReU3d5zLV8c5KBfa2w8R5anhQeBojduZEGEad8kkHuKU52Zg93FeWHvH1qgZaEJMHH4nN7gKXz9mvWDhYwyF4vt3Uy2NhCHC3N5pL1gMme27YcoPcTEia1fxKZtnt6rtEozzTrAgCJGswigkFbkafiV5QaJwLKTUxtzhkZ57eEuLPte9UvJHzhhXUQ2CV7R2BUkJjYZy3Zsx6YYvdYWiAFFkWUwNEGA4QpShUHciBfsQVHQ7pN41YcyYUhbywQDFnTVgEmdUZ1XCBi3gyK5U3tDQmFzP1u9m3mWrUA8qB9mRDE7ptNDm5c3c1458L6uXLUth7sdMaa1Was5LCmCdmNDtvNpCDAEt1in6q6mrZFR85aCSU9b1baNGwZoCqPpPvydkVe63gXWoi8ebvdyxARrqACFrSB3ZdY3uJBw8CTMNkKK6MvcefMkSVVsbLd36TQAtYSCqrpiMc5dQuKcEu5QfciwvWYXYx8WFNAgKwP2mv49KCTvfozNDUCbjzDwSx92Zv5zjG8HbFpB13bY9UZGeyTPvv7gGxCzjGjJGbW6FRAheRQaaje5fUgCNM95Tv7wBmAMRHHFgWafeK1sdFH7dtCX9u898HucGTaboSKLsVh8J78gbbkHErwjMh7y9YRkceq5TTYS5da4kHnyNKYWSbxgZrmFg44XGKoeYcqoHB3XTZrdsf7F5fFeNwnihkmADvhAcaxXUmVqq4rQFZH84a1iC3WBWXYcqiZH2L7ujGWV7mMDT4HBEerDYjc8rNY4xGTPfivCrBCJW1i14aqW8xRdsdgTM88eTksvC3WPJLJ7iMzfKXeL7fMW1Ek6QGyQtLBW98vEESpdcDg6DeZ5rMz6VqjTGGqcCaFGfHoqtfxMDaBAEsyQ8h7XDX6dg1wq9wH6j4Tw7Tj1MEv1b8uj5NJkozZdzVdYA2QyE2Dp8vuurQG6uVdTDNww2d88RBQ8sVgjxN8gR45y4woJLhFAaNTAtrY6wDTxyXST13ni6oyqdYxjFVk9Am4v3DzH7Y2K8iRVSHfTk4FRbPULyaeK6wt2anvMJH1XdvVRgc14h67MnBxMgMD1UFk8AErN7CDj26fppe3c5G6KozJe4cSqQUGbBjVzBnrHCruqrfZBn5hNZHTV37bQiomqhRQXohxhuKEnNrGbAe1xNvJr9X";
let received_with_surb = ServerResponse::Received(ReconstructedMessage {
message: b"foomp".to_vec(),
reply_SURB: Some(ReplySURB::from_base58_string(reply_surb_string).unwrap()),
});
let bytes = received_with_surb.serialize();
let recovered = ServerResponse::deserialize(&bytes).unwrap();
match recovered {
ServerResponse::Received(reconstructed) => {
assert_eq!(reconstructed.message, b"foomp".to_vec());
assert_eq!(
reconstructed.reply_SURB.unwrap().to_base58_string(),
reply_surb_string
)
}
_ => unreachable!(),
}
let received_without_surb = ServerResponse::Received(ReconstructedMessage {
message: b"foomp".to_vec(),
reply_SURB: None,
});
let bytes = received_without_surb.serialize();
let recovered = ServerResponse::deserialize(&bytes).unwrap();
match recovered {
ServerResponse::Received(reconstructed) => {
assert_eq!(reconstructed.message, b"foomp".to_vec());
assert!(reconstructed.reply_SURB.is_none())
}
_ => unreachable!(),
}
}
#[test]
fn self_address_response_serialization_works() {
let recipient = Recipient::try_from_base58_string("CytBseW6yFXUMzz4SGAKdNLGR7q3sJLLYxyBGvutNEQV.4QXYyEVc5fUDjmmi8PrHN9tdUFV4PCvSJE1278cHyvoe@4sBbL1ngf1vtNqykydQKTFh26sQCw888GpUqvPvyNB4f").unwrap();
let recipient_string = recipient.to_string();
let self_address_response = ServerResponse::SelfAddress(recipient);
let bytes = self_address_response.serialize();
let recovered = ServerResponse::deserialize(&bytes).unwrap();
match recovered {
ServerResponse::SelfAddress(recipient) => {
assert_eq!(recipient.to_string(), recipient_string)
}
_ => unreachable!(),
}
}
#[test]
fn error_response_serialization_works() {
let dummy_error = error::Error::new(ErrorKind::UnknownRequest, "foomp message".to_string());
let error_response = ServerResponse::Error(dummy_error.clone());
let bytes = error_response.serialize();
let recovered = ServerResponse::deserialize(&bytes).unwrap();
match recovered {
ServerResponse::Error(error) => assert_eq!(error, dummy_error),
_ => unreachable!(),
}
}
}
@@ -0,0 +1,153 @@
// Copyright 2020 Nym Technologies SA
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::error::ErrorKind;
use crate::requests::ClientRequest;
use crate::responses::ServerResponse;
use nymsphinx::addressing::clients::Recipient;
use nymsphinx::anonymous_replies::ReplySURB;
use serde::{Deserialize, Serialize};
use std::convert::{TryFrom, TryInto};
// local text equivalent of `ClientRequest` for easier serialization + deserialization with serde
// TODO: figure out if there's an easy way to avoid defining it
#[allow(non_snake_case)]
#[derive(Serialize, Deserialize, Debug)]
#[serde(tag = "type", rename_all = "camelCase")]
pub(super) enum ClientRequestText {
#[serde(rename_all = "camelCase")]
Send {
message: String,
recipient: String,
with_reply_surb: bool,
},
SelfAddress,
#[serde(rename_all = "camelCase")]
Reply {
message: String,
reply_SURB: String,
},
}
impl TryFrom<String> for ClientRequestText {
type Error = serde_json::Error;
fn try_from(msg: String) -> Result<Self, Self::Error> {
serde_json::from_str(&msg)
}
}
impl TryInto<ClientRequest> for ClientRequestText {
type Error = crate::error::Error;
fn try_into(self) -> Result<ClientRequest, Self::Error> {
match self {
ClientRequestText::Send {
message,
recipient,
with_reply_surb,
} => {
let message_bytes = message.into_bytes();
let recipient = Recipient::try_from_base58_string(recipient).map_err(|err| {
Self::Error::new(ErrorKind::MalformedRequest, err.to_string())
})?;
Ok(ClientRequest::Send {
message: message_bytes,
recipient,
with_reply_surb,
})
}
ClientRequestText::SelfAddress => Ok(ClientRequest::SelfAddress),
ClientRequestText::Reply {
message,
reply_SURB,
} => {
let message_bytes = message.into_bytes();
let reply_surb = ReplySURB::from_base58_string(reply_SURB).map_err(|err| {
Self::Error::new(ErrorKind::MalformedRequest, err.to_string())
})?;
Ok(ClientRequest::Reply {
message: message_bytes,
reply_surb,
})
}
}
}
}
// local text equivalent of `ServerResponse` for easier serialization + deserialization with serde
// TODO: figure out if there's an easy way to avoid defining it
#[allow(non_snake_case)]
#[derive(Serialize, Deserialize, Debug)]
#[serde(tag = "type", rename_all = "camelCase")]
pub(super) enum ServerResponseText {
#[serde(rename_all = "camelCase")]
Received {
message: String,
reply_SURB: Option<String>,
},
SelfAddress {
address: String,
},
Error {
message: String,
},
}
impl TryFrom<String> for ServerResponseText {
type Error = serde_json::Error;
fn try_from(msg: String) -> Result<Self, <ServerResponseText as TryFrom<String>>::Error> {
serde_json::from_str(&msg)
}
}
impl Into<String> for ServerResponseText {
fn into(self) -> String {
// per serde_json docs:
/*
/// Serialization can fail if `T`'s implementation of `Serialize` decides to
/// fail, or if `T` contains a map with non-string keys.
*/
// this is not the case here.
serde_json::to_string(&self).unwrap()
}
}
impl From<ServerResponse> for ServerResponseText {
fn from(resp: ServerResponse) -> Self {
match resp {
ServerResponse::Received(reconstructed) => {
ServerResponseText::Received {
// TODO: ask DH what is more appropriate, lossy utf8 conversion or returning error and then
// pure binary later
message: String::from_utf8_lossy(&reconstructed.message).into_owned(),
reply_SURB: reconstructed
.reply_SURB
.map(|reply_surb| reply_surb.to_base58_string()),
}
}
ServerResponse::SelfAddress(recipient) => ServerResponseText::SelfAddress {
address: recipient.to_string(),
},
ServerResponse::Error(err) => ServerResponseText::Error {
message: err.to_string(),
},
}
}
}
+3 -5
View File
@@ -38,9 +38,7 @@ impl MixnetResponseListener {
}
}
async fn on_message(&self, bytes: Vec<u8>) {
let reconstructed_message =
ReconstructedMessage::try_from_bytes(&bytes).expect("todo: error handling");
async fn on_message(&self, reconstructed_message: ReconstructedMessage) {
let raw_message = reconstructed_message.message;
if reconstructed_message.reply_SURB.is_some() {
println!("this message had a surb - we didn't do anything with it");
@@ -70,8 +68,8 @@ impl MixnetResponseListener {
pub(crate) async fn run(&mut self) {
while let Some(received_responses) = self.mix_response_receiver.next().await {
for received_response in received_responses {
self.on_message(received_response.into_bytes()).await;
for reconstructed_message in received_responses {
self.on_message(reconstructed_message).await;
}
}
println!("We should never see this message");
+1 -1
View File
@@ -64,7 +64,7 @@ pub struct NodeData {
pub fn create_sphinx_packet(topology_json: &str, msg: &str, recipient: &str) -> Vec<u8> {
utils::set_panic_hook(); // nicer js errors.
let recipient = Recipient::try_from_string(recipient).unwrap();
let recipient = Recipient::try_from_base58_string(recipient).unwrap();
let route =
sphinx_route_to(topology_json, &recipient.gateway()).expect("todo: error handling...");
+3 -2
View File
@@ -7,5 +7,6 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
crypto = { path = "../../crypto" }
nymsphinx-types = { path = "../types" }
crypto = { path = "../../crypto" } # all addresses are expressed in terms on their crypto keys
nymsphinx-types = { path = "../types" } # we need to be able to refer to some types defined inside sphinx crate
serde = "1.0" # implementing serialization/deserialization for some types, like `Recipient`
+82 -5
View File
@@ -7,6 +7,9 @@
use crate::nodes::{NodeIdentity, NODE_IDENTITY_SIZE};
use crypto::asymmetric::{encryption, identity};
use nymsphinx_types::Destination;
use serde::de::{Error as SerdeError, Unexpected, Visitor};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::fmt::{self, Formatter};
// Not entirely sure whether this is the correct place for those, but let's see how it's going
// to work out
@@ -24,6 +27,30 @@ pub enum RecipientFormattingError {
MalformedGatewayError(identity::SignatureError),
}
impl fmt::Display for RecipientFormattingError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> fmt::Result {
match self {
RecipientFormattingError::MalformedRecipientError => {
write!(f, "recipient is malformed")
}
RecipientFormattingError::MalformedIdentityError(id_err) => {
write!(f, "recipient's identity key is malformed: {}", id_err)
}
RecipientFormattingError::MalformedEncryptionKeyError(enc_err) => {
write!(f, "recipient's encryption key is malformed: {}", enc_err)
}
RecipientFormattingError::MalformedGatewayError(id_err) => write!(
f,
"recipient gateway's identity key is malformed: {}",
id_err
),
}
}
}
// since we have Debug and Display might as well slap Error on top of it too
impl std::error::Error for RecipientFormattingError {}
impl From<encryption::EncryptionKeyError> for RecipientFormattingError {
fn from(err: encryption::EncryptionKeyError) -> Self {
RecipientFormattingError::MalformedEncryptionKeyError(err)
@@ -38,6 +65,56 @@ pub struct Recipient {
gateway: NodeIdentity,
}
// Serialize + Deserialize is not really used anymore (it was for a CBOR experiment)
// however, if we decided we needed it again, it's already here
impl Serialize for Recipient {
fn serialize<S>(&self, serializer: S) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error>
where
S: Serializer,
{
serializer.serialize_bytes(&self.to_bytes())
}
}
impl<'de> Deserialize<'de> for Recipient {
fn deserialize<D>(deserializer: D) -> Result<Self, <D as Deserializer<'de>>::Error>
where
D: Deserializer<'de>,
{
struct RecipientVisitor;
impl<'de> Visitor<'de> for RecipientVisitor {
type Value = Recipient;
fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
write!(formatter, "A recipient is 96-byte sequence containing two ed25519 public keys an one x25519 public key all in compressed forms.")
}
fn visit_bytes<E>(self, bytes: &[u8]) -> Result<Self::Value, E>
where
E: SerdeError,
{
if bytes.len() != Recipient::LEN {
return Err(SerdeError::invalid_length(bytes.len(), &self));
}
let mut recipient_bytes = [0u8; Recipient::LEN];
// this shouldn't panic as we just checked for length
recipient_bytes.copy_from_slice(&bytes);
Recipient::try_from_bytes(recipient_bytes).or_else(|_| {
Err(SerdeError::invalid_value(
Unexpected::Other("At least one of the curve points was malformed"),
&self,
))
})
}
}
deserializer.deserialize_bytes(RecipientVisitor)
}
}
impl Recipient {
pub const LEN: usize = CLIENT_IDENTITY_SIZE + CLIENT_ENCRYPTION_KEY_SIZE + NODE_IDENTITY_SIZE;
@@ -77,7 +154,7 @@ impl Recipient {
&self.gateway
}
pub fn into_bytes(self) -> [u8; Self::LEN] {
pub fn to_bytes(&self) -> [u8; Self::LEN] {
let mut out = [0u8; Self::LEN];
out[..CLIENT_IDENTITY_SIZE].copy_from_slice(&self.client_identity.to_bytes());
out[CLIENT_IDENTITY_SIZE..CLIENT_IDENTITY_SIZE + CLIENT_ENCRYPTION_KEY_SIZE]
@@ -113,7 +190,7 @@ impl Recipient {
})
}
pub fn try_from_string<S: Into<String>>(
pub fn try_from_base58_string<S: Into<String>>(
full_address: S,
) -> Result<Self, RecipientFormattingError> {
let string_address = full_address.into();
@@ -151,7 +228,7 @@ impl Recipient {
// ADDRESS . ENCRYPTION @ GATEWAY_ID
impl std::fmt::Display for Recipient {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}.{}@{}",
@@ -179,7 +256,7 @@ mod tests {
);
let str_recipient = recipient.to_string();
let recovered_recipient = Recipient::try_from_string(str_recipient).unwrap();
let recovered_recipient = Recipient::try_from_base58_string(str_recipient).unwrap();
// as long as byte representation of internal keys are identical, it's all fine
assert_eq!(
@@ -208,7 +285,7 @@ mod tests {
*gateway_id_pair.public_key(),
);
let bytes_recipient = recipient.clone().into_bytes();
let bytes_recipient = recipient.clone().to_bytes();
let recovered_recipient = Recipient::try_from_bytes(bytes_recipient).unwrap();
// as long as byte representation of internal keys are identical, it's all fine
@@ -9,6 +9,7 @@ edition = "2018"
[dependencies]
rand = {version = "0.7.3", features = ["wasm-bindgen"]}
bs58 = "0.3"
serde = "1.0"
crypto = { path = "../../crypto" }
nymsphinx-addressing = { path = "../addressing" }
@@ -22,18 +22,45 @@ use nymsphinx_params::packet_sizes::PacketSize;
use nymsphinx_params::{ReplySURBKeyDigestAlgorithm, DEFAULT_NUM_MIX_HOPS};
use nymsphinx_types::{delays, Error as SphinxError, SURBMaterial, SphinxPacket, SURB};
use rand::{CryptoRng, RngCore};
use serde::de::{Error as SerdeError, Visitor};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::convert::TryFrom;
use std::fmt::{self, Formatter};
use std::time;
use topology::{NymTopology, NymTopologyError};
#[derive(Debug)]
pub enum ReplySURBError {
NonPaddedMessageError,
MalformedStringError,
UnpaddedMessageError,
MalformedStringError(bs58::decode::Error),
RecoveryError(SphinxError),
InvalidEncryptionKeyData(SURBEncryptionKeyError),
}
impl fmt::Display for ReplySURBError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> fmt::Result {
match self {
ReplySURBError::UnpaddedMessageError => {
write!(f, "tried to use reply SURB with an unpadded message")
}
ReplySURBError::MalformedStringError(decode_err) => {
write!(f, "reply SURB is incorrectly formatted: {}", decode_err)
}
ReplySURBError::RecoveryError(sphinx_err) => {
write!(f, "failed to recover reply SURB from bytes: {}", sphinx_err)
}
ReplySURBError::InvalidEncryptionKeyData(surb_key_err) => write!(
f,
"failed to recover reply SURB encryption key from bytes: {}",
surb_key_err
),
}
}
}
// since we have Debug and Display might as well slap Error on top of it too
impl std::error::Error for ReplySURBError {}
impl From<SURBEncryptionKeyError> for ReplySURBError {
fn from(err: SURBEncryptionKeyError) -> Self {
ReplySURBError::InvalidEncryptionKeyData(err)
@@ -46,6 +73,44 @@ pub struct ReplySURB {
encryption_key: SURBEncryptionKey,
}
// Serialize + Deserialize is not really used anymore (it was for a CBOR experiment)
// however, if we decided we needed it again, it's already here
impl Serialize for ReplySURB {
fn serialize<S>(&self, serializer: S) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error>
where
S: Serializer,
{
serializer.serialize_bytes(&self.to_bytes())
}
}
impl<'de> Deserialize<'de> for ReplySURB {
fn deserialize<D>(deserializer: D) -> Result<Self, <D as Deserializer<'de>>::Error>
where
D: Deserializer<'de>,
{
struct ReplySURBVisitor;
impl<'de> Visitor<'de> for ReplySURBVisitor {
type Value = ReplySURB;
fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
write!(formatter, "A replySURB must contain a valid symmetric encryption key and a correctly formed sphinx header")
}
fn visit_bytes<E>(self, bytes: &[u8]) -> Result<Self::Value, E>
where
E: SerdeError,
{
ReplySURB::from_bytes(bytes)
.or_else(|_| Err(SerdeError::invalid_length(bytes.len(), &self)))
}
}
deserializer.deserialize_bytes(ReplySURBVisitor)
}
}
impl ReplySURB {
pub fn max_msg_len(packet_size: PacketSize) -> usize {
// For detailed explanation (of ack overhead) refer to common\nymsphinx\src\preparer.rs::available_plaintext_per_packet()
@@ -126,7 +191,7 @@ impl ReplySURB {
pub fn from_base58_string<S: Into<String>>(val: S) -> Result<Self, ReplySURBError> {
let bytes = match bs58::decode(val.into()).into_vec() {
Ok(decoded) => decoded,
Err(_) => return Err(ReplySURBError::MalformedStringError),
Err(err) => return Err(ReplySURBError::MalformedStringError(err)),
};
Self::from_bytes(&bytes)
}
@@ -145,7 +210,7 @@ impl ReplySURB {
let packet_size = packet_size.unwrap_or_else(Default::default);
if message.len() != packet_size.plaintext_size() {
return Err(ReplySURBError::NonPaddedMessageError);
return Err(ReplySURBError::UnpaddedMessageError);
}
// this can realistically only fail on too long messages and we just checked for that
-3
View File
@@ -12,12 +12,9 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use crypto::blake3;
pub mod message_types;
pub mod packet_sizes;
use crypto::aes_ctr::Aes128Ctr;
pub use message_types::MessageType;
pub use packet_sizes::PacketSize;
// If somebody can provide an argument why it might be reasonable to have more than 255 mix hops,
+4 -4
View File
@@ -26,7 +26,7 @@ use nymsphinx_anonymous_replies::reply_surb::ReplySURB;
use nymsphinx_chunking::fragment::{Fragment, FragmentIdentifier};
use nymsphinx_params::packet_sizes::PacketSize;
use nymsphinx_params::{
MessageType, PacketEncryptionAlgorithm, PacketHkdfAlgorithm, ReplySURBEncryptionAlgorithm,
PacketEncryptionAlgorithm, PacketHkdfAlgorithm, ReplySURBEncryptionAlgorithm,
ReplySURBKeyDigestAlgorithm, DEFAULT_NUM_MIX_HOPS,
};
use nymsphinx_types::builder::SphinxPacketBuilder;
@@ -175,7 +175,7 @@ where
let reply_key = reply_surb.encryption_key();
// if there's a reply surb, the message takes form of `1 || REPLY_KEY || REPLY_SURB || MSG`
Ok((
std::iter::once(MessageType::WithReplySURB as u8)
std::iter::once(true as u8)
.chain(reply_surb.to_bytes().iter().cloned())
.chain(message.into_iter())
.collect(),
@@ -184,7 +184,7 @@ where
} else {
// but if there's no reply surb, the message takes form of `0 || MSG`
Ok((
std::iter::once(MessageType::WithoutReplySURB as u8)
std::iter::once(false as u8)
.chain(message.into_iter())
.collect(),
None,
@@ -405,7 +405,7 @@ where
#[cfg(test)]
pub(crate) fn test_fixture() -> MessagePreparer<rand::rngs::OsRng> {
let rng = rand::rngs::OsRng;
let dummy_address = Recipient::try_from_string("CytBseW6yFXUMzz4SGAKdNLGR7q3sJLLYxyBGvutNEQV.4QXYyEVc5fUDjmmi8PrHN9tdUFV4PCvSJE1278cHyvoe@4sBbL1ngf1vtNqykydQKTFh26sQCw888GpUqvPvyNB4f").unwrap();
let dummy_address = Recipient::try_from_base58_string("CytBseW6yFXUMzz4SGAKdNLGR7q3sJLLYxyBGvutNEQV.4QXYyEVc5fUDjmmi8PrHN9tdUFV4PCvSJE1278cHyvoe@4sBbL1ngf1vtNqykydQKTFh26sQCw888GpUqvPvyNB4f").unwrap();
MessagePreparer {
rng,
+4 -45
View File
@@ -18,9 +18,7 @@ use crypto::symmetric::stream_cipher;
use nymsphinx_anonymous_replies::reply_surb::{ReplySURB, ReplySURBError};
use nymsphinx_chunking::fragment::Fragment;
use nymsphinx_chunking::reconstruction::MessageReconstructor;
use nymsphinx_params::{
MessageType, PacketEncryptionAlgorithm, PacketHkdfAlgorithm, DEFAULT_NUM_MIX_HOPS,
};
use nymsphinx_params::{PacketEncryptionAlgorithm, PacketHkdfAlgorithm, DEFAULT_NUM_MIX_HOPS};
// TODO: should this live in this file?
#[allow(non_snake_case)]
@@ -33,45 +31,6 @@ pub struct ReconstructedMessage {
pub reply_SURB: Option<ReplySURB>,
}
impl ReconstructedMessage {
pub fn into_bytes(self) -> Vec<u8> {
if let Some(reply_surb) = self.reply_SURB {
std::iter::once(MessageType::WithReplySURB as u8)
.chain(reply_surb.to_bytes().iter().cloned())
.chain(self.message.into_iter())
.collect()
} else {
std::iter::once(MessageType::WithoutReplySURB as u8)
.chain(self.message.into_iter())
.collect()
}
}
pub fn try_from_bytes(b: &[u8]) -> Result<Self, MessageRecoveryError> {
if b.is_empty() {
return Err(MessageRecoveryError::TooShortMessageError);
}
match b[0] {
n if n == MessageType::WithReplySURB as u8 => {
let surb_len = ReplySURB::serialized_len(DEFAULT_NUM_MIX_HOPS);
if b.len() < surb_len + 1 {
return Err(MessageRecoveryError::TooShortMessageError);
}
Ok(ReconstructedMessage {
reply_SURB: Some(ReplySURB::from_bytes(&b[1..1 + surb_len])?),
message: b[1 + surb_len..].to_vec(),
})
}
n if n == MessageType::WithoutReplySURB as u8 => Ok(ReconstructedMessage {
message: b[1..].to_vec(),
reply_SURB: None,
}),
_ => Err(MessageRecoveryError::InvalidSurbPrefixError),
}
}
}
#[derive(Debug)]
pub enum MessageRecoveryError {
InvalidSurbPrefixError,
@@ -122,11 +81,11 @@ impl MessageReceiver {
message: &mut Vec<u8>,
) -> Result<Option<ReplySURB>, MessageRecoveryError> {
match message[0] {
n if n == MessageType::WithoutReplySURB as u8 => {
n if n == false as u8 => {
message.remove(0);
Ok(None)
}
n if n == MessageType::WithReplySURB as u8 => {
n if n == true as u8 => {
let surb_len: usize = ReplySURB::serialized_len(self.num_mix_hops);
// note the extra +1 (due to 0/1 message prefix)
let surb_bytes = &message[1..1 + surb_len];
@@ -327,7 +286,7 @@ mod message_receiver {
// the actual 'correctness' of the underlying message doesn't matter for this test
let message = vec![42; 100];
let dummy_recipient = Recipient::try_from_string("CytBseW6yFXUMzz4SGAKdNLGR7q3sJLLYxyBGvutNEQV.4QXYyEVc5fUDjmmi8PrHN9tdUFV4PCvSJE1278cHyvoe@FioFa8nMmPpQnYi7JyojoTuwGLeyNS8BF4ChPr29zUML").unwrap();
let dummy_recipient = Recipient::try_from_base58_string("CytBseW6yFXUMzz4SGAKdNLGR7q3sJLLYxyBGvutNEQV.4QXYyEVc5fUDjmmi8PrHN9tdUFV4PCvSJE1278cHyvoe@FioFa8nMmPpQnYi7JyojoTuwGLeyNS8BF4ChPr29zUML").unwrap();
let average_delay = Duration::from_millis(500);
let topology = topology_fixture();
+2 -1
View File
@@ -17,4 +17,5 @@ tokio = { version = "0.2", features = ["full"] }
tokio-tungstenite = "0.10.1"
nymsphinx = { path = "../../common/nymsphinx" }
simple-socks5-requests = { path = "simple-socks5-requests" }
simple-socks5-requests = { path = "simple-socks5-requests" }
websocket-requests = { path = "../../clients/native/websocket-requests" }
+22 -15
View File
@@ -1,15 +1,14 @@
use crate::{controller::Controller, websocket};
use futures::SinkExt;
use futures_util::StreamExt;
use nymsphinx::params::MessageType;
use nymsphinx::receiver::ReconstructedMessage;
use nymsphinx::addressing::clients::Recipient;
use simple_socks5_requests::Request;
use tokio::net::TcpStream;
use tokio::runtime::Runtime;
use tokio_tungstenite::tungstenite::protocol::Message;
use tokio_tungstenite::WebSocketStream;
use websocket::WebsocketConnectionError;
use websocket_requests::{requests::ClientRequest, responses::ServerResponse};
pub struct ServiceProvider {
runtime: Runtime,
}
@@ -30,25 +29,33 @@ impl ServiceProvider {
println!("\nAll systems go. Press CTRL-C to stop the server.");
while let Some(msg) = websocket_reader.next().await {
let data = msg.unwrap().into_data();
let received = match ServerResponse::deserialize(&data).expect("todo: error handling") {
ServerResponse::Received(received) => received,
ServerResponse::Error(err) => {
panic!("received error from native client! - {}", err)
},
_ => unimplemented!("probably should never be reached?")
};
let reconstructed_message = ReconstructedMessage::try_from_bytes(&data).expect("todo: error handling");
let raw_message = reconstructed_message.message;
let raw_message = received.message;
let request = Request::try_from_bytes(&raw_message).unwrap();
let response = controller.process_request(request).await.unwrap();
if response.is_none() { // restart the loop if we got nothing back
continue;
}
let response = match controller.process_request(request).await.unwrap() {
None => continue, // restart the loop if we got nothing back
Some(response) => response,
};
// TODO: wire SURBs in here once they're available
let return_address = "7tVXwePpo6SM99sqM1xEp6S4T1TSpxYx97fTpEdvmF7i.GgrN8998SmwvQghNEvqtPPZCgMQqJovWBrzspMnBESsE@e3vUAo6YhB7zq3GH8B4k3iiGT4H2USjdd5ZMZoUsHdF";
let recipient = nymsphinx::addressing::clients::Recipient::try_from_string(return_address).unwrap();
let recipient = Recipient::try_from_base58_string(return_address).unwrap();
let response_message = std::iter::once(MessageType::WithoutReplySURB as u8)
.chain(recipient.into_bytes().iter().cloned())
.chain(response.unwrap().into_bytes().into_iter())
.collect();
// make 'request' to native-websocket client
let response_message = ClientRequest::Send {
recipient,
message: response.into_bytes(),
with_reply_surb: false
};
let message = Message::Binary(response_message);
let message = Message::Binary(response_message.serialize());
websocket_writer.send(message).await.unwrap();
}
});