Compare commits

..

46 Commits

Author SHA1 Message Date
Lev 4e19042f5b Add requirements.txt 3 years ago
Lev a2c5059646 Name input in the python interface 3 years ago
Lev 3cef4b50d2 Improved text field in python interface, added images 3 years ago
Lev 93f83e6b4a add binary 3 years ago
Lev 3f49b7a551 Documentation 3 years ago
Lev d9afc776c0 Profile button 3 years ago
Lev ea5b473763 Made the network a separate crate 3 years ago
Lev f94c9849d6 Using a waker in rust's Degeon stream 3 years ago
Lev 8164fa9874 Integrating rust with python interface 3 years ago
Lev 05785e078c Add handling loop iteration for python 3 years ago
Alexey c712b5a7be The send button in interface 3 years ago
Alexey 82b9109585 Wrote a pygame button 3 years ago
Alexey 49824ef937 Input field in interface 3 years ago
Alexey b501b69f64 Rendering messages in interface 3 years ago
Lev 81d129ec59 Rendering active chat in interface (header) 3 years ago
Alexey 7c76981c1f Design in interface: changed colors and sizes 3 years ago
Alexey a509fb8e45 Split interface code into separate files 3 years ago
Alexey 204310e159 ActiveChat and Degeon.main_loop in interface 3 years ago
Alexey c09a392bc0 Degeon_py: started writing interface 3 years ago
Lev 8e58048c4e Move degeon protocol and worker into degeon_core 3 years ago
Alexey acb5d4f8d5 Profile screen 3 years ago
Lev 84e1b5c879 Documentation and serialization 3 years ago
Lev 0ccd0bbced Fixed the messenger 3 years ago
Lev 226b9536ce Images 3 years ago
Lev 114dd10448 README, images 3 years ago
Lev 2240ae80ae Fix some IP things for devices behind NAT and bad connections 3 years ago
Lev 68d2fb2af3 Wrote a messenger) 3 years ago
Lev c90fc8e0a6 Improved binary 3 years ago
Lev d8c85c96aa Add binary 3 years ago
Lev 3a7a320605 Serialization + main loop starter 3 years ago
Lev ae901e5009 Sending messages is now working 3 years ago
Lev 5089de2af6 The testnet can now create tunnels through IP protocol 3 years ago
Lev 53252f72b8 Sending messages 3 years ago
Lev 0c11f286f7 Tunnel generation (it's even working) 3 years ago
Lev 3e61c27317 Peer sharing in IP 3 years ago
Lev fa49945652 Wrote some logic in TestInterface, fixed some warnings 3 years ago
Lev 362102c7dc Wrote TestInterface 3 years ago
Prokhor 445b285b79 Created IPPackage header. Added receive queue 3 years ago
Alexey 9da108486c Created IPInterface constructor. Made nonblocking mainloop in IP 3 years ago
Lev 41a0842193 Cryptography in message (signing, checks), new serialization for PublicKey 3 years ago
Lev eede8b9c23 Hashing of everything 3 years ago
Lev e0e7a49520 Move test for ip from examples back to the file 3 years ago
Lev 2d6275debc Fix clippy warnings and no_std build 3 years ago
Alexey 57397e20fd Fixed incoming connections in IP interface 3 years ago
Alexey e46f611b4a Removed unfinished line 3 years ago
Alexey bb26f910ff Created IP inteface. Implemented send method 3 years ago
  1. 476
      Cargo.lock
  2. 22
      Cargo.toml
  3. 74
      README.md
  4. 9
      degeon/Cargo.toml
  5. BIN
      degeon/src/CRC35.otf
  6. 152
      degeon/src/chat.rs
  7. 35
      degeon/src/degeon_worker.rs
  8. 8
      degeon/src/gui_events.rs
  9. 72
      degeon/src/main.rs
  10. 16
      degeon/src/message.rs
  11. BIN
      degeon/src/profile_logo.png
  12. 475
      degeon/src/state.rs
  13. 65
      degeon/src/styles.rs
  14. BIN
      degeon_bin
  15. 26
      degeon_core/Cargo.toml
  16. 89
      degeon_core/src/chat.rs
  17. 388
      degeon_core/src/degeon_worker.rs
  18. 54
      degeon_core/src/gui_events.rs
  19. 44
      degeon_core/src/lib.rs
  20. 125
      degeon_core/src/message.rs
  21. 13
      degeon_py/active_chat.py
  22. 4
      degeon_py/button.py
  23. 3
      degeon_py/config.py
  24. 56
      degeon_py/degeon.py
  25. BIN
      degeon_py/degeon_core.so
  26. 15
      degeon_py/input_field.py
  27. 26
      degeon_py/message.py
  28. 1
      degeon_py/requirements.txt
  29. 51
      degeon_py/utils.py
  30. 49
      examples/test_ip_connection.rs
  31. BIN
      images/degeon_logo.png
  32. BIN
      images/dependency_graph.png
  33. 210
      images/dependency_graph.svg
  34. 674
      images/explanation.svg
  35. BIN
      images/logo.png
  36. 96
      images/logo.svg
  37. BIN
      images/scheme.png
  38. 35
      ironforce/Cargo.toml
  39. 4
      ironforce/src/bin/worker.rs
  40. 22
      ironforce/src/crypto.rs
  41. 0
      ironforce/src/interface.rs
  42. 191
      ironforce/src/interfaces/ip.rs
  43. 0
      ironforce/src/interfaces/mod.rs
  44. 76
      ironforce/src/ironforce.rs
  45. 2
      ironforce/src/lib.rs
  46. 33
      ironforce/src/message.rs
  47. 0
      ironforce/src/res.rs
  48. 9
      ironforce/src/transport.rs
  49. 4
      ironforce/src/tunnel.rs

476
Cargo.lock generated

@ -18,6 +18,12 @@ version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a13739d7177fbd22bb0ed28badfff9f372f8bef46c863db4e1c6248f6b223b6e"
[[package]]
name = "adler32"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234"
[[package]]
name = "ahash"
version = "0.4.7"
@ -37,12 +43,6 @@ dependencies = [
"xml-rs",
]
[[package]]
name = "android_glue"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "000444226fcff248f2bc4c7625be32c63caccfecc2723a2b9f78a7487a49c407"
[[package]]
name = "approx"
version = "0.5.0"
@ -221,12 +221,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e"
[[package]]
name = "cgl"
version = "0.3.2"
name = "chrono"
version = "0.4.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ced0551234e87afee12411d535648dd89d2e7f34c78b753395567aff3d447ff"
checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"
dependencies = [
"libc",
"num-integer",
"num-traits",
"time",
"winapi 0.3.9",
]
[[package]]
@ -279,22 +283,6 @@ dependencies = [
"cc",
]
[[package]]
name = "cocoa"
version = "0.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c54201c07dcf3a5ca33fececb8042aed767ee4bfd5a0235a8ceabcda956044b2"
dependencies = [
"bitflags",
"block",
"cocoa-foundation",
"core-foundation 0.9.2",
"core-graphics 0.22.3",
"foreign-types",
"libc",
"objc",
]
[[package]]
name = "cocoa"
version = "0.24.0"
@ -326,6 +314,12 @@ dependencies = [
"objc",
]
[[package]]
name = "color_quant"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
[[package]]
name = "const-oid"
version = "0.6.2"
@ -441,6 +435,15 @@ dependencies = [
"objc",
]
[[package]]
name = "crc32fast"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "738c290dfaea84fc1ca15ad9c168d083b05a714e1efddd8edaab678dc28d2836"
dependencies = [
"cfg-if 1.0.0",
]
[[package]]
name = "crossbeam-channel"
version = "0.5.1"
@ -548,14 +551,44 @@ dependencies = [
"syn",
]
[[package]]
name = "deflate"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73770f8e1fe7d64df17ca66ad28994a0a623ea497fa69486e14984e715c5d174"
dependencies = [
"adler32",
"byteorder",
]
[[package]]
name = "degeon"
version = "0.1.0"
dependencies = [
"base64",
"chrono",
"degeon_core",
"futures",
"iced",
"iced_native",
"ironforce",
"serde",
"serde_json",
]
[[package]]
name = "degeon_core"
version = "0.1.0"
dependencies = [
"base64",
"chrono",
"futures",
"iced",
"ironforce",
"pyo3",
"rand",
"serde",
"serde_json",
]
[[package]]
@ -989,7 +1022,7 @@ checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753"
dependencies = [
"cfg-if 1.0.0",
"libc",
"wasi 0.10.2+wasi-snapshot-preview1",
"wasi 0.10.0+wasi-snapshot-preview1",
]
[[package]]
@ -1068,7 +1101,7 @@ dependencies = [
"bitflags",
"gfx-auxil",
"gfx-hal",
"glow 0.7.2",
"glow",
"js-sys",
"khronos-egl",
"libloading 0.6.7",
@ -1140,14 +1173,13 @@ dependencies = [
]
[[package]]
name = "gl_generator"
version = "0.14.0"
name = "gif"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d"
checksum = "c3a7187e78088aead22ceedeee99779455b23fc231fe13ec443f99bb71694e5b"
dependencies = [
"khronos_api",
"log",
"xml-rs",
"color_quant",
"weezl",
]
[[package]]
@ -1159,18 +1191,6 @@ dependencies = [
"version_check",
]
[[package]]
name = "glow"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1625b792e2f9267116dd41eb7d325e0ea2572ceba5069451906745e04f852f33"
dependencies = [
"js-sys",
"slotmap",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "glow"
version = "0.7.2"
@ -1183,90 +1203,6 @@ dependencies = [
"web-sys",
]
[[package]]
name = "glow_glyph"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0510450eb46dd2b8e3284b50b08bfd870f1ef93a55045f54d7376fbcb63b1cb"
dependencies = [
"bytemuck",
"glow 0.6.1",
"glyph_brush",
"log",
]
[[package]]
name = "glutin"
version = "0.26.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ae1cbb9176b9151c4ce03f012e3cd1c6c18c4be79edeaeb3d99f5d8085c5fa3"
dependencies = [
"android_glue",
"cgl",
"cocoa 0.23.0",
"core-foundation 0.9.2",
"glutin_egl_sys",
"glutin_emscripten_sys",
"glutin_gles2_sys",
"glutin_glx_sys",
"glutin_wgl_sys",
"lazy_static",
"libloading 0.6.7",
"log",
"objc",
"osmesa-sys",
"parking_lot",
"wayland-client 0.28.6",
"wayland-egl",
"winapi 0.3.9",
"winit",
]
[[package]]
name = "glutin_egl_sys"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2abb6aa55523480c4adc5a56bbaa249992e2dddb2fc63dc96e04a3355364c211"
dependencies = [
"gl_generator",
"winapi 0.3.9",
]
[[package]]
name = "glutin_emscripten_sys"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "80de4146df76e8a6c32b03007bc764ff3249dcaeb4f675d68a06caf1bac363f1"
[[package]]
name = "glutin_gles2_sys"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8094e708b730a7c8a1954f4f8a31880af00eb8a1c5b5bf85d28a0a3c6d69103"
dependencies = [
"gl_generator",
"objc",
]
[[package]]
name = "glutin_glx_sys"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e393c8fc02b807459410429150e9c4faffdb312d59b8c038566173c81991351"
dependencies = [
"gl_generator",
"x11-dl",
]
[[package]]
name = "glutin_wgl_sys"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3da5951a1569dbab865c6f2a863efafff193a93caf05538d193e9e3816d21696"
dependencies = [
"gl_generator",
]
[[package]]
name = "glyph_brush"
version = "0.7.3"
@ -1395,8 +1331,6 @@ checksum = "05279cf5a83b960a1a01cff455fede8a33df720f3ff4fc9d33be79fcea9d2466"
dependencies = [
"iced_core",
"iced_futures",
"iced_glow",
"iced_glutin",
"iced_web",
"iced_wgpu",
"iced_winit",
@ -1420,34 +1354,6 @@ dependencies = [
"wasm-bindgen-futures",
]
[[package]]
name = "iced_glow"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c860ee895d4092101a4e640c4acdf8a3bac04a02b86c20888da038c46a47912"
dependencies = [
"bytemuck",
"euclid",
"glow 0.6.1",
"glow_glyph",
"glyph_brush",
"iced_graphics",
"iced_native",
"log",
]
[[package]]
name = "iced_glutin"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3be8da44dd0e796276089dbc8462490bf73f3f45bc5c04c41024ed11d577eff3"
dependencies = [
"glutin",
"iced_graphics",
"iced_native",
"iced_winit",
]
[[package]]
name = "iced_graphics"
version = "0.2.0"
@ -1514,6 +1420,7 @@ dependencies = [
"guillotiere",
"iced_graphics",
"iced_native",
"image",
"log",
"raw-window-handle 0.3.4",
"wgpu",
@ -1553,6 +1460,33 @@ dependencies = [
"unicode-normalization",
]
[[package]]
name = "image"
version = "0.23.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24ffcb7e7244a9bf19d35bf2883b9c080c4ced3c07a9895572178cdb8f13f6a1"
dependencies = [
"bytemuck",
"byteorder",
"color_quant",
"gif",
"jpeg-decoder",
"num-iter",
"num-rational",
"num-traits",
"png",
]
[[package]]
name = "include_optional"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3328afceb205b7534c691f71b12334973dbfdfe0eecb9e345bddfbb925bc55fc"
dependencies = [
"quote",
"syn",
]
[[package]]
name = "indexmap"
version = "1.7.0"
@ -1563,6 +1497,29 @@ dependencies = [
"hashbrown 0.11.2",
]
[[package]]
name = "indoc"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47741a8bc60fb26eb8d6e0238bbb26d8575ff623fdc97b1a2c00c050b9684ed8"
dependencies = [
"indoc-impl",
"proc-macro-hack",
]
[[package]]
name = "indoc-impl"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce046d161f000fffde5f432a0d034d0341dc152643b2598ed5bfce44c4f3a8f0"
dependencies = [
"proc-macro-hack",
"proc-macro2",
"quote",
"syn",
"unindent",
]
[[package]]
name = "inplace_it"
version = "0.3.3"
@ -1593,6 +1550,29 @@ version = "0.1.0"
dependencies = [
"base64",
"core-error",
"include_optional",
"rand",
"rand_os",
"rayon",
"rsa",
"serde",
"serde_cbor",
"serde_json",
"sha2",
"spin 0.9.2",
]
[[package]]
name = "ironforce_degeon"
version = "0.1.0"
dependencies = [
"base64",
"chrono",
"core-error",
"futures",
"iced",
"iced_native",
"include_optional",
"rand",
"rand_os",
"rayon",
@ -1625,6 +1605,15 @@ dependencies = [
"libc",
]
[[package]]
name = "jpeg-decoder"
version = "0.1.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "229d53d58899083193af11e15917b5640cd40b29ff475a1fe4ef725deb02d0f2"
dependencies = [
"rayon",
]
[[package]]
name = "js-sys"
version = "0.3.46"
@ -1654,12 +1643,6 @@ dependencies = [
"libloading 0.6.7",
]
[[package]]
name = "khronos_api"
version = "3.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc"
[[package]]
name = "lazy_static"
version = "1.4.0"
@ -1805,6 +1788,15 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "miniz_oxide"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "791daaae1ed6889560f8c4359194f56648355540573244a5448a83ba1ecc7435"
dependencies = [
"adler32",
]
[[package]]
name = "mio"
version = "0.6.23"
@ -2009,6 +2001,17 @@ dependencies = [
"num-traits",
]
[[package]]
name = "num-rational"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07"
dependencies = [
"autocfg 1.0.1",
"num-integer",
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.14"
@ -2111,15 +2114,6 @@ dependencies = [
"num-traits",
]
[[package]]
name = "osmesa-sys"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88cfece6e95d2e717e0872a7f53a8684712ad13822a7979bc760b9c77ec0013b"
dependencies = [
"shared_library",
]
[[package]]
name = "owned_ttf_parser"
version = "0.6.0"
@ -2163,6 +2157,25 @@ dependencies = [
"winapi 0.3.9",
]
[[package]]
name = "paste"
version = "0.1.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "45ca20c77d80be666aef2b45486da86238fabe33e38306bd3118fe4af33fa880"
dependencies = [
"paste-impl",
"proc-macro-hack",
]
[[package]]
name = "paste-impl"
version = "0.1.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d95a7db200b97ef370c8e6de0088252f7e0dfff7d047a28528e47456c0fc98b6"
dependencies = [
"proc-macro-hack",
]
[[package]]
name = "pathfinder_geometry"
version = "0.5.1"
@ -2258,6 +2271,18 @@ version = "0.3.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58893f751c9b0412871a09abd62ecd2a00298c6c83befa223ef98c52aef40cbe"
[[package]]
name = "png"
version = "0.16.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c3287920cb847dee3de33d301c463fba14dda99db24214ddf93f83d3021f4c6"
dependencies = [
"bitflags",
"crc32fast",
"deflate",
"miniz_oxide",
]
[[package]]
name = "ppv-lite86"
version = "0.2.15"
@ -2273,6 +2298,12 @@ dependencies = [
"toml",
]
[[package]]
name = "proc-macro-hack"
version = "0.5.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
[[package]]
name = "proc-macro2"
version = "1.0.32"
@ -2282,6 +2313,54 @@ dependencies = [
"unicode-xid",
]
[[package]]
name = "pyo3"
version = "0.14.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35100f9347670a566a67aa623369293703322bb9db77d99d7df7313b575ae0c8"
dependencies = [
"cfg-if 1.0.0",
"indoc",
"libc",
"parking_lot",
"paste",
"pyo3-build-config",
"pyo3-macros",
"unindent",
]
[[package]]
name = "pyo3-build-config"
version = "0.14.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d12961738cacbd7f91b7c43bc25cfeeaa2698ad07a04b3be0aa88b950865738f"
dependencies = [
"once_cell",
]
[[package]]
name = "pyo3-macros"
version = "0.14.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc0bc5215d704824dfddddc03f93cb572e1155c68b6761c37005e1c288808ea8"
dependencies = [
"pyo3-macros-backend",
"quote",
"syn",
]
[[package]]
name = "pyo3-macros-backend"
version = "0.14.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71623fc593224afaab918aa3afcaf86ed2f43d34f6afde7f3922608f253240df"
dependencies = [
"proc-macro2",
"pyo3-build-config",
"quote",
"syn",
]
[[package]]
name = "quote"
version = "1.0.10"
@ -2584,16 +2663,6 @@ dependencies = [
"opaque-debug",
]
[[package]]
name = "shared_library"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a9e7e0f2bfae24d8a5b5a66c5b257a83c7412304311512a0c054cd5e619da11"
dependencies = [
"lazy_static",
"libc",
]
[[package]]
name = "slab"
version = "0.4.5"
@ -2792,6 +2861,17 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7572415bd688d401c52f6e36f4c8e805b9ae1622619303b9fa835d531db0acae"
[[package]]
name = "time"
version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255"
dependencies = [
"libc",
"wasi 0.10.0+wasi-snapshot-preview1",
"winapi 0.3.9",
]
[[package]]
name = "tinyvec"
version = "1.5.1"
@ -2910,6 +2990,12 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
[[package]]
name = "unindent"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f14ee04d9415b52b3aeab06258a3f07093182b88ba0f9b8d203f211a7a7d41c7"
[[package]]
name = "url"
version = "2.2.2"
@ -2947,9 +3033,9 @@ checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
[[package]]
name = "wasi"
version = "0.10.2+wasi-snapshot-preview1"
version = "0.10.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
[[package]]
name = "wasm-bindgen"
@ -3095,16 +3181,6 @@ dependencies = [
"xcursor",
]
[[package]]
name = "wayland-egl"
version = "0.28.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99ba1ab1e18756b23982d36f08856d521d7df45015f404a2d7c4f0b2d2f66956"
dependencies = [
"wayland-client 0.28.6",
"wayland-sys 0.28.6",
]
[[package]]
name = "wayland-protocols"
version = "0.28.6"
@ -3183,6 +3259,12 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "weezl"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8b77fdfd5a253be4ab714e4ffa3c49caf146b4de743e97510c0656cf90f1e8e"
[[package]]
name = "wgpu"
version = "0.7.1"
@ -3326,7 +3408,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da4eda6fce0eb84bd0a33e3c8794eb902e1033d0a1d5a31bc4f19b1b4bbff597"
dependencies = [
"bitflags",
"cocoa 0.24.0",
"cocoa",
"core-foundation 0.9.2",
"core-graphics 0.22.3",
"core-video-sys",

22
Cargo.toml

@ -1,16 +1,16 @@
[package]
name = "ironforce"
name = "ironforce_degeon"
version = "0.1.0"
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[workspace]
members = ["degeon"]
members = ["degeon", "degeon_core", "ironforce"]
[features]
default = []
std = ["rayon"]
[lib]
name = "ironforce"
path = "ironforce/src/lib.rs"
[dependencies]
rand_os = "*"
@ -26,10 +26,8 @@ serde_cbor = "0.11.2"
serde_json = "1.0.72"
spin = "0.9.2"
base64 = "0.13.0"
[profile.dev.package.num-bigint-dig]
opt-level = 3
[[bin]]
name = "worker"
required-features = ["std"]
include_optional = "1.0.1"
iced = { version = "0.3.0", features = ["image"] }
futures = "0.3.18"
iced_native = "0.4.0"
chrono = "0.4.19"

74
README.md

@ -1,3 +1,75 @@
# Ironforce + Degeon
IronForce is a peer-to-peer decentralized network, Degeon is a messenger built on it.
In this repository there is three Cargo crates and one python project:
- `ironforce` is the decentralized network
- `degeon_core` contains messenger's protocol
- `degeon` is the messenger app in Rust + Iced
- `degeon_py` is a version of the messenger app in Python + Pygame
![](images/dependency_graph.png)
# Ironforce
IronForce is a decentralized network
The Ironforce network:
- Has messages encrypted and signed using RSA
- Can build efficient tunnels between two nodes
- Is anonymous: it's very hard to create a connection between a node's IP and its public key
- Can be extended to support any interface, like Bluetooth or even radio
- Can run on ARM microcontrollers
![](images/scheme.png)
### Running IronForce
The `IF` network has a worker daemon that can be used to read the broadcast messages and write them.
Also, you can us `IF` as a Rust library (as it's done in Degeon).
To get started:
1. Clone this repository: `git clone ssh://git@gitlab.ennucore.com:10022/ironforce/ironforce.git`
2. Install Rust and Cargo. The best way to do this is to use RustUp:
```shell
sudo pacman -S rustup # this is for Arch Linux, on other distros use the corresponding package manager
rustup default nightly # this will install rust & cargo (nightly toolchain)
```
3. Run the worker:
```shell
cd ironforce/ironforce
cargo build --features std # This will build ironforce network
# To run the worker, use this:
cargo run --bin worker --features std
```
# Degeon
Degeon is a messenger built on IronForce to show its abilities.
Its core is in the crate `degeon_core`. The crate can be used as a Python module or in Rust.
To build it, install Cargo and Rust, then do this:
```shell
cd ironforce/degeon_core
cargo build
```
You will find the `libdegeon_core.so` file that can be used in Python in the `target/debug` directory.
It should be renamed to `degeon_core.so`.
In Windows Rust should generate a `.dll` file that you will be able to use likewise.
There are two GUI options for this interface:
- A Rust + Iced client (preferred)
- A Pygame client
To run the Rust client, `cd` into the `degeon` folder of this repository, then use cargo:
`cargo run`.
Note: this application requires Vulkan to be installed.
For me on Arch Linux and Intel GPU the installation command was `sudo pacman -S vulkan-intel`.
On most systems it should be pre-installed.

9
degeon/Cargo.toml

@ -6,7 +6,12 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
iced = { version = "0.3", features = ["glow"] }
ironforce = { path = "../", features = ["std"] }
iced = { version = "0.3.0", features = ["image"] }
ironforce = { path = "../ironforce", features = ["std"] }
degeon_core = { path = "../degeon_core" }
base64 = "0.13.0"
serde = { version = "1.0" }
serde_json = "1.0.72"
futures = "0.3.18"
iced_native = "0.4.0"
chrono = "0.4.19"

BIN
degeon/src/CRC35.otf

Binary file not shown.

152
degeon/src/chat.rs

@ -0,0 +1,152 @@
use crate::gui_events::GuiEvent;
use crate::state;
use crate::styles::style;
pub use degeon_core::Chat;
use iced::{Align, Button, Column, Element, Length, Row, Text, TextInput};
use iced_native::button;
/// A chat that can be rendered in iced
pub trait RenderableChat {
/// Render header of the chat
fn header<'a>(name: String, bio: String) -> Element<'a, GuiEvent>;
/// Render the sending field
fn send_field<'a>(
input: String,
text_input_state: &'a mut iced::text_input::State,
send_button_state: &'a mut iced::button::State,
) -> Element<'a, GuiEvent>;
/// Render chat preview
fn preview<'a>(
&'a self,
state: &'a mut button::State,
i: usize,
is_selected: bool,
) -> Element<'a, GuiEvent>;
/// Render the chat view
fn view<'a>(
&'a self,
text_input_state: &'a mut iced::text_input::State,
send_button_state: &'a mut iced::button::State,
scroll_state: &'a mut iced::scrollable::State,
) -> Element<'a, GuiEvent>;
}
impl RenderableChat for Chat {
/// Render header of the chat
///
/// The header is a rectangle (container) with name and bio
fn header<'a>(name: String, bio: String) -> Element<'a, GuiEvent> {
iced::container::Container::new(Column::with_children(
vec![
Text::new(name.as_str()).size(20).color(iced::Color::WHITE).into(),
Text::new(bio.as_str()).size(13).color(iced::Color::from_rgb(0.6, 0.6, 0.6)).into(),
]))
.style(style::Container::Primary)
.width(Length::Fill)
.height(Length::Units(50))
.padding(10)
.into()
}
/// Render the sending field
///
/// This field consists of a text input and a send button.
/// When the button is clicked, this element creates an event
fn send_field<'a>(
input: String,
text_input_state: &'a mut iced::text_input::State,
send_button_state: &'a mut iced::button::State,
) -> Element<'a, GuiEvent> {
Row::new()
.width(Length::Fill)
.padding(15)
// Text field
.push(
TextInput::new(text_input_state, "Message", input.as_str(), |st| {
GuiEvent::Typed(st)
})
.padding(8)
.width(Length::Fill),
)
// Send button
.push(
Button::new(send_button_state, Text::new("Send"))
.on_press(GuiEvent::SendMessage)
.style(style::Button::Secondary)
.padding(20)
.width(Length::Units(80)),
)
.spacing(25)
.height(Length::Units(100))
.into()
}
/// Render chat preview
fn preview<'a>(
&'a self,
state: &'a mut button::State,
i: usize,
is_selected: bool,
) -> Element<'a, GuiEvent> {
Button::new(state, Text::new(self.profile.name.as_str()))
.width(Length::Fill)
.padding(10)
.style(if is_selected {
style::Button::Primary
} else {
style::Button::InactiveChat
})
.on_press(GuiEvent::ChatSelect(i))
.into()
}
/// Render the chat view
///
/// Chat view consists of a header, the messages and the input field
fn view<'a>(
&'a self,
text_input_state: &'a mut iced::text_input::State,
send_button_state: &'a mut iced::button::State,
scroll_state: &'a mut iced::scrollable::State,
) -> Element<'a, GuiEvent> {
let pkey_clone = self.pkey.clone();
let msgs = self
.messages
.iter()
.filter_map(move |msg| state::view_message(msg, pkey_clone.clone()))
.collect();
Column::new()
.align_items(Align::End)
.height(Length::Fill)
.width(Length::FillPortion(4))
// Header
.push(Self::header(self.profile.name.clone(), self.profile.bio.clone()))
// Messages (scrollable)
.push(
iced::Container::new(
iced::Scrollable::new(scroll_state)
.push(
Column::with_children(msgs)
.padding(20)
.spacing(10)
.align_items(Align::End),
)
.align_items(Align::End)
.width(Length::Fill),
)
.align_y(Align::End)
.height(Length::FillPortion(9)),
)
.spacing(10)
// New message field
.push(Self::send_field(
self.input.to_string(),
text_input_state,
send_button_state,
))
.into()
}
}

35
degeon/src/degeon_worker.rs

@ -0,0 +1,35 @@
use crate::gui_events::GuiEvent;
pub use degeon_core::{Degeon, DegeonData};
/// A wrapper around `degeon_core`'s `Degeon` that can create a subscription for events
pub(crate) struct DegeonContainer {
inner: Degeon,
}
impl DegeonContainer {
/// Create a new container from inner data
pub fn new(data: &Degeon) -> Self {
Self { inner: data.clone() }
}
}
impl<H, I> iced_native::subscription::Recipe<H, I> for DegeonContainer
where
H: std::hash::Hasher,
{
type Output = GuiEvent;
fn hash(&self, state: &mut H) {
use std::hash::Hash;
std::any::TypeId::of::<Self>().hash(state);
self.inner.ironforce.lock().unwrap().hash(state);
}
fn stream(
self: Box<Self>,
_input: futures::stream::BoxStream<'static, I>,
) -> futures::stream::BoxStream<'static, Self::Output> {
Box::pin(self.inner)
}
}

8
degeon/src/gui_events.rs

@ -1,6 +1,2 @@
#[derive(Clone, Debug)]
pub enum GuiEvent {
ChatSelect(usize),
Typed(String),
SendClick,
}
// Just reusing events from `degeon_core`
pub use degeon_core::{GuiEvent, AppScreen};

72
degeon/src/main.rs

@ -2,69 +2,17 @@ extern crate serde;
mod message;
mod state;
mod gui_events;
mod chat;
mod styles;
mod degeon_worker;
use iced::Sandbox;
use ironforce::res::IFResult;
use ironforce::{IronForce, Message, MessageType, PublicKey};
use crate::state::State;
fn main_if() -> IFResult<()> {
let ironforce = IronForce::from_file("".to_string())?;
let if_keys = ironforce.keys.clone();
println!(
"Our public key: {}",
base64::encode(if_keys.get_public().to_vec().as_slice())
);
let (_thread, if_mutex) = ironforce.launch_main_loop(100);
let stdin = std::io::stdin();
let if_mutex_clone = if_mutex.clone();
let if_keys_clone = if_keys.clone();
std::thread::spawn(move || loop {
if let Some(msg) = if_mutex_clone.lock().unwrap().read_message() {
println!(
"New message: {}",
String::from_utf8(msg.get_decrypted(&if_keys_clone).unwrap()).unwrap()
);
}
std::thread::sleep(std::time::Duration::from_millis(300))
});
loop {
let mut buf = String::new();
stdin.read_line(&mut buf)?;
let msg_base = if buf.starts_with('>') {
let target_base64 = buf
.split(')')
.next()
.unwrap()
.trim_start_matches(">(")
.to_string();
let target = if let Ok(res) = base64::decode(target_base64) {
res
} else {
println!("Wrong b64.");
continue;
};
buf = buf
.split(')')
.skip(1)
.map(|s| s.to_string())
.collect::<Vec<String>>()
.join(")");
Message::build()
.message_type(MessageType::SingleCast)
.recipient(&PublicKey::from_vec(target).unwrap())
} else {
Message::build().message_type(MessageType::Broadcast)
};
if_mutex
.lock()
.unwrap()
.send_to_all(msg_base.content(buf.into_bytes()).sign(&if_keys).build()?)?;
}
}
use iced::Application;
use crate::state::DegeonApp;
fn main() -> Result<(), Box<dyn std::error::Error>> {
// let ironforce = IronForce::from_file("".to_string()).unwrap();
// let _if_keys = ironforce.keys.clone();
Ok(State::run(iced::Settings::default())?)
// Change default font
let mut settings = iced::Settings::default();
settings.default_font = Some(include_bytes!("CRC35.otf"));
// Create and run the app
Ok(DegeonApp::run(settings)?)
}

16
degeon/src/message.rs

@ -1,14 +1,2 @@
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum Message {
Text(String),
File(Vec<u8>),
Service(ServiceMsg),
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum ServiceMsg {
NameRequest,
NameStatement(String),
}
/// Messages here are just from `degeon_core`
pub use degeon_core::{ProtocolMsg, Profile, DegMessage, DegMessageContent};

BIN
degeon/src/profile_logo.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

475
degeon/src/state.rs

@ -1,186 +1,99 @@
use crate::chat::Chat;
use crate::chat::RenderableChat;
use crate::degeon_worker::{Degeon, DegeonContainer};
use crate::gui_events::GuiEvent;
use crate::message::Message;
use crate::message::{DegMessage, DegMessageContent, ProtocolMsg};
use crate::styles::style;
use core::default::Default;
use degeon_core::AppScreen;
use iced::{
button, Align, Button, Column, Element, HorizontalAlignment, Length, Row, Sandbox, Settings,
Text, TextInput, VerticalAlignment,
button, Align, Application, Button, Column, Element, HorizontalAlignment, Length, Row, Text,
TextInput, VerticalAlignment,
};
use ironforce::{Keys, PublicKey};
use serde::{Deserialize, Serialize};
use ironforce::PublicKey;
#[derive(Clone, Debug)]
pub struct Chat {
pkey: PublicKey,
messages: Vec<(bool, Message)>,
name: String,
scrolled: f32,
pub input: String,
}
pub fn view_message(msg: &(bool, Message)) -> Option<Element<GuiEvent>> {
let msg = &msg.1;
match msg {
Message::Text(t) => Some(
/// Render a message into an iced `Element`
///
/// A message is a rounded rectangle with text.
/// The background color of the rectangle depends on the sender of the message.
pub fn view_message(msg: &DegMessage, pkey: PublicKey) -> Option<Element<GuiEvent>> {
let is_from_me = pkey != msg.sender;
match &msg.content {
DegMessageContent::Text(t) => Some(
iced::Row::new()
.push(
iced::Container::new(Text::new(t.as_str()))
.padding(10)
.style(style::Container::Message)
.into(),
),
Message::File(_) => None,
Message::Service(_) => None,
}
}
mod style {
use iced::container::Style;
use iced::{button, Background, Color, Vector};
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum Button {
Primary,
Secondary,
Destructive,
InactiveChat,
}
impl button::StyleSheet for Button {
fn active(&self) -> button::Style {
button::Style {
background: Some(Background::Color(match self {
Button::Primary => Color::from_rgb(0.11, 0.35, 0.75),
Button::Secondary => Color::from_rgb(0.3, 0.1, 0.7),
Button::Destructive => Color::from_rgb(0.8, 0.2, 0.2),
Button::InactiveChat => Color::from_rgb(0.3, 0.52, 0.9),
})),
border_radius: 5.0,
shadow_offset: Vector::new(1.0, 1.0),
text_color: if self != &Button::InactiveChat { Color::WHITE } else { Color::BLACK },
..button::Style::default()
}
}
}
pub enum Container {
Primary,
Message,
}
impl iced::container::StyleSheet for Container {
fn style(&self) -> Style {
iced::container::Style {
text_color: Some(Color::WHITE),
background: Some(Background::Color(match self {
Container::Primary => Color::from_rgb(18. / 256., 25. / 256., 70. / 256.),
Container::Message => Color::from_rgb(0., 0.1, 0.8),
})),
border_radius: 5.0,
border_width: 0.6,
border_color: Color::TRANSPARENT,
}
}
}
}
impl Chat {
pub fn header<'a>(name: String) -> Element<'a, GuiEvent> {
iced::container::Container::new(Text::new(name.as_str()).color(iced::Color::WHITE))
.style(style::Container::Primary)
.width(Length::Fill)
.height(Length::Units(50))
.padding(10)
.into()
}
pub fn send_field<'a>(
input: String,
text_input_state: &'a mut iced::text_input::State,
send_button_state: &'a mut iced::button::State,
) -> Element<'a, GuiEvent> {
Row::new()
.width(Length::Fill)
.padding(15)
.push(
TextInput::new(text_input_state, "Message", input.as_str(), |st| {
GuiEvent::Typed(st)
.style(if is_from_me {
style::Container::MyMessage
} else {
style::Container::NotMyMessage
})
.padding(8)
.width(Length::Fill),
)
.push(
Button::new(send_button_state, Text::new("Send"))
.on_press(GuiEvent::SendClick)
.style(style::Button::Secondary)
.padding(20)
.width(Length::Units(80)),
)
.spacing(25)
.height(Length::Units(100))
.into()
}
pub fn preview<'a>(&'a self, state: &'a mut button::State, i: usize, is_selected: bool) -> Element<'a, GuiEvent> {
Button::new(state, Text::new(self.name.as_str()))
.width(Length::Fill)
.padding(10)
.style(if is_selected { style::Button::Primary } else { style::Button::InactiveChat })
.on_press(GuiEvent::ChatSelect(i))
.into()
}
pub fn view<'a>(
&'a self,
text_input_state: &'a mut iced::text_input::State,
send_button_state: &'a mut iced::button::State,
) -> Element<'a, GuiEvent> {
let msgs = self.messages.iter().filter_map(view_message).collect();
Column::new()
.align_items(Align::End)
.height(Length::Fill)
.width(Length::FillPortion(4))
.push(Self::header(self.name.clone()))
.push(
Column::with_children(msgs)
.padding(20)
.spacing(10)
.align_items(Align::End)
.height(Length::FillPortion(9)),
.align_x(if is_from_me { Align::End } else { Align::Start }),
)
.spacing(10)
.push(Self::send_field(
self.input.to_string(),
text_input_state,
send_button_state,
))
.into()
}
pub fn example(i: usize) -> Chat {
Self {
pkey: Keys::generate().get_public(),
messages: vec![(false, Message::Text(format!("Example message {}", i)))],
name: format!("Example user ({})", i),
scrolled: 0.0,
input: "".to_string(),
}
.align_items(if is_from_me { Align::End } else { Align::Start })
.width(if is_from_me {
Length::Shrink
} else {
Length::Fill
})
.into(),
),
DegMessageContent::File(_) => None,
DegMessageContent::Service => None,
}
}
#[derive(Default, Clone, Debug)]
pub struct State {
chats: Vec<Chat>,
my_name: String,
/// The main application struct (for iced)
#[derive(Default)]
pub struct DegeonApp {
/// The container for logic, data and IF
pub data: Degeon,
/// Current screen
screen: AppScreen,
/// Selected chat (on the main screen)
selected_chat: usize,
pub send_button_state: iced::button::State,
/// Send button
send_button_state: iced::button::State,
/// Message input field
text_input_state: iced::text_input::State,
/// Buttons for chat previews
preview_button_states: Vec<button::State>,
/// Name input on profile screen
name_input_state: iced::text_input::State,
/// Bio input on profile screen
bio_input_state: iced::text_input::State,
/// Button on the profile screen
profile_done_button_state: iced::button::State,
/// Scroll state
scroll: iced::scrollable::State,
/// The button at the left-upper corner of the screen
profile_logo_state: iced::button::State,
}
impl State {
impl DegeonApp {
/// Render the list of chats with all previews
fn chat_list<'a>(
chats: &'a Vec<Chat>,
chats: &'a [Chat],
preview_button_states: &'a mut Vec<button::State>,
selected: usize
selected: usize,
profile_logo_state: &'a mut button::State,
) -> Element<'a, GuiEvent> {
while preview_button_states.len() < chats.len() {
preview_button_states.push(Default::default())
}
Column::new()
.push(
iced::Button::new(
profile_logo_state,
iced::Container::new(iced::Image::new(iced::image::Handle::from_memory(
include_bytes!("profile_logo.png").to_vec(),
)))
.width(Length::Units(25)),
)
.on_press(GuiEvent::ChangeScreen(AppScreen::ProfileEditor)),
)
.push(
Column::with_children(
chats
.iter()
@ -191,16 +104,19 @@ impl State {
)
.padding(20)
.spacing(10)
.align_items(Align::Start)
.align_items(Align::Start),
)
.width(Length::FillPortion(1))
.into()
}
/// Render active chat section
pub fn active_chat<'a>(
chats: &'a Vec<Chat>,
chats: &'a [Chat],
selected_chat: usize,
send_button_state: &'a mut button::State,
text_input_state: &'a mut iced::text_input::State,
scroll_state: &'a mut iced::scrollable::State,
) -> Element<'a, GuiEvent> {
if selected_chat >= chats.len() {
Text::new("No chat")
@ -209,62 +125,219 @@ impl State {
.width(Length::FillPortion(4))
.into()
} else {
chats[selected_chat].view(text_input_state, send_button_state)
}
}
}
impl Sandbox for State {
type Message = GuiEvent;
fn new() -> Self {
let mut st = Self::default();
st.chats = vec![Chat::example(1), Chat::example(2)];
st.preview_button_states = vec![Default::default(), Default::default()];
st
}
fn title(&self) -> String {
String::from("Degeon")
}
fn update(&mut self, message: GuiEvent) {
match message {
GuiEvent::ChatSelect(i) => self.selected_chat = i,
GuiEvent::Typed(st) => self.chats[self.selected_chat].input = st,
GuiEvent::SendClick => {
if self.chats[self.selected_chat].input.is_empty() {
return;
}
let new_msg = Message::Text(self.chats[self.selected_chat].input.clone());
self.chats[self.selected_chat].input = String::new();
self.chats[self.selected_chat]
.messages
.push((true, new_msg));
// todo
chats[selected_chat].view(text_input_state, send_button_state, scroll_state)
}
}
}
fn view(&mut self) -> Element<GuiEvent> {
impl DegeonApp {
/// Render the main screen with chat list and active chat on it
fn main_view(&mut self) -> Element<GuiEvent> {
let Self {
chats,
data: Degeon { chats, .. },
selected_chat,
send_button_state,
text_input_state,
preview_button_states,
scroll: scroll_state,
profile_logo_state,
..
} = self;
Row::new()
.padding(20)
.push(Self::chat_list(chats, preview_button_states, *selected_chat))
.push(Self::chat_list(
chats,
preview_button_states,
*selected_chat,
profile_logo_state,
))
.push(Self::active_chat(
chats,
*selected_chat,
send_button_state,
text_input_state,
scroll_state,
))
.height(Length::Fill)
.into()
}
/// Render the screen with profile settings
fn profile_editor(&mut self) -> Element<GuiEvent> {
let Self {
data:
Degeon {
profile: crate::message::Profile { name, bio },
..
},
profile_done_button_state,
name_input_state,
bio_input_state,
..
} = self;
iced::Container::new(
Column::new()
.align_items(Align::Center)
.width(Length::Fill)
.spacing(60)
.padding(40)
.push(Text::new("Profile").size(40))
// Name input
.push(
iced::Container::new(
Row::with_children(vec![
Text::new("Name").into(),
TextInput::new(name_input_state, "Name", name.as_str(), |name| {
GuiEvent::ChangeName(name)
})
.padding(5)
.into(),
])
.spacing(10)
.max_width(250)
.align_items(Align::Center),
)
.align_x(Align::Center)
.padding(40)
.style(style::Container::Field),
)
// Bio input
.push(
iced::Container::new(
Row::with_children(vec![
Text::new("Bio").into(),
TextInput::new(bio_input_state, "Bio", bio.as_str(), |bio| {
GuiEvent::ChangeBio(bio)
})
.padding(5)
.into(),
])
.spacing(10)
.max_width(250)
.align_items(Align::Center),
)
.align_x(Align::Center)
.padding(40)
.style(style::Container::Field),
)
.push(
Button::new(profile_done_button_state, Text::new("Done"))
.style(style::Button::Primary)
.on_press(GuiEvent::ChangeScreen(AppScreen::Main)),
),
)
.align_x(Align::Center)
.width(Length::Fill)
.into()
}
}
impl Application for DegeonApp {
type Executor = iced::executor::Default;
type Message = GuiEvent;
type Flags = ();
// Create a new application instance
fn new(_: ()) -> (Self, iced::Command<GuiEvent>) {
let mut data = Degeon::restore_from_file("".to_string()).unwrap();
if data.chats.is_empty() {
data.chats = vec![Chat::example(0, &data.keys), Chat::example(1, &data.keys)];
}
data.send_multicast(ProtocolMsg::Ping).unwrap();
let mut scroll: iced::scrollable::State = Default::default();
scroll.scroll_to(
1.,
iced::Rectangle::with_size(iced::Size::ZERO),
iced::Rectangle::with_size(iced::Size::UNIT),
);
let st = DegeonApp {
screen: if data.profile.name.is_empty() {
AppScreen::ProfileEditor
} else {
AppScreen::Main
},
data,
selected_chat: 0,
send_button_state: Default::default(),
text_input_state: Default::default(),
preview_button_states: vec![Default::default(), Default::default()],
name_input_state: Default::default(),
bio_input_state: Default::default(),
profile_done_button_state: Default::default(),
scroll,
profile_logo_state: Default::default(),
};
(st, iced::Command::none())
}
fn title(&self) -> String {
String::from("Degeon")
}
fn update(&mut self, message: GuiEvent, _: &mut iced::Clipboard) -> iced::Command<GuiEvent> {
self.data.process_event(&message, false).unwrap();
match message {
GuiEvent::ChatSelect(i) => self.selected_chat = i,
GuiEvent::Typed(st) => self.data.chats[self.selected_chat].input = st,
GuiEvent::SendMessage => {
if self.data.chats[self.selected_chat].input.is_empty() {
return iced::Command::none();
}
let new_msg = DegMessage::new_text(
self.data.chats[self.selected_chat].input.clone(),
&self.data.keys.get_public(),
);
self.data.chats[self.selected_chat].input = String::new();
self.data.chats[self.selected_chat]
.messages
.push(new_msg.clone());
let data_cloned = self.data.clone();
let target = self.data.chats[self.selected_chat].pkey.clone();
std::thread::spawn(move || {
data_cloned
.send_message(ProtocolMsg::NewMessage(new_msg), &target)
.unwrap()
});
self.data.save_to_file("".to_string()).unwrap();
}
GuiEvent::ChangeScreen(sc) => {
let prev_screen = self.screen;
self.screen = sc;
self.data.save_to_file("".to_string()).unwrap();
if prev_screen == AppScreen::ProfileEditor {
return self
.data
.get_broadcast_send_command(ProtocolMsg::ProfileResponse(
self.data.get_profile(),
));
}
}
GuiEvent::ChangeName(name) => self.data.profile.name = name,
GuiEvent::ChangeBio(bio) => self.data.profile.bio = bio,
// The following events are already handled in Degeon::process_event
GuiEvent::NewMessageInChat(_pkey, _msg) => {}
GuiEvent::SetProfile(_pkey, _profile) => {}
GuiEvent::None => {}
GuiEvent::WeHaveToSendProfile(target) => {
println!("WHTSP");
return self.data.get_send_command(
ProtocolMsg::ProfileResponse(self.data.get_profile()),
&target,
);
}
}
iced::Command::none()
}
fn subscription(&self) -> iced::Subscription<GuiEvent> {
iced::Subscription::from_recipe(DegeonContainer::new(&self.data))
}
fn view(&mut self) -> Element<'_, Self::Message> {
match self.screen {
AppScreen::Main => self.main_view(),
AppScreen::ProfileEditor => self.profile_editor(),
AppScreen::PeerInput => todo!(),
}
}
}

65
degeon/src/styles.rs

@ -0,0 +1,65 @@
pub mod style {
use iced::container::Style;
use iced::{button, Background, Color, Vector};
/// The styles for buttons used in the app
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum Button {
Primary,
Secondary,
#[allow(dead_code)]
Destructive,
InactiveChat,
}
impl button::StyleSheet for Button {
fn active(&self) -> button::Style {
button::Style {
background: Some(Background::Color(match self {
Button::Primary => Color::from_rgb(0.11, 0.35, 0.75),
Button::Secondary => Color::from_rgb(0.3, 0.1, 0.7),
Button::Destructive => Color::from_rgb(0.8, 0.2, 0.2),
Button::InactiveChat => Color::from_rgb(0.3, 0.52, 0.9),
})),
border_radius: 5.0,
shadow_offset: Vector::new(1.0, 1.0),
text_color: if self != &Button::InactiveChat {
Color::WHITE
} else {
Color::BLACK
},
..button::Style::default()
}
}
}
/// The styles for containers used in the app
#[derive(Copy, Clone, PartialEq)]
pub enum Container {
Primary,
MyMessage,
NotMyMessage,
Field,
}
impl iced::container::StyleSheet for Container {
fn style(&self) -> Style {
iced::container::Style {
text_color: if *self != Container::Field { Some(Color::WHITE) } else { Some(Color::BLACK) },
background: Some(Background::Color(match self {
Container::Primary => Color::from_rgb(18. / 256., 25. / 256., 70. / 256.),
Container::MyMessage => Color::from_rgb(0., 0.1, 0.8),
Container::NotMyMessage => Color::from_rgb(0., 0.1, 0.4),
Container::Field => Color::TRANSPARENT,
})),
border_radius: 5.0,
border_width: 0.9,
border_color: if *self != Container::Field {
Color::TRANSPARENT
} else {
Color::BLACK
},
}
}
}
}

BIN
degeon_bin

Binary file not shown.

26
degeon_core/Cargo.toml

@ -0,0 +1,26 @@
[package]
name = "degeon_core"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
ironforce = { path = "../ironforce", features = ["std"] }
base64 = "0.13.0"
serde = { version = "1.0" }
serde_json = "1.0.72"
futures = "0.3.18"
chrono = "0.4.19"
iced = { version = "0.3.0" }
rand = "0.8.4"
[dependencies.pyo3]
version = "0.14.0"
features = ["extension-module"]
[lib]
name = "degeon_core"
path = "src/lib.rs"
crate-type = ["cdylib", "rlib"]

89
degeon_core/src/chat.rs

@ -0,0 +1,89 @@
use crate::message::{DegMessage, DegMessageContent, Profile};
use ironforce::{Keys, PublicKey};
use pyo3::prelude::*;
use serde::{Deserialize, Serialize};
/// A chat in the messenger
///
/// Contains user's profile and all the messages
#[derive(Clone, Debug, Serialize, Deserialize)]
#[pyclass]
pub struct Chat {
/// Public key of the other user
pub pkey: PublicKey,
/// Messages in this chat
#[pyo3(get)]
pub messages: Vec<DegMessage>,
/// Profile of the other user
#[pyo3(get, set)]
pub profile: Profile,
/// Scroll position
pub scrolled: f32,
/// Message in the field
pub input: String,
}
impl Chat {
/// Create a new chat
pub fn new(pkey: PublicKey) -> Self {
Self {
pkey,
messages: vec![],
profile: Profile {
name: "".to_string(),
bio: "".to_string(),
},
scrolled: 0.0,
input: "".to_string(),
}
}
/// Create an example chat
pub fn example(i: usize, my_keys: &Keys) -> Chat {
// A dummy key
let pkey = Keys::generate().get_public();
let mut messages = vec![];
if i == 0 {
for st in vec![
"Welcome to Degeon!",
"It's a messenger app built on the IronForce decentralized network",
"Thanks to IronForce decentralized network, it's completely secure and anonymous",
"Your messages are encrypted and signed by you",
] {
messages.push(DegMessage {
sender: pkey.clone(),
timestamp: 0,
content: DegMessageContent::Text(st.to_string()),
});
}
} else {
messages.push(DegMessage {
sender: pkey.clone(),
timestamp: 0,
content: DegMessageContent::Text(
"You can save information by sending messages in this chat:".to_string(),
),
});
messages.push(DegMessage {
sender: my_keys.get_public(),
timestamp: 0,
content: DegMessageContent::Text("Example saved message".to_string()),
});
}
Self {
messages,
profile: Profile {
name: (if i == 0 {
"Degeon Info"
} else {
"Saved messages"
})
.to_string(),
bio: "".to_string(),
},
scrolled: 0.0,
pkey,
input: "".to_string(),
}
}
}

388
degeon_core/src/degeon_worker.rs

@ -0,0 +1,388 @@
use crate::chat::Chat;
use crate::gui_events::GuiEvent;
use crate::message::{Profile, ProtocolMsg};
use crate::DegMessage;
use futures::Stream;
use ironforce::res::IFResult;
use ironforce::{IronForce, Keys, Message, MessageType, PublicKey};
use pyo3::prelude::*;
use serde::{Deserialize, Serialize};
use std::pin::Pin;
use std::sync::{Arc, Mutex};
use std::task::{Context, Poll};
/// The container for logic, data, IF and protocol interactions
#[derive(Clone)]
#[pyclass]
pub struct Degeon {
/// The list of all chats for this instance
#[pyo3(get, set)]
pub chats: Vec<Chat>,
/// Profile of this user
#[pyo3(get, set)]
pub profile: Profile,
/// Keys of this user
pub keys: Keys,
/// The IF worker
pub ironforce: Arc<Mutex<IronForce>>,
}
/// Data for serialization
#[derive(Serialize, Deserialize)]
pub struct DegeonData {
pub chats: Vec<Chat>,
pub profile: Profile,
pub keys: Keys,
}
/// Load IF and launch the main loop
///
/// Returns ironforce and keys
fn get_initialized_ironforce() -> (Arc<Mutex<IronForce>>, Keys) {
let ironforce = IronForce::from_file("".to_string()).unwrap();
let keys = ironforce.keys.clone();
println!("ID: {}", keys.get_public().get_short_id());
let (_thread, ironforce) = ironforce.launch_main_loop(270);
(ironforce, keys)
}
impl Default for Degeon {
fn default() -> Self {
let (ironforce, keys) = get_initialized_ironforce();
let st = Self {
chats: vec![Chat::example(0, &keys), Chat::example(1, &keys)],
profile: Profile::default(),
keys,
ironforce,
};
let self_clone = st.clone();
std::thread::spawn(move || {
std::thread::sleep(std::time::Duration::from_secs(2));
loop {
self_clone.send_multicast(ProtocolMsg::Ping).unwrap();
std::thread::sleep(std::time::Duration::from_secs(120));
}
});
st
}
}
impl Degeon {
/// Get profile for the current user
pub fn get_profile(&self) -> Profile {
self.profile.clone()
}
/// Find a chat in the list for a given public key
pub fn chat_with(&self, pkey: &PublicKey) -> Option<usize> {
self.chats.iter().position(|chat| &chat.pkey == pkey)
}
/// Process the incoming message and act accordingly
pub fn process_message(&self, msg: ironforce::Message) -> IFResult<Option<GuiEvent>> {
let deg_msg: ProtocolMsg =
match serde_json::from_slice(msg.get_decrypted(&self.keys)?.as_slice()) {
Ok(r) => r,
Err(_) => return Ok(None),
};
let sender = msg.get_sender(&self.keys).unwrap();
println!(
"check_rec: {:?}, sender==self: {:?}",
msg.check_recipient(&self.keys),
sender == self.keys.get_public()
);
if !msg.check_recipient(&self.keys) || sender == self.keys.get_public() {
return Ok(None);
}
println!("{:?}", deg_msg);
Ok(match &deg_msg {
ProtocolMsg::NewMessage(deg_msg) => {
Some(GuiEvent::NewMessageInChat(sender, deg_msg.clone()))
}
ProtocolMsg::ProfileRequest | ProtocolMsg::Ping => {
Some(GuiEvent::WeHaveToSendProfile(sender))
}
ProtocolMsg::ProfileResponse(prof) => Some(GuiEvent::SetProfile(sender, prof.clone())),
})
}
/// Send a multicast message through the network
pub fn send_multicast(&self, msg: ProtocolMsg) -> IFResult<()> {
self.ironforce.lock().unwrap().send_to_all(
Message::build()
.message_type(MessageType::Broadcast)
.content(serde_json::to_vec(&msg)?)
.sign(&self.keys)
.build()?,
)
}
/// Send a message to a target through the network
pub fn send_message(&self, msg: ProtocolMsg, target: &PublicKey) -> IFResult<()> {
// if self.ironforce.lock().unwrap().get_tunnel(target).is_none() {
// println!("Creating a tunnel");
// self.ironforce
// .lock()
// .unwrap()
// .initialize_tunnel_creation(target)?;
// let mut counter = 0;
// while self.ironforce.lock().unwrap().get_tunnel(target).is_none() {
// std::thread::sleep(std::time::Duration::from_millis(350));
// counter += 1;
// if counter > 100 {
// return Err(IFError::TunnelNotFound);
// }
// }
// }
println!("Sending: {:?}", msg);
self.ironforce.lock().unwrap().send_to_all(
Message::build()
.message_type(MessageType::Broadcast)
.content(serde_json::to_vec(&msg)?)
.recipient(target)
.sign(&self.keys)
.build()?,
)
}
/// Created an iced command that sends a message to a target
pub fn get_send_command(
&self,
msg: ProtocolMsg,
target: &PublicKey,
) -> iced::Command<GuiEvent> {
let if_clone = self.ironforce.clone();
let target = target.clone();
let keys = self.keys.clone();
println!("Creating a send command: {:?}", msg);
iced::Command::perform(async {}, move |_| {
println!("Sending message: {:?}", msg);
if_clone
.lock()
.unwrap()
.send_to_all(
Message::build()
.message_type(MessageType::Broadcast)
.content(serde_json::to_vec(&msg).unwrap())
.recipient(&target)
.sign(&keys)
.build()
.unwrap(),
)
.unwrap();
GuiEvent::None
})
}
/// Created an iced command that sends a message to everybody
pub fn get_broadcast_send_command(
&self,
msg: ProtocolMsg,
) -> iced::Command<GuiEvent> {
let if_clone = self.ironforce.clone();
let keys = self.keys.clone();
println!("Creating a send command: {:?}", msg);
iced::Command::perform(async {}, move |_| {
println!("Sending message: {:?}", msg);
if_clone
.lock()
.unwrap()
.send_to_all(
Message::build()
.message_type(MessageType::Broadcast)
.content(serde_json::to_vec(&msg).unwrap())
.sign(&keys)
.build()
.unwrap(),
)
.unwrap();
GuiEvent::None
})
}
/// Process a GuiEvent if it's connected to the worker (like sending a message)
pub fn process_event(&mut self, event: &GuiEvent, perform_sending: bool) -> IFResult<()> {
match event {
GuiEvent::NewMessageInChat(pkey, msg) => {
if self.chat_with(&pkey).is_none() {
self.chats.push(Chat::new(pkey.clone()))
}
let ind = self.chat_with(&pkey).unwrap();
self.chats[ind].messages.push(msg.clone());
self.save_to_file("".to_string())?;
}
GuiEvent::SetProfile(pkey, profile) => {
if self.chat_with(&pkey).is_none() {
self.chats.push(Chat::new(pkey.clone()))
}
let ind = self.chat_with(&pkey).unwrap();
self.chats[ind].profile = profile.clone();
self.save_to_file("".to_string())?;
}
GuiEvent::WeHaveToSendProfile(target) if perform_sending => {
let target = target.clone();
let self_cloned = self.clone();
std::thread::spawn(move || {
self_cloned.send_message(
ProtocolMsg::ProfileResponse(self_cloned.get_profile()),
&target,
)
});
}
_ => {}
}
Ok(())
}
}
#[pymethods]
impl Degeon {
/// Create a new text message and send it (in thread)
///
/// `text` is the content of the message,
/// `chat_i` is the index of the chat in the list
pub fn send_text_message(&mut self, text: String, chat_i: usize) {
let msg = DegMessage::new_text(text, &self.keys.get_public());
self.chats[chat_i].messages.push(msg.clone());
let cloned_self = self.clone();
std::thread::spawn(move || {
cloned_self
.send_message(
ProtocolMsg::NewMessage(msg),
&cloned_self.chats[chat_i].pkey,
)
.map_err(|e| println!("There was an error in Rust: {:?}", e))
});
}
/// Handle one message
pub fn handling_loop_iteration(&mut self) {
let event = self.read_message_and_create_event();
if let Some(event) = event {
self.process_event(&event, true)
.unwrap_or_else(|e| println!("Error: {:?}", e));
}
}
/// Get length of the IF's message queue
///
/// If IF worker is locked, returns 0
pub fn message_queue_len(&self) -> usize {
self.ironforce.try_lock().map(|r| r.messages.len()).unwrap_or(0)
}
/// Check if the message was written by the current user (since `PublicKey` isn't a python type).
/// Returns True if the author of the message is the current user
pub fn check_message_ownership(&self, message: &DegMessage) -> bool {
message.sender == self.keys.get_public()
}
}
pub const DEFAULT_FILENAME: &str = ".degeon.json";
impl Degeon {
/// Store most of the necessary data to string
pub fn serialize_to_string(&self) -> serde_json::Result<String> {
let data = DegeonData {
chats: self.chats.clone(),
profile: self.get_profile(),
keys: self.keys.clone(),
};
serde_json::to_string(&data)
}
/// Restore `Degeon` from serialized data
pub fn restore_from_string(data: String) -> IFResult<Self> {
let data_res: serde_json::Result<DegeonData> = serde_json::from_str(data.as_str());
let data = match data_res {
Ok(r) => r,
Err(_) => return Ok(Self::default()),
};
let (ironforce, _keys) = get_initialized_ironforce();
ironforce.lock().unwrap().keys = data.keys.clone();
let deg = Degeon {
chats: data.chats,
profile: data.profile,
keys: data.keys,
ironforce,
};
Ok(deg)
}
/// Save to a file. If no filename is provided, the default is used
pub fn save_to_file(&self, filename: String) -> IFResult<()> {
let data = self.serialize_to_string()?;
let filename = if filename.is_empty() {
DEFAULT_FILENAME.to_string()
} else {
filename
};
std::fs::write(filename, data)?;
Ok(())
}
/// Restore from a file. If no filename is provided, the default is used
pub fn restore_from_file(filename: String) -> IFResult<Self> {
let filename = if filename.is_empty() {
DEFAULT_FILENAME.to_string()
} else {
filename
};
let content = std::fs::read_to_string(filename).unwrap_or_default();
Self::restore_from_string(content)
}
}
impl Degeon {
/// Get one message from the IF message queue and process it, resulting in an event
pub fn read_message_and_create_event(&self) -> Option<GuiEvent> {
let msg_raw = self.ironforce.lock().unwrap().read_message();
let msg = msg_raw
.as_ref()
.map(|msg| self.process_message(msg.clone()).unwrap());
if msg_raw.is_some() {
let msg_deg: ProtocolMsg = match serde_json::from_slice(
msg_raw
.as_ref()
.unwrap()
.get_decrypted(&self.keys)
.unwrap()
.as_slice(),
) {
Ok(r) => r,
Err(_) => {
println!("Couldn't deserialize {:?}", msg_raw);
return Some(GuiEvent::None);
}
};
println!("{:?} -> {:?}", msg_deg, msg);
}
match msg {
Some(Some(event)) => Some(event),
_ => None,
}
}
}
impl Stream for Degeon {
type Item = GuiEvent;
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
if self.ironforce.lock().unwrap().messages.is_empty() {
let waker = cx.waker().clone();
std::thread::spawn(move || {
std::thread::sleep(std::time::Duration::from_millis(1000));
waker.wake();
});
return Poll::Pending;
}
let timestamp_0 = std::time::Instant::now();
let msg = self.read_message_and_create_event();
if timestamp_0.elapsed() > std::time::Duration::from_millis(800) {
println!("Poll_next took {:?}", timestamp_0.elapsed());
}
Poll::Ready(Some(msg.unwrap_or(GuiEvent::None)))
}
}

54
degeon_core/src/gui_events.rs

@ -0,0 +1,54 @@
use crate::message::{DegMessage, Profile};
use ironforce::PublicKey;
use serde::{Serialize, Deserialize};
/// The screens (pages) of the app
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq)]
pub enum AppScreen {
/// The one with the chats
Main,
/// Settings and profile
ProfileEditor,
/// The screen that appears if no peers are available and asks for the IP address of at least one peer
#[allow(dead_code)]
PeerInput,
}
impl Default for AppScreen {
fn default() -> Self {
AppScreen::Main
}
}
/// An enum with all possible events for this application
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum GuiEvent {
/// Selection of a chat
ChatSelect(usize),
/// The user changed the value of "new message" field
Typed(String),
/// The user clicked "Send"
SendMessage,
/// A new messaged arrived
NewMessageInChat(PublicKey, DegMessage),
/// A profile response arrived and we should store it
SetProfile(PublicKey, Profile),
/// We should send profile (in response to profile request)
WeHaveToSendProfile(PublicKey),
/// Go to another screen
ChangeScreen(AppScreen),
/// Changed the name in the field on profile screen
ChangeName(String),
/// Changed the bio in the field on profile screen
ChangeBio(String),
/// Nothing happened
None,
}
impl GuiEvent {
pub fn get_json(&self) -> String {
serde_json::to_string(&self).unwrap()
}
}

44
degeon_core/src/lib.rs

@ -0,0 +1,44 @@
extern crate serde;
extern crate pyo3;
mod chat;
mod degeon_worker;
mod gui_events;
mod message;
pub use chat::Chat;
pub use degeon_worker::{Degeon, DegeonData, DEFAULT_FILENAME};
pub use message::{DegMessage, Profile, ProtocolMsg, DegMessageContent, DegMessageContentPy};
pub use gui_events::{AppScreen, GuiEvent};
use pyo3::prelude::*;
use pyo3::wrap_pyfunction;
#[pyfunction]
fn new_degeon() -> PyResult<Degeon> {
Ok(Degeon::restore_from_file("".to_string()).unwrap())
}
/// Check if there is stored Degeon profile here
#[pyfunction]
fn is_data_available() -> bool {
std::path::Path::new(DEFAULT_FILENAME).exists()
}
/// Create a new profile with name
#[pyfunction]
fn new_degeon_with_name(name: String) -> PyResult<Degeon> {
let mut deg = Degeon::default();
deg.profile.name = name;
Ok(deg)
}
/// Define structs and functions that are exported into python
#[pymodule]
fn degeon_core(_py: Python, m: &PyModule) -> PyResult<()> {
m.add_class::<Degeon>()?;
m.add_class::<DegMessageContentPy>()?;
m.add_class::<DegMessage>()?;
m.add_function(wrap_pyfunction!(new_degeon, m)?)?;
m.add_function(wrap_pyfunction!(is_data_available, m)?)?;
m.add_function(wrap_pyfunction!(new_degeon_with_name, m)?)?;
Ok(())
}

125
degeon_core/src/message.rs

@ -0,0 +1,125 @@
use ironforce::PublicKey;
use pyo3::prelude::*;
use serde::{Deserialize, Serialize};
/// A message in the messenger
#[derive(Clone, Debug, Serialize, Deserialize)]
#[pyclass]
pub struct DegMessage {
/// Sender's public key
pub sender: PublicKey,
/// UNIX epoch time at which this message was created
pub timestamp: i64,
/// Message content
pub content: DegMessageContent,
}
impl DegMessage {
/// Create a simple text message
pub fn new_text(text: String, my_key: &PublicKey) -> DegMessage {
Self {
sender: my_key.clone(),
timestamp: chrono::Utc::now().timestamp(),
content: DegMessageContent::Text(text),
}
}
}
#[pymethods]
impl DegMessage {
pub fn get_content_py(&self) -> DegMessageContentPy {
self.content.get_py()
}
}
/// The content of the message
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum DegMessageContent {
/// A text message
Text(String),
File(Vec<u8>),
Service,
}
/// User's profile
#[derive(Clone, Debug, Serialize, Deserialize, Default)]
#[pyclass]
pub struct Profile {
/// User's name
#[pyo3(get, set)]
pub name: String,
/// User's description
#[pyo3(get, set)]
pub bio: String,
}
/// A protocol message (that's sent through IF)
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum ProtocolMsg {
/// Requesting profile
ProfileRequest,
/// Responding to the profile request with a profile
ProfileResponse(Profile),
/// A peer discovery
Ping,
/// A message is sent
NewMessage(DegMessage),
}
/// DegMessageContent, but a struct for python
#[derive(Clone, Debug, Serialize, Deserialize)]
#[pyclass]
pub struct DegMessageContentPy {
/// If `DegMessageContent` is `Text(st)`, then this is `Some(st)`
#[pyo3(get, set)]
pub text: Option<String>,
/// If `DegMessageContent` is `File(data)`, then this is `Some(data)`
#[pyo3(get, set)]
pub file: Option<Vec<u8>>,
}
impl DegMessageContent {
/// Convert from `DegMessageContent` to the corresponding `DegMessageContentPy`
pub fn get_py(&self) -> DegMessageContentPy {
match self {
DegMessageContent::Text(text) => DegMessageContentPy {
text: Some(text.clone()),
file: None,
},
DegMessageContent::File(data) => DegMessageContentPy {
text: None,
file: Some(data.clone()),
},
DegMessageContent::Service => DegMessageContentPy {
text: None,
file: None,
},
}
}
}
impl DegMessageContentPy {
/// Convert from `DegMessageContentPy` to the corresponding `DegMessageContent`
pub fn to_enum(&self) -> DegMessageContent {
match self {
DegMessageContentPy {
text: Some(text), ..
} => DegMessageContent::Text(text.clone()),
DegMessageContentPy {
file: Some(data), ..
} => DegMessageContent::File(data.clone()),
_ => DegMessageContent::Service,
}
}
}
#[pymethods]
impl DegMessageContentPy {
#[staticmethod]
pub fn new_text(text: String) -> Self {
Self {
text: Some(text),
file: None,
}
}
}

13
degeon_py/active_chat.py

@ -26,7 +26,7 @@ class ActiveChat:
:param header_height (int): height of the header (the title)
"""
chat: dc.Chat
input_field: TextField = field(default_factory=lambda: TextField())
input_field: TextField = field(default_factory=lambda: TextField(placeholder='New message'))
send_button: Button = field(default_factory=lambda: Button(height=CHAT_PREVIEW_HEIGHT, width=100, text='Send',
top_left=(WIDTH - 112, HEIGHT - 85)))
height: int = HEIGHT
@ -44,14 +44,14 @@ class ActiveChat:
"""
return cls(chat=chat, **kwargs)
def get_messages(self) -> typing.Iterable[Message]:
def get_messages(self, core: dc.Degeon) -> typing.Iterable[Message]:
"""
Get an iterator over all messages in this chat in the backwards order
This function creates a python `message.Message` object from rust instances
:return: an iterator of `message.Message` objects
"""
for msg in reversed(self.chat.messages):
yield Message(text=msg.get_content_py().text, is_from_me=False)
yield Message(text=msg.get_content_py().text, is_from_me=core.check_message_ownership(msg))
def get_header(self) -> pygame.Surface:
"""
@ -66,7 +66,7 @@ class ActiveChat:
surface.blit(name_surface, (20, 20))
return surface
def render(self) -> pygame.Surface:
def render(self, core: dc.Degeon) -> pygame.Surface:
"""
Creates a pygame surface and draws the chat on it
:return: the surface with the chat on it
@ -77,9 +77,10 @@ class ActiveChat:
# Render messages
# This is the y0 for the last message
last_message_y = self.height - MESSAGE_HEIGHT * 2
for i, message in zip(range(30), self.get_messages()):
for i, message in zip(range(30), self.get_messages(core)):
msg_surface = message.render()
surface.blit(msg_surface, (0, last_message_y - (MESSAGE_HEIGHT + 30) * (i + 1)))
last_message_y -= msg_surface.get_height() + 30
surface.blit(msg_surface, (0, last_message_y - 30))
# Render header
header = self.get_header()
surface.blit(header, (0, 10))

4
degeon_py/button.py

@ -87,7 +87,7 @@ class Button:
# if this is a mouse event
if event.type in [pygame.MOUSEBUTTONUP, pygame.MOUSEMOTION, pygame.MOUSEBUTTONDOWN]:
# if the mouse event is inside the button
if self.top_left[0] <= event.pos[0] < self.bottom and self.top_left[1] <= event.pos[1] < self.right:
if self.top_left[0] <= event.pos[0] < self.right and self.top_left[1] <= event.pos[1] < self.bottom:
if event.type == pygame.MOUSEBUTTONUP:
self.is_pressed = False
return True
@ -96,4 +96,6 @@ class Button:
self.is_pressed = True
elif event.type == pygame.MOUSEMOTION:
self.is_hovered = True
else:
self.is_hovered = False
return False

3
degeon_py/config.py

@ -3,7 +3,8 @@ import pygame
pygame.init()
# Fontss
# Fonts
font_medium = pygame.font.Font('CRC35.otf', 25)
font = pygame.font.Font('CRC35.otf', 32)
font_large = pygame.font.Font('CRC35.otf', 50)

56
degeon_py/degeon.py

@ -4,23 +4,34 @@ from dataclasses import dataclass, field
import typing
import pygame
from button import Button
from chat_selector import ChatSelector
from active_chat import ActiveChat
from config import FPS, DARKER_BLUE, font, WHITE, WIDTH, CHAT_SELECTOR_WIDTH, HEIGHT
import degeon_core as dc
from input_field import TextField
@dataclass
class Degeon:
"""
The main class with everything connected to the app: the data, the
Attributes
Attributes:
:param core (dc.Degeon): the rust worker
:param chat_selector (ChatSelector): chat list widget
:param active_chat (typing.Optional[ActiveChat]): current chat widget
:param fps (int): FPS rate
"""
core: 'dc.Degeon'
core: typing.Optional[dc.Degeon]
chat_selector: ChatSelector
active_chat: typing.Optional[ActiveChat] = None
name_input_field: TextField = field(
default_factory=lambda: TextField(placeholder='Name', top_left_corner=(WIDTH // 5, HEIGHT // 3)))
name_input_button: Button = field(default_factory=lambda: Button(text='Done', width=200, height=100,
top_left=(round(2 * WIDTH / 5), 2 * HEIGHT // 3)))
has_profile_popup_opened: bool = False
has_no_peers_popup: bool = False
clock: pygame.time.Clock = field(default_factory=pygame.time.Clock)
@ -32,19 +43,38 @@ class Degeon:
Create a new default instance with settings from file
:return: a Degeon instance
"""
if dc.is_data_available():
core: dc.Degeon = dc.new_degeon()
else:
# core: dc.Degeon = dc.new_degeon_with_name(input('Enter name: '))
core = None
chat_selector = ChatSelector()
return cls(core=core, chat_selector=chat_selector)
def register(self, name: str):
"""
Create new rust-Degeon instance with name
:param name: user's name
"""
self.core = dc.new_degeon_with_name(name)
self.chat_selector.chats = self.core.chats
def render(self, screen: pygame.Surface):
"""
Render everything on the screen
:param screen: the main screen
"""
if self.core is None:
screen.fill(DARKER_BLUE)
button_surface: pygame.Surface = self.name_input_button.render()
screen.blit(button_surface, self.name_input_button.top_left)
name_field_surface: pygame.Surface = self.name_input_field.render()
screen.blit(name_field_surface, self.name_input_field.top_left_corner)
return
chats_surface = self.chat_selector.render()
screen.blit(chats_surface, (0, 0))
if self.active_chat is not None:
active_chat_surface = self.active_chat.render()
active_chat_surface = self.active_chat.render(self.core)
screen.blit(active_chat_surface, (self.active_chat.delta_x, 0))
else:
text_surface: pygame.Surface = font.render('<- Select chat in the menu', True, WHITE)
@ -55,30 +85,46 @@ class Degeon:
"""
Do all the necessary Rust work
"""
pass # todo
if self.core is None:
return
while self.core.message_queue_len():
self.core.handling_loop_iteration()
def tick(self):
"""
Handle incoming messages, update chats, create no_peers popup if necessary
"""
if self.core is None:
return
# process events in core
self.process_core_messages()
if self.core is not None:
self.chat_selector.chats = self.core.chats
if 0 <= self.chat_selector.active_chat < len(self.chat_selector.chats) and self.active_chat is None:
self.active_chat = ActiveChat.new(self.chat_selector.chats[self.chat_selector.active_chat])
if self.active_chat is not None and self.core is not None:
self.active_chat.chat = self.core.chats[self.chat_selector.active_chat]
def process_event(self, event: pygame.event.Event):
"""
Process an event
:param event: pygame event
"""
if self.core is None:
self.name_input_field.process_event(event)
if self.name_input_button.process_event(event):
self.register(self.name_input_field.value)
return
if self.chat_selector.process_event(event):
if 0 <= self.chat_selector.active_chat < len(self.chat_selector.chats):
self.active_chat = ActiveChat.new(self.chat_selector.chats[self.chat_selector.active_chat])
else:
self.active_chat = None
if self.active_chat is not None:
self.active_chat.process_event(event)
# If the result is a string, it's a message
result: typing.Optional[str] = self.active_chat.process_event(event)
if result:
self.core.send_text_message(result, self.chat_selector.active_chat)
def main_loop(self, screen: pygame.Surface):
"""

BIN
degeon_py/degeon_core.so

Binary file not shown.

15
degeon_py/input_field.py

@ -1,8 +1,10 @@
import time
import typing
import pygame
from config import font, MESSAGE_HEIGHT, WIDTH, CHAT_SELECTOR_WIDTH, HEIGHT, WHITE, DARKER_BLUE, GREY
from utils import render_text
from dataclasses import dataclass
@ -30,7 +32,7 @@ class TextField:
padding = 5
pygame.draw.rect(surface, DARKER_BLUE, (padding, padding, self.width - padding * 2, self.height - padding * 2))
if not self.value and self.placeholder:
placeholder_text: pygame.Surface = font.render(self.placeholder, True, GREY)
placeholder_text: pygame.Surface = font.render(self.placeholder, True, (GREY + WHITE) // 2)
surface.blit(
placeholder_text,
(
@ -39,14 +41,7 @@ class TextField:
)
)
if self.value:
message_text: pygame.Surface = font.render(self.value, True, WHITE)
surface.blit(
message_text,
(
40, # or, if we want to center, `(self.width - message_text.get_width()) // 2,`
(self.height - message_text.get_height()) // 2
)
)
render_text(surface, (10, 10), font, self.value, WHITE, self.cursor_position if time.time() % 1 < 0.5 else None)
return surface
def process_event(self, event: pygame.event.Event):
@ -69,7 +64,7 @@ class TextField:
elif event.unicode:
self.value = self.value[:self.cursor_position] + event.unicode + self.value[self.cursor_position:]
self.cursor_position += 1
# print(self.is_focused, event.type, getattr(event, 'key', None), getattr(event, 'unicode', None), self.value)
self.cursor_position = max(0, min(self.cursor_position, len(self.value)))
def collect(self) -> str:
"""

26
degeon_py/message.py

@ -4,7 +4,8 @@ from dataclasses import dataclass
import pygame
from config import WIDTH, CHAT_SELECTOR_WIDTH, MESSAGE_HEIGHT, font, BLUE, DARK_BLUE, WHITE, DARKER_BLUE
from config import WIDTH, CHAT_SELECTOR_WIDTH, MESSAGE_HEIGHT, font_medium, BLUE, DARK_BLUE, WHITE, DARKER_BLUE
from utils import render_text
@dataclass
@ -27,16 +28,21 @@ class Message:
Creates a surface with a rectangle and the message text written on it
:return: the surface with rendered message
"""
surface = pygame.Surface((self.chat_width, self.height))
surface = pygame.Surface((self.chat_width, self.height * 10))
surface.fill(DARKER_BLUE)
bg_color = BLUE * self.is_from_me + DARK_BLUE * (not self.is_from_me)
text_surface: pygame.Surface = font.render(self.text, True, WHITE)
padding = 5
padding = 7
# Size of the scaled text surface
blit_height: int = self.height - padding * 2
blit_width: int = round(text_surface.get_width() * blit_height / text_surface.get_height())
x: int = 0 if not self.is_from_me else self.chat_width - blit_width - padding * 2
pygame.draw.rect(surface, bg_color, (x, 0, blit_width + padding * 2, self.height))
text_surface: pygame.Surface = pygame.transform.smoothscale(text_surface, (blit_width, blit_height))
surface.blit(text_surface, (x + padding, padding))
return surface
blit_width: int = self.chat_width // 2
x: int = 0 if not self.is_from_me else self.chat_width - blit_width - padding * 3.5
pygame.draw.rect(surface, bg_color, (x, 0, blit_width + padding * 2, self.height * 10))
text_surface: pygame.Surface = pygame.Surface((blit_width, blit_height * 10))
text_surface.fill(bg_color)
text_height = render_text(text_surface, (0, 0), font_medium, self.text, WHITE)
# text_surface = pygame.transform.chop(text_surface, (0, 0, blit_width, text_height))
surface.blit(text_surface, (x + padding, padding, blit_width, text_height))
new_surface = pygame.Surface((self.chat_width, text_height + 2 * padding))
new_surface.fill(bg_color)
new_surface.blit(surface, (0, 0, new_surface.get_width(), new_surface.get_height()))
return new_surface

1
degeon_py/requirements.txt

@ -0,0 +1 @@
pygame

51
degeon_py/utils.py

@ -0,0 +1,51 @@
from typing import Optional
import typing
import pygame
def draw_cursor(surface, cur_position, high, c, size=4):
pygame.draw.line(surface, c,
cur_position,
(cur_position[0], cur_position[1] + high), size)
def get_word_length(ft, word: str):
word_surface = ft.render(word, 0, pygame.Color('black'))
return word_surface.get_size()
def render_text(surface, position: typing.Tuple[int, int], font: pygame.font, text: str, c=pygame.Color('black'),
cursor_position: Optional[int] = None) -> int:
"""Render text with hyphenation"""
if len(list(filter(bool, text.split()))) == 0:
return 0
need_cursor = cursor_position is not None
lines = [word.split() for word in text.splitlines()]
space_size = font.size(' ')[0]
max_width, max_height = surface.get_size()
x, y = position
symbols_counter = 0
for line in lines:
for word in line:
w, h = get_word_length(font, word)
if w > max_width or h > max_height:
raise ValueError("Surface is too small")
if x + w >= max_width:
x = position[0]
y += h
surface.blit(font.render(word, 0, c), (x, y))
if need_cursor and symbols_counter + len(word) >= cursor_position:
# It means that cursor is in this word or after this word
cur_coord = (x + get_word_length(font,
word[:cursor_position - symbols_counter])[0], y)
draw_cursor(surface, cur_coord, h, c)
need_cursor = False
symbols_counter += len(word) + 1
x += w + space_size
y += h
x = position[0]
return y

49
examples/test_ip_connection.rs

@ -1,49 +0,0 @@
#[cfg(feature = "std")]
use ironforce::interface::Interface;
#[cfg(feature = "std")]
use ironforce::interfaces::ip::IPInterface;
#[cfg(feature = "std")]
use std::net;
use std::thread;
use std::time::Duration;
#[cfg(feature = "std")]
use ironforce::res::IFResult;
#[cfg(feature = "std")]
fn main() {
println!("hello");
test();
}
#[cfg(feature = "std")]
fn test() -> IFResult<()> {
let message = *b"Hello world from iron forest";
let mut interface1 = IPInterface::new()?;
let mut interface2 = IPInterface::new()?;
let t1 = std::thread::spawn(move || {
interface1.send(&message, Some(String::from("127.0.0.1:50001"))).unwrap();
interface1
});
thread::sleep(Duration::from_millis(10));
let t2 = std::thread::spawn(move || {
interface2.main_loop_iteration().unwrap();
interface2
});
let res1 = t1.join();
match res1 {
Ok(_) => println!("Ok"),
Err(e) => println!("{:?}", e)
}
let res2 = t2.join();
match res2 {
Ok(_) => println!("Ok"),
Err(e) => println!("{:?}", e)
}
Ok(())
}
#[cfg(not(feature = "std"))]
fn main() {}

BIN
images/degeon_logo.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 422 KiB

BIN
images/dependency_graph.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 898 KiB

210
images/dependency_graph.svg

@ -0,0 +1,210 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="250mm"
height="240mm"
viewBox="0 0 250 240"
version="1.1"
id="svg9118"
inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20, custom)"
sodipodi:docname="dependency_graph.svg"
inkscape:export-filename="/home/ennucore/dev/ironforce/images/dependency_graph.png"
inkscape:export-xdpi="1121.49"
inkscape:export-ydpi="1121.49"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview9120"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:document-units="mm"
showgrid="false"
inkscape:zoom="0.64765011"
inkscape:cx="217.71015"
inkscape:cy="561.25984"
inkscape:window-width="1920"
inkscape:window-height="1008"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="layer1"
height="250mm" />
<defs
id="defs9115">
<marker
style="overflow:visible"
id="Arrow1Lend"
refX="0"
refY="0"
orient="auto"
inkscape:stockid="Arrow1Lend"
inkscape:isstock="true">
<path
transform="matrix(-0.8,0,0,-0.8,-10,0)"
style="fill:context-stroke;fill-rule:evenodd;stroke:context-stroke;stroke-width:1pt"
d="M 0,0 5,-5 -12.5,0 5,5 Z"
id="path13831" />
</marker>
<marker
style="overflow:visible"
id="Arrow1Lstart"
refX="0"
refY="0"
orient="auto"
inkscape:stockid="Arrow1Lstart"
inkscape:isstock="true">
<path
transform="matrix(0.8,0,0,0.8,10,0)"
style="fill:context-stroke;fill-rule:evenodd;stroke:context-stroke;stroke-width:1pt"
d="M 0,0 5,-5 -12.5,0 5,5 Z"
id="path13828" />
</marker>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient9344"
id="linearGradient9338"
x1="83.937424"
y1="159.45372"
x2="145.70277"
y2="112.71262"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.49339231,0,0,0.49339231,-105.19621,-43.347381)" />
<linearGradient
inkscape:collect="always"
id="linearGradient9344">
<stop
style="stop-color:#ab4251;stop-opacity:1"
offset="0"
id="stop9340" />
<stop
style="stop-color:#ffffff;stop-opacity:1"
offset="1"
id="stop9342" />
</linearGradient>
</defs>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<rect
style="fill:none;stroke:#282e46;stroke-width:2"
id="rect9201"
width="140.00172"
height="47.340633"
x="61.117081"
y="19.290443"
ry="3.7234547" />
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:16.0891px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.402227"
x="101.82484"
y="48.617577"
id="text10363"><tspan
sodipodi:role="line"
id="tspan10361"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'Inconsolata LGC';-inkscape-font-specification:'Inconsolata LGC';stroke-width:0.402227"
x="101.82484"
y="48.617577">IronForce</tspan></text>
<rect
style="fill:none;stroke:#282e46;stroke-width:2"
id="rect12337"
width="140.00172"
height="47.340633"
x="60.334785"
y="91.636803"
ry="3.7234547" />
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:16.0891px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.402227"
x="78.434021"
y="120.14689"
id="text12341"><tspan
sodipodi:role="line"
id="tspan12339"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'Inconsolata LGC';-inkscape-font-specification:'Inconsolata LGC';stroke-width:0.402227"
x="78.434021"
y="120.14689">Degeon Core</tspan></text>
<rect
style="fill:none;stroke:#282e46;stroke-width:1.54988"
id="rect12445"
width="108.49297"
height="36.686161"
x="7.193059"
y="174.48308"
ry="2.8854549" />
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:9.87778px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.311702"
x="21.218885"
y="196.57671"
id="text12449"><tspan
sodipodi:role="line"
id="tspan12447"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:9.87778px;font-family:'Inconsolata LGC';-inkscape-font-specification:'Inconsolata LGC';stroke-width:0.311702"
x="21.218885"
y="196.57671">Rust Interface</tspan></text>
<rect
style="fill:none;stroke:#282e46;stroke-width:1.54988"
id="rect13683"
width="108.49297"
height="36.686161"
x="135.25137"
y="174.48308"
ry="2.8854549" />
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:9.87778px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.311702"
x="142.9272"
y="196.57671"
id="text13687"><tspan
sodipodi:role="line"
id="tspan13685"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:9.87778px;font-family:'Inconsolata LGC';-inkscape-font-specification:'Inconsolata LGC';stroke-width:0.311702"
x="142.9272"
y="196.57671">Python Interface</tspan></text>
<path
style="fill:none;stroke:#000000;stroke-width:1.2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#Arrow1Lend)"
d="M 59.025518,171.39275 128.53989,142.25592"
id="path13824" />
<path
style="fill:none;stroke:#000000;stroke-width:1.2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#Arrow1Lend)"
d="M 185.9168,170.63863 128.53989,142.25592"
id="path13826" />
<path
style="fill:none;stroke:#000000;stroke-width:1.2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#Arrow1Lend)"
d="M 131.85776,89.509589 V 68.933222"
id="path14217" />
<g
id="g44157"
transform="matrix(0.50052676,0,0,0.50052676,110.21806,30.396512)"
inkscape:export-filename="/home/ennucore/dev/ironforce/scheme.png"
inkscape:export-xdpi="1121.49"
inkscape:export-ydpi="1121.49">
<circle
style="fill:#282e46;fill-opacity:1;stroke-width:0.130543"
id="path3611"
cx="-50.126827"
cy="23.901407"
r="26.844465" />
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:33.1511px;line-height:1.25;font-family:sans-serif;fill:url(#linearGradient9338);fill-opacity:1;stroke:none;stroke-width:0.207194"
x="-65.295166"
y="35.411083"
id="text4421"><tspan
sodipodi:role="line"
id="tspan4419"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:33.1511px;font-family:'Intro Rust G';-inkscape-font-specification:'Intro Rust G';fill:url(#linearGradient9338);fill-opacity:1;stroke-width:0.207194"
x="-65.295166"
y="35.411083">IF</tspan></text>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 8.0 KiB

674
images/explanation.svg

@ -0,0 +1,674 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="297mm"
height="210mm"
viewBox="0 0 297 210"
version="1.1"
id="svg5"
inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20, custom)"
sodipodi:docname="explanation.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview7"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:document-units="mm"
showgrid="false"
inkscape:zoom="0.99504919"
inkscape:cx="558.26386"
inkscape:cy="397.46779"
inkscape:window-width="1920"
inkscape:window-height="1008"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="layer1" />
<defs
id="defs2">
<marker
style="overflow:visible;"
id="Arrow1Mend"
refX="0.0"
refY="0.0"
orient="auto"
inkscape:stockid="Arrow1Mend"
inkscape:isstock="true">
<path
transform="scale(0.4) rotate(180) translate(10,0)"
style="fill-rule:evenodd;fill:context-stroke;stroke:context-stroke;stroke-width:1.0pt;"
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
id="path9101" />
</marker>
<marker
style="overflow:visible"
id="Arrow1Lend"
refX="0"
refY="0"
orient="auto"
inkscape:stockid="Arrow1Lend"
inkscape:isstock="true">
<path
transform="matrix(-0.8,0,0,-0.8,-10,0)"
style="fill:context-stroke;fill-rule:evenodd;stroke:context-stroke;stroke-width:1pt"
d="M 0,0 5,-5 -12.5,0 5,5 Z"
id="path9095" />
</marker>
<marker
style="overflow:visible"
id="Arrow1Lend-9"
refX="0"
refY="0"
orient="auto"
inkscape:stockid="Arrow1Lend"
inkscape:isstock="true">
<path
transform="matrix(-0.8,0,0,-0.8,-10,0)"
style="fill:context-stroke;fill-rule:evenodd;stroke:context-stroke;stroke-width:1pt"
d="M 0,0 5,-5 -12.5,0 5,5 Z"
id="path9095-1" />
</marker>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient9344"
id="linearGradient9338"
x1="83.937424"
y1="159.45372"
x2="145.70277"
y2="112.71262"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.49339231,0,0,0.49339231,-105.19621,-43.347381)" />
<linearGradient
inkscape:collect="always"
id="linearGradient9344">
<stop
style="stop-color:#ab4251;stop-opacity:1"
offset="0"
id="stop9340" />
<stop
style="stop-color:#ffffff;stop-opacity:1"
offset="1"
id="stop9342" />
</linearGradient>
</defs>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<rect
style="fill:none;stroke:#1a5fb4;stroke-width:0.450001"
id="rect846"
width="106.5354"
height="93.487419"
x="8.5933933"
y="5.2217455"
ry="8.0541668"
inkscape:export-filename="/home/ennucore/dev/ironforce/scheme.png"
inkscape:export-xdpi="1121.49"
inkscape:export-ydpi="1121.49" />
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:9.34861px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.247343"
x="17.57098"
y="46.311989"
id="text2360"
inkscape:export-filename="/home/ennucore/dev/ironforce/scheme.png"
inkscape:export-xdpi="1121.49"
inkscape:export-ydpi="1121.49"><tspan
sodipodi:role="line"
id="tspan2358"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:9.34861px;font-family:'Inconsolata LGC';-inkscape-font-specification:'Inconsolata LGC';stroke-width:0.247343"
x="17.57098"
y="46.311989">IronForce worker</tspan></text>
<g
id="g44157"
transform="matrix(0.50052676,0,0,0.50052676,86.42112,10.813918)"
inkscape:export-filename="/home/ennucore/dev/ironforce/scheme.png"
inkscape:export-xdpi="1121.49"
inkscape:export-ydpi="1121.49">
<circle
style="fill:#282e46;fill-opacity:1;stroke-width:0.130543"
id="path3611"
cx="-50.126827"
cy="23.901407"
r="26.844465" />
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:33.1511px;line-height:1.25;font-family:sans-serif;fill:url(#linearGradient9338);fill-opacity:1;stroke:none;stroke-width:0.207194"
x="-65.295166"
y="35.411083"
id="text4421"><tspan
sodipodi:role="line"
id="tspan4419"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:33.1511px;font-family:'Intro Rust G';-inkscape-font-specification:'Intro Rust G';fill:url(#linearGradient9338);fill-opacity:1;stroke-width:0.207194"
x="-65.295166"
y="35.411083">IF</tspan></text>
</g>
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:9.89368px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.247343"
x="177.74512"
y="23.484995"
id="text2360-3"
inkscape:export-filename="/home/ennucore/dev/ironforce/scheme.png"
inkscape:export-xdpi="1121.49"
inkscape:export-ydpi="1121.49"><tspan
sodipodi:role="line"
id="tspan2358-6"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'Inconsolata LGC';-inkscape-font-specification:'Inconsolata LGC';stroke-width:0.247343"
x="177.74512"
y="23.484995">Transport</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:9.89368px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.247343"
x="150.64513"
y="113.5675"
id="text2360-3-9"
inkscape:export-filename="/home/ennucore/dev/ironforce/scheme.png"
inkscape:export-xdpi="1121.49"
inkscape:export-ydpi="1121.49"><tspan
sodipodi:role="line"
id="tspan2358-6-3"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'Inconsolata LGC';-inkscape-font-specification:'Inconsolata LGC';stroke-width:0.247343"
x="150.64513"
y="113.5675">Tunnel</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:5.29167px;line-height:1.4;font-family:'Inconsolata LGC';-inkscape-font-specification:'Inconsolata LGC';white-space:pre;inline-size:71.6192;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583"
x="33.883114"
y="151.60315"
id="text4446"
transform="translate(-15.875,-84.666647)"
inkscape:export-filename="/home/ennucore/dev/ironforce/scheme.png"
inkscape:export-xdpi="1121.49"
inkscape:export-ydpi="1121.49"><tspan
x="33.883114"
y="151.60315"
id="tspan6402">keys
</tspan><tspan
x="33.883114"
y="159.01149"
id="tspan6404">messages
</tspan><tspan
x="33.883114"
y="166.41983"
id="tspan6406">transport
</tspan><tspan
x="33.883114"
y="173.82817"
id="tspan6408">tunnels</tspan></text>
<path
style="fill:none;stroke:#000000;stroke-width:0.265;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#Arrow1Lend)"
d="m 51.174695,80.963928 c 36.826182,0 59.037355,-4.807892 71.522105,-13.48959 15.03496,-10.455072 5.67718,-25.115468 43.44786,-25.115468"
id="path8991"
sodipodi:nodetypes="csc"
inkscape:export-filename="/home/ennucore/dev/ironforce/scheme.png"
inkscape:export-xdpi="1121.49"
inkscape:export-ydpi="1121.49" />
<rect
style="fill:none;stroke:#1a5fb4;stroke-width:0.264999;stroke-miterlimit:4;stroke-dasharray:none"
id="rect9382"
width="118.39396"
height="73.67466"
x="168.431"
y="9.6066799"
ry="8.0541553"
inkscape:export-filename="/home/ennucore/dev/ironforce/scheme.png"
inkscape:export-xdpi="1121.49"
inkscape:export-ydpi="1121.49" />
<rect
style="fill:none;stroke:#1a5fb4;stroke-width:0.264999;stroke-miterlimit:4;stroke-dasharray:none"
id="rect9627"
width="61.851238"
height="32.60709"
x="175.8605"
y="39.549267"
ry="8.0541553"
inkscape:export-filename="/home/ennucore/dev/ironforce/scheme.png"
inkscape:export-xdpi="1121.49"
inkscape:export-ydpi="1121.49" />
<rect
style="fill:none;stroke:#1a5fb4;stroke-width:0.264999;stroke-miterlimit:4;stroke-dasharray:1.06, 0.264999;stroke-dashoffset:0"
id="rect9711"
width="37.725643"
height="13.677299"
x="244.65224"
y="39.549267"
ry="3.5077736"
inkscape:export-filename="/home/ennucore/dev/ironforce/scheme.png"
inkscape:export-xdpi="1121.49"
inkscape:export-ydpi="1121.49" />
<rect
style="fill:none;stroke:#1a5fb4;stroke-width:0.264999;stroke-miterlimit:4;stroke-dasharray:1.06, 0.264999;stroke-dashoffset:0"
id="rect9735"
width="37.049065"
height="13.496747"
x="245.31032"
y="58.290329"
ry="3.5077665"
inkscape:export-filename="/home/ennucore/dev/ironforce/scheme.png"
inkscape:export-xdpi="1121.49"
inkscape:export-ydpi="1121.49" />
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:7.05556px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583"
x="183.20638"
y="58.762375"
id="text10765"
inkscape:export-filename="/home/ennucore/dev/ironforce/scheme.png"
inkscape:export-xdpi="1121.49"
inkscape:export-ydpi="1121.49"><tspan
sodipodi:role="line"
id="tspan10763"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.05556px;font-family:'Inconsolata LGC';-inkscape-font-specification:'Inconsolata LGC';stroke-width:0.264583"
x="183.20638"
y="58.762375">IP Interface</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:5.29167px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583"
x="250.38287"
y="48.283943"
id="text10765-7"
inkscape:export-filename="/home/ennucore/dev/ironforce/scheme.png"
inkscape:export-xdpi="1121.49"
inkscape:export-ydpi="1121.49"><tspan
sodipodi:role="line"
id="tspan10763-5"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:5.29167px;font-family:'Inconsolata LGC';-inkscape-font-specification:'Inconsolata LGC';stroke-width:0.264583"
x="250.38287"
y="48.283943">Bluetooth</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:5.29167px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583"
x="255.28795"
y="67.200043"
id="text10765-7-3"
inkscape:export-filename="/home/ennucore/dev/ironforce/scheme.png"
inkscape:export-xdpi="1121.49"
inkscape:export-ydpi="1121.49"><tspan
sodipodi:role="line"
id="tspan10763-5-5"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:5.29167px;font-family:'Inconsolata LGC';-inkscape-font-specification:'Inconsolata LGC';stroke-width:0.264583"
x="255.28795"
y="67.200043">Radio</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:4.93889px;line-height:1.25;font-family:sans-serif;fill:#646363;fill-opacity:1;stroke:none;stroke-width:0.264583"
x="16.850649"
y="54.463711"
id="text15881"
inkscape:export-filename="/home/ennucore/dev/ironforce/scheme.png"
inkscape:export-xdpi="1121.49"
inkscape:export-ydpi="1121.49"><tspan
sodipodi:role="line"
id="tspan15879"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:4.93889px;font-family:'Inconsolata LGC';-inkscape-font-specification:'Inconsolata LGC';fill:#646363;fill-opacity:1;stroke-width:0.264583"
x="16.850649"
y="54.463711">Manages all the high-level logic</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:4.93889px;line-height:1.25;font-family:sans-serif;fill:#646363;fill-opacity:1;stroke:none;stroke-width:0.264583"
x="177.25208"
y="31.169495"
id="text15881-6"
inkscape:export-filename="/home/ennucore/dev/ironforce/scheme.png"
inkscape:export-xdpi="1121.49"
inkscape:export-ydpi="1121.49"><tspan
sodipodi:role="line"
id="tspan15879-2"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:4.93889px;font-family:'Inconsolata LGC';-inkscape-font-specification:'Inconsolata LGC';fill:#646363;fill-opacity:1;stroke-width:0.264583"
x="177.25208"
y="31.169495">Manages communicating with neighbors</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:4.93889px;line-height:1;font-family:sans-serif;fill:#646363;fill-opacity:1;stroke:none;stroke-width:0.264583"
x="151.44145"
y="121.18497"
id="text15881-6-6"
inkscape:export-filename="/home/ennucore/dev/ironforce/scheme.png"
inkscape:export-xdpi="1121.49"
inkscape:export-ydpi="1121.49"><tspan
sodipodi:role="line"
id="tspan15879-2-0"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:4.93889px;font-family:'Inconsolata LGC';-inkscape-font-specification:'Inconsolata LGC';fill:#646363;fill-opacity:1;stroke-width:0.264583"
x="151.44145"
y="122.37146">An efficient path </tspan><tspan
sodipodi:role="line"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:4.93889px;font-family:'Inconsolata LGC';-inkscape-font-specification:'Inconsolata LGC';fill:#646363;fill-opacity:1;stroke-width:0.264583"
x="151.44145"
y="127.60938"
id="tspan31525">from one node to another</tspan></text>
<path
style="fill:none;stroke:#000000;stroke-width:0.265;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#Arrow1Lend)"
d="m 60.158434,62.055153 c -5.042315,0 -11.192659,3.575902 -24.273172,3.575902"
id="path22086"
sodipodi:nodetypes="cc"
inkscape:export-filename="/home/ennucore/dev/ironforce/scheme.png"
inkscape:export-xdpi="1121.49"
inkscape:export-ydpi="1121.49" />
<path
style="fill:none;stroke:#000000;stroke-width:0.265;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#Arrow1Lend-9)"
d="m 61.630871,68.80989 c -5.042315,0 -6.677553,4.097977 -16.625154,4.097977"
id="path22086-2"
sodipodi:nodetypes="cc"
inkscape:export-filename="/home/ennucore/dev/ironforce/scheme.png"
inkscape:export-xdpi="1121.49"
inkscape:export-ydpi="1121.49" />
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:3.52778px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583"
x="61.826317"
y="63.422222"
id="text23762"
inkscape:export-filename="/home/ennucore/dev/ironforce/scheme.png"
inkscape:export-xdpi="1121.49"
inkscape:export-ydpi="1121.49"><tspan
sodipodi:role="line"
id="tspan23760"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:3.52778px;font-family:'Inconsolata LGC';-inkscape-font-specification:'Inconsolata LGC';stroke-width:0.264583"
x="61.826317"
y="63.422222">For encrypting messages</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:2.82222px;line-height:1.25;font-family:sans-serif;white-space:pre;inline-size:40.1471;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583"
x="62.18861"
y="50.598217"
id="text23762-7"
transform="translate(0,17.991672)"
inkscape:export-filename="/home/ennucore/dev/ironforce/scheme.png"
inkscape:export-xdpi="1121.49"
inkscape:export-ydpi="1121.49"><tspan
x="62.18861"
y="50.598217"
id="tspan6412"><tspan
style="font-family:'Inconsolata LGC';-inkscape-font-specification:'Inconsolata LGC'"
id="tspan6410">Messages are collected
</tspan></tspan><tspan
x="62.18861"
y="54.296866"
id="tspan6416"><tspan
style="font-family:'Inconsolata LGC';-inkscape-font-specification:'Inconsolata LGC'"
id="tspan6414">into a queue
</tspan></tspan><tspan
x="62.18861"
y="57.995517"
id="tspan6420"><tspan
style="font-family:'Inconsolata LGC';-inkscape-font-specification:'Inconsolata LGC'"
id="tspan6418">in another thread</tspan></tspan></text>
<rect
style="fill:none;fill-opacity:1;stroke:#1a5fb4;stroke-width:0.264999;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0"
id="rect29127"
width="131.27422"
height="80.150284"
x="144.03688"
y="100.24098"
ry="3.5077665"
inkscape:export-filename="/home/ennucore/dev/ironforce/scheme.png"
inkscape:export-xdpi="1121.49"
inkscape:export-ydpi="1121.49" />
<circle
style="fill:none;fill-opacity:1;stroke:#1a5fb4;stroke-width:0.264999;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0"
id="path33335"
cx="175.70769"
cy="154.38387"
r="6.170032"
inkscape:export-filename="/home/ennucore/dev/ironforce/scheme.png"
inkscape:export-xdpi="1121.49"
inkscape:export-ydpi="1121.49" />
<circle
style="fill:none;fill-opacity:1;stroke:#1a5fb4;stroke-width:0.264999;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0"
id="path33335-6"
cx="195.01611"
cy="142.44"
r="6.170032"
inkscape:export-filename="/home/ennucore/dev/ironforce/scheme.png"
inkscape:export-xdpi="1121.49"
inkscape:export-ydpi="1121.49" />
<circle
style="fill:none;fill-opacity:1;stroke:#1a5fb4;stroke-width:0.264999;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0"
id="path33335-2"
cx="195.01611"
cy="168.89836"
r="6.170032"
inkscape:export-filename="/home/ennucore/dev/ironforce/scheme.png"
inkscape:export-xdpi="1121.49"
inkscape:export-ydpi="1121.49" />
<circle
style="fill:none;fill-opacity:1;stroke:#1a5fb4;stroke-width:0.264999;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0"
id="path33335-61"
cx="210.89113"
cy="154.08167"
r="6.170032"
inkscape:export-filename="/home/ennucore/dev/ironforce/scheme.png"
inkscape:export-xdpi="1121.49"
inkscape:export-ydpi="1121.49" />
<circle
style="fill:none;fill-opacity:1;stroke:#1a5fb4;stroke-width:0.264999;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0"
id="path33335-8"
cx="233.21059"
cy="169.51178"
r="6.170032"
inkscape:export-filename="/home/ennucore/dev/ironforce/scheme.png"
inkscape:export-xdpi="1121.49"
inkscape:export-ydpi="1121.49" />
<circle
style="fill:none;fill-opacity:1;stroke:#1a5fb4;stroke-width:0.264999;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0"
id="path33335-7"
cx="233.7605"
cy="140.73239"
r="6.170032"
inkscape:export-filename="/home/ennucore/dev/ironforce/scheme.png"
inkscape:export-xdpi="1121.49"
inkscape:export-ydpi="1121.49" />
<circle
style="fill:none;fill-opacity:1;stroke:#1a5fb4;stroke-width:0.264999;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0"
id="path33335-9"
cx="248.32843"
cy="154.43222"
r="6.170032"
inkscape:export-filename="/home/ennucore/dev/ironforce/scheme.png"
inkscape:export-xdpi="1121.49"
inkscape:export-ydpi="1121.49" />
<path
style="fill:#1c71d8;stroke:#1a5fb4;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 180.83782,150.86848 9.12905,-4.79725"
id="path33547"
inkscape:export-filename="/home/ennucore/dev/ironforce/scheme.png"
inkscape:export-xdpi="1121.49"
inkscape:export-ydpi="1121.49" />
<path
style="fill:none;stroke:#1a5fb4;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 200.13891,146.14863 5.96908,3.93308"
id="path33812"
inkscape:export-filename="/home/ennucore/dev/ironforce/scheme.png"
inkscape:export-xdpi="1121.49"
inkscape:export-ydpi="1121.49" />
<path
style="fill:none;stroke:#1a5fb4;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 216.18252,150.63428 12.1567,-6.80918"
id="path33847"
sodipodi:nodetypes="cc"
inkscape:export-filename="/home/ennucore/dev/ironforce/scheme.png"
inkscape:export-xdpi="1121.49"
inkscape:export-ydpi="1121.49" />
<path
style="fill:none;stroke:#1a5fb4;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 238.59271,144.77704 5.65362,5.05602"
id="path34269"
sodipodi:nodetypes="cc"
inkscape:export-filename="/home/ennucore/dev/ironforce/scheme.png"
inkscape:export-xdpi="1121.49"
inkscape:export-ydpi="1121.49" />
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:9.87778px;line-height:1.25;font-family:sans-serif;fill:#1a5fb4;fill-opacity:1;stroke:none;stroke-width:0.264583"
x="172.59557"
y="157.42624"
id="text35365"
inkscape:export-filename="/home/ennucore/dev/ironforce/scheme.png"
inkscape:export-xdpi="1121.49"
inkscape:export-ydpi="1121.49"><tspan
sodipodi:role="line"
id="tspan35363"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:9.87778px;font-family:'Inconsolata LGC';-inkscape-font-specification:'Inconsolata LGC';fill:#1a5fb4;fill-opacity:1;stroke-width:0.264583"
x="172.59557"
y="157.42624">A</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:9.87778px;line-height:1.25;font-family:sans-serif;fill:#1a5fb4;fill-opacity:1;stroke:none;stroke-width:0.264583"
x="245.73502"
y="158.07724"
id="text35365-2"
inkscape:export-filename="/home/ennucore/dev/ironforce/scheme.png"
inkscape:export-xdpi="1121.49"
inkscape:export-ydpi="1121.49"><tspan
sodipodi:role="line"
id="tspan35363-0"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:9.87778px;font-family:'Inconsolata LGC';-inkscape-font-specification:'Inconsolata LGC';fill:#1a5fb4;fill-opacity:1;stroke-width:0.264583"
x="245.73502"
y="158.07724">B</tspan></text>
<path
style="fill:none;stroke:#000000;stroke-width:0.265;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#Arrow1Lend)"
d="m 49.263104,88.1016 c 48.905701,0 81.209076,-4.448346 111.236186,11.120146"
id="path39105"
sodipodi:nodetypes="cc"
inkscape:export-filename="/home/ennucore/dev/ironforce/scheme.png"
inkscape:export-xdpi="1121.49"
inkscape:export-ydpi="1121.49" />
<g
id="g39376"
transform="matrix(0.45409146,0,0,0.45409146,31.061511,94.796231)"
inkscape:export-filename="/home/ennucore/dev/ironforce/images/degeon_logo.png"
inkscape:export-xdpi="1121.49"
inkscape:export-ydpi="1121.49">
<path
d="m 46.303616,184.7066 c 7.73086,-1.52982 13.840883,-6.70904 21.987933,-5.12339 1.985169,0.38629 7.916068,1.97432 5.763948,-2.24155 -1.360488,-2.66462 -6.466152,-2.6588 -8.965936,-3.09563 -2.146829,-0.37544 -2.518039,-0.38946 -2.028031,-2.56169 0.523611,-2.32119 1.455738,-2.86835 3.522398,-3.73592 3.063346,-1.28614 6.074569,-3.00249 8.859308,-4.69635 2.342621,-1.42505 4.705086,-3.10356 6.724915,-5.12339 1.521883,-1.52189 4.344458,-5.17155 6.564312,-4.42966 5.774796,1.92961 11.273107,3.95632 17.024877,5.39036 2.96836,0.74004 6.27671,2.46274 8.75268,2.66832 4.90988,0.40746 7.80865,-9.69804 7.57873,-13.66255 -0.14552,-2.50772 -0.5543,-6.75349 -2.88211,-8.11213 -1.42637,-0.83238 -4.18862,-0.77602 -5.76368,-0.85407 -9.29799,-0.46117 -18.443312,2.18889 -27.752411,1.06759 -4.295511,-0.51752 -7.162271,-2.45242 -10.780448,-4.58973 -3.092979,-1.82721 -6.380163,-2.81066 -9.660202,-4.21613 -3.353329,-1.43722 -7.07681,-2.21589 -10.513748,-3.46922 -2.35241,-0.85778 -2.785533,-1.82695 -4.376208,-3.4155 -2.010569,-2.00819 -3.066521,-4.88104 -5.123656,-6.93817 -2.100527,-2.10053 -3.953669,-4.31747 -6.190721,-6.29762 -4.250267,-3.76211 -9.460442,-7.94728 -15.530777,-5.92402 -2.823369,0.94113 -5.056717,2.1635 -6.884723,4.64318 -1.106487,1.50098 -1.403614,2.72282 -1.707621,4.58972 -0.167746,1.03029 0.01667,1.00965 -0.320146,1.81452 -0.294216,0.70299 -0.989541,1.21258 -1.280847,2.02803 -0.6731,1.88357 -0.305065,4.67042 1.600993,5.12365 -0.294216,-0.0701 1.657086,-0.41883 1.707886,-0.42703 0.841904,-0.136 1.715823,-0.0262 2.56196,-0.10663 4.463521,-0.42519 7.593277,2.07671 8.752681,6.61802 3.120496,12.22481 0.262731,28.58797 12.381706,36.50483 3.822436,2.49714 7.502261,1.60099 11.794861,1.60099 4.696618,0 6.56537,2.36591 6.777831,6.61802 -1.454415,-0.2667 -4.790281,-1.80392 -5.870311,-2.8821 -0.695854,-0.6948 -1.998397,-0.80751 -2.882106,-0.58711 -1.209939,0.30162 -1.621896,3.13822 -2.033323,3.16045 -4.399227,0.23759 -5.496718,0.42386 -4.584435,4.36456 0.225954,0.97552 0.509852,0.54743 0.2667,1.76107 -0.150283,0.75089 -0.382323,1.4224 -0.160073,2.1881 0.354013,1.22026 1.51421,2.07143 2.667794,2.34818"
id="path3"
style="clip-rule:evenodd;overflow:visible;fill:#0e0d0d;fill-rule:evenodd;stroke-width:0.264583" />
<path
d="m 46.410243,183.42575 c 6.414293,-2.4421 12.911402,-5.93883 19.960166,-5.65732 1.456531,0.0582 6.461125,2.04523 6.617758,0.53393 0.16219,-1.56792 -2.650331,-1.72085 -3.629024,-1.9214 -1.344084,-0.27543 -2.86438,-0.34978 -4.2164,-0.80063 -2.890838,-0.96361 -3.500967,1.32847 -3.671359,-3.27607 -0.02937,-0.7964 -1.086908,-0.48816 -1.29196,-0.0863 -0.365654,0.71676 0.370681,1.84918 0.213519,2.56196 -0.467784,2.1217 -1.212321,1.33006 -2.882107,0.42678 -1.568714,-0.84852 -3.201722,-2.03306 -4.910137,-2.5617 -1.366837,-0.42307 -4.131998,-0.0259 -2.348177,1.60099 0.908315,0.82868 3.823758,0.31274 5.016764,0.53393 2.900098,0.53737 1.289844,1.37584 -0.854075,1.38774 -3.129227,0.0172 -6.254749,-1.54384 -9.446418,-0.48022 -0.645583,0.21511 -0.950383,0.8771 -0.579173,1.48035 0.869156,1.41261 1.078706,0.70141 2.447131,0.49424 1.383506,-0.20955 2.745846,-0.64029 4.162954,-0.64029 1.431132,0 4.189942,-0.89165 5.443538,-0.21379 2.311664,1.25069 -2.830248,1.39489 -3.842544,1.49437 -1.677987,0.16484 -4.191794,0.0437 -5.763948,0.64029 -1.862931,0.70723 -3.028156,3.6285 -0.426508,4.4831"
id="path5"
style="clip-rule:evenodd;overflow:visible;fill:#ff7f00;fill-rule:evenodd;stroke-width:0.264583" />
<path
d="m 59.432505,170.40349 c 0.716756,-0.85725 2.687902,0.0294 2.722033,-0.10663 0.762265,-3.04932 3.513403,-3.44672 6.137275,-4.69662 3.504671,-1.66925 6.447632,-4.08225 9.766829,-5.97746 2.372255,-1.35467 7.277894,-5.23055 8.165307,-7.57846 -11.99859,5.84597 -36.092606,23.12961 -45.364135,4.26958 -1.60364,-3.26178 -2.006336,-6.68364 -0.854075,-10.14016 0.932921,-2.7985 0.230716,-5.22049 0.640291,-7.89887 0.447146,-2.92391 1.747309,0.35851 1.921405,2.13466 0.311679,3.1832 -1.494103,6.04229 -1.494103,9.28635 0,11.33607 9.555692,16.06206 18.999465,12.91537 3.006989,-1.00198 6.016096,-1.94125 8.859043,-3.36206 1.379273,-0.68924 2.789503,-1.27159 4.162955,-1.97485 2.193395,-1.12316 3.050116,-1.17872 3.41577,-3.73592 -2.993231,0.15319 -6.415616,2.13413 -9.286345,1.9214 -2.579952,-0.19129 -1.026584,-1.16893 0.640291,-1.70762 1.583532,-0.51197 3.278717,-0.83926 4.910138,-1.17422 0.697177,-0.14314 1.821921,-0.14287 2.454804,-0.42677 0.248179,-0.11139 1.138237,-0.0587 1.281112,-0.21352 0.19685,-0.21325 0.490538,-3.43588 0.320146,-3.62929 -0.642408,-0.72893 -3.509433,0.27887 -4.269581,0.42704 -1.430073,0.27913 -2.735527,0.53366 -4.216665,0.53366 -2.524124,0 0.780521,-1.43219 1.548078,-1.60126 1.291166,-0.28416 2.633133,-0.51858 3.949435,-0.74691 3.001698,-0.52097 3.068902,-0.25877 3.415506,-3.84281 -2.24155,0.1778 -4.483894,0.34554 -6.724385,0.53393 -0.747448,0.0627 -4.358481,0.88423 -3.735917,-0.42704 0.361156,-0.76041 3.059377,-0.86598 3.842279,-0.9607 1.348052,-0.16299 2.711715,-0.26088 4.056327,-0.42704 1.895211,-0.23416 3.9243,-1.28931 1.227667,-2.1881 -2.199481,-0.73316 -6.928908,0.50958 -8.592608,-1.12078 -1.385888,-1.35837 2.013479,-0.74692 2.828396,-0.74692 1.442772,0 2.826279,0.42677 4.269581,0.42677 0.5334,0 1.099343,0.10055 1.654439,0.1069 -1.236662,-2.15371 -6.133835,-3.65522 -8.43227,-4.80298 -2.816755,-1.40679 -6.320632,-2.2741 -9.392973,-3.04218 -2.693988,-0.67337 -6.529652,-3.30994 -8.752681,-3.14881 0.408516,-0.0296 -0.935302,0.84085 -0.960438,0.85408 0.184679,-0.0971 -2.752196,0.57652 -1.90156,0.62388 -2.321719,-0.12911 -0.695854,0.29501 -1.941248,1.19063 -1.255183,0.90276 -1.100402,-0.84535 -1.821127,1.67772 -0.260879,0.91281 -1.528498,-0.27384 -2.021417,1.20438 -0.391054,1.17264 -0.372004,0.59849 -0.960702,1.06733 -0.07832,0.0624 -1.267089,-0.46328 -1.387739,-0.53366 0.02884,1.34091 -0.806715,1.61634 -1.707886,0.85381 -0.580231,1.82377 -1.115748,3.28559 -2.668587,1.38774 0.09366,1.71027 -0.849577,1.76477 -1.707621,0.74718 -0.15875,1.11099 0.03387,2.8784 -1.38774,2.13466 0.686065,8.9199 1.653646,17.75037 7.471834,24.44353 2.697956,3.10356 6.583362,4.16904 10.727266,3.57584 1.941513,-0.27807 4.10845,-0.10689 6.030912,0.21326 3.04509,0.50641 2.727325,1.90632 4.163219,3.68194"
id="path7"
style="clip-rule:evenodd;overflow:visible;fill:#e6e6e8;fill-rule:evenodd;stroke-width:0.264583" />
<path
d="m 112.26847,159.51589 c 1.35044,-0.50509 2.53392,-3.1295 3.41577,-4.37595 -2.35823,-1.75313 -5.06597,4.04707 -3.41577,4.37595"
id="path9"
style="clip-rule:evenodd;overflow:visible;fill:#666666;fill-rule:evenodd;stroke-width:0.264583" />
<path
d="m 109.49326,158.76897 c 1.13929,-1.37345 2.02353,-2.79241 2.77521,-4.37595 -3.96319,-1.42345 -7.9756,-2.27779 -12.06156,-3.20251 -4.308737,-0.97499 -9.875041,-4.13068 -13.128886,0.64055 4.034366,1.21074 8.150489,2.23071 12.06156,3.73566 3.286386,1.2647 7.291656,1.73064 10.353676,3.20225"
id="path11"
style="clip-rule:evenodd;overflow:visible;fill:#e6e6e8;fill-rule:evenodd;stroke-width:0.264583" />
<path
d="m 116.11075,153.75247 c 1.13083,-2.00978 2.55535,-5.59012 -0.64029,-5.76395 -0.36248,1.8124 -2.37993,5.91582 0.64029,5.76395"
id="path13"
style="clip-rule:evenodd;overflow:visible;fill:#666666;fill-rule:evenodd;stroke-width:0.264583" />
<path
d="m 80.99393,153.11191 c 0.173302,-0.14287 0.424392,-0.23336 0.533664,-0.32014 -0.444764,0.004 -0.529695,0.3167 -0.533664,0.32014"
id="path15"
style="clip-rule:evenodd;overflow:visible;fill:#666666;fill-rule:evenodd;stroke-width:0.264583" />
<path
d="m 112.90876,152.89839 c 0.40932,-1.143 0.70882,-2.32674 0.90753,-3.5224 0.30824,-1.85472 -0.0579,-1.49092 -1.65444,-1.81477 -2.23785,-0.45403 -4.97338,-1.06733 -7.25832,-1.06733 -4.02299,0 -8.6061,-1.26445 -11.84804,1.81451 6.662208,1.0795 13.37919,2.72971 19.85327,4.58999"
id="path17"
style="clip-rule:evenodd;overflow:visible;fill:#e6e6e8;fill-rule:evenodd;stroke-width:0.264583" />
<path
d="m 81.207184,151.08388 c 3.694642,-0.51303 3.814498,-0.0971 4.056063,-3.84228 -3.395663,0.18812 -4.60719,0.0439 -4.056063,3.84228"
id="path19"
style="clip-rule:evenodd;overflow:visible;fill:#666666;fill-rule:evenodd;stroke-width:0.264583" />
<path
d="m 88.145353,149.37626 c 1.352021,-0.81836 2.704041,-1.63698 4.056062,-2.45507 -2.995348,0.0831 -3.774016,-0.65008 -4.056062,2.45507"
id="path21"
style="clip-rule:evenodd;overflow:visible;fill:#e6e6e8;fill-rule:evenodd;stroke-width:0.264583" />
<path
d="m 117.71201,147.13444 c 0.0397,-0.45587 0.46355,-2.11798 0.21378,-2.45506 -0.17224,-0.23204 -1.92643,-0.56119 -2.13492,-0.42704 -2.04496,1.31577 1.06627,2.59715 1.92114,2.8821"
id="path23"
style="clip-rule:evenodd;overflow:visible;fill:#666666;fill-rule:evenodd;stroke-width:0.264583" />
<path
d="m 114.18961,146.17374 c 0.19103,-2.52836 -1.7018,-2.56169 -3.89572,-2.56169 -1.49596,0 -3.02631,-0.13944 -4.53655,-0.21352 -2.64424,-0.12991 -5.29801,0.0746 -7.898869,0 -0.0254,0.49345 -0.08149,1.04166 -0.106362,1.38774 5.518151,0.17039 10.945551,1.08744 16.437501,1.38747"
id="path25"
style="clip-rule:evenodd;overflow:visible;fill:#e6e6e8;fill-rule:evenodd;stroke-width:0.264583" />
<path
d="m 81.420967,145.96049 c 4.021667,-0.43498 3.918744,-0.1569 4.375944,-4.16296 -3.052498,0.37227 -5.899414,0.35005 -4.375944,4.16296"
id="path27"
style="clip-rule:evenodd;overflow:visible;fill:#666666;fill-rule:evenodd;stroke-width:0.264583" />
<path
d="m 93.909301,145.64008 c 1.84732,-1.34356 2.88872,-1.38906 1.17422,-2.5617 -0.707495,-0.48392 -1.906587,-0.51699 -2.775214,-0.74692 -0.893498,-0.23653 -1.836738,-0.39846 -2.722033,-0.694 -1.99443,-0.66569 -1.252803,2.22171 -1.33403,3.68247 1.73408,0 4.386263,-0.43418 5.657057,0.32015"
id="path29"
style="clip-rule:evenodd;overflow:visible;fill:#e6e6e8;fill-rule:evenodd;stroke-width:0.264583" />
<path
d="m 116.8582,143.18501 c 0.41328,-0.91969 -0.28575,-2.72124 -1.70789,-2.66859 0.13441,1.41102 -0.13811,2.68843 1.70789,2.66859"
id="path31"
style="clip-rule:evenodd;overflow:visible;fill:#666666;fill-rule:evenodd;stroke-width:0.264583" />
<path
d="m 97.324807,142.75797 c 1.586441,-1.3081 5.643563,-0.58658 7.791983,-0.53366 2.95962,0.073 5.75336,0.27755 8.64605,0.53366 -0.28363,-3.11758 -1.05145,-2.24155 -3.68247,-2.24155 -1.51342,0 -3.03133,-0.003 -4.53655,0.10663 -3.12605,0.2286 -6.198655,0.76994 -9.286607,1.38774 0.347133,0.24289 0.687917,0.5633 1.067594,0.74718"
id="path33"
style="clip-rule:evenodd;overflow:visible;fill:#e6e6e8;fill-rule:evenodd;stroke-width:0.264583" />
<path
d="m 81.954632,140.62331 c 0.213519,0 0.426773,0 0.640292,0 -0.27358,-0.0413 -0.652727,-0.17198 -0.960438,-0.21352 -0.01085,0.0344 -0.108214,0.21379 -0.106892,0.21352 0.06932,0.0458 0.570442,0.096 0.427038,0"
id="path35"
style="clip-rule:evenodd;overflow:visible;fill:#666666;fill-rule:evenodd;stroke-width:0.264583" />
<path
d="m 30.933176,136.56725 c 0.102923,-0.40243 0.213519,-0.98345 0.213519,-1.54781 0,-1.44066 0.8509,-0.51514 1.281113,-0.69374 0.992981,-0.41195 2.058722,-0.72152 2.668322,0.42677 0.306917,-1.2274 0.593196,-3.55414 2.028296,-1.92114 -0.02831,-2.06189 1.527175,-1.57268 2.348177,-0.53392 0.439209,-1.53644 0.631032,-3.02472 2.24155,-1.601 0.384175,-1.04695 -0.286543,-3.74226 1.707886,-2.45507 -0.364861,-2.01162 1.213643,-1.61713 2.134658,-0.85407 -0.3556,-3.08293 1.157552,-0.78264 1.814512,-1.06707 0.355336,-0.15398 0.576528,0.87366 0.668073,-0.22542 0.07646,-0.91546 -0.04683,-0.9914 0.719402,-1.05569 -2.419879,-3.37158 -4.561152,-6.91118 -7.471568,-9.82001 -2.223823,-2.2225 -4.935008,-3.869 -7.151423,-6.08409 -4.58761,-4.58417 -14.081919,-3.53986 -17.078589,2.45533 -0.585788,1.17158 -1.036373,2.92894 -0.533665,4.16269 0.456142,1.11892 1.31445,1.31498 2.24155,2.24155 0.664633,0.66437 0.599281,2.58392 1.334029,2.82866 1.016529,0.33867 2.193131,0.51911 3.095361,1.12078 1.838854,1.22607 3.398572,2.06507 4.750064,3.94943 2.371725,3.3065 2.134923,7.03368 2.988733,10.67382"
id="path37"
style="clip-rule:evenodd;overflow:visible;fill:#666666;fill-rule:evenodd;stroke-width:0.264583" />
<path
d="m 16.523439,121.94426 c 3.790156,0 1.388269,-2.26589 0,0"
id="path39"
style="clip-rule:evenodd;overflow:visible;fill:#ffbf00;fill-rule:evenodd;stroke-width:0.264583" />
<path
d="m 14.495408,121.51696 c 1.056216,-0.84482 3.352535,-1.89363 2.881841,-3.09536 -0.04445,-0.11351 -1.462352,-0.75565 -1.600993,-0.74719 -1.425575,0.0892 -1.843352,2.51672 -1.280848,3.84255"
id="path41"
style="clip-rule:evenodd;overflow:visible;fill:#ffbf00;fill-rule:evenodd;stroke-width:0.264583" />
<path
d="m 19.298654,117.03412 c 2.201597,0.14341 2.681552,-3.39725 0.960702,-4.37647 -2.285207,-1.30016 -3.538538,3.77984 -0.960702,4.37647"
id="path43"
style="clip-rule:evenodd;overflow:visible;fill:#0e0d0d;fill-rule:evenodd;stroke-width:0.264583" />
<path
d="m 19.192027,115.32624 c 0.08864,-0.4408 0.509852,-0.67575 -0.106892,-0.85408 -0.01032,0.0617 0.08334,1.00965 0.106892,0.85408"
id="path45"
style="clip-rule:evenodd;overflow:visible;fill:#ffffff;fill-rule:evenodd;stroke-width:0.264583" />
</g>
<circle
style="fill:none;fill-opacity:1;stroke:#c01c28;stroke-width:4;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
id="path39400"
cx="58.1259"
cy="167.87944"
r="39.348465"
inkscape:export-filename="/home/ennucore/dev/ironforce/images/degeon_logo.png"
inkscape:export-xdpi="1121.49"
inkscape:export-ydpi="1121.49" />
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:10.5833px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583"
x="41.161209"
y="187.81125"
id="text41324"
inkscape:export-filename="/home/ennucore/dev/ironforce/images/degeon_logo.png"
inkscape:export-xdpi="1121.49"
inkscape:export-ydpi="1121.49"><tspan
sodipodi:role="line"
id="tspan41322"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'Inconsolata LGC';-inkscape-font-specification:'Inconsolata LGC';stroke-width:0.264583"
x="41.161209"
y="187.81125">Degeon</tspan></text>
<path
style="fill:none;stroke:#c01c28;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#Arrow1Mend)"
d="M 58.794197,125.3495 V 102.52049"
id="path42639"
sodipodi:nodetypes="cc"
inkscape:export-filename="/home/ennucore/dev/ironforce/scheme.png"
inkscape:export-xdpi="1121.49"
inkscape:export-ydpi="1121.49" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 40 KiB

BIN
images/logo.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

96
images/logo.svg

@ -0,0 +1,96 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="210mm"
height="297mm"
viewBox="0 0 210 297"
version="1.1"
id="svg5"
inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20, custom)"
sodipodi:docname="logo.svg"
inkscape:export-filename="/home/ennucore/dev/ironforce/logo.png"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview7"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:document-units="mm"
showgrid="false"
inkscape:zoom="1.2953002"
inkscape:cx="336.21549"
inkscape:cy="568.59405"
inkscape:window-width="3840"
inkscape:window-height="2088"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="layer1" />
<defs
id="defs2">
<linearGradient
inkscape:collect="always"
id="linearGradient9344">
<stop
style="stop-color:#ab4251;stop-opacity:1"
offset="0"
id="stop9340" />
<stop
style="stop-color:#ffffff;stop-opacity:1"
offset="1"
id="stop9342" />
</linearGradient>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient9344"
id="linearGradient9338"
x1="83.937424"
y1="159.45372"
x2="145.70277"
y2="112.71262"
gradientUnits="userSpaceOnUse" />
</defs>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:10.5833px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583"
x="54.465954"
y="101.28693"
id="text1970"><tspan
sodipodi:role="line"
id="tspan1968"
style="stroke-width:0.264583"
x="54.465954"
y="101.28693" /></text>
<circle
style="fill:#282e46;stroke-width:0.264583;fill-opacity:1"
id="path3611"
cx="111.61379"
cy="136.29881"
r="54.407951" />
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:67.1901px;line-height:1.25;font-family:sans-serif;fill:url(#linearGradient9338);fill-opacity:1;stroke:none;stroke-width:0.419938"
x="80.870842"
y="159.62645"
id="text4421"><tspan
sodipodi:role="line"
id="tspan4419"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:67.1901px;font-family:'Intro Rust G';-inkscape-font-specification:'Intro Rust G';fill:url(#linearGradient9338);fill-opacity:1;stroke-width:0.419938"
x="80.870842"
y="159.62645">IF</tspan></text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

BIN
images/scheme.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

35
ironforce/Cargo.toml

@ -0,0 +1,35 @@
[package]
name = "ironforce"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
name = "ironforce"
path = "src/lib.rs"
[features]
default = []
std = ["rayon"]
[dependencies]
rand_os = "*"
# x25519-dalek = "0.6.0"
# ed25519-dalek = { version = "1.0", features = ["serde"] }
sha2 = "0.8.1"
rand = "*"
rsa = { version = "0.5", features = ["serde"] }
serde = { version = "1.0", features = ["derive", "alloc"], default-features = false }
rayon = { version = "1.5.1", optional = true }
core-error = "0.0.1-rc4"
serde_cbor = "0.11.2"
serde_json = "1.0.72"
spin = "0.9.2"
base64 = "0.13.0"
include_optional = "1.0.1"
[[bin]]
name = "worker"
required-features = ["std"]

4
src/bin/worker.rs → ironforce/src/bin/worker.rs

@ -8,7 +8,7 @@ fn main() -> IFResult<()> {
"Our public key: {}",
base64::encode(if_keys.get_public().to_vec().as_slice())
);
let (_thread, if_mutex) = ironforce.launch_main_loop(100);
let (_thread, if_mutex) = ironforce.launch_main_loop(50);
let stdin = std::io::stdin();
let if_mutex_clone = if_mutex.clone();
let if_keys_clone = if_keys.clone();
@ -19,7 +19,7 @@ fn main() -> IFResult<()> {
String::from_utf8(msg.get_decrypted(&if_keys_clone).unwrap()).unwrap()
);
}
std::thread::sleep(std::time::Duration::from_millis(300))
std::thread::sleep(std::time::Duration::from_millis(200))
});
loop {
let mut buf = String::new();

22
src/crypto.rs → ironforce/src/crypto.rs

@ -9,13 +9,15 @@ use rsa::errors::Result as RsaRes;
use rsa::{BigUint, PaddingScheme, PublicKey as RPK, PublicKeyParts, RsaPrivateKey, RsaPublicKey};
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha224};
use core::hash::Hash;
use core::hash::Hasher;
static KEY_LENGTH: usize = 2048;
static ENCRYPTION_CHUNK_SIZE: usize = 240;
static ENCRYPTION_OUTPUT_CHUNK_SIZE: usize = 256;
/// Public key of a node
#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct PublicKey {
pub key: RsaPublicKey,
}
@ -99,6 +101,18 @@ impl PublicKey {
}
}
impl Hash for PublicKey {
fn hash<H: Hasher>(&self, state: &mut H) {
Hash::hash(&self.to_vec(), state)
}
}
impl PartialEq for PublicKey {
fn eq(&self, other: &Self) -> bool {
self.key == other.key
}
}
impl PublicKey {
fn hash(&self) -> Vec<u8> {
self.to_vec()
@ -112,6 +126,12 @@ pub struct Keys {
private_key: RsaPrivateKey,
}
impl Hash for Keys {
fn hash<H: Hasher>(&self, state: &mut H) {
Hash::hash(&self.get_public(), state)
}
}
impl Keys {
/// Generate new random key
pub fn generate() -> Self {

0
src/interface.rs → ironforce/src/interface.rs

191
src/interfaces/ip.rs → ironforce/src/interfaces/ip.rs

@ -2,9 +2,9 @@ use alloc::borrow::ToOwned;
use alloc::string::{String, ToString};
use alloc::vec;
use alloc::vec::Vec;
use core::ops::RangeInclusive;
use core::str::FromStr;
use core::time::Duration;
use include_optional::include_str_optional;
use rayon::prelude::*;
use serde::{Deserialize, Serialize};
use std::net::TcpStream;
@ -17,11 +17,13 @@ use crate::std::io::{Read, Write};
use crate::std::println;
pub const DEFAULT_PORT: u16 = 50000;
const SOCKET_RANGE: RangeInclusive<u16> = 50000..=50010;
/// The threshold for the number of peers below which we are desperate
const PEER_THRESHOLD: usize = 70;
/// Default peers
const DEFAULT_PEERS_FILE: Option<&'static str> = include_str_optional!(".if_ip_peers");
type Peer = (net::IpAddr, u16);
/// Interface for interactions using tcp sockets
@ -99,16 +101,18 @@ impl Interface for IPInterface {
self.connections.push(stream)
}
Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => {}
Err(e) => return Err(IFError::from(e)),
Err(e) => println!("An error happened with an incoming connection: {:?}", e),
}
}
let mut new_connections: Vec<TcpStream> = vec![];
for connection in &mut self.connections {
let mut connections_to_delete = vec![];
for (i, connection) in self.connections.iter_mut().enumerate() {
let res: std::io::Result<()> = {
connection.set_nonblocking(true)?;
let mut buf = [0u8; 6];
let peek_res = connection.peek(&mut buf);
if peek_res.is_err() || peek_res.unwrap() < 6 {
continue
continue;
}
let mut header: [u8; 6] = [0, 0, 0, 0, 0, 0];
match connection.read_exact(&mut header) {
@ -121,6 +125,21 @@ impl Interface for IPInterface {
}
Err(e) => {
println!("Error: {:?}", e);
connections_to_delete.push(i);
let connection_addr = if let Ok(r) = connection.peer_addr() {
r
} else {
continue;
};
if let Some(peer) = self
.peers
.iter()
.find(|p| compare_addrs(p, connection_addr))
{
if let Ok(Some(conn)) = IPInterface::new_connection(peer) {
new_connections.push(conn)
}
}
continue;
}
};
@ -160,7 +179,7 @@ impl Interface for IPInterface {
message,
};
self.package_queue
.push((package, format!("{:?}", connection.peer_addr()?)));
.insert(0, (package, format!("{:?}", connection.peer_addr()?)));
}
MessageType::PeersShared => {
let peers: Vec<Peer> = serde_cbor::from_slice(message.as_slice())?;
@ -174,42 +193,58 @@ impl Interface for IPInterface {
}
}
}
Ok(())
};
if res.is_err() && res.unwrap_err().kind() == std::io::ErrorKind::BrokenPipe {
connections_to_delete.push(i)
};
}
for (j, index) in connections_to_delete.iter().enumerate() {
self.connections.remove(index - j);
}
for conn in new_connections.iter_mut() {
self.initialize_connection(conn)?;
self.initialize_connection(conn)
.unwrap_or_else(|e| println!("Couldn't initialize connection: {:?}", e));
}
self.connections.extend(new_connections);
self.main_loop_iterations += 1;
// Every 50 iterations we connect to everyone we know
if self.main_loop_iterations % 50 == 0 {
let connected_addresses = self
.connections
.iter()
.filter_map(|conn| conn.peer_addr().ok())
.collect::<Vec<_>>();
let peers_we_do_not_have_connections_with = self
.peers
.iter()
.filter(|p| {
!connected_addresses
.iter()
.any(|addr| compare_addrs(p, *addr))
})
.copied()
.collect::<Vec<_>>();
let peers_we_do_not_have_connections_with = self.disconnected_peers();
self.connections
.extend(IPInterface::get_connections_to_peers(
&peers_we_do_not_have_connections_with,
self.peers.len() < PEER_THRESHOLD * 2,
));
}
if self.connections.is_empty() {
for peer in self.peers.clone() {
self.obtain_connection(&peer)
.map(|_| ())
.unwrap_or_else(|e| println!("Error in obtaining connection: {:?}", e));
}
}
// We do a peer exchange every 30 iterations
if self.main_loop_iterations % 30 == 0 && !self.connections.is_empty() {
let connection_index =
(self.main_loop_iterations / 30) as usize % self.connections.len();
match IPInterface::request_peers(&mut self.connections[connection_index]) {
Err(e) if e.kind() == std::io::ErrorKind::BrokenPipe => {
let peer = (
self.connections[connection_index].peer_addr()?.ip(),
self.connections[connection_index].peer_addr()?.port(),
);
self.connections.remove(connection_index);
let connection_index = self.obtain_connection(&peer)?;
IPInterface::request_peers(&mut self.connections[connection_index])?;
}
Err(e) => println!("An error in peer sharing: {:?}", e),
_ => {}
};
}
Ok(())
}
@ -219,8 +254,8 @@ impl Interface for IPInterface {
fn id(&self) -> &str {
&*self.id
}
fn send(&mut self, message: &[u8], interface_data: Option<TargetingData>) -> IFResult<()> {
fn send(&mut self, message: &[u8], interface_data: Option<TargetingData>) -> IFResult<()> {
let package = IPPackage {
version: 0,
package_type: MessageType::Common,
@ -231,13 +266,50 @@ impl Interface for IPInterface {
match interface_data {
Some(ip_string) => {
let addr: net::SocketAddr = ip_string.parse().expect("Unable to parse address");
let peer = (addr.ip(), addr.port());
let index = self.obtain_connection(&peer)?;
match IPInterface::send_package(&mut self.connections[index], package.clone()) {
Ok(_) => {}
Err(_) => {
self.remove_all_connections_to_peer(&peer);
let index = self.obtain_connection(&(addr.ip(), addr.port()))?;
IPInterface::send_package(&mut self.connections[index], package)?;
IPInterface::send_package(&mut self.connections[index], package)
.map_err(|e| {
println!("Error while sending: {:?}", e);
e
})
.unwrap_or_default();
}
}
}
None => {
for conn in &mut self.connections {
IPInterface::send_package(conn, package.clone())?;
if self.connections.len() < PEER_THRESHOLD
&& self.connections.len() < self.peers.len()
{
let new_connections = IPInterface::get_connections_to_peers(
&self.disconnected_peers(),
self.peers.len() < PEER_THRESHOLD,
);
self.connections.extend(new_connections);
}
let connections_to_delete = self
.connections
.iter_mut()
.enumerate()
.filter_map(|(i, conn)| {
IPInterface::send_package(conn, package.clone())
.err()
.map(|_| i)
})
.collect::<Vec<_>>();
for (j, index) in connections_to_delete.iter().enumerate() {
self.connections.remove(index - j);
}
self.connections
.extend(IPInterface::get_connections_to_peers(
&self.disconnected_peers(),
self.peers.len() < PEER_THRESHOLD,
))
}
};
Ok(())
@ -272,17 +344,19 @@ impl Interface for IPInterface {
IPInterface::new(data.port, data.peers)
} else {
let ip_path = std::path::Path::new(".if_ip_peers");
let peers = if ip_path.exists() {
std::fs::read_to_string(ip_path)
.unwrap()
.split('\n')
.filter_map(|line| net::SocketAddr::from_str(line).ok())
.map(|addr| (addr.ip(), addr.port()))
.collect()
let data = if ip_path.exists() {
std::fs::read_to_string(ip_path).unwrap()
} else if let Some(data) = DEFAULT_PEERS_FILE {
data.to_string()
} else {
println!("Warning: there are no peers in IP, which makes it essentially useless");
vec![]
"".to_string()
};
let peers = data
.split('\n')
.filter_map(|line| net::SocketAddr::from_str(line).ok())
.map(|addr| (addr.ip(), addr.port()))
.collect();
IPInterface::new(DEFAULT_PORT, peers)
}
}
@ -296,7 +370,7 @@ impl IPInterface {
.filter_map(|r| r.ok())
.filter_map(|r| r)
.map(|mut c| -> IFResult<TcpStream> {
println!("Requesting peers from {:?}", c.peer_addr().unwrap());
println!("Requesting peers from {:?}", c.peer_addr().ok());
if do_peer_request {
Self::request_peers(&mut c)?;
}
@ -306,6 +380,37 @@ impl IPInterface {
.collect::<Vec<_>>()
}
fn connected_addresses(&self) -> Vec<net::SocketAddr> {
self.connections
.iter()
.filter_map(|conn| conn.peer_addr().ok())
.collect::<Vec<_>>()
}
fn disconnected_peers(&self) -> Vec<Peer> {
let connected_addresses = self.connected_addresses();
self.peers
.iter()
.filter(|p| {
!connected_addresses
.iter()
.any(|addr| compare_addrs(p, *addr))
})
.copied()
.collect::<Vec<_>>()
}
fn remove_all_connections_to_peer(&mut self, peer: &Peer) {
while let Some(ind) = self
.connections
.iter()
.filter_map(|conn| conn.peer_addr().ok())
.position(|addr| compare_addrs(peer, addr))
{
self.connections.remove(ind);
}
}
pub fn new(port: u16, peers: Vec<Peer>) -> IFResult<Self> {
let listener = match create_tcp_listener(port) {
Some(listener) => listener,
@ -339,9 +444,8 @@ impl IPInterface {
Ok(())
}
fn send_package(stream: &mut net::TcpStream, package: IPPackage) -> IFResult<()> {
fn send_package(stream: &mut net::TcpStream, package: IPPackage) -> std::io::Result<()> {
stream.set_write_timeout(Some(std::time::Duration::from_millis(700)))?;
#[cfg(test)]
stream.set_nonblocking(false)?;
let mut header: Vec<u8> = vec![package.version, package.package_type.as_u8()];
for byte in size_to_bytes(package.size) {
@ -360,7 +464,7 @@ impl IPInterface {
Ok(())
}
fn request_peers(conn: &mut TcpStream) -> IFResult<()> {
fn request_peers(conn: &mut TcpStream) -> std::io::Result<()> {
IPInterface::send_package(
conn,
IPPackage {
@ -374,9 +478,12 @@ impl IPInterface {
}
fn obtain_connection(&mut self, addr: &Peer) -> IFResult<usize> {
if let Some(pos) = self.connections.iter().position(|con| {
con.peer_addr().is_ok() && compare_addrs(addr, con.peer_addr().unwrap())
}) {
if let Some(pos) = self
.connections
.iter()
.filter_map(|conn| conn.peer_addr().ok())
.position(|pa| compare_addrs(addr, pa))
{
return Ok(pos);
}
if let Some(conn) = Self::new_connection(addr)? {
@ -387,7 +494,7 @@ impl IPInterface {
}
}
fn new_connection(addr: &Peer) -> IFResult<Option<TcpStream>> {
fn new_connection(addr: &Peer) -> std::io::Result<Option<TcpStream>> {
for port in addr.1..addr.1 + 3 {
match net::TcpStream::connect_timeout(
&net::SocketAddr::new(addr.0, port as u16),

0
src/interfaces/mod.rs → ironforce/src/interfaces/mod.rs

76
src/ironforce.rs → ironforce/src/ironforce.rs

@ -16,7 +16,8 @@ const TUNNEL_MAX_REPEAT_COUNT: u32 = 3;
#[cfg(feature = "std")]
pub const DEFAULT_FILE: &str = ".if_data.json";
/// Main worker
/// Main IF worker
#[derive(Hash)]
pub struct IronForce {
/// Keys for this instance
pub keys: Keys,
@ -29,7 +30,7 @@ pub struct IronForce {
/// and some kind of decentralized storage
additional_modules: Vec<()>,
/// Non-service messages to give outside
messages: Vec<Message>,
pub messages: Vec<Message>,
/// Tunnels that has not been confirmed yet (no backward spread)
///
/// `[(Tunnel, Optional target node, local peer ids)]`
@ -53,9 +54,13 @@ pub struct IronForce {
/// Data for the serialization of IF
#[derive(Serialize, Deserialize)]
pub struct IFSerializationData {
/// Worker's keys
pub keys: Keys,
/// Saved tunnels that go through this node
pub tunnels: Vec<Tunnel>,
/// Peers for transport
pub peers: Vec<PeerInfo>,
/// Data for all interfaces (in IP, for example, that's port and IPs of peers)
pub interfaces_data: Vec<alloc::string::String>,
}
@ -70,6 +75,12 @@ impl IFSerializationData {
}
}
impl Default for IronForce {
fn default() -> Self {
Self::new()
}
}
impl IronForce {
/// Create new worker
pub fn new() -> Self {
@ -88,7 +99,7 @@ impl IronForce {
}
/// Create a new tunnel to another node
fn initialize_tunnel_creation(&mut self, destination: &PublicKey) -> IFResult<()> {
pub fn initialize_tunnel_creation(&mut self, destination: &PublicKey) -> IFResult<()> {
let tunnel = TunnelPublic::new_singlecast();
self.tunnels_pending
.push((tunnel.clone(), Some(destination.clone()), (0, 0)));
@ -108,6 +119,7 @@ impl IronForce {
/// Send a multicast or broadcast message
pub fn send_to_all(&mut self, message: Message) -> IFResult<()> {
self.processed_messages.push(message.message_id);
self.transport
.send_message(serde_cbor::to_vec(&message)?, None)
}
@ -119,6 +131,7 @@ impl IronForce {
mut message: Message,
direction: Option<bool>,
) -> IFResult<()> {
self.processed_messages.push(message.message_id);
let tunnel: Tunnel = if let Some(tun) = self
.tunnels
.iter()
@ -144,10 +157,9 @@ impl IronForce {
Ok(())
}
/// Send a message to another node,
/// creating a new tunnel if needed
pub fn send_message(&mut self, message: Message, destination: &PublicKey) -> IFResult<()> {
if let Some(Some(tunnel_id)) = self
/// Find a tunnel to another node (and return its id)
pub fn get_tunnel(&self, destination: &PublicKey) -> Option<u64> {
if let Some(Some(tun)) = self
.tunnels
.iter()
.find(|t| {
@ -159,9 +171,28 @@ impl IronForce {
})
.map(|tunnel| tunnel.id)
{
Some(tun)
} else {
None
}
}
/// Send a message to another node,
/// creating a new tunnel if needed
pub fn send_message(&mut self, message: Message, destination: &PublicKey) -> IFResult<()> {
if let Some(tunnel_id) = self.get_tunnel(destination) {
self.send_through_tunnel(tunnel_id, message, None)
} else {
Err(IFError::TunnelNotFound)
self.initialize_tunnel_creation(destination)?;
while self.get_tunnel(destination).is_none() {
if !self.has_background_worker {
self.main_loop_iteration()?
}
#[cfg(feature = "std")]
std::thread::sleep(std::time::Duration::from_millis(10));
}
let tunnel_id = self.get_tunnel(destination).unwrap();
self.send_through_tunnel(tunnel_id, message, None)
}
}
@ -266,7 +297,7 @@ impl IronForce {
MessageType::SingleCast if message.check_recipient(&self.keys) => {
#[cfg(feature = "std")]
println!("New message: {:?}", message.get_decrypted(&self.keys));
self.messages.push(message.clone())
self.messages.insert(0, message.clone())
}
MessageType::SingleCast => {
if let Some(tunnel) = self
@ -284,7 +315,11 @@ impl IronForce {
}
}
MessageType::Broadcast => {
self.messages.push(message.clone());
#[cfg(feature = "std")]
println!("New message: {:?}", message.get_decrypted(&self.keys));
if message.check_recipient(&self.keys) {
self.messages.insert(0, message.clone());
}
self.send_to_all(message)?;
}
}
@ -296,6 +331,7 @@ impl IronForce {
self.messages.pop()
}
/// Run one iteration of main loop: accepting incoming connections and messages, processing them
pub fn main_loop_iteration(&mut self) -> IFResult<()> {
self.transport.main_loop_iteration()?;
while let Some((msg, inc_peer)) = self.transport.receive() {
@ -304,10 +340,12 @@ impl IronForce {
Ok(())
}
/// Get an id for the public key of the worker
fn short_id(&self) -> alloc::string::String {
self.keys.get_public().get_short_id()
}
/// Get `IFSerializationData` that can be stored in a file
pub fn get_serialization_data(&self) -> IFSerializationData {
IFSerializationData {
keys: self.keys.clone(),
@ -317,6 +355,7 @@ impl IronForce {
}
}
/// Restore from `IFSerializationData`
pub fn from_serialization_data(data: IFSerializationData) -> IFResult<Self> {
Ok(Self {
keys: data.keys,
@ -332,16 +371,28 @@ impl IronForce {
})
}
/// Load from file (`filename`) with `IFSerializationData`
///
/// If the filename is empty, the default filename is used
#[cfg(feature = "std")]
pub fn from_file(filename: alloc::string::String) -> IFResult<Self> {
let filename = if filename.is_empty() { DEFAULT_FILE.to_string() } else { filename };
let filename = if filename.is_empty() {
DEFAULT_FILE.to_string()
} else {
filename
};
if std::path::Path::new(&filename).exists() {
Self::from_serialization_data(serde_json::from_str(std::fs::read_to_string(filename)?.as_str())?)
Self::from_serialization_data(serde_json::from_str(
std::fs::read_to_string(filename)?.as_str(),
)?)
} else {
Ok(Self::new())
}
}
/// Save `IFSerializationData` to a file with `filename`
///
/// If `filename` is None, the default filename is used
#[cfg(feature = "std")]
pub fn save_to_file(&self, filename: Option<alloc::string::String>) -> IFResult<()> {
std::fs::write(
@ -351,6 +402,7 @@ impl IronForce {
Ok(())
}
/// Spawn a thread with IF main loop and return `Arc<Mutex<IF>>`
#[cfg(feature = "std")]
pub fn launch_main_loop(
mut self,

2
src/lib.rs → ironforce/src/lib.rs

@ -12,6 +12,8 @@ extern crate rsa;
extern crate serde;
extern crate core_error;
extern crate spin;
#[cfg(feature = "std")]
extern crate include_optional;
mod crypto;
mod ironforce;

33
src/message.rs → ironforce/src/message.rs

@ -10,7 +10,7 @@ use sha2::Digest;
pub(crate) type MessageBytes = Vec<u8>;
/// Signature of the message: optional and optionally encrypted sender's key and signed hash
#[derive(Serialize, Deserialize, Clone, Debug)]
#[derive(Serialize, Deserialize, Clone, Debug, Hash)]
pub enum Signature {
/// The message is signed. Author is unknown
NotSigned,
@ -40,7 +40,7 @@ impl Signature {
}
/// Network name and version
#[derive(Serialize, Deserialize, Clone, Debug)]
#[derive(Serialize, Deserialize, Clone, Debug, Hash)]
pub struct NetworkInfo {
network_name: String,
version: String,
@ -55,7 +55,7 @@ impl Default for NetworkInfo {
}
}
#[derive(Serialize, Deserialize, Clone, Debug)]
#[derive(Serialize, Deserialize, Clone, Debug, Hash)]
pub enum MessageType {
SingleCast,
Broadcast,
@ -83,7 +83,7 @@ impl MessageType {
}
}
#[derive(Serialize, Deserialize, Clone, Debug)]
#[derive(Serialize, Deserialize, Clone, Debug, Hash)]
pub enum ServiceMessageType {
/// Creating a tunnel - stage 1
///
@ -92,7 +92,7 @@ pub enum ServiceMessageType {
TunnelBuildingBackwardMovement(TunnelPublic),
}
#[derive(Serialize, Deserialize, Clone, Debug)]
#[derive(Serialize, Deserialize, Clone, Debug, Hash)]
pub enum MessageContent {
/// Just plaintext message content
Plain(Vec<u8>),
@ -102,6 +102,12 @@ pub enum MessageContent {
None,
}
impl Default for MessageContent {
fn default() -> Self {
MessageContent::None
}
}
impl MessageContent {
pub fn hash(&self) -> Vec<u8> {
match self {
@ -121,7 +127,7 @@ impl MessageContent {
}
/// The struct for messages that are sent in the network
#[derive(Serialize, Deserialize, Clone, Debug)]
#[derive(Serialize, Deserialize, Clone, Debug, Hash)]
pub struct Message {
/// Content of the message (not to be confused with the bytes that we are sending through interfaces)
///
@ -180,9 +186,13 @@ impl Message {
/// Check if this message is for this set of keys
pub fn check_recipient(&self, keys: &Keys) -> bool {
if self.recipient_verification.is_none() {
true
} else {
keys.decrypt_data(&self.recipient_verification.clone().unwrap())
.is_ok()
}
}
/// Get decrypted content of the message
pub fn get_decrypted(&self, keys: &Keys) -> IFResult<Vec<u8>> {
@ -220,7 +230,7 @@ impl Message {
}
/// Try to get sender from the signature
fn get_sender(&self, keys: &Keys) -> Option<PublicKey> {
pub fn get_sender(&self, keys: &Keys) -> Option<PublicKey> {
match &self.signature {
Signature::NotSigned => None,
Signature::Signed { sender, .. } => Some(sender.clone()),
@ -247,6 +257,7 @@ impl Message {
}
/// Message builder to create a new message step-by-step, like `Message::build().message_type(...).sign(...)`
#[derive(Default)]
pub struct MessageBuilder {
content: MessageContent,
/// The type of the message to be built
@ -262,13 +273,7 @@ pub struct MessageBuilder {
impl MessageBuilder {
/// Create a new `MessageBuilder` with default parameters
pub fn new() -> Self {
Self {
content: MessageContent::None,
message_type: None,
sender: None,
recipient: None,
tunnel_id: (0, false),
}
Default::default()
}
pub fn content(mut self, cont: Vec<u8>) -> Self {

0
src/res.rs → ironforce/src/res.rs

9
src/transport.rs → ironforce/src/transport.rs

@ -13,7 +13,7 @@ use rayon::prelude::*;
use std::println;
/// An identification of a peer - something that we can use to send a message to id
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Hash)]
pub struct PeerInfo {
/// Something to locally identify this peer
pub peer_id: u64,
@ -196,6 +196,13 @@ impl Transport {
}
}
impl core::hash::Hash for Transport {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
core::hash::Hash::hash(&self.get_interfaces_data(), state);
core::hash::Hash::hash(&self.peers, state);
}
}
#[cfg(test)]
use crate::interface::test_interface::SimpleTestInterface;

4
src/tunnel.rs → ironforce/src/tunnel.rs

@ -5,7 +5,7 @@ use sha2::Digest;
use alloc::vec;
/// A tunnel that is used for communication
#[derive(Serialize, Clone, Deserialize, Debug)]
#[derive(Serialize, Clone, Deserialize, Debug, Hash)]
pub struct Tunnel {
/// Tunnel's id.
/// By the way, this id is `None` until the tunnel is validated in the backward movement
@ -25,7 +25,7 @@ pub struct Tunnel {
}
/// Tunnel, but only the fields that are ok to share
#[derive(Serialize, Clone, Deserialize, Debug)]
#[derive(Serialize, Clone, Deserialize, Debug, Hash)]
pub struct TunnelPublic {
/// Tunnel's id
pub id: Option<u64>,
Loading…
Cancel
Save