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:
committed by
GitHub
parent
eec6034684
commit
7d2d512405
Generated
+99
-76
@@ -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",
|
||||
]
|
||||
|
||||
@@ -6,6 +6,7 @@ opt-level = "s"
|
||||
|
||||
members = [
|
||||
"clients/native",
|
||||
"clients/native/websocket-requests",
|
||||
"clients/socks5",
|
||||
"clients/webassembly",
|
||||
"clients/client-core",
|
||||
|
||||
+20
-17
@@ -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))
|
||||
|
||||
+46
-41
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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!",
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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",
|
||||
}
|
||||
}
|
||||
}
|
||||
+4
-9
@@ -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(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
|
||||
@@ -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...");
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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" }
|
||||
@@ -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();
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user