This commit is contained in:
JOLIMAITRE Matthieu 2022-08-22 01:59:56 +02:00
commit 62788c1b26
23 changed files with 1532 additions and 0 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
/db
/target

710
Cargo.lock generated Normal file
View file

@ -0,0 +1,710 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "android_system_properties"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7ed72e1635e121ca3e79420540282af22da58be50de153d36f81ddc6b83aa9e"
dependencies = [
"libc",
]
[[package]]
name = "async-trait"
version = "0.1.57"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76464446b8bc32758d7e88ee1a804d9914cd9b1cb264c029899680b0be29826f"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bumpalo"
version = "3.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1ad822118d20d2c234f427000d5acc36eabe1e29a348c89b63dd60b13f28e5d"
[[package]]
name = "byteorder"
version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
[[package]]
name = "bytes"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chrono"
version = "0.4.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1"
dependencies = [
"iana-time-zone",
"js-sys",
"num-integer",
"num-traits",
"time",
"wasm-bindgen",
"winapi",
]
[[package]]
name = "core-foundation-sys"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
[[package]]
name = "crc32fast"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
dependencies = [
"cfg-if",
]
[[package]]
name = "crossbeam-epoch"
version = "0.9.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "045ebe27666471bb549370b4b0b3e51b07f56325befa4284db65fc89c02511b1"
dependencies = [
"autocfg",
"cfg-if",
"crossbeam-utils",
"memoffset",
"once_cell",
"scopeguard",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51887d4adc7b564537b15adcfb307936f8075dfcd5f00dde9a9f1d29383682bc"
dependencies = [
"cfg-if",
"once_cell",
]
[[package]]
name = "fs2"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213"
dependencies = [
"libc",
"winapi",
]
[[package]]
name = "fxhash"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c"
dependencies = [
"byteorder",
]
[[package]]
name = "getrandom"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6"
dependencies = [
"cfg-if",
"libc",
"wasi 0.11.0+wasi-snapshot-preview1",
]
[[package]]
name = "harsh-client"
version = "0.1.0"
dependencies = [
"harsh_common",
"sled",
"telecomande",
"tokio",
]
[[package]]
name = "harsh_common"
version = "0.1.0"
dependencies = [
"serde",
"serde_json",
]
[[package]]
name = "harsh_server"
version = "0.1.0"
dependencies = [
"chrono",
"harsh_common",
"rand",
"serde",
"serde_json",
"sled",
"telecomande",
"tokio",
]
[[package]]
name = "hermit-abi"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
dependencies = [
"libc",
]
[[package]]
name = "iana-time-zone"
version = "0.1.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad2bfd338099682614d3ee3fe0cd72e0b6a41ca6a87f6a74a3bd593c91650501"
dependencies = [
"android_system_properties",
"core-foundation-sys",
"js-sys",
"wasm-bindgen",
"winapi",
]
[[package]]
name = "instant"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
dependencies = [
"cfg-if",
]
[[package]]
name = "itoa"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754"
[[package]]
name = "js-sys"
version = "0.3.59"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "258451ab10b34f8af53416d1fdab72c22e805f0c92a1136d59470ec0b11138b2"
dependencies = [
"wasm-bindgen",
]
[[package]]
name = "libc"
version = "0.2.132"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5"
[[package]]
name = "lock_api"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53"
dependencies = [
"autocfg",
"scopeguard",
]
[[package]]
name = "log"
version = "0.4.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
dependencies = [
"cfg-if",
]
[[package]]
name = "memchr"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]]
name = "memoffset"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
dependencies = [
"autocfg",
]
[[package]]
name = "mio"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf"
dependencies = [
"libc",
"log",
"wasi 0.11.0+wasi-snapshot-preview1",
"windows-sys",
]
[[package]]
name = "num-integer"
version = "0.1.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
dependencies = [
"autocfg",
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
dependencies = [
"autocfg",
]
[[package]]
name = "num_cpus"
version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1"
dependencies = [
"hermit-abi",
"libc",
]
[[package]]
name = "once_cell"
version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "074864da206b4973b84eb91683020dbefd6a8c3f0f38e054d93954e891935e4e"
[[package]]
name = "parking_lot"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99"
dependencies = [
"instant",
"lock_api",
"parking_lot_core 0.8.5",
]
[[package]]
name = "parking_lot"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
dependencies = [
"lock_api",
"parking_lot_core 0.9.3",
]
[[package]]
name = "parking_lot_core"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216"
dependencies = [
"cfg-if",
"instant",
"libc",
"redox_syscall",
"smallvec",
"winapi",
]
[[package]]
name = "parking_lot_core"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929"
dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"smallvec",
"windows-sys",
]
[[package]]
name = "pin-project-lite"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
[[package]]
name = "ppv-lite86"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872"
[[package]]
name = "proc-macro2"
version = "1.0.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rand"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"libc",
"rand_chacha",
"rand_core",
]
[[package]]
name = "rand_chacha"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
dependencies = [
"getrandom",
]
[[package]]
name = "redox_syscall"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
dependencies = [
"bitflags",
]
[[package]]
name = "ryu"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09"
[[package]]
name = "scopeguard"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "serde"
version = "1.0.144"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f747710de3dcd43b88c9168773254e809d8ddbdf9653b84e2554ab219f17860"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.144"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94ed3a816fb1d101812f83e789f888322c34e291f894f19590dc310963e87a00"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38dd04e3c8279e75b31ef29dbdceebfe5ad89f4d0937213c53f7d49d01b3d5a7"
dependencies = [
"itoa",
"ryu",
"serde",
]
[[package]]
name = "signal-hook-registry"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0"
dependencies = [
"libc",
]
[[package]]
name = "sled"
version = "0.34.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f96b4737c2ce5987354855aed3797279def4ebf734436c6aa4552cf8e169935"
dependencies = [
"crc32fast",
"crossbeam-epoch",
"crossbeam-utils",
"fs2",
"fxhash",
"libc",
"log",
"parking_lot 0.11.2",
]
[[package]]
name = "smallvec"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1"
[[package]]
name = "socket2"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0"
dependencies = [
"libc",
"winapi",
]
[[package]]
name = "syn"
version = "1.0.99"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58dbef6ec655055e20b86b15a8cc6d439cca19b667537ac6a1369572d151ab13"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "telecomande"
version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e2911385f0e73674cd9a881c3121b27a78bbc1a4f25fce42989e85c6b571679"
dependencies = [
"async-trait",
"tokio",
]
[[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",
]
[[package]]
name = "tokio"
version = "1.20.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a8325f63a7d4774dd041e363b2409ed1c5cbbd0f867795e661df066b2b0a581"
dependencies = [
"autocfg",
"bytes",
"libc",
"memchr",
"mio",
"num_cpus",
"once_cell",
"parking_lot 0.12.1",
"pin-project-lite",
"signal-hook-registry",
"socket2",
"tokio-macros",
"winapi",
]
[[package]]
name = "tokio-macros"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "unicode-ident"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf"
[[package]]
name = "wasi"
version = "0.10.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "wasm-bindgen"
version = "0.2.82"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc7652e3f6c4706c8d9cd54832c4a4ccb9b5336e2c3bd154d5cccfbf1c1f5f7d"
dependencies = [
"cfg-if",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.82"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "662cd44805586bd52971b9586b1df85cdbbd9112e4ef4d8f41559c334dc6ac3f"
dependencies = [
"bumpalo",
"log",
"once_cell",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.82"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b260f13d3012071dfb1512849c033b1925038373aea48ced3012c09df952c602"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.82"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5be8e654bdd9b79216c2929ab90721aa82faf65c48cdf08bdc4e7f51357b80da"
dependencies = [
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.82"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6598dd0bd3c7d51095ff6531a5b23e02acdc81804e30d8f07afb77b7215a140a"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-sys"
version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2"
dependencies = [
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_msvc"
version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47"
[[package]]
name = "windows_i686_gnu"
version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6"
[[package]]
name = "windows_i686_msvc"
version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024"
[[package]]
name = "windows_x86_64_gnu"
version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1"
[[package]]
name = "windows_x86_64_msvc"
version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680"

3
Cargo.toml Normal file
View file

@ -0,0 +1,3 @@
[workspace]
members = ["harsh-client", "harsh-server", "harsh-common"]

1
harsh-client/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/target

12
harsh-client/Cargo.toml Normal file
View file

@ -0,0 +1,12 @@
[package]
name = "harsh-client"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
sled = "0.34.7"
telecomande = "1.2.2"
tokio = { version = "1.20.1", features = ["full"] }
harsh_common = { path = "../harsh-common" }

154
harsh-client/src/main.rs Normal file
View file

@ -0,0 +1,154 @@
use tokio::{
io::{stdin, AsyncBufReadExt, AsyncWriteExt, BufReader},
net::TcpStream,
};
#[tokio::main]
async fn main() {
println!("starting client ...");
let stream = TcpStream::connect("localhost:8080").await.unwrap();
println!("connected to 'localhost:8080'");
let (reader, writer) = stream.into_split();
tokio::spawn(async {
let mut reader = BufReader::new(reader);
loop {
let mut line = String::new();
reader.read_line(&mut line).await.unwrap();
println!("received '{line}'");
}
});
let input_loop = tokio::spawn(async {
let mut input = BufReader::new(stdin());
let mut writer = writer;
loop {
let mut line = String::new();
input.read_line(&mut line).await.unwrap();
let input = commands::parse(&line);
match input {
None => println!("failed to parse command"),
Some(commands::Command::Help) => commands::help(),
Some(commands::Command::Request(cmd)) => {
println!("sending..");
writer.write_all(cmd.serialize().as_bytes()).await.unwrap();
writer.write_all(b"\n").await.unwrap();
}
}
}
});
println!("awaiting input ...");
input_loop.await.unwrap();
}
mod commands {
pub enum Command {
Help,
Request(harsh_common::ClientRequest),
}
pub fn parse(input: &str) -> Option<Command> {
let mut parts = smart_split(input).into_iter();
let command = match parts.next()?.as_str() {
"help" => return Some(Command::Help),
"ping" => {
let rest = parts.collect::<Box<[_]>>();
let content = rest.join(" ");
harsh_common::ClientRequest::new_ping(content)
}
_ => return None,
};
Some(Command::Request(command))
}
pub fn smart_split(input: &str) -> Vec<String> {
let input = input.trim();
let mut result = Vec::new();
let mut capturing = false;
let mut ignoring = false;
let mut current = String::new();
for char in input.chars() {
let char: char = char;
if ignoring {
current.push(char);
ignoring = false;
continue;
}
match char {
'\\' => {
ignoring = true;
}
'"' => {
capturing = !capturing;
}
' ' if !capturing => {
result.push(current);
current = String::new();
}
_ => current.push(char),
}
}
result.push(current);
result
}
#[test]
fn test_smart_split() {
assert_eq!(
smart_split("hello world"),
vec!["hello".to_string(), "world".to_string()]
);
assert_eq!(
smart_split(r#""lorem ipsum" "dolor amit""#),
vec!["lorem ipsum".to_string(), "dolor amit".to_string()]
);
assert_eq!(
smart_split(r#"lorem "ipsum do"lor "amit""#),
vec![
"lorem".to_string(),
"ipsum dolor".to_string(),
"amit".to_string()
]
);
}
pub struct Description {
name: &'static str,
params: &'static [&'static str],
desc: &'static str,
}
impl Description {
pub const fn new(
name: &'static str,
params: &'static [&'static str],
desc: &'static str,
) -> Self {
Self { name, desc, params }
}
pub const ALL: &'static [Self] = &[
// all commands
Self::new("help", &[], "returns a help message"),
Self::new(
"ping",
&["content"],
"sends a ping with the specified content",
),
];
}
pub fn help() {
for &Description { name, params, desc } in Description::ALL {
let mut usage = params.iter().map(|s| s.to_string()).collect::<Vec<_>>();
usage.insert(0, name.to_string());
let usage = usage.join(" ");
println!("{name}:\n\tusage:\n\t\t{usage}\n\n\tdescription:\n\t\t{desc}\n");
}
}
}

2
harsh-common/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
/target
/Cargo.lock

10
harsh-common/Cargo.toml Normal file
View file

@ -0,0 +1,10 @@
[package]
name = "harsh_common"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
serde = { version = "1.0.144", features = ["derive"] }
serde_json = "1.0.83"

View file

@ -0,0 +1,42 @@
#[derive(Debug)]
pub enum ClientRequest {
Ping(Ping),
}
#[derive(Debug)]
pub struct Ping {
pub content: String,
}
impl ClientRequest {
pub fn new_ping(content: String) -> Self {
Self::Ping(Ping { content })
}
pub fn try_parse(line: &str) -> Option<Self> {
let command: repr::Command = serde_json::from_str(line).ok()?;
let mapped = match command {
repr::Command::ping { content } => Self::Ping(Ping { content }),
};
Some(mapped)
}
pub fn serialize(self) -> String {
let mapped = match self {
Self::Ping(Ping { content }) => repr::Command::ping { content },
};
serde_json::to_string(&mapped).unwrap()
}
}
mod repr {
#![allow(non_camel_case_types)]
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum Command {
ping { content: String },
}
}

14
harsh-common/src/lib.rs Normal file
View file

@ -0,0 +1,14 @@
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
let result = 2 + 2;
assert_eq!(result, 4);
}
}
pub use client::{ClientRequest, Ping};
mod client;
pub use server::{Pong, ServerRequest};
mod server;

View file

@ -0,0 +1,40 @@
pub enum ServerRequest {
Pong(Pong),
}
pub struct Pong {
pub content: String,
}
impl ServerRequest {
pub fn new_pong(content: String) -> Self {
Self::Pong(Pong { content })
}
pub fn try_parse(line: &str) -> Option<Self> {
let command: repr::Command = serde_json::from_str(line).ok()?;
let mapped = match command {
repr::Command::pong { content } => Self::Pong(Pong { content }),
};
Some(mapped)
}
pub fn serialize(self) -> String {
let mapped = match self {
Self::Pong(Pong { content }) => repr::Command::pong { content },
};
serde_json::to_string(&mapped).unwrap()
}
}
mod repr {
#![allow(non_camel_case_types)]
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum Command {
pong { content: String },
}
}

1
harsh-server/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/target

17
harsh-server/Cargo.toml Normal file
View file

@ -0,0 +1,17 @@
[package]
name = "harsh_server"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
#sleded = "1.0"
sled = "0.34"
telecomande = "1.2"
tokio = { version = "1.20", features = ["full"] }
harsh_common = { path = "../harsh-common/" }
chrono = "0.4"
rand = "0.8.5"

View file

@ -0,0 +1,61 @@
use harsh_common::{Ping, Pong, ServerRequest};
use telecomande::{Processor, Remote};
use harsh_common::ClientRequest;
use crate::{sessions, Addr, SessionProc, StorageProc};
#[derive(Debug)]
pub enum GatewayCmd {
Request(Addr, String),
ClosedConnection(Addr),
}
pub struct GatewayProc {
client_handler: Remote<SessionProc>,
storage: Remote<StorageProc>,
}
impl GatewayProc {
pub fn new(client_handler: Remote<SessionProc>, storage: Remote<StorageProc>) -> Self {
Self {
client_handler,
storage,
}
}
async fn handle_request(&mut self, address: Addr, request: ClientRequest) {
match request {
ClientRequest::Ping(Ping { content }) => {
println!("received ping! '{content:?}'");
let response = ServerRequest::Pong(Pong { content });
let content = response.serialize();
self.client_handler
.send(sessions::SessionCmd::Send(address, content))
.unwrap();
}
}
}
}
#[telecomande::async_trait]
impl Processor for GatewayProc {
type Command = GatewayCmd;
type Error = ();
async fn handle(&mut self, command: Self::Command) -> Result<(), Self::Error> {
match command {
GatewayCmd::Request(address, request) => {
if let Some(request) = ClientRequest::try_parse(&request) {
self.handle_request(address, request).await;
} else {
println!("failed to parse command");
}
}
GatewayCmd::ClosedConnection(address) => self
.client_handler
.send(sessions::SessionCmd::RemoveSession(address))
.unwrap(),
}
Ok(())
}
}

42
harsh-server/src/main.rs Normal file
View file

@ -0,0 +1,42 @@
use telecomande::{Executor, SimpleExecutor};
use tokio::net::TcpListener;
#[tokio::main]
async fn main() {
println!("starting server ...");
let client_handler = SimpleExecutor::new(SessionProc::default()).spawn();
let storage = SimpleExecutor::new(StorageProc::new("./db")).spawn();
let gateway =
SimpleExecutor::new(GatewayProc::new(client_handler.remote(), storage.remote())).spawn();
println!("spawned gateway");
let listener = TcpListener::bind("localhost:8080").await.unwrap();
println!("listening on 'localhost:8080' ...");
let client_handler = client_handler.remote();
loop {
let (stream, address) = listener.accept().await.unwrap();
println!("new connection from '{address:?}'");
client_handler
.send(sessions::SessionCmd::AddSession(
stream,
address,
gateway.remote(),
))
.unwrap();
}
}
mod utils;
pub use utils::{Addr, Id};
mod gateway;
pub use gateway::{GatewayCmd, GatewayProc};
mod sessions;
pub use sessions::{SessionCmd, SessionProc};
pub use storage::{StorageCmd, StorageProc};
mod storage;

View file

@ -0,0 +1,82 @@
use std::{collections::HashMap, net::SocketAddr};
use telecomande::{Processor, Remote};
use tokio::{
io::{AsyncBufReadExt, AsyncWriteExt, BufReader},
net::{
tcp::{OwnedReadHalf, OwnedWriteHalf},
TcpStream,
},
task::JoinHandle,
};
use crate::{gateway, Addr};
#[derive(Debug)]
pub enum SessionCmd {
AddSession(TcpStream, SocketAddr, Remote<gateway::GatewayProc>),
RemoveSession(Addr),
Send(Addr, String),
}
#[derive(Debug, Default)]
pub struct SessionProc {
clients: HashMap<Addr, (OwnedWriteHalf, JoinHandle<()>)>,
}
impl SessionProc {
fn add_client(
&mut self,
stream: TcpStream,
address: Addr,
remote: Remote<gateway::GatewayProc>,
) {
let (reader, writer) = stream.into_split();
let handle = tokio::spawn(session(address.clone(), reader, remote));
self.clients.insert(address, (writer, handle));
}
}
#[telecomande::async_trait]
impl Processor for SessionProc {
type Command = SessionCmd;
type Error = ();
async fn handle(&mut self, command: Self::Command) -> Result<(), Self::Error> {
match command {
SessionCmd::AddSession(stream, address, remote) => {
let address = Addr::new(address);
self.add_client(stream, address, remote)
}
SessionCmd::RemoveSession(address) => {
self.clients.remove(&address);
}
SessionCmd::Send(address, content) => {
if let Some((client, _)) = self.clients.get_mut(&address) {
client.write_all(content.as_bytes()).await.unwrap();
client.write_all(b"\n").await.unwrap();
} else {
println!("failed to find session with address '{address:?}'")
}
}
};
Ok(())
}
}
async fn session(address: Addr, reader: OwnedReadHalf, remote: Remote<gateway::GatewayProc>) {
let mut reader = BufReader::new(reader);
loop {
let mut line = String::new();
if let Err(error) = reader.read_line(&mut line).await {
eprintln!("{error}");
break;
}
remote
.send(gateway::GatewayCmd::Request(address.clone(), line.clone()))
.unwrap();
}
remote
.send(gateway::GatewayCmd::ClosedConnection(address))
.unwrap();
}

202
harsh-server/src/storage.rs Normal file
View file

@ -0,0 +1,202 @@
use sled::Db;
use telecomande::Processor;
use tokio::sync::oneshot::{self, Receiver, Sender};
use crate::Id;
#[derive(Debug)]
pub enum StorageCmd {
ChannelCreate(String, Sender<Id>),
ChannelDelete(Id),
ChannelGetAll(Sender<Vec<Id>>),
ChannelGetName(Id, Sender<Option<String>>),
}
impl StorageCmd {
fn new_channel_create(name: impl ToString) -> (Self, Receiver<Id>) {
let (s, r) = oneshot::channel();
(Self::ChannelCreate(name.to_string(), s), r)
}
fn new_channel_delete(id: Id) -> Self {
Self::ChannelDelete(id)
}
fn new_channel_get_all() -> (Self, Receiver<Vec<Id>>) {
let (s, r) = oneshot::channel();
(Self::ChannelGetAll(s), r)
}
fn new_channel_get_name(id: Id) -> (Self, Receiver<Option<String>>) {
let (s, r) = oneshot::channel();
(Self::ChannelGetName(id, s), r)
}
}
pub struct StorageProc {
base: Db,
}
impl StorageProc {
pub fn new<S>(path: S) -> Self
where
S: ToString,
{
let path = path.to_string();
let base = sled::open(path).unwrap();
Self { base }
}
fn get<S, T>(&self, path: S) -> Option<T>
where
S: ToString,
T: SerDeser,
{
let path = path.to_string();
T::read(&self.base, path)
}
fn set<S, T>(&self, path: S, item: T)
where
S: ToString,
T: SerDeser,
{
let path = path.to_string();
item.write(&self.base, path)
}
fn list(&self, path: impl ToString) -> Vec<Id> {
let path = path.to_string();
list(&self.base, path).collect() // TODO: turn into iterator with limits
}
// firsts (x)
// lasts (x)
// from (id, x)
// to (id, x)
fn remove(&self, path: impl ToString) {
let path = path.to_string();
remove(&self.base, path)
}
}
#[telecomande::async_trait]
impl Processor for StorageProc {
type Command = StorageCmd;
type Error = ();
async fn handle(&mut self, command: Self::Command) -> Result<(), Self::Error> {
match command {
// channels
StorageCmd::ChannelDelete(id) => self.remove(format!("/channels/{id}")),
StorageCmd::ChannelCreate(name, sender) => {
let item = Channel::new(name);
let id = item.get_id();
self.set(format!("/channels/{id}"), item);
sender.send(id).unwrap();
}
StorageCmd::ChannelGetAll(sender) => {
let results = self.list("/channels/");
sender.send(results).unwrap();
}
StorageCmd::ChannelGetName(id, sender) => {
let result = self
.get::<_, Channel>(format!("/channels/{id}"))
.map(|channel| channel.get_name().to_string());
sender.send(result).unwrap();
} //
// ChannelGetParent
// messages
// c
// d
// l
// gcontent
};
Ok(())
}
}
mod models;
pub use models::{Channel, Msg, SerDeser, User};
fn list(db: &Db, path: impl ToString) -> impl Iterator<Item = Id> {
let path = path.to_string();
let len = path.len();
db.scan_prefix(path)
.filter_map(move |result| -> Option<Id> {
let (key, _) = result.ok()?;
let string = String::from_utf8(key.iter().cloned().collect()).unwrap();
let suffix = &string[len..];
Id::from_string(suffix)
})
}
fn remove(db: &Db, path: impl ToString) {
let path = path.to_string();
db.remove(path).unwrap();
}
#[test]
fn test_list() {
let db = sled::open("/tmp/test-db").unwrap();
db.insert("/some/path/123", b"hello1").unwrap();
db.insert("/some/path/1234", b"hello2").unwrap();
db.insert("/some/path/12345", b"hello3").unwrap();
let results = list(&db, "/some/path/".to_string());
let vec = results.collect::<Vec<_>>();
assert_eq!(
vec,
vec![
Id::from_string("123").unwrap(),
Id::from_string("1234").unwrap(),
Id::from_string("12345").unwrap()
]
);
}
#[tokio::test]
async fn test_channels() {
use telecomande::{Executor, SimpleExecutor};
// cleaning;
std::fs::remove_dir_all("/tmp/db-test").ok();
// instantiation
let store = SimpleExecutor::new(StorageProc::new("/tmp/db-test")).spawn();
let remote = store.remote();
// insertion
let (cmd, rec) = StorageCmd::new_channel_create("a-channel");
remote.send(cmd).unwrap();
let id = rec.await.unwrap();
// query all
let (cmd, rec) = StorageCmd::new_channel_get_all();
remote.send(cmd).unwrap();
let result = rec.await.unwrap();
assert_eq!(result.len(), 1);
let first = result[0];
assert_eq!(first, id);
// query property
let (cmd, rec) = StorageCmd::new_channel_get_name(id);
remote.send(cmd).unwrap();
let result = rec.await.unwrap();
assert_eq!(result.unwrap(), "a-channel".to_string());
// insertion
let (cmd, rec) = StorageCmd::new_channel_create("b-channel");
remote.send(cmd).unwrap();
let id2 = rec.await.unwrap();
// query all
let (cmd, rec) = StorageCmd::new_channel_get_all();
remote.send(cmd).unwrap();
let result = rec.await.unwrap();
assert_eq!(result.len(), 2);
// query property
let (cmd, rec) = StorageCmd::new_channel_get_name(id2);
remote.send(cmd).unwrap();
let result = rec.await.unwrap();
assert_eq!(result.unwrap(), "b-channel".to_string());
}

View file

@ -0,0 +1,67 @@
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use sled::Db;
use crate::Id;
#[derive(Debug, Serialize, Deserialize)]
pub struct Channel {
id: Id,
name: String,
}
impl Channel {
pub fn new(name: String) -> Self {
let id = Id::from_now();
Self { id, name }
}
pub fn get_id(&self) -> Id {
self.id
}
pub fn get_name(&self) -> &str {
&self.name
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct User {
id: Id,
name: String,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Msg {
id: Id,
content: String,
}
pub trait SerDeser: Serialize + DeserializeOwned {
fn ser(&self) -> Vec<u8>;
fn deser(input: &[u8]) -> Option<Self>;
fn read(db: &Db, path: String) -> Option<Self>;
fn write(&self, db: &Db, path: String);
}
impl<T> SerDeser for T
where
T: Serialize + DeserializeOwned,
{
fn ser(&self) -> Vec<u8> {
serde_json::to_vec(self).unwrap()
}
fn deser(input: &[u8]) -> Option<Self> {
serde_json::from_slice(input).ok()
}
fn read(db: &Db, path: String) -> Option<Self> {
let bytes = db.get(path).unwrap()?;
Self::deser(&bytes)
}
fn write(&self, db: &Db, path: String) {
let bytes = self.ser();
db.insert(path, bytes).unwrap();
}
}

56
harsh-server/src/utils.rs Normal file
View file

@ -0,0 +1,56 @@
use std::{fmt::Display, net::SocketAddr};
use rand::random;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Addr(String);
impl Addr {
pub fn new(address: SocketAddr) -> Self {
let string = format!("{address:?}");
Self(string)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct Id(u64);
impl Id {
pub fn from_now() -> Self {
let ms = chrono::Utc::now().timestamp_millis() as u64;
let total = (ms * 1000) + rand_range(1000);
Self(total)
}
pub fn from_string(input: &str) -> Option<Self> {
let inner: u64 = input.parse().ok()?;
Some(Self(inner))
}
}
#[test]
fn test_string_convertion() {
let id = Id::from_now();
let str = id.to_string();
assert_eq!(id, Id::from_string(&str).unwrap());
}
fn rand_range(n: u64) -> u64 {
let random: u64 = random();
random % n
}
impl Display for Id {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let inner = self.0;
let padded = format!("{inner:0>20}"); // pads to the left to make 20 chars of length
f.write_str(&padded)
}
}
#[test]
fn length_of_max() {
assert_eq!(u64::MAX, 18446744073709551615_u64);
assert_eq!(20, "18446744073709551615".len())
}

4
start-client.sh Executable file
View file

@ -0,0 +1,4 @@
#!/bin/sh
cd harsh-client;
cargo run;

4
start-server.sh Executable file
View file

@ -0,0 +1,4 @@
#!/bin/sh
cd harsh-server;
cargo run;

3
watch-client.sh Executable file
View file

@ -0,0 +1,3 @@
#!/bin/sh
nodemon "harsh-client/src" "harsh-server/src" "harsh-common/src" -x "./start-client.sh" -e "rs"

3
watch-server.sh Normal file
View file

@ -0,0 +1,3 @@
#!/bin/sh
nodemon "harsh-client/src" "harsh-server/src" "harsh-common/src" -x "./start-server.sh" -e "rs"