new WS client connection, working from WASM too

Niko PLP 2 years ago
parent 43f794e89e
commit 8c4915d9c5
  1. 432
      Cargo.lock
  2. 12
      README.md
  3. 28
      ng-app-js/Cargo.toml
  4. 12
      ng-app-js/README.md
  5. 2
      ng-app-js/index.html
  6. 3
      ng-app-js/src/client_connection.rs
  7. 58
      ng-app-js/src/lib.rs
  8. 3
      ngcli/Cargo.toml
  9. 4
      ngcli/src/main.rs
  10. 3
      ngd/Cargo.toml
  11. 8
      ngd/src/main.rs
  12. 6
      p2p-broker/Cargo.toml
  13. 113
      p2p-broker/src/auth.rs
  14. 4
      p2p-broker/src/broker_store/config.rs
  15. 29
      p2p-broker/src/broker_store/peer.rs
  16. 28
      p2p-broker/src/broker_store/repostoreinfo.rs
  17. 213
      p2p-broker/src/server.rs
  18. 3
      p2p-broker/src/server_connection.rs
  19. 27
      p2p-broker/src/server_ws.rs
  20. 41
      p2p-client-ws/Cargo.toml
  21. 2
      p2p-client-ws/src/connection_ws.rs
  22. 53
      p2p-client-ws/src/lib.rs
  23. 330
      p2p-client-ws/src/remote_ws.rs
  24. 198
      p2p-client-ws/src/remote_ws_wasm.rs
  25. 5
      p2p-client/Cargo.toml
  26. 1
      p2p-client/src/lib.rs
  27. 5
      p2p-net/Cargo.toml
  28. 60
      p2p-net/src/actor.rs
  29. 2
      p2p-net/src/actors/mod.rs
  30. 20
      p2p-net/src/actors/noise.rs
  31. 84
      p2p-net/src/broker.rs
  32. 142
      p2p-net/src/connection.rs
  33. 41
      p2p-net/src/errors.rs
  34. 48
      p2p-net/src/lib.rs
  35. 75
      p2p-net/src/types.rs
  36. 32
      p2p-net/src/utils.rs
  37. 4
      p2p-repo/Cargo.toml
  38. 31
      p2p-repo/src/types.rs
  39. 2
      p2p-stores-lmdb/Cargo.toml
  40. 2
      p2p-stores-lmdb/src/broker_store.rs
  41. 2
      p2p-stores-lmdb/src/lib.rs
  42. 1
      p2p-stores-lmdb/src/repo_store.rs

432
Cargo.lock generated

@ -2,6 +2,41 @@
# It is not intended for manual editing.
version = 3
[[package]]
name = "aead"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b613b8e1e3cf911a086f53f03bf286f52fd7a7258e4fa606f0ef220d39d8877"
dependencies = [
"generic-array",
]
[[package]]
name = "aes"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8"
dependencies = [
"cfg-if",
"cipher 0.3.0",
"cpufeatures",
"opaque-debug",
]
[[package]]
name = "aes-gcm"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df5f85a83a7d8b0442b6aa7b504b8212c1733da07b98aae43d4bc21b2cb3cdf6"
dependencies = [
"aead",
"aes",
"cipher 0.3.0",
"ctr",
"ghash",
"subtle",
]
[[package]]
name = "anstyle"
version = "0.3.1"
@ -140,6 +175,24 @@ dependencies = [
"futures-micro",
]
[[package]]
name = "async-process"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6381ead98388605d0d9ff86371043b5aa922a3905824244de40dc263a14fcba4"
dependencies = [
"async-io",
"async-lock",
"autocfg",
"blocking",
"cfg-if",
"event-listener",
"futures-lite",
"libc",
"signal-hook",
"windows-sys 0.42.0",
]
[[package]]
name = "async-std"
version = "1.12.0"
@ -151,6 +204,7 @@ dependencies = [
"async-global-executor",
"async-io",
"async-lock",
"async-process",
"crossbeam-utils",
"futures-channel",
"futures-core",
@ -198,6 +252,17 @@ dependencies = [
"tungstenite",
]
[[package]]
name = "async_io_stream"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6d7b9decdf35d8908a7e3ef02f64c5e9b1695e230154c0e8de3969142d9b94c"
dependencies = [
"futures",
"pharos",
"rustc_version",
]
[[package]]
name = "atomic-waker"
version = "1.1.0"
@ -231,6 +296,15 @@ version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
[[package]]
name = "blake2"
version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe"
dependencies = [
"digest 0.10.6",
]
[[package]]
name = "blake3"
version = "1.3.3"
@ -245,6 +319,16 @@ dependencies = [
"digest 0.10.6",
]
[[package]]
name = "blob-uuid"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83fc15853171b33280f5614e77f5fa4debd33f51a86c44daa4ba3d759674c561"
dependencies = [
"base64",
"uuid 1.3.0",
]
[[package]]
name = "block-buffer"
version = "0.9.0"
@ -319,6 +403,18 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chacha20"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c80e5460aa66fe3b91d40bcbdab953a597b60053e34d684ac6903f863b680a6"
dependencies = [
"cfg-if",
"cipher 0.3.0",
"cpufeatures",
"zeroize",
]
[[package]]
name = "chacha20"
version = "0.9.0"
@ -326,10 +422,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7fc89c7c5b9e7a02dfe45cd2367bae382f9ed31c61ca8debe5f827c420a2f08"
dependencies = [
"cfg-if",
"cipher",
"cipher 0.4.4",
"cpufeatures",
]
[[package]]
name = "chacha20poly1305"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a18446b09be63d457bbec447509e85f662f32952b035ce892290396bc0b0cff5"
dependencies = [
"aead",
"chacha20 0.8.2",
"cipher 0.3.0",
"poly1305",
"zeroize",
]
[[package]]
name = "cipher"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7"
dependencies = [
"generic-array",
]
[[package]]
name = "cipher"
version = "0.4.4"
@ -349,6 +467,16 @@ dependencies = [
"crossbeam-utils",
]
[[package]]
name = "console_error_panic_hook"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc"
dependencies = [
"cfg-if",
"wasm-bindgen",
]
[[package]]
name = "constant_time_eq"
version = "0.2.5"
@ -393,6 +521,15 @@ dependencies = [
"syn",
]
[[package]]
name = "ctr"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "049bb91fb4aaf0e3c7efa6cd5ef877dbbbd15b39dad06d9948de4ec8a75761ea"
dependencies = [
"cipher 0.3.0",
]
[[package]]
name = "cuckoofilter"
version = "0.5.0"
@ -417,6 +554,20 @@ dependencies = [
"zeroize",
]
[[package]]
name = "curve25519-dalek"
version = "4.0.0-rc.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d4ba9852b42210c7538b75484f9daa0655e9a3ac04f693747bb0f02cf3cfe16"
dependencies = [
"cfg-if",
"fiat-crypto",
"packed_simd_2",
"platforms",
"subtle",
"zeroize",
]
[[package]]
name = "debug_print"
version = "1.0.0"
@ -470,11 +621,11 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d"
dependencies = [
"curve25519-dalek",
"curve25519-dalek 3.2.1",
"ed25519",
"rand 0.7.3",
"serde",
"sha2",
"sha2 0.9.9",
"zeroize",
]
@ -538,6 +689,12 @@ dependencies = [
"instant",
]
[[package]]
name = "fiat-crypto"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93ace6ec7cc19c8ed33a32eaa9ea692d7faea05006b5356b9e2b668ec4bc3955"
[[package]]
name = "fnv"
version = "1.0.7"
@ -683,8 +840,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce"
dependencies = [
"cfg-if",
"js-sys",
"libc",
"wasi 0.9.0+wasi-snapshot-preview1",
"wasm-bindgen",
]
[[package]]
@ -700,6 +859,16 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "ghash"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1583cc1656d7839fd3732b80cf4f38850336cdb9b8ded1cd399ca62958de3c99"
dependencies = [
"opaque-debug",
"polyval",
]
[[package]]
name = "gloo-timers"
version = "0.2.6"
@ -840,6 +1009,12 @@ version = "0.2.140"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99227334921fae1a979cf0bfdfcc6b3e5ce376ef57e16fb6fb3ea2ed6095f80c"
[[package]]
name = "libm"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fc7aa29613bd6a620df431842069224d8bc9011086b1db4c0e0cd47fa03ec9a"
[[package]]
name = "linux-raw-sys"
version = "0.1.4"
@ -897,8 +1072,21 @@ checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
name = "ng-app-js-sdk"
version = "0.1.0"
dependencies = [
"getrandom 0.2.8",
"async-std",
"debug_print",
"futures",
"getrandom 0.1.16",
"p2p-client-ws",
"p2p-net",
"p2p-repo",
"pharos",
"serde",
"serde_bare",
"serde_bytes",
"snow",
"wasm-bindgen",
"wasm-bindgen-test",
"ws_stream_wasm",
]
[[package]]
@ -913,6 +1101,7 @@ dependencies = [
"futures",
"p2p-broker",
"p2p-client",
"p2p-client-ws",
"p2p-net",
"p2p-repo",
"p2p-stores-lmdb",
@ -927,6 +1116,7 @@ version = "0.1.0"
dependencies = [
"async-std",
"p2p-broker",
"p2p-net",
]
[[package]]
@ -988,7 +1178,7 @@ dependencies = [
"async-std",
"async-trait",
"async-tungstenite",
"chacha20",
"chacha20 0.9.0",
"debug_print",
"futures",
"getrandom 0.2.8",
@ -1006,20 +1196,46 @@ dependencies = [
[[package]]
name = "p2p-client"
version = "0.1.0"
dependencies = [
"async-channel",
"async-oneshot",
"async-std",
"async-trait",
"chacha20 0.9.0",
"debug_print",
"futures",
"p2p-net",
"p2p-repo",
"serde",
"serde_bare",
"serde_bytes",
"snow",
"xactor",
]
[[package]]
name = "p2p-client-ws"
version = "0.1.0"
dependencies = [
"async-channel",
"async-oneshot",
"async-std",
"async-trait",
"async-tungstenite",
"chacha20",
"chacha20 0.9.0",
"debug_print",
"futures",
"getrandom 0.2.8",
"p2p-client",
"p2p-net",
"p2p-repo",
"pharos",
"serde",
"serde_bare",
"serde_bytes",
"wasm-bindgen",
"wasm-bindgen-test",
"ws_stream_wasm",
"xactor",
]
@ -1028,6 +1244,7 @@ name = "p2p-net"
version = "0.1.0"
dependencies = [
"async-broadcast",
"async-std",
"async-trait",
"blake3",
"debug_print",
@ -1037,6 +1254,8 @@ dependencies = [
"serde",
"serde_bare",
"serde_bytes",
"unique_id",
"wasm-bindgen",
]
[[package]]
@ -1044,7 +1263,7 @@ name = "p2p-repo"
version = "0.1.0"
dependencies = [
"blake3",
"chacha20",
"chacha20 0.9.0",
"debug_print",
"ed25519-dalek",
"fastbloom-rs",
@ -1073,7 +1292,7 @@ name = "p2p-verifier"
version = "0.1.0"
dependencies = [
"blake3",
"chacha20",
"chacha20 0.9.0",
"p2p-net",
"p2p-repo",
"serde",
@ -1081,6 +1300,16 @@ dependencies = [
"serde_bytes",
]
[[package]]
name = "packed_simd_2"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1914cd452d8fccd6f9db48147b29fd4ae05bea9dc5d9ad578509f72415de282"
dependencies = [
"cfg-if",
"libm",
]
[[package]]
name = "parking"
version = "2.0.0"
@ -1122,6 +1351,16 @@ version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e"
[[package]]
name = "pharos"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e9567389417feee6ce15dd6527a8a1ecac205ef62c2932bcf3d9f6fc5b78b414"
dependencies = [
"futures",
"rustc_version",
]
[[package]]
name = "pin-project-lite"
version = "0.2.9"
@ -1140,6 +1379,12 @@ version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160"
[[package]]
name = "platforms"
version = "3.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3d7ddaed09e0eb771a79ab0fd64609ba0afb0a8366421957936ad14cbd13630"
[[package]]
name = "polling"
version = "2.5.2"
@ -1154,6 +1399,29 @@ dependencies = [
"windows-sys 0.42.0",
]
[[package]]
name = "poly1305"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "048aeb476be11a4b6ca432ca569e375810de9294ae78f4774e78ea98a9246ede"
dependencies = [
"cpufeatures",
"opaque-debug",
"universal-hash",
]
[[package]]
name = "polyval"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8419d2b623c7c0896ff2d5d96e2cb4ede590fed28fcc34934f4c33c036e620a1"
dependencies = [
"cfg-if",
"cpufeatures",
"opaque-debug",
"universal-hash",
]
[[package]]
name = "ppv-lite86"
version = "0.2.17"
@ -1321,7 +1589,7 @@ dependencies = [
"serde_derive",
"thiserror",
"url",
"uuid",
"uuid 0.8.2",
]
[[package]]
@ -1343,6 +1611,15 @@ dependencies = [
"syn",
]
[[package]]
name = "rustc_version"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
dependencies = [
"semver",
]
[[package]]
name = "rustix"
version = "0.36.9"
@ -1357,12 +1634,30 @@ dependencies = [
"windows-sys 0.45.0",
]
[[package]]
name = "scoped-tls"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294"
[[package]]
name = "scopeguard"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "semver"
version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed"
[[package]]
name = "send_wrapper"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73"
[[package]]
name = "serde"
version = "1.0.142"
@ -1425,6 +1720,36 @@ dependencies = [
"opaque-debug",
]
[[package]]
name = "sha2"
version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0"
dependencies = [
"cfg-if",
"cpufeatures",
"digest 0.10.6",
]
[[package]]
name = "signal-hook"
version = "0.3.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "732768f1176d21d09e076c23a93123d40bba92d50c4058da34d45c8de8e682b9"
dependencies = [
"libc",
"signal-hook-registry",
]
[[package]]
name = "signal-hook-registry"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1"
dependencies = [
"libc",
]
[[package]]
name = "signature"
version = "1.6.4"
@ -1446,6 +1771,22 @@ version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
[[package]]
name = "snow"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ccba027ba85743e09d15c03296797cad56395089b832b48b5a5217880f57733"
dependencies = [
"aes-gcm",
"blake2",
"chacha20poly1305",
"curve25519-dalek 4.0.0-rc.1",
"rand_core 0.6.4",
"rustc_version",
"sha2 0.10.6",
"subtle",
]
[[package]]
name = "socket2"
version = "0.4.9"
@ -1608,6 +1949,27 @@ version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c"
[[package]]
name = "unique_id"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae605c39dfbdec433798d4a8b03ffbac711dc51cdeb1ba5c725bdcaf24e464cc"
dependencies = [
"blob-uuid",
"lazy_static",
"uuid 1.3.0",
]
[[package]]
name = "universal-hash"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f214e8f697e925001e66ec2c6e37a4ef93f0f78c2eed7814394e10c62025b05"
dependencies = [
"generic-array",
"subtle",
]
[[package]]
name = "url"
version = "2.3.1"
@ -1631,6 +1993,15 @@ version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7"
[[package]]
name = "uuid"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1674845326ee10d37ca60470760d4288a6f80f304007d92e5c53bab78c9cfd79"
dependencies = [
"getrandom 0.2.8",
]
[[package]]
name = "value-bag"
version = "1.0.0-alpha.9"
@ -1740,6 +2111,30 @@ version = "0.2.84"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d"
[[package]]
name = "wasm-bindgen-test"
version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6db36fc0f9fb209e88fb3642590ae0205bb5a56216dabd963ba15879fe53a30b"
dependencies = [
"console_error_panic_hook",
"js-sys",
"scoped-tls",
"wasm-bindgen",
"wasm-bindgen-futures",
"wasm-bindgen-test-macro",
]
[[package]]
name = "wasm-bindgen-test-macro"
version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0734759ae6b3b1717d661fe4f016efcfb9828f5edb4520c18eaee05af3b43be9"
dependencies = [
"proc-macro2",
"quote",
]
[[package]]
name = "web-sys"
version = "0.3.61"
@ -1871,6 +2266,25 @@ dependencies = [
"memchr",
]
[[package]]
name = "ws_stream_wasm"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7999f5f4217fe3818726b66257a4475f71e74ffd190776ad053fa159e50737f5"
dependencies = [
"async_io_stream",
"futures",
"js-sys",
"log",
"pharos",
"rustc_version",
"send_wrapper",
"thiserror",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
]
[[package]]
name = "xactor"
version = "0.7.11"

@ -32,6 +32,7 @@ Read our [getting started guide](https://docs.nextgraph.org/en/getting-started/)
```
cargo install wasm-pack
// optionally, if you want a Rust REPL: cargo install evcxr_repl
git clone git@git.nextgraph.org:NextGraph/nextgraph-rs.git
cd nextgraph-rs
cargo build
@ -84,6 +85,17 @@ Test end-to-end client and server:
cargo test --package ngcli -- --nocapture
```
Test WASM websocket
```
cd ng-app-js
wasm-pack test --chrome --headless
```
Test Rust websocket
```
cargo test --package p2p-client-ws --lib -- --nocapture
```
### Build release binaries
```

@ -15,9 +15,27 @@ crate-type = ["cdylib"]
[dependencies]
wasm-bindgen = "0.2"
getrandom = "0.2.7"
# p2p-client = { path = "../p2p-client" }
ws_stream_wasm = "0.7"
p2p-net = { path = "../p2p-net" }
p2p-repo = { path = "../p2p-repo" }
p2p-client-ws = { path = "../p2p-client-ws" }
async-std = { version = "1.12.0", features = ["attributes","unstable"] }
futures = "0.3.24"
pharos = "0.5"
debug_print = "1.0.0"
serde = { version = "1.0", features = ["derive"] }
serde_bare = "0.5.0"
serde_bytes = "0.11.7"
snow = "0.9.2"
getrandom = { version = "0.1.1", features = ["wasm-bindgen"] }
[target.'cfg(target_arch = "wasm32")'.dependencies.getrandom]
version = "0.2.7"
features = ["js"]
# [target.'cfg(target_arch = "wasm32")'.dependencies.getrandom]
# version = "0.2.7"
# features = ["js"]
# [target.'cfg(target_arch = "wasm32")'.dependencies]
# wasm-bindgen-futures = "0.4.34"
[dev-dependencies]
wasm-bindgen-test = "^0.3"

@ -50,6 +50,18 @@ cd pkg-node
// npm publish --access=public
```
For testing in vanilla JS
```
wasm-pack build --target web -d web
python3 -m http.server
// open http://localhost:8000
```
Or automated testing with headless chrome:
```
wasm-pack test --chrome --headless
```
### Plain JS web app
```

@ -18,7 +18,7 @@
<body>
<p>run <code>python3 -m http.server</code> to use it</p>
<script type="module">
import init, { greet } from "./pkg/ng_app_js_sdk.js";
import init, { greet } from "./web/ng_app_js_sdk.js";
init().then(() => {
greet("WebAssembly");
});

@ -0,0 +1,3 @@
pub struct ClientConnection {}
impl ClientConnection {}

@ -1,13 +1,56 @@
use async_std::task;
#[cfg(target_arch = "wasm32")]
use p2p_client_ws::remote_ws_wasm::ConnectionWebSocket;
use p2p_net::broker::*;
use p2p_net::log;
use p2p_net::types::IP;
use p2p_net::utils::{spawn_and_log_error, ResultSend};
use p2p_repo::utils::generate_keypair;
use std::net::IpAddr;
use std::str::FromStr;
use std::sync::Arc;
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
extern {
extern "C" {
pub fn alert(s: &str);
}
#[cfg(target_arch = "wasm32")]
#[wasm_bindgen]
pub fn greet(name: &str) {
log!("I say: {}", name);
let mut random_buf = [0u8; 32];
getrandom::getrandom(&mut random_buf).unwrap();
//spawn_and_log_error(testt("ws://127.0.0.1:3012"));
async fn method() -> ResultSend<()> {
log!("start connecting");
let cnx = Arc::new(ConnectionWebSocket {});
let (priv_key, pub_key) = generate_keypair();
let broker = Broker::new();
let res = broker
.connect(
cnx,
IP::try_from(&IpAddr::from_str("127.0.0.1").unwrap()).unwrap(),
None,
priv_key,
pub_key,
pub_key,
)
.await;
log!("broker.connect : {:?}", res);
//res.expect_throw("assume the connection succeeds");
Ok(())
}
spawn_and_log_error(method());
//spawn_and_log_error(Arc::clone(&cnx).open("ws://127.0.0.1:3012", priv_key, pub_key));
}
#[cfg(not(target_arch = "wasm32"))]
#[wasm_bindgen]
pub fn greet(name: &str) {
alert(&format!("I say: {}", name));
task::spawn(async move {});
}
#[wasm_bindgen]
@ -16,3 +59,16 @@ pub fn change(name: &str) -> JsValue {
getrandom::getrandom(&mut random_buf).unwrap();
JsValue::from_str(&format!("Hellooo, {}!", name))
}
#[cfg(target_arch = "wasm32")]
#[cfg(test)]
mod test {
use wasm_bindgen_test::*;
wasm_bindgen_test_configure!(run_in_browser);
use crate::greet;
#[wasm_bindgen_test]
pub async fn test_greet() {
greet("test");
}
}

@ -11,10 +11,11 @@ repository = "https://git.nextgraph.org/NextGraph/nextgraph-rs"
debug_print = "1.0.0"
p2p-repo = { path = "../p2p-repo" }
p2p-net = { path = "../p2p-net" }
p2p-client-ws = { path = "../p2p-client-ws" }
p2p-client = { path = "../p2p-client" }
p2p-broker = { path = "../p2p-broker" }
p2p-stores-lmdb = { path = "../p2p-stores-lmdb" }
async-std = { version = "1.7.0", features = ["attributes"] }
async-std = { version = "1.12.0", features = ["attributes"] }
futures = "0.3.24"
xactor = "0.7.11"
tempfile = "3"

@ -29,7 +29,7 @@ use p2p_net::errors::*;
use p2p_net::types::*;
use p2p_net::broker_connection::*;
use p2p_client::connection_remote::*;
use p2p_client::connection_ws::*;
use p2p_client_ws::connection_ws::*;
use p2p_broker::connection_local::*;
fn block_size() -> usize {
@ -471,6 +471,8 @@ async fn test(cnx: &mut impl BrokerConnection, pub_key: PubKey, priv_key: PrivKe
.overlay_connect(&repo, true)
.await?;
debug_println!("put_block");
let my_block_id = public_overlay_cnx
.put_block(&Block::new(
vec![],

@ -9,4 +9,5 @@ repository = "https://git.nextgraph.org/NextGraph/nextgraph-rs"
[dependencies]
p2p-broker = { path = "../p2p-broker" }
async-std = { version = "1.7.0", features = ["attributes"] }
p2p-net = { path = "../p2p-net" }
async-std = { version = "1.12.0", features = ["attributes"] }

@ -1,18 +1,20 @@
// Copyright (c) 2022-2023 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
use p2p_broker::server_ws::run_server;
use p2p_net::WS_PORT;
#[async_std::main]
async fn main() -> std::io::Result<()> {
println!("Starting NextGraph daemon...");
run_server("127.0.0.1:3012").await
run_server(format!("127.0.0.1:{}", WS_PORT).as_str()).await?;
Ok(())
}

@ -16,7 +16,7 @@ chacha20 = "0.9.0"
serde = { version = "1.0", features = ["derive"] }
serde_bare = "0.5.0"
serde_bytes = "0.11.7"
async-std = { version = "1.7.0", features = ["attributes"] }
async-std = { version = "1.12.0", features = ["attributes"] }
futures = "0.3.24"
rust-fsm = "0.6.0"
getrandom = "0.2.7"
@ -25,7 +25,3 @@ tempfile = "3"
hex = "0.4.3"
async-trait = "0.1.64"
async-tungstenite = { version = "0.17.2", features = ["async-std-runtime"] }
[target.'cfg(target_arch = "wasm32")'.dependencies.getrandom]
version = "0.2.7"
features = ["js"]

@ -19,37 +19,38 @@ use p2p_net::errors::*;
use p2p_net::types::*;
use rust_fsm::*;
state_machine! {
derive(Debug)
AuthProtocolClient(Ready)
Ready(ClientHelloSent) => ClientHelloSent,
ClientHelloSent(ServerHelloReceived) => ServerHelloReceived,
ServerHelloReceived(ClientAuthSent) => ClientAuthSent,
ClientAuthSent(AuthResultReceived) => AuthResult,
AuthResult => {
Ok => BrokerProtocol,
Error => Closed,
},
}
state_machine! {
derive(Debug)
AuthProtocolServer(Ready)
Ready(ClientHelloReceived) => ClientHelloReceived,
ClientHelloReceived(ServerHelloSent) => ServerHelloSent,
ServerHelloSent(ClientAuthReceived) => ClientAuthReceived,
ClientAuthReceived => {
Ok => AuthResultOk,
Error => AuthResultError,
},
AuthResultOk(AuthResultSent) => BrokerProtocol,
AuthResultError(AuthResultSent) => Closed,
}
// state_machine! {
// derive(Debug)
// AuthProtocolClient(Ready)
// Ready(ClientHelloSent) => ClientHelloSent,
// ClientHelloSent(ServerHelloReceived) => ServerHelloReceived,
// ServerHelloReceived(ClientAuthSent) => ClientAuthSent,
// ClientAuthSent(AuthResultReceived) => AuthResult,
// AuthResult => {
// Ok => BrokerProtocol,
// Error => Closed,
// },
// }
// state_machine! {
// derive(Debug)
// AuthProtocolServer(Ready)
// Ready(ClientHelloReceived) => ClientHelloReceived,
// ClientHelloReceived(ServerHelloSent) => ServerHelloSent,
// ServerHelloSent(ClientAuthReceived) => ClientAuthReceived,
// ClientAuthReceived => {
// Ok => AuthResultOk,
// Error => AuthResultError,
// },
// AuthResultOk(AuthResultSent) => BrokerProtocol,
// AuthResultError(AuthResultSent) => Closed,
// }
#[derive(Debug)]
pub struct AuthProtocolHandler {
machine: StateMachine<AuthProtocolServer>,
//machine: StateMachine<AuthProtocolServer>,
nonce: Option<Vec<u8>>,
user: Option<PubKey>,
}
@ -57,7 +58,7 @@ pub struct AuthProtocolHandler {
impl AuthProtocolHandler {
pub fn new() -> AuthProtocolHandler {
AuthProtocolHandler {
machine: StateMachine::new(),
//machine: StateMachine::new(),
nonce: None,
user: None,
}
@ -68,10 +69,10 @@ impl AuthProtocolHandler {
}
pub fn handle_init(&mut self, client_hello: ClientHello) -> Result<Vec<u8>, ProtocolError> {
let _ = self
.machine
.consume(&AuthProtocolServerInput::ClientHelloReceived)
.map_err(|_e| ProtocolError::InvalidState)?;
// let _ = self
// .machine
// .consume(&AuthProtocolServerInput::ClientHelloReceived)
// .map_err(|_e| ProtocolError::InvalidState)?;
let mut random_buf = [0u8; 32];
getrandom::getrandom(&mut random_buf).unwrap();
@ -81,10 +82,10 @@ impl AuthProtocolHandler {
});
self.nonce = Some(nonce);
let _ = self
.machine
.consume(&AuthProtocolServerInput::ServerHelloSent)
.map_err(|_e| ProtocolError::InvalidState)?;
// let _ = self
// .machine
// .consume(&AuthProtocolServerInput::ServerHelloSent)
// .map_err(|_e| ProtocolError::InvalidState)?;
//debug_println!("sending nonce to client: {:?}", self.nonce);
@ -110,13 +111,13 @@ impl AuthProtocolHandler {
handler: &mut AuthProtocolHandler,
frame: Vec<u8>,
) -> Result<Vec<u8>, ProtocolError> {
match handler.machine.state() {
&AuthProtocolServerState::ServerHelloSent => {
// match handler.machine.state() {
// &AuthProtocolServerState::ServerHelloSent => {
let message = serde_bare::from_slice::<ClientAuth>(&frame)?;
let _ = handler
.machine
.consume(&AuthProtocolServerInput::ClientAuthReceived)
.map_err(|_e| ProtocolError::InvalidState)?;
// let _ = handler
// .machine
// .consume(&AuthProtocolServerInput::ClientAuthReceived)
// .map_err(|_e| ProtocolError::InvalidState)?;
// verifying client auth
@ -136,10 +137,10 @@ impl AuthProtocolHandler {
// );
if message.nonce() != handler.nonce.as_ref().unwrap() {
let _ = handler
.machine
.consume(&AuthProtocolServerInput::Error)
.map_err(|_e| ProtocolError::InvalidState);
// let _ = handler
// .machine
// .consume(&AuthProtocolServerInput::Error)
// .map_err(|_e| ProtocolError::InvalidState);
return Err(ProtocolError::AccessDenied);
}
@ -147,17 +148,17 @@ impl AuthProtocolHandler {
// TODO check that the device has been registered for this user. if not, return AccessDenied
// all is good, we advance the FSM and send back response
let _ = handler
.machine
.consume(&AuthProtocolServerInput::Ok)
.map_err(|_e| ProtocolError::InvalidState)?;
// let _ = handler
// .machine
// .consume(&AuthProtocolServerInput::Ok)
// .map_err(|_e| ProtocolError::InvalidState)?;
handler.user = Some(message.user());
Ok(vec![]) // without any metadata
}
_ => Err(ProtocolError::InvalidState),
}
//}
//_ => Err(ProtocolError::InvalidState),
//}
}
let res = process_state(self, frame);

@ -1,7 +1,7 @@
// Copyright (c) 2022-2023 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
@ -9,10 +9,10 @@
//! Broker Config, persisted to store
use p2p_net::types::*;
use p2p_repo::broker_store::BrokerStore;
use p2p_repo::store::*;
use p2p_repo::types::*;
use p2p_net::types::*;
use serde::{Deserialize, Serialize};
use serde_bare::{from_slice, to_vec};

@ -1,7 +1,7 @@
// Copyright (c) 2022-2023 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
@ -9,10 +9,10 @@
//! Peer
use p2p_net::types::*;
use p2p_repo::broker_store::BrokerStore;
use p2p_repo::store::*;
use p2p_repo::types::*;
use p2p_net::types::*;
use serde::{Deserialize, Serialize};
use serde_bare::{from_slice, to_vec};
@ -79,13 +79,13 @@ impl<'a> Peer<'a> {
Self::PREFIX,
&to_vec(&id)?,
Some(Self::VERSION),
& to_vec(&advert.version())?,
&to_vec(&advert.version())?,
)?;
tx.put(
Self::PREFIX,
&to_vec(&id)?,
Some(Self::ADVERT),
& to_vec(&advert)?,
&to_vec(&advert)?,
)?;
Ok(())
})?;
@ -131,12 +131,21 @@ impl<'a> Peer<'a> {
if current_advert.version() >= advert.version() {
return Ok(());
}
self.store.replace(
Self::PREFIX,
&to_vec(&self.id)?,
Some(Self::ADVERT),
to_vec(advert)?,
)
self.store.write_transaction(&|tx| {
tx.replace(
Self::PREFIX,
&to_vec(&self.id)?,
Some(Self::VERSION),
&to_vec(&advert.version())?,
)?;
tx.replace(
Self::PREFIX,
&to_vec(&self.id)?,
Some(Self::ADVERT),
&to_vec(&advert)?,
)?;
Ok(())
})
}
pub fn advert(&self) -> Result<PeerAdvert, StorageError> {
match self

@ -19,21 +19,21 @@ use p2p_net::types::*;
use serde::{Deserialize, Serialize};
use serde_bare::{from_slice, to_vec};
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub enum RepoStoreId {
Overlay(OverlayId),
Repo(PubKey),
}
// #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
// pub enum RepoStoreId {
// Overlay(OverlayId),
// Repo(PubKey),
// }
impl From<RepoStoreId> for String {
fn from(id: RepoStoreId) -> Self {
hex::encode(to_vec(&id).unwrap())
}
}
// impl From<RepoStoreId> for String {
// fn from(id: RepoStoreId) -> Self {
// hex::encode(to_vec(&id).unwrap())
// }
// }
pub struct RepoStoreInfo<'a> {
/// RepoStore ID
id: RepoStoreId,
id: RepoHash,
store: &'a dyn BrokerStore,
}
@ -48,7 +48,7 @@ impl<'a> RepoStoreInfo<'a> {
const SUFFIX_FOR_EXIST_CHECK: u8 = Self::KEY;
pub fn open(
id: &RepoStoreId,
id: &RepoHash,
store: &'a dyn BrokerStore,
) -> Result<RepoStoreInfo<'a>, StorageError> {
let opening = RepoStoreInfo {
@ -61,7 +61,7 @@ impl<'a> RepoStoreInfo<'a> {
Ok(opening)
}
pub fn create(
id: &RepoStoreId,
id: &RepoHash,
key: &SymKey,
store: &'a dyn BrokerStore,
) -> Result<RepoStoreInfo<'a>, StorageError> {
@ -84,7 +84,7 @@ impl<'a> RepoStoreInfo<'a> {
)
.is_ok()
}
pub fn id(&self) -> &RepoStoreId {
pub fn id(&self) -> &RepoHash {
&self.id
}
pub fn key(&self) -> Result<SymKey, StorageError> {

@ -11,6 +11,7 @@
use std::collections::HashMap;
use std::collections::HashSet;
use std::net::SocketAddr;
use std::pin::Pin;
use std::sync::Arc;
use std::sync::RwLock;
@ -22,7 +23,6 @@ use crate::broker_store::config::ConfigMode;
use crate::connection_local::BrokerConnectionLocal;
use crate::broker_store::overlay::Overlay;
use crate::broker_store::peer::Peer;
use crate::broker_store::repostoreinfo::RepoStoreId;
use crate::broker_store::repostoreinfo::RepoStoreInfo;
use async_std::task;
use debug_print::*;
@ -76,16 +76,35 @@ enum ProtocolType {
}
pub struct ProtocolHandler {
addr: Option<SocketAddr>,
broker: Arc<BrokerServer>,
protocol: ProtocolType,
auth_protocol: Option<AuthProtocolHandler>,
broker_protocol: Option<BrokerProtocolHandler>,
broker_protocol: Option<Arc<BrokerProtocolHandler>>,
ext_protocol: Option<ExtProtocolHandler>,
r: Option<async_channel::Receiver<Vec<u8>>>,
s: async_channel::Sender<Vec<u8>>,
}
impl ProtocolHandler {
pub fn register(&mut self, addr: SocketAddr) {
self.addr = Some(addr);
}
pub fn deregister(&mut self) {
match &self.protocol {
ProtocolType::Start => (),
ProtocolType::Auth => (),
ProtocolType::Broker => {
let _ = self.broker_protocol.as_ref().unwrap().deregister(self.addr.unwrap());
},
ProtocolType::Ext => (),
ProtocolType::Core => (),
}
self.addr = None;
}
pub fn async_frames_receiver(&mut self) -> async_channel::Receiver<Vec<u8>> {
self.r.take().unwrap()
}
@ -130,14 +149,25 @@ impl ProtocolHandler {
match res.1.await {
None => {
// we switch to Broker protocol
self.protocol = ProtocolType::Broker;
self.broker_protocol = Some(BrokerProtocolHandler {
let bp = Arc::new(BrokerProtocolHandler {
user: self.auth_protocol.as_ref().unwrap().get_user().unwrap(),
broker: Arc::clone(&self.broker),
async_frames_sender: self.s.clone(),
});
self.auth_protocol = None;
(res.0, OptionFuture::from(None))
let registration = Arc::clone(&bp).register(self.addr.unwrap());
match registration {
Ok(_) => {
self.protocol = ProtocolType::Broker;
self.broker_protocol = Some(Arc::clone(&bp));
self.auth_protocol = None;
return (res.0, OptionFuture::from(None))
},
Err(e) => {
let val = e.clone() as u16;
let reply = AuthResult::V0(AuthResultV0 { result:val, metadata:vec![] });
return (Ok(serde_bare::to_vec(&reply).unwrap()), OptionFuture::from(Some(async move { val }.boxed())))
}
}
}
Some(e) => (res.0, OptionFuture::from(Some(async move { e }.boxed()))),
}
@ -334,6 +364,18 @@ impl BrokerProtocolHandler {
);
}
pub fn register(self: Arc<Self>, addr: SocketAddr) -> Result<(),ProtocolError> {
//FIXME: peer_id must be real one
self.broker.add_client_peer(PubKey::Ed25519PubKey([0;32]), Arc::clone(&self))
}
pub fn deregister(&self, addr: SocketAddr) -> Result<(),ProtocolError> {
self.broker.remove_client_peer(PubKey::Ed25519PubKey([0;32]));
Ok(())
}
pub async fn handle_incoming(
&self,
msg: BrokerMessage,
@ -457,19 +499,57 @@ impl BrokerProtocolHandler {
}
}
pub enum PeerConnection {
CORE(IP),
CLIENT(Arc<BrokerProtocolHandler>),
NONE,
}
pub struct BrokerPeerInfo {
lastPeerAdvert: Option<PeerAdvert>,
connected: PeerConnection,
}
const REPO_STORES_SUBDIR: &str = "repos";
pub struct BrokerServer {
store: LmdbBrokerStore,
mode: ConfigMode,
repo_stores: Arc<RwLock<HashMap<RepoStoreId, LmdbRepoStore>>>,
repo_stores: Arc<RwLock<HashMap<RepoHash, LmdbRepoStore>>>,
// only used in ConfigMode::Local
// try to change it to this version below in order to avoid double hashmap lookup in local mode. but hard to do...
//overlayid_to_repostore: HashMap<RepoStoreId, &'a LmdbRepoStore>,
overlayid_to_repostore: Arc<RwLock<HashMap<OverlayId, RepoStoreId>>>,
//overlayid_to_repostore: Arc<RwLock<HashMap<OverlayId, RepoStoreId>>>,
peers: RwLock<HashMap<DirectPeerId, BrokerPeerInfo>>,
//local_connections:
}
impl BrokerServer {
pub fn add_client_peer(&self, peer_id: DirectPeerId, bph: Arc<BrokerProtocolHandler>) -> Result<(),ProtocolError> {
let mut writer = self.peers.write().expect("write peers hashmap");
let bpi = BrokerPeerInfo {
lastPeerAdvert: None, //TODO: load from store
connected: PeerConnection::CLIENT(bph)
};
if !writer.get(&peer_id).is_none() {
return Err(ProtocolError::PeerAlreadyConnected);
}
writer.insert(peer_id.clone(), bpi);
Ok(())
}
pub fn remove_client_peer(&self, peer_id: DirectPeerId) {
let mut writer = self.peers.write().expect("write peers hashmap");
writer.remove(&peer_id);
}
pub fn new(store: LmdbBrokerStore, mode: ConfigMode) -> Result<BrokerServer, BrokerError> {
let mut configmode: ConfigMode;
{
@ -480,32 +560,32 @@ impl BrokerServer {
store,
mode: configmode,
repo_stores: Arc::new(RwLock::new(HashMap::new())),
overlayid_to_repostore: Arc::new(RwLock::new(HashMap::new())),
//overlayid_to_repostore: Arc::new(RwLock::new(HashMap::new())),
peers: RwLock::new(HashMap::new())
})
}
fn open_or_create_repostore<F, R>(
&self,
repostore_id: RepoStoreId,
repo_hash: RepoHash,
f: F,
) -> Result<R, ProtocolError>
where
F: FnOnce(&LmdbRepoStore) -> Result<R, ProtocolError>,
{
// first let's find it in the BrokerStore.repostoreinfo table in order to get the encryption key
let info = RepoStoreInfo::open(&repostore_id, &self.store)
let info = RepoStoreInfo::open(&repo_hash, &self.store)
.map_err(|e| BrokerError::OverlayNotFound)?;
let key = info.key()?;
let mut path = self.store.path();
path.push(REPO_STORES_SUBDIR);
path.push::<String>(repostore_id.clone().into());
path.push::<String>(repo_hash.clone().into());
std::fs::create_dir_all(path.clone()).map_err(|_e| ProtocolError::WriteError )?;
println!("path for repo store: {}", path.to_str().unwrap());
let repo = LmdbRepoStore::open(&path, *key.slice());
let mut writer = self.repo_stores.write().expect("write repo_store hashmap");
writer.insert(repostore_id.clone(), repo);
f(writer.get(&repostore_id).unwrap())
writer.insert(repo_hash.clone(), repo);
f(writer.get(&repo_hash).unwrap())
}
fn get_repostore_from_overlay_id<F, R>(
@ -516,53 +596,55 @@ impl BrokerServer {
where
F: FnOnce(&LmdbRepoStore) -> Result<R, ProtocolError>,
{
if self.mode == ConfigMode::Core {
let repostore_id = RepoStoreId::Overlay(*overlay_id);
//FIXME: the whole purpose of get_repostore_from_overlay_id is gone. review all of it
let repostore_id = *overlay_id;
{
let reader = self.repo_stores.read().expect("read repo_store hashmap");
let rep = reader.get(&repostore_id);
match rep {
Some(repo) => return f(repo),
None => {
// we need to open/create it
// TODO: last_access
return self.open_or_create_repostore(repostore_id, |repo| f(repo));
}
}
} else {
// it is ConfigMode::Local
{
let reader = self
.overlayid_to_repostore
.read()
.expect("read overlayid_to_repostore hashmap");
match reader.get(&overlay_id) {
Some(repostoreid) => {
let reader = self.repo_stores.read().expect("read repo_store hashmap");
match reader.get(repostoreid) {
Some(repo) => return f(repo),
None => return Err(ProtocolError::BrokerError),
}
}
None => {}
};
if let Some(repo) = rep {
return f(repo)
}
// we need to open/create it
// first let's find it in the BrokerStore.overlay table to retrieve its repo_pubkey
debug_println!("searching for overlayId {}", overlay_id);
let overlay = Overlay::open(overlay_id, &self.store)?;
debug_println!("found overlayId {}", overlay_id);
let repo_id = overlay.repo()?;
let repostore_id = RepoStoreId::Repo(repo_id);
let mut writer = self
.overlayid_to_repostore
.write()
.expect("write overlayid_to_repostore hashmap");
writer.insert(*overlay_id, repostore_id.clone());
// now opening/creating the RepoStore
// TODO: last_access
return self.open_or_create_repostore(repostore_id, |repo| f(repo));
}
// we need to open/create it
// TODO: last_access
return self.open_or_create_repostore(repostore_id, |repo| f(repo));
// } else {
// // it is ConfigMode::Local
// {
// let reader = self
// .overlayid_to_repostore
// .read()
// .expect("read overlayid_to_repostore hashmap");
// match reader.get(&overlay_id) {
// Some(repostoreid) => {
// let reader = self.repo_stores.read().expect("read repo_store hashmap");
// match reader.get(repostoreid) {
// Some(repo) => return f(repo),
// None => return Err(ProtocolError::BrokerError),
// }
// }
// None => {}
// };
// }
// // we need to open/create it
// // first let's find it in the BrokerStore.overlay table to retrieve its repo_pubkey
// debug_println!("searching for overlayId {}", overlay_id);
// let overlay = Overlay::open(overlay_id, &self.store)?;
// debug_println!("found overlayId {}", overlay_id);
// let repo_id = overlay.repo()?;
// let repostore_id = RepoStoreId::Repo(repo_id);
// let mut writer = self
// .overlayid_to_repostore
// .write()
// .expect("write overlayid_to_repostore hashmap");
// writer.insert(*overlay_id, repostore_id.clone());
// // now opening/creating the RepoStore
// // TODO: last_access
// return self.open_or_create_repostore(repostore_id, |repo| f(repo));
// }
}
pub fn local_connection(&mut self, user: PubKey) -> BrokerConnectionLocal {
@ -572,6 +654,7 @@ impl BrokerServer {
pub fn protocol_handler(self: Arc<Self>) -> ProtocolHandler {
let (s, r) = async_channel::unbounded::<Vec<u8>>();
return ProtocolHandler {
addr:None,
broker: Arc::clone(&self),
protocol: ProtocolType::Start,
auth_protocol: None,
@ -821,12 +904,12 @@ impl BrokerServer {
})
}
fn compute_repostore_id(&self, overlay: OverlayId, repo_id: Option<PubKey>) -> RepoStoreId {
match self.mode {
ConfigMode::Core => RepoStoreId::Overlay(overlay),
ConfigMode::Local => RepoStoreId::Repo(repo_id.unwrap()),
}
}
// fn compute_repostore_id(&self, overlay: OverlayId, repo_id: Option<PubKey>) -> RepoStoreId {
// match self.mode {
// ConfigMode::Core => RepoStoreId::Overlay(overlay),
// ConfigMode::Local => RepoStoreId::Repo(repo_id.unwrap()),
// }
// }
pub fn join_overlay(
&self,
@ -861,7 +944,7 @@ impl BrokerServer {
let key = SymKey::ChaCha20Key(random_buf);
let _ = RepoStoreInfo::create(
&self.compute_repostore_id(overlay_id, repo_id),
&overlay_id,
&key,
&self.store,
)?; // TODO in case of error, delete the previously created Overlay

@ -0,0 +1,3 @@
pub struct ServerConnection {}
impl ServerConnection {}

@ -2,7 +2,7 @@
* Copyright (c) 2022-2023 Niko Bonnieure, Par le Peuple, NextGraph.org developers
* All rights reserved.
* Licensed under the Apache License, Version 2.0
* <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
* <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
* or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
* at your option. All files in the project carrying such
* notice may not be copied, modified, or distributed except
@ -11,6 +11,8 @@
//! WebSocket implementation of the Broker
use crate::broker_store::config::ConfigMode;
use crate::server::*;
use async_std::net::{TcpListener, TcpStream};
use async_std::sync::Mutex;
use async_std::task;
@ -18,16 +20,17 @@ use async_tungstenite::accept_async;
use async_tungstenite::tungstenite::protocol::Message;
use debug_print::*;
use futures::{SinkExt, StreamExt};
use crate::broker_store::config::ConfigMode;
use crate::server::*;
use p2p_stores_lmdb::broker_store::LmdbBrokerStore;
use p2p_stores_lmdb::repo_store::LmdbRepoStore;
use std::fs;
use std::sync::Arc;
use tempfile::Builder;
use std::{thread, time};
use tempfile::Builder;
async fn connection_loop(tcp: TcpStream, mut handler: ProtocolHandler) -> std::io::Result<()> {
let addr = tcp.peer_addr().unwrap();
handler.register(addr);
let mut ws = accept_async(tcp).await.unwrap();
let (mut tx, mut rx) = ws.split();
@ -38,13 +41,8 @@ async fn connection_loop(tcp: TcpStream, mut handler: ProtocolHandler) -> std::i
let ws_in_task = Arc::clone(&tx_mutex);
task::spawn(async move {
while let Ok(frame) = receiver.recv().await {
let mut sink = ws_in_task
.lock()
.await;
if sink.send(Message::binary(frame))
.await
.is_err()
{
let mut sink = ws_in_task.lock().await;
if sink.send(Message::binary(frame)).await.is_err() {
break;
}
}
@ -69,6 +67,11 @@ async fn connection_loop(tcp: TcpStream, mut handler: ProtocolHandler) -> std::i
//TODO implement PING messages
if msg.is_close() {
debug_println!("CLOSE from CLIENT");
if let Message::Close(Some(cf)) = msg {
debug_println!("CLOSE FRAME {:?}", cf);
} else if let Message::Close(None) = msg {
debug_println!("without CLOSE FRAME");
}
break;
} else if msg.is_binary() {
//debug_println!("server received binary: {:?}", msg);
@ -106,6 +109,7 @@ async fn connection_loop(tcp: TcpStream, mut handler: ProtocolHandler) -> std::i
}
}
}
handler.deregister();
let mut sink = tx_mutex.lock().await;
let _ = sink.send(Message::Close(None)).await;
let _ = sink.close().await;
@ -137,7 +141,6 @@ pub async fn run_server_accept_one(addrs: &str) -> std::io::Result<()> {
Ok(())
}
pub async fn run_server(addrs: &str) -> std::io::Result<()> {
let root = tempfile::Builder::new()
.prefix("node-daemon")

@ -0,0 +1,41 @@
[package]
name = "p2p-client-ws"
version = "0.1.0"
edition = "2021"
license = "MIT/Apache-2.0"
authors = ["Niko PLP <niko@nextgraph.org>"]
description = "P2P Client module of NextGraph"
repository = "https://git.nextgraph.org/NextGraph/nextgraph-rs"
[dependencies]
debug_print = "1.0.0"
p2p-repo = { path = "../p2p-repo" }
p2p-net = { path = "../p2p-net" }
chacha20 = "0.9.0"
serde = { version = "1.0", features = ["derive"] }
serde_bare = "0.5.0"
serde_bytes = "0.11.7"
async-trait = "0.1.64"
async-std = { version = "1.12.0", features = ["attributes","unstable"] }
futures = "0.3.24"
async-channel = "1.7.1"
async-oneshot = "0.5.0"
ws_stream_wasm = "0.7"
pharos = "0.5"
wasm-bindgen = "0.2"
[dev-dependencies]
wasm-bindgen-test = "^0.3"
[target.'cfg(target_arch = "wasm32")'.dependencies.getrandom]
version = "0.2.7"
features = ["js"]
# [target.'cfg(target_arch = "wasm32")'.dependencies]
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
getrandom = "0.2.7"
xactor = "0.7.11"
async-tungstenite = { version = "0.17.2", features = ["async-std-runtime"] }
p2p-client = { path = "../p2p-client" }

@ -18,7 +18,7 @@ use p2p_repo::utils::{generate_keypair, now_timestamp};
use p2p_net::errors::*;
use p2p_net::types::*;
use p2p_net::broker_connection::*;
use crate::connection_remote::*;
use p2p_client::connection_remote::*;
use futures::{future, pin_mut, stream, SinkExt, StreamExt};
use async_tungstenite::async_std::connect_async;

@ -0,0 +1,53 @@
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
#[macro_export]
macro_rules! before {
( $self:expr, $request_id:ident, $addr:ident, $receiver:ident ) => {
let mut actor = BrokerMessageActor::new();
let $receiver = actor.receiver();
let mut $addr = actor
.start()
.await
.map_err(|_e| ProtocolError::ActorError)?;
let $request_id = $addr.actor_id();
//debug_println!("actor ID {}", $request_id);
{
let mut map = $self.actors.write().expect("RwLock poisoned");
map.insert($request_id, $addr.downgrade());
}
};
}
macro_rules! after {
( $self:expr, $request_id:ident, $addr:ident, $receiver:ident, $reply:ident ) => {
//debug_println!("waiting for reply");
$addr.wait_for_stop().await; // TODO add timeout and close connection if there's no reply
let r = $receiver.await;
if r.is_err() {
return Err(ProtocolError::Closing);
}
let $reply = r.unwrap();
//debug_println!("reply arrived {:?}", $reply);
{
let mut map = $self.actors.write().expect("RwLock poisoned");
map.remove(&$request_id);
}
};
}
//pub mod connection_ws;
#[cfg(not(target_arch = "wasm32"))]
pub mod remote_ws;
#[cfg(target_arch = "wasm32")]
pub mod remote_ws_wasm;

@ -0,0 +1,330 @@
/*
* Copyright (c) 2022-2023 Niko Bonnieure, Par le Peuple, NextGraph.org developers
* All rights reserved.
* Licensed under the Apache License, Version 2.0
* <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
* or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
* at your option. All files in the project carrying such
* notice may not be copied, modified, or distributed except
* according to those terms.
*/
//! WebSocket Remote Connection to a Broker
use std::sync::Arc;
use async_std::net::TcpStream;
use async_tungstenite::tungstenite::protocol::frame::coding::CloseCode;
use async_tungstenite::tungstenite::protocol::CloseFrame;
use async_tungstenite::WebSocketStream;
use debug_print::*;
use async_std::sync::Mutex;
use futures::io::Close;
use futures::FutureExt;
use futures::{future, pin_mut, select, stream, StreamExt};
use async_std::task;
use p2p_net::errors::*;
use p2p_net::log;
use p2p_net::types::*;
use p2p_net::utils::{spawn_and_log_error, ResultSend};
use p2p_net::{connection::*, WS_PORT};
use p2p_repo::types::*;
use p2p_repo::utils::{generate_keypair, now_timestamp};
use async_tungstenite::async_std::connect_async;
use async_tungstenite::tungstenite::{Error, Message};
pub struct ConnectionWebSocket {}
#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
impl IConnection for ConnectionWebSocket {
async fn open(
self: Arc<Self>,
ip: IP,
peer_pubk: PrivKey,
peer_privk: PubKey,
remote_peer: DirectPeerId,
) -> Result<(), NetError> {
let mut cnx = ConnectionBase::new(ConnectionDir::Client);
let url = format!("ws://{}:{}", ip, WS_PORT);
let res = connect_async(url).await;
match (res) {
Err(e) => {
debug_println!("Cannot connect: {:?}", e);
Err(NetError::ConnectionError)
}
Ok((mut websocket, _)) => {
//let ws = Arc::new(Mutex::new(Box::pin(websocket)));
// let (write, read) = ws.split();
// let mut stream_read = read.map(|msg_res| match msg_res {
// Err(e) => {
// debug_println!("READ ERROR {:?}", e);
// ConnectionCommand::Error(NetError::IoError)
// }
// Ok(message) => {
// if message.is_close() {
// debug_println!("CLOSE FROM SERVER");
// ConnectionCommand::Close
// } else {
// ConnectionCommand::Msg(
// serde_bare::from_slice::<ProtocolMessage>(&message.into_data())
// .unwrap(),
// )
// }
// }
// });
// async fn write_transform(cmd: ConnectionCommand) -> Result<Message, Error> {
// match cmd {
// ConnectionCommand::Error(_) => Err(Error::AlreadyClosed), //FIXME
// ConnectionCommand::ProtocolError(_) => Err(Error::AlreadyClosed), //FIXME
// ConnectionCommand::Close => {
// // todo close cnx. }
// Err(Error::AlreadyClosed)
// }
// ConnectionCommand::Msg(msg) => Ok(Message::binary(
// serde_bare::to_vec(&msg)
// .map_err(|_| Error::AlreadyClosed) //FIXME
// .unwrap(),
// )),
// }
// }
// let stream_write = write
// .with(|message| write_transform(message))
// .sink_map_err(|e| NetError::IoError);
// ws.close(Some(CloseFrame {
// code: CloseCode::Library(4000),
// reason: std::borrow::Cow::Borrowed(""),
// }))
// .await;
cnx.start_read_loop();
let s = cnx.take_sender();
let r = cnx.take_receiver();
//let ws_in_task = Arc::clone(&ws);
task::spawn(async move {
debug_println!("START of WS loop");
//let w = ws_in_task.lock().await;
ws_loop(websocket, s, r).await;
// .close(Some(CloseFrame {
// code: CloseCode::Library(4000),
// reason: std::borrow::Cow::Borrowed(""),
// }))
// .await;
debug_println!("END of WS loop");
});
//spawn_and_log_error(ws_loop(ws, cnx.take_sender(), cnx.take_receiver()));
log!("sending...");
// cnx.send(ConnectionCommand::Close).await;
// cnx.send(ConnectionCommand::Msg(ProtocolMessage::Start(
// StartProtocol::Auth(ClientHello::V0()),
// )))
// .await;
//cnx.close().await;
// let _ = cnx.inject(last_command).await;
// let _ = cnx.close_streams().await;
// Note that since WsMeta::connect resolves to an opened connection, we don't see
// any Open events here.
//
//assert!(evts.next().await.unwrap_throw().is_closing());
// TODO wait for close
//log!("WS closed {:?}", last_event.clone());
//Ok(cnx)
Ok(())
}
}
}
async fn accept(&mut self) -> Result<(), NetError> {
let cnx = ConnectionBase::new(ConnectionDir::Server);
Ok(())
}
fn tp(&self) -> TransportProtocol {
TransportProtocol::WS
}
}
async fn close_ws(
stream: &mut WebSocketStream<TcpStream>,
receiver: &mut Sender<ConnectionCommand>,
code: u16,
reason: &str,
) -> Result<(), NetError> {
log!("close_ws");
let _ = futures::SinkExt::send(receiver, ConnectionCommand::Close).await;
stream
.close(Some(CloseFrame {
code: CloseCode::Library(code),
reason: std::borrow::Cow::Borrowed(reason),
}))
.await
.map_err(|_e| NetError::WsError)?;
Ok(())
}
async fn ws_loop(
mut ws: WebSocketStream<TcpStream>,
sender: Receiver<ConnectionCommand>,
mut receiver: Sender<ConnectionCommand>,
) -> Result<(), NetError> {
async fn inner_loop(
stream: &mut WebSocketStream<TcpStream>,
mut sender: Receiver<ConnectionCommand>,
receiver: &mut Sender<ConnectionCommand>,
) -> Result<ProtocolError, NetError> {
//let mut rx_sender = sender.fuse();
pin_mut!(stream);
loop {
select! {
r = stream.next().fuse() => match r {
Some(Ok(msg)) => {
log!("GOT MESSAGE {:?}", msg);
if msg.is_close() {
if let Message::Close(Some(cf)) = msg {
log!("CLOSE from server: {}",cf.reason);
let last_command = match cf.code {
CloseCode::Normal =>
ConnectionCommand::Close,
CloseCode::Library(c) => {
if c < 4950 {
ConnectionCommand::ProtocolError(
ProtocolError::try_from(c - 4000).unwrap(),
)
} else {
ConnectionCommand::Error(NetError::try_from(c - 4949).unwrap())
}
},
_ => ConnectionCommand::Error(NetError::WsError)
};
let _ = futures::SinkExt::send(receiver, last_command).await;
}
else {
let _ = futures::SinkExt::send(receiver, ConnectionCommand::Close).await;
log!("CLOSE from server");
}
} else {
futures::SinkExt::send(receiver,ConnectionCommand::Msg(serde_bare::from_slice::<ProtocolMessage>(&msg.into_data())?)).await
.map_err(|_e| NetError::IoError)?;
}
return Ok(ProtocolError::Closing);
},
Some(Err(e)) => break,
None => break
},
s = sender.next().fuse() => match s {
Some(msg) => {
log!("SENDING MESSAGE {:?}", msg);
match msg {
ConnectionCommand::Msg(m) => {
if let ProtocolMessage::Start(s) = m {
futures::SinkExt::send(&mut stream, Message::binary(serde_bare::to_vec(&s)?)).await.map_err(|_e| NetError::IoError)?;
}
},
ConnectionCommand::Error(e) => {
return Err(e);
},
ConnectionCommand::ProtocolError(e) => {
return Ok(e);
},
ConnectionCommand::Close => {
break;
}
}
},
None => break
},
}
}
Ok(ProtocolError::NoError)
}
match inner_loop(&mut ws, sender, &mut receiver).await {
Ok(proto_err) => {
if proto_err == ProtocolError::Closing {
ws.close(None).await.map_err(|_e| NetError::WsError)?;
} else if proto_err == ProtocolError::NoError {
close_ws(&mut ws, &mut receiver, 1000, "").await?;
} else {
let mut code = proto_err.clone() as u16;
if code > 949 {
code = ProtocolError::OtherError as u16;
}
close_ws(&mut ws, &mut receiver, code + 4000, &proto_err.to_string()).await?;
return Err(NetError::ProtocolError);
}
}
Err(e) => {
close_ws(
&mut ws,
&mut receiver,
e.clone() as u16 + 4949,
&e.to_string(),
)
.await?;
return Err(e);
}
}
log!("END OF LOOP");
Ok(())
}
#[cfg(test)]
mod test {
use crate::remote_ws::*;
use async_std::task;
use p2p_net::broker::*;
use p2p_net::errors::NetError;
use p2p_net::log;
use p2p_net::types::IP;
use p2p_net::utils::{spawn_and_log_error, ResultSend};
use p2p_repo::utils::generate_keypair;
use std::net::IpAddr;
use std::str::FromStr;
#[async_std::test]
pub async fn test_ws() -> Result<(), NetError> {
let mut random_buf = [0u8; 32];
getrandom::getrandom(&mut random_buf).unwrap();
//spawn_and_log_error(testt("ws://127.0.0.1:3012"));
log!("start connecting");
let cnx = Arc::new(ConnectionWebSocket {});
let (priv_key, pub_key) = generate_keypair();
let broker = Broker::new();
let res = broker
.connect(
cnx,
IP::try_from(&IpAddr::from_str("127.0.0.1").unwrap()).unwrap(),
None,
priv_key,
pub_key,
pub_key,
)
.await;
log!("broker.connect : {:?}", res);
//res.expect_throw("assume the connection succeeds");
Ok(())
}
}

@ -0,0 +1,198 @@
/*
* Copyright (c) 2022-2023 Niko Bonnieure, Par le Peuple, NextGraph.org developers
* All rights reserved.
* Licensed under the Apache License, Version 2.0
* <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
* or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
* at your option. All files in the project carrying such
* notice may not be copied, modified, or distributed except
* according to those terms.
*/
//! WebSocket for Wasm Remote Connection to a Broker
use futures::FutureExt;
use futures::{future, pin_mut, select, stream, SinkExt, StreamExt};
use p2p_net::connection::*;
use p2p_net::errors::*;
use p2p_net::log;
use p2p_net::types::*;
use p2p_net::utils::*;
use p2p_net::{connection::*, WS_PORT};
use p2p_repo::types::*;
use p2p_repo::utils::{generate_keypair, now_timestamp};
use std::sync::Arc;
use {
pharos::{Filter, Observable, ObserveConfig},
wasm_bindgen::UnwrapThrowExt,
ws_stream_wasm::*,
};
pub struct ConnectionWebSocket {}
#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
impl IConnection for ConnectionWebSocket {
fn tp(&self) -> TransportProtocol {
TransportProtocol::WS
}
async fn open(
self: Arc<Self>,
ip: IP,
peer_pubk: PrivKey,
peer_privk: PubKey,
remote_peer: DirectPeerId,
) -> Result<(), NetError> {
//pub async fn testt(url: &str) -> ResultSend<()> {
let mut cnx = ConnectionBase::new(ConnectionDir::Client);
let url = format!("ws://{}:{}", ip, WS_PORT);
let (mut ws, wsio) = WsMeta::connect(url, None).await.map_err(|e| {
//log!("{:?}", e);
NetError::ConnectionError
})?;
let mut evts = ws
.observe(ObserveConfig::default())
//.observe(Filter::Pointer(WsEvent::is_closed).into())
.await
.expect_throw("observe");
//let (mut sender_tx, sender_rx) = mpsc::unbounded();
//let (mut receiver_tx, receiver_rx) = mpsc::unbounded();
cnx.start_read_loop();
spawn_and_log_error(ws_loop(ws, wsio, cnx.take_sender(), cnx.take_receiver()));
//spawn_and_log_error(read_loop(receiver_rx, sender_tx.clone()));
log!("sending...");
// cnx.send(ConnectionCommand::Close).await;
// cnx.send(ConnectionCommand::Msg(ProtocolMessage::Start(
// StartProtocol::Auth(ClientHello::V0()),
// )))
// .await;
cnx.close().await;
// Note that since WsMeta::connect resolves to an opened connection, we don't see
// any Open events here.
//
//assert!(evts.next().await.unwrap_throw().is_closing());
let last_event = evts.next().await;
log!("WS closed {:?}", last_event.clone());
let last_command = match last_event {
None => ConnectionCommand::Close,
Some(WsEvent::Open) => ConnectionCommand::Error(NetError::WsError), // this should never happen
Some(WsEvent::Error) => ConnectionCommand::Error(NetError::ConnectionError),
Some(WsEvent::Closing) => ConnectionCommand::Close,
Some(WsEvent::Closed(ce)) => {
if ce.code == 1000 {
ConnectionCommand::Close
} else if ce.code < 4000 {
ConnectionCommand::Error(NetError::WsError)
} else if ce.code < 4950 {
ConnectionCommand::ProtocolError(
ProtocolError::try_from(ce.code - 4000).unwrap(),
)
} else {
ConnectionCommand::Error(NetError::try_from(ce.code - 4949).unwrap())
}
}
Some(WsEvent::WsErr(_e)) => ConnectionCommand::Error(NetError::WsError),
};
let _ = cnx.inject(last_command).await;
let _ = cnx.close_streams().await;
//Ok(cnx)
Ok(())
}
async fn accept(&mut self) -> Result<(), NetError> {
!unimplemented!()
}
}
async fn ws_loop(
ws: WsMeta,
mut stream: WsStream,
sender: Receiver<ConnectionCommand>,
receiver: Sender<ConnectionCommand>,
) -> ResultSend<()> {
async fn inner_loop(
stream: &mut WsStream,
mut sender: Receiver<ConnectionCommand>,
mut receiver: Sender<ConnectionCommand>,
) -> Result<ProtocolError, NetError> {
//let mut rx_sender = sender.fuse();
loop {
select! {
r = stream.next().fuse() => match r {
Some(msg) => {
log!("GOT MESSAGE {:?}", msg);
if let WsMessage::Binary(b) = msg {
receiver.send(ConnectionCommand::Msg(serde_bare::from_slice::<ProtocolMessage>(&b)?)).await
.map_err(|_e| NetError::IoError)?;
}
else {
break;
}
},
None => break
},
s = sender.next().fuse() => match s {
Some(msg) => {
log!("SENDING MESSAGE {:?}", msg);
match msg {
ConnectionCommand::Msg(m) => {
if let ProtocolMessage::Start(s) = m {
stream.send(WsMessage::Binary(serde_bare::to_vec(&s)?)).await.map_err(|_e| NetError::IoError)?;
}
},
ConnectionCommand::Error(e) => {
return Err(e);
},
ConnectionCommand::ProtocolError(e) => {
return Ok(e);
},
ConnectionCommand::Close => {
break;
}
}
},
None => break
},
}
}
Ok(ProtocolError::NoError)
}
match inner_loop(&mut stream, sender, receiver).await {
Ok(proto_err) => {
if proto_err == ProtocolError::NoError {
ws.close_code(1000).await.map_err(|_e| NetError::WsError)?;
} else {
let mut code = proto_err.clone() as u16;
if code > 949 {
code = ProtocolError::OtherError as u16;
}
ws.close_reason(code + 4000, proto_err.to_string())
.await
.map_err(|_e| NetError::WsError)?;
return Err(Box::new(proto_err));
}
}
Err(e) => {
ws.close_reason(e.clone() as u16 + 4949, e.to_string())
.await
.map_err(|_e| NetError::WsError)?;
return Err(Box::new(e));
}
}
Ok(())
}

@ -17,8 +17,9 @@ serde_bare = "0.5.0"
serde_bytes = "0.11.7"
xactor = "0.7.11"
async-trait = "0.1.64"
async-std = { version = "1.7.0", features = ["attributes"] }
async-std = { version = "1.12.0", features = ["attributes"] }
futures = "0.3.24"
async-channel = "1.7.1"
async-oneshot = "0.5.0"
async-tungstenite = { version = "0.17.2", features = ["async-std-runtime"] }
snow = "0.9.2"

@ -44,4 +44,3 @@ macro_rules! after {
pub mod connection_remote;
pub mod connection_ws;

@ -17,4 +17,7 @@ num_enum = "0.5.7"
async-broadcast = "0.4.1"
futures = "0.3.24"
async-trait = "0.1.64"
blake3 = "1.3.1"
blake3 = "1.3.1"
async-std = { version = "1.12.0", features = ["attributes","unstable"] }
wasm-bindgen = "0.2"
unique_id = "0.1.5"

@ -0,0 +1,60 @@
use futures::{channel::mpsc, SinkExt};
use serde::de::DeserializeOwned;
use crate::{connection::*, errors::ProtocolError};
use std::marker::PhantomData;
pub trait BrokerRequest: DeserializeOwned {}
pub trait BrokerResponse: DeserializeOwned {
fn test(&self);
}
impl BrokerResponse for () {
fn test(&self) {}
}
pub trait IActor: EActor {
fn process_request(&self) {}
}
#[async_trait::async_trait]
pub trait EActor {
async fn handle(&mut self, cmd: ConnectionCommand);
}
pub struct Actor<'a, A: BrokerRequest, B: BrokerResponse> {
id: i64,
phantom_a: PhantomData<&'a A>,
phantom_b: PhantomData<&'a B>,
receiver: Receiver<ConnectionCommand>,
receiver_tx: Sender<ConnectionCommand>,
}
#[async_trait::async_trait]
impl<A: BrokerRequest + std::marker::Sync, B: BrokerResponse + std::marker::Sync> EActor
for Actor<'_, A, B>
{
async fn handle(&mut self, cmd: ConnectionCommand) {
let _ = self.receiver_tx.send(cmd).await;
}
}
impl<A: BrokerRequest, B: BrokerResponse> Actor<'_, A, B> {
pub fn new(id: i64) -> Self {
let (mut receiver_tx, receiver) = mpsc::unbounded::<ConnectionCommand>();
Self {
id,
receiver,
receiver_tx,
phantom_a: PhantomData,
phantom_b: PhantomData,
}
}
pub fn request(&self, msg: A, stream: Option<A>) -> Result<B, ProtocolError> {
let b: Vec<u8> = vec![];
let a = serde_bare::from_slice::<B>(&b).unwrap();
Ok(a)
}
}

@ -0,0 +1,2 @@
pub mod noise;
pub use noise::*;

@ -0,0 +1,20 @@
use crate::{actor::*, errors::ProtocolError};
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct NoiseV0 {
data: Vec<u8>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum Noise {
V0(NoiseV0),
}
impl BrokerRequest for Noise {}
impl Actor<'_, Noise, ()> {}
impl IActor for Actor<'_, Noise, ()> {
//fn process_request(&self) {}
}

@ -0,0 +1,84 @@
use crate::connection::*;
use crate::errors::*;
use crate::types::*;
use crate::utils::ResultSend;
use p2p_repo::types::{PrivKey, PubKey};
use p2p_repo::utils::generate_keypair;
use std::collections::HashMap;
use std::net::IpAddr;
use std::sync::{Arc, RwLock};
use crate::actor::*;
pub enum PeerConnection {
Core(IP),
Client(Box<Arc<dyn IConnection>>),
NONE,
}
pub struct BrokerPeerInfo {
lastPeerAdvert: Option<PeerAdvert>, //FIXME: remove Option
connected: PeerConnection,
}
pub struct DirectConnection {
ip: IP,
interface: String,
remote_peer_id: DirectPeerId,
tp: TransportProtocol,
//dir: ConnectionDir,
cnx: Box<Arc<dyn IConnection>>,
}
pub struct Broker {
//actors: Arc<RwLock<HashMap<i64, Box<dyn IActor>>>>,
direct_connections: Arc<RwLock<HashMap<IP, DirectConnection>>>,
peers: Arc<RwLock<HashMap<DirectPeerId, BrokerPeerInfo>>>,
}
impl Broker {
pub fn new() -> Self {
Broker {
direct_connections: Arc::new(RwLock::new(HashMap::new())),
peers: Arc::new(RwLock::new(HashMap::new())),
}
}
pub async fn connect(
&self,
cnx: Arc<dyn IConnection>,
ip: IP,
core: Option<String>,
peer_pubk: PrivKey,
peer_privk: PubKey,
remote_peer_id: DirectPeerId,
) -> Result<(), NetError> {
// TODO check that not already connected to peer
//IpAddr::from_str("127.0.0.1");
//cnx.open(url, peer_pubk, peer_privk).await?;
//let cnx = Arc::new();
let (priv_key, pub_key) = generate_keypair();
Arc::clone(&cnx)
.open(ip, priv_key, pub_key, remote_peer_id)
.await?;
let connected = if core.is_some() {
let dc = DirectConnection {
ip,
interface: core.unwrap(),
remote_peer_id,
tp: cnx.tp(),
cnx: Box::new(Arc::clone(&cnx)),
};
self.direct_connections.write().unwrap().insert(ip, dc);
PeerConnection::Core(ip)
} else {
PeerConnection::Client(Box::new(Arc::clone(&cnx)))
};
let bpi = BrokerPeerInfo {
lastPeerAdvert: None,
connected,
};
self.peers.write().unwrap().insert(remote_peer_id, bpi);
Ok(())
}
}

@ -0,0 +1,142 @@
static NOISE_CONFIG: &'static str = "Noise_XK_25519_ChaChaPoly_BLAKE2b";
use std::sync::Arc;
use crate::actors::*;
use crate::errors::NetError;
use crate::errors::ProtocolError;
use crate::log;
use crate::types::*;
use crate::utils::*;
use async_std::stream::StreamExt;
use futures::{channel::mpsc, select, Future, FutureExt, SinkExt};
use p2p_repo::types::{PrivKey, PubKey};
use unique_id::sequence::SequenceGenerator;
use unique_id::Generator;
use unique_id::GeneratorFromSeed;
pub type Sender<T> = mpsc::UnboundedSender<T>;
pub type Receiver<T> = mpsc::UnboundedReceiver<T>;
#[derive(Debug)]
pub enum ConnectionCommand {
Msg(ProtocolMessage),
Error(NetError),
ProtocolError(ProtocolError),
Close,
}
#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
pub trait IConnection {
async fn open(
self: Arc<Self>,
ip: IP,
peer_pubk: PrivKey,
peer_privk: PubKey,
remote_peer: DirectPeerId,
) -> Result<(), NetError>;
async fn accept(&mut self) -> Result<(), NetError>;
fn tp(&self) -> TransportProtocol;
}
#[derive(PartialEq)]
pub enum ConnectionDir {
Server,
Client,
}
pub struct ConnectionBase {
sender: Option<Receiver<ConnectionCommand>>,
receiver: Option<Sender<ConnectionCommand>>,
sender_tx: Option<Sender<ConnectionCommand>>,
receiver_tx: Option<Sender<ConnectionCommand>>,
dir: ConnectionDir,
next_request_id: SequenceGenerator,
}
impl ConnectionBase {
pub fn new(dir: ConnectionDir) -> Self {
Self {
receiver: None,
sender: None,
sender_tx: None,
receiver_tx: None,
next_request_id: SequenceGenerator::new(1),
dir,
}
}
pub fn take_sender(&mut self) -> Receiver<ConnectionCommand> {
self.sender.take().unwrap()
}
pub fn take_receiver(&mut self) -> Sender<ConnectionCommand> {
self.receiver.take().unwrap()
}
pub fn guard(&mut self, dir: ConnectionDir) -> Result<(), NetError> {
if self.dir == dir {
Ok(())
} else {
Err(NetError::DirectionAlreadySet)
}
}
async fn read_loop(
mut receiver: Receiver<ConnectionCommand>,
mut sender: Sender<ConnectionCommand>,
) -> ResultSend<()> {
while let Some(msg) = receiver.next().await {
log!("RECEIVED: {:?}", msg);
// sender
// .send(ConnectionCommand::Close)
// .await
// .map_err(|e| "channel send error")?
if let ConnectionCommand::Close = msg {
log!("EXIT READ LOOP");
break;
}
}
Ok(())
}
pub async fn request(&mut self) {
let mut id = self.next_request_id.next_id();
if self.dir == ConnectionDir::Server {
id = !id + 1;
}
// id
}
pub async fn send(&mut self, cmd: ConnectionCommand) {
let _ = self.sender_tx.as_mut().unwrap().send(cmd).await;
}
pub async fn inject(&mut self, cmd: ConnectionCommand) {
let _ = self.receiver_tx.as_mut().unwrap().send(cmd).await;
}
pub async fn close_streams(&mut self) {
let _ = self.receiver_tx.as_mut().unwrap().close_channel();
let _ = self.sender_tx.as_mut().unwrap().close_channel();
}
pub async fn close(&mut self) {
log!("closing...");
self.send(ConnectionCommand::Close).await;
}
pub fn start_read_loop(&mut self) {
let (sender_tx, sender_rx) = mpsc::unbounded();
let (receiver_tx, receiver_rx) = mpsc::unbounded();
self.sender = Some(sender_rx);
self.receiver = Some(receiver_tx.clone());
self.sender_tx = Some(sender_tx.clone());
self.receiver_tx = Some(receiver_tx);
spawn_and_log_error(Self::read_loop(receiver_rx, sender_tx));
}
}

@ -3,7 +3,7 @@
// This code is partly derived from work written by TG x Thoth from P2Pcollab.
// Copyright 2022 TG x Thoth
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
@ -11,19 +11,39 @@
use crate::types::BrokerMessage;
use core::fmt;
use num_enum::IntoPrimitive;
use num_enum::TryFromPrimitive;
use p2p_repo::object::ObjectParseError;
use p2p_repo::types::Block;
use p2p_repo::types::ObjectId;
use num_enum::IntoPrimitive;
use num_enum::TryFromPrimitive;
use std::convert::From;
use std::convert::TryFrom;
use std::error::Error;
#[derive(Debug, Eq, PartialEq, TryFromPrimitive, IntoPrimitive, Clone)]
#[repr(u16)]
pub enum NetError {
DirectionAlreadySet = 1,
WsError,
IoError,
ConnectionError,
SerializationError,
ProtocolError,
} //MAX 50 NetErrors
impl Error for NetError {}
impl fmt::Display for NetError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", self)
}
}
#[derive(Debug, Eq, PartialEq, TryFromPrimitive, IntoPrimitive, Clone)]
#[repr(u16)]
pub enum ProtocolError {
WriteError = 1,
WsError,
ActorError,
InvalidState,
SignatureError,
@ -42,9 +62,14 @@ pub enum ProtocolError {
InvalidValue,
UserAlreadyExists,
RepoIdRequired,
Closing,
ConnectionError,
}
PeerAlreadyConnected,
NoError,
OtherError,
Closing,
} //MAX 949 ProtocolErrors
impl ProtocolError {
pub fn is_stream(&self) -> bool {
@ -91,6 +116,12 @@ impl From<serde_bare::error::Error> for ProtocolError {
}
}
impl From<serde_bare::error::Error> for NetError {
fn from(e: serde_bare::error::Error) -> Self {
NetError::SerializationError
}
}
impl From<BrokerMessage> for Result<(), ProtocolError> {
fn from(msg: BrokerMessage) -> Self {
if !msg.is_response() {

@ -3,3 +3,51 @@ pub mod types;
pub mod errors;
pub mod broker_connection;
pub mod broker;
pub mod connection;
pub mod actor;
pub mod actors;
pub mod utils;
pub static WS_PORT: u16 = 1025;
#[cfg(target_arch = "wasm32")]
use wasm_bindgen::prelude::*;
#[cfg(target_arch = "wasm32")]
#[wasm_bindgen]
extern "C" {
// Use `js_namespace` here to bind `console.log(..)` instead of just
// `log(..)`
#[wasm_bindgen(js_namespace = console)]
pub fn log(s: &str);
// The `console.log` is quite polymorphic, so we can bind it with multiple
// signatures. Note that we need to use `js_name` to ensure we always call
// `log` in JS.
#[wasm_bindgen(js_namespace = console, js_name = log)]
fn log_u32(a: u32);
// Multiple arguments too!
#[wasm_bindgen(js_namespace = console, js_name = log)]
fn log_many(a: &str, b: &str);
}
#[cfg(target_arch = "wasm32")]
#[macro_export]
macro_rules! log {
// Note that this is using the `log` function imported above during
// `bare_bones`
($($t:tt)*) => (log(&format_args!($($t)*).to_string()))
}
#[cfg(not(target_arch = "wasm32"))]
#[macro_export]
macro_rules! log {
($($t:tt)*) => (debug_print::debug_println!($($t)*))
}

@ -3,17 +3,20 @@
// This code is partly derived from work written by TG x Thoth from P2Pcollab.
// Copyright 2022 TG x Thoth
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
//! P2P network protocol types
//!
//! Corresponds to the BARE schema
use core::fmt;
use std::net::IpAddr;
use crate::actors::*;
use p2p_repo::types::*;
use serde::{Deserialize, Serialize};
@ -21,16 +24,22 @@ use serde::{Deserialize, Serialize};
// COMMON TYPES FOR MESSAGES
//
/// Peer ID: public key of the node
pub type PeerId = PubKey;
pub type DirectPeerId = PubKey;
/// Peer ID: public key of the node, or an encrypted version of it
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq)]
pub enum PeerId {
DIRECT(DirectPeerId),
FORWARDED([u8; 32]),
}
/// Overlay ID
///
/// - for public overlays that need to be discovered by public key:
/// BLAKE3 hash over the repository public key
/// - for private overlays:
/// - for read overlays that need to be discovered by public key:
/// BLAKE3 hash over the repository public key (of root doc)
/// - for write overlays:
/// BLAKE3 keyed hash over the repository public key
/// - key: BLAKE3 derive_key ("NextGraph OverlayId BLAKE3 key", repo_secret)
/// - key: BLAKE3 derive_key ("NextGraph OverlayId BLAKE3 key", repo_secret, root_secret)
pub type OverlayId = Digest;
/// Overlay session ID
@ -55,16 +64,43 @@ pub type IPv4 = [u8; 4];
pub type IPv6 = [u8; 16];
/// IP address
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub enum IP {
IPv4(IPv4),
IPv6(IPv6),
}
impl fmt::Display for IP {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let t: IpAddr = self.try_into().unwrap();
write!(f, "{}", t)
}
}
impl From<&IpAddr> for IP {
#[inline]
fn from(ip: &IpAddr) -> IP {
match ip {
IpAddr::V4(v4) => IP::IPv4(v4.octets()),
IpAddr::V6(v6) => IP::IPv6(v6.octets()),
}
}
}
impl From<&IP> for IpAddr {
#[inline]
fn from(ip: &IP) -> IpAddr {
match ip {
IP::IPv4(v4) => IpAddr::from(*v4),
IP::IPv6(v6) => IpAddr::from(*v6),
}
}
}
/// IP transport protocol
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub enum IPTransportProtocol {
TLS,
pub enum TransportProtocol {
WS,
QUIC,
}
@ -73,7 +109,7 @@ pub enum IPTransportProtocol {
pub struct IPTransportAddr {
pub ip: IP,
pub port: u16,
pub protocol: IPTransportProtocol,
pub protocol: TransportProtocol,
}
/// Network address
@ -769,6 +805,7 @@ pub enum BrokerRequestContentV0 {
AddClient(AddClient),
DelClient(DelClient),
}
/// Broker request
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct BrokerRequestV0 {
@ -1532,6 +1569,20 @@ pub enum ExtResponse {
V0(ExtResponseV0),
}
///
/// PROTOCOL MESSAGES
///
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum ProtocolMessage {
Noise(Noise),
Start(StartProtocol),
ServerHello(ServerHello),
ClientAuth(ClientAuth),
AuthResult(AuthResult),
ExtResponse(ExtResponse),
BrokerMessage(BrokerMessage),
}
///
/// AUTHENTICATION MESSAGES
///

@ -0,0 +1,32 @@
use crate::log;
use async_std::task;
use futures::{channel::mpsc, select, Future, FutureExt, SinkExt};
#[cfg(target_arch = "wasm32")]
pub fn spawn_and_log_error<F>(fut: F) -> task::JoinHandle<()>
where
F: Future<Output = ResultSend<()>> + 'static,
{
task::spawn_local(async move {
if let Err(e) = fut.await {
log!("EXCEPTION {}", e)
}
})
}
#[cfg(target_arch = "wasm32")]
pub type ResultSend<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;
#[cfg(not(target_arch = "wasm32"))]
pub type ResultSend<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;
#[cfg(not(target_arch = "wasm32"))]
pub fn spawn_and_log_error<F>(fut: F) -> task::JoinHandle<()>
where
F: Future<Output = ResultSend<()>> + Send + 'static,
{
task::spawn(async move {
if let Err(e) = fut.await {
eprintln!("{}", e)
}
})
}

@ -11,10 +11,10 @@ repository = "https://git.nextgraph.org/NextGraph/nextgraph-rs"
blake3 = "1.3.1"
chacha20 = "0.9.0"
ed25519-dalek = "1.0.1"
rand = "0.7"
rand = { version = "0.7", features = ["getrandom"] }
serde = { version = "1.0.142", features = ["derive"] }
serde_bare = "0.5.0"
serde_bytes = "0.11.7"
fastbloom-rs = "0.5.3"
debug_print = "1.0.0"
hex = "0.4.3"
hex = "0.4.3"

@ -15,6 +15,7 @@
use core::fmt;
use serde::{Deserialize, Serialize};
use serde_bare::to_vec;
use std::collections::HashMap;
use std::hash::Hash;
@ -138,6 +139,34 @@ pub type BloomFilter1K = [[u8; 32]; 32];
// REPOSITORY TYPES
//
/// List of Permissions
pub enum PermissionType {
ADD_BRANCH, REMOVE_BRANCH, CHANGE_NAME,
ADD_MEMBER, REMOVE_MEMBER, CHANGE_PERMISSION,
TRANSACTION, SNAPSHOT, SHARING, CHANGE_ACK_CONFIG,
}
/// List of Identity types
pub enum Identity {
ORG_SITE(PubKey), PERSO_SITE(PubKey),
ORG_PUBLIC(PubKey), ORG_PROTECTED(PubKey), ORG_PRIVATE(PubKey),
PERSO_PUBLIC(PubKey), PERSO_PROTECTED(PubKey), PERSO_PRIVATE(PubKey),
GROUP(RepoId), DIALOG(RepoId), DOCUMENT(RepoId), DIALOG_OVERLAY(Digest),
}
/// RepoHash:
/// BLAKE3 hash of the RepoId
pub type RepoHash = Digest;
impl From<RepoHash> for String {
fn from(id: RepoHash) -> Self {
hex::encode(to_vec(&id).unwrap())
}
}
/// RepoId is a PubKey
pub type RepoId = PubKey;
/// Block ID:
/// BLAKE3 hash over the serialized Object with encrypted content
pub type BlockId = Digest;
@ -239,7 +268,7 @@ pub enum Block {
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct RepositoryV0 {
/// Repo public key ID
pub id: PubKey,
pub id: RepoId,
/// List of branches
pub branches: Vec<ObjectRef>,

@ -15,7 +15,7 @@ serde_bare = "0.5.0"
tempfile = "3"
hex = "0.4.3"
[dependencies.rkv]
[target.'cfg(not(target_arch = "wasm32"))'.dependencies.rkv]
git = "https://git.nextgraph.org/NextGraph/rkv.git"
rev = "f04b03957b52ebc802e58438f430cd569e55a24d"
features = [ "lmdb" ]

@ -187,7 +187,7 @@ impl<'a> WriteTransaction for LmdbTransaction<'a> {
}
}
#[derive(Debug)]
pub struct LmdbBrokerStore {
/// the main store where all the properties of keys are stored
main_store: MultiStore<LmdbDatabase>,

@ -1,3 +1,5 @@
#[cfg(not(target_arch = "wasm32"))]
pub mod repo_store;
#[cfg(not(target_arch = "wasm32"))]
pub mod broker_store;

@ -27,6 +27,7 @@ use rkv::{
use serde::{Deserialize, Serialize};
use serde_bare::error::Error;
#[derive(Debug)]
pub struct LmdbRepoStore {
/// the main store where all the repo blocks are stored
main_store: SingleStore<LmdbDatabase>,

Loading…
Cancel
Save