Merge pull request #1 from kleo-dev/working-node

Working node system
This commit is contained in:
Klesti Selimaj
2025-10-04 22:23:40 +02:00
committed by GitHub
16 changed files with 2007 additions and 0 deletions
+9
View File
@@ -19,3 +19,12 @@ target
# and can be added to the global gitignore or merged into this file. For a more nuclear # and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder. # option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/ #.idea/
# Added by cargo
/target
*.db
config.json
Generated
+814
View File
@@ -0,0 +1,814 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "adler2"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
[[package]]
name = "android_system_properties"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
dependencies = [
"libc",
]
[[package]]
name = "anyhow"
version = "1.0.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
[[package]]
name = "autocfg"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
[[package]]
name = "base64"
version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
name = "bitflags"
version = "2.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394"
[[package]]
name = "block-buffer"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
dependencies = [
"generic-array",
]
[[package]]
name = "bumpalo"
version = "3.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
[[package]]
name = "bytes"
version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
[[package]]
name = "cc"
version = "1.2.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1d05d92f4b1fd76aad469d46cdd858ca761576082cd37df81416691e50199fb"
dependencies = [
"find-msvc-tools",
"shlex",
]
[[package]]
name = "cfg-if"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9"
[[package]]
name = "chrono"
version = "0.4.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2"
dependencies = [
"iana-time-zone",
"js-sys",
"num-traits",
"wasm-bindgen",
"windows-link",
]
[[package]]
name = "core-foundation-sys"
version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]]
name = "cpufeatures"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
dependencies = [
"libc",
]
[[package]]
name = "crc32fast"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511"
dependencies = [
"cfg-if",
]
[[package]]
name = "crypto-common"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
dependencies = [
"generic-array",
"typenum",
]
[[package]]
name = "digest"
version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
"block-buffer",
"crypto-common",
]
[[package]]
name = "fallible-iterator"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649"
[[package]]
name = "fallible-streaming-iterator"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
[[package]]
name = "find-msvc-tools"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0399f9d26e5191ce32c498bebd31e7a3ceabc2745f0ac54af3f335126c3f24b3"
[[package]]
name = "flate2"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d"
dependencies = [
"crc32fast",
"miniz_oxide",
]
[[package]]
name = "fnv"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "foldhash"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
[[package]]
name = "generic-array"
version = "0.14.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
dependencies = [
"typenum",
"version_check",
]
[[package]]
name = "getrandom"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "hashbrown"
version = "0.15.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
dependencies = [
"foldhash",
]
[[package]]
name = "hashlink"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1"
dependencies = [
"hashbrown",
]
[[package]]
name = "http"
version = "1.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565"
dependencies = [
"bytes",
"fnv",
"itoa",
]
[[package]]
name = "httparse"
version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
[[package]]
name = "iana-time-zone"
version = "0.1.64"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb"
dependencies = [
"android_system_properties",
"core-foundation-sys",
"iana-time-zone-haiku",
"js-sys",
"log",
"wasm-bindgen",
"windows-core",
]
[[package]]
name = "iana-time-zone-haiku"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
dependencies = [
"cc",
]
[[package]]
name = "itoa"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
[[package]]
name = "js-sys"
version = "0.3.81"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec48937a97411dcb524a265206ccd4c90bb711fca92b2792c407f268825b9305"
dependencies = [
"once_cell",
"wasm-bindgen",
]
[[package]]
name = "libc"
version = "0.2.176"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174"
[[package]]
name = "libsqlite3-sys"
version = "0.35.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "133c182a6a2c87864fe97778797e46c7e999672690dc9fa3ee8e241aa4a9c13f"
dependencies = [
"pkg-config",
"vcpkg",
]
[[package]]
name = "log"
version = "0.4.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432"
[[package]]
name = "memchr"
version = "2.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
[[package]]
name = "miniz_oxide"
version = "0.8.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
dependencies = [
"adler2",
]
[[package]]
name = "num-traits"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
]
[[package]]
name = "once_cell"
version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "percent-encoding"
version = "2.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
[[package]]
name = "pkg-config"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
[[package]]
name = "proc-macro2"
version = "1.0.101"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1"
dependencies = [
"proc-macro2",
]
[[package]]
name = "ring"
version = "0.17.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
dependencies = [
"cc",
"cfg-if",
"getrandom",
"libc",
"untrusted",
"windows-sys",
]
[[package]]
name = "rusqlite"
version = "0.37.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "165ca6e57b20e1351573e3729b958bc62f0e48025386970b6e4d29e7a7e71f3f"
dependencies = [
"bitflags",
"fallible-iterator",
"fallible-streaming-iterator",
"hashlink",
"libsqlite3-sys",
"smallvec",
]
[[package]]
name = "rustls"
version = "0.23.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd3c25631629d034ce7cd9940adc9d45762d46de2b0f57193c4443b92c6d4d40"
dependencies = [
"log",
"once_cell",
"ring",
"rustls-pki-types",
"rustls-webpki",
"subtle",
"zeroize",
]
[[package]]
name = "rustls-pemfile"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50"
dependencies = [
"rustls-pki-types",
]
[[package]]
name = "rustls-pki-types"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79"
dependencies = [
"zeroize",
]
[[package]]
name = "rustls-webpki"
version = "0.103.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e10b3f4191e8a80e6b43eebabfac91e5dcecebb27a71f04e820c47ec41d314bf"
dependencies = [
"ring",
"rustls-pki-types",
"untrusted",
]
[[package]]
name = "rustversion"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
[[package]]
name = "ryu"
version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
[[package]]
name = "serde"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
dependencies = [
"serde_core",
"serde_derive",
]
[[package]]
name = "serde_core"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.145"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c"
dependencies = [
"itoa",
"memchr",
"ryu",
"serde",
"serde_core",
]
[[package]]
name = "sha1"
version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
dependencies = [
"cfg-if",
"cpufeatures",
"digest",
]
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "smallvec"
version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
[[package]]
name = "subtle"
version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]]
name = "syn"
version = "2.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "typenum"
version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
[[package]]
name = "unicode-ident"
version = "1.0.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d"
[[package]]
name = "untrusted"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
[[package]]
name = "ureq"
version = "3.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99ba1025f18a4a3fc3e9b48c868e9beb4f24f4b4b1a325bada26bd4119f46537"
dependencies = [
"base64",
"flate2",
"log",
"percent-encoding",
"rustls",
"rustls-pemfile",
"rustls-pki-types",
"ureq-proto",
"utf-8",
"webpki-roots",
]
[[package]]
name = "ureq-proto"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60b4531c118335662134346048ddb0e54cc86bd7e81866757873055f0e38f5d2"
dependencies = [
"base64",
"http",
"httparse",
"log",
]
[[package]]
name = "utf-8"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
[[package]]
name = "vcpkg"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]]
name = "version_check"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "voxa-node"
version = "0.1.0"
dependencies = [
"anyhow",
"base64",
"chrono",
"once_cell",
"rusqlite",
"serde",
"serde_json",
"sha1",
"ureq",
]
[[package]]
name = "wasi"
version = "0.11.1+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]]
name = "wasm-bindgen"
version = "0.2.104"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1da10c01ae9f1ae40cbfac0bac3b1e724b320abfcf52229f80b547c0d250e2d"
dependencies = [
"cfg-if",
"once_cell",
"rustversion",
"wasm-bindgen-macro",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.104"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "671c9a5a66f49d8a47345ab942e2cb93c7d1d0339065d4f8139c486121b43b19"
dependencies = [
"bumpalo",
"log",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.104"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ca60477e4c59f5f2986c50191cd972e3a50d8a95603bc9434501cf156a9a119"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.104"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f07d2f20d4da7b26400c9f4a0511e6e0345b040694e8a75bd41d578fa4421d7"
dependencies = [
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.104"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bad67dc8b2a1a6e5448428adec4c3e84c43e561d8c9ee8a9e5aabeb193ec41d1"
dependencies = [
"unicode-ident",
]
[[package]]
name = "webpki-roots"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e8983c3ab33d6fb807cfcdad2491c4ea8cbc8ed839181c7dfd9c67c83e261b2"
dependencies = [
"rustls-pki-types",
]
[[package]]
name = "windows-core"
version = "0.62.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6844ee5416b285084d3d3fffd743b925a6c9385455f64f6d4fa3031c4c2749a9"
dependencies = [
"windows-implement",
"windows-interface",
"windows-link",
"windows-result",
"windows-strings",
]
[[package]]
name = "windows-implement"
version = "0.60.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edb307e42a74fb6de9bf3a02d9712678b22399c87e6fa869d6dfcd8c1b7754e0"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "windows-interface"
version = "0.59.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0abd1ddbc6964ac14db11c7213d6532ef34bd9aa042c2e5935f59d7908b46a5"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "windows-link"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65"
[[package]]
name = "windows-result"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7084dcc306f89883455a206237404d3eaf961e5bd7e0f312f7c91f57eb44167f"
dependencies = [
"windows-link",
]
[[package]]
name = "windows-strings"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7218c655a553b0bed4426cf54b20d7ba363ef543b52d515b3e48d7fd55318dda"
dependencies = [
"windows-link",
]
[[package]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "zeroize"
version = "1.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
+15
View File
@@ -0,0 +1,15 @@
[package]
name = "voxa-node"
version = "0.1.0"
edition = "2024"
[dependencies]
serde = { version = "1.0.228", features = ["derive"] }
serde_json = "1.0.145"
anyhow = "1.0.100"
base64 = "0.22.1"
sha1 = "0.10.6"
once_cell = "1.21.3"
chrono = "0.4.42"
ureq = "3.1.2"
rusqlite = "0.37.0"
+37
View File
@@ -1,2 +1,39 @@
# voxa-node # voxa-node
The server for the DM and notification system of Voxa The server for the DM and notification system of Voxa
### What is a Voxa Node?
A voxa node is a server that handles DMs sent to you and push message notifications, a client will keep a constant connection with their chosen node and when a sender wants to send a DM or a message notification they can make a HTTP request to the same port, it's important to note that a node server only handles the receiving side, unless both parties are in the same node.
A analogy to a node is a email server but real time.
### What will a Voxa Node store?
A voxa node stores messages that are sent to you, additionally push notifications and after 3 days of inactivity notifications will be removed.
### Why Voxa Nodes over traditional PM/DM structure
Traditional structures often are a privacy threat even with End-To-End encryption, additionally they are very resource intensive which in my situation is necessary to reduce as much as possible.
Another big issue with the traditional single server structure is that it may apply censorship, our biggest enemy.
```mermaid
sequenceDiagram
participant Bob
participant AuthServer as Alice's Node (3rd Party Server)
participant Alice
participant Cloud as Voxa Cloud
Bob->>Bob: Hash message (SHA-256)
Bob->>Bob: Sign hash with Private Key
Bob->>AuthServer: Send {message, signature}
AuthServer->>Alice: Relay {message, signature}
Alice->>Cloud: Request Bob's Public Key
Cloud-->>Alice: Return Bob's Public Key
Alice->>Alice: Verify signature using Public Key<br/>and recompute SHA-256(message)
Alice-->>Alice: Accept message if signature is valid
```
+30
View File
@@ -0,0 +1,30 @@
#[macro_export]
macro_rules! export_plugin {
($p:expr) => {
#[unsafe(no_mangle)]
pub extern "C" fn load_plugin() -> $crate::utils::plugin::DynPlugin {
$p
}
};
}
#[macro_export]
macro_rules! logger {
(const $i:ident $name:expr) => {
pub const $i: $crate::once_cell::sync::Lazy<$crate::utils::logger::Logger> =
$crate::once_cell::sync::Lazy::new(|| $crate::utils::logger::Logger::new($name));
};
($i:ident $name:expr) => {
pub const $i: $crate::once_cell::sync::Lazy<$crate::utils::logger::Logger> =
$crate::once_cell::sync::Lazy::new(|| $crate::utils::logger::Logger::new($name));
};
(const $name:expr) => {
$crate::once_cell::sync::Lazy::new(|| $crate::utils::logger::Logger::new($name))
};
($name:expr) => {
$crate::logger::utils::Logger::new($name)
};
}
+22
View File
@@ -0,0 +1,22 @@
#![allow(dead_code)]
mod macros;
mod requests;
mod server;
mod types;
mod utils;
use std::path::PathBuf;
pub use anyhow::Context as ErrorContext;
pub use anyhow::Result;
pub use once_cell;
use crate::server::ServerConfig;
use crate::utils::vfs;
fn main() -> Result<()> {
let root = PathBuf::from("");
let config: ServerConfig = vfs::read_config(&root.join("config.json"))?;
config.build(&root).run()?;
Ok(())
}
+25
View File
@@ -0,0 +1,25 @@
use std::sync::Arc;
use serde::Deserialize;
use crate::{ErrorContext, logger};
use crate::{server::Server, utils::client::Client};
logger!(LOGGER "Auth");
#[derive(Debug, Deserialize)]
struct AuthApiRes {
user_id: u32,
}
pub fn auth(_server: &Arc<Server>, client: &mut Client, token: &str) -> crate::Result<u32> {
let mut res = ureq::get(format!(
"http://localhost:3000/api/auth?intents=server&token={token}"
))
.call()
.context("Failed to authenticate")?;
let api_res: AuthApiRes = serde_json::from_str(&res.body_mut().read_to_string()?)?;
client.set_uuid(api_res.user_id);
LOGGER.info(format!("{} successfully authenticated", api_res.user_id));
Ok(api_res.user_id)
}
+77
View File
@@ -0,0 +1,77 @@
use std::sync::Arc;
use crate::{server::Server, types, utils::client::Client};
crate::logger!(LOGGER "Message Manager");
pub fn send(
server: &Arc<Server>,
client: &Client,
channel_id: u32,
contents: &str,
) -> crate::Result<()> {
LOGGER.info(format!("SendMessage to {channel_id}: {contents}"));
if contents.is_empty() {
client.send(types::ResponseError::InvalidRequest(format!(
"Invalid message: empty message"
)))?;
return Ok(());
}
let msg = server.db.insert_message(
channel_id,
client.get_uuid()?,
&contents,
chrono::Utc::now().timestamp(),
)?;
let server = server.clone();
for c in server.clients.lock().unwrap().iter() {
let c = c.clone();
let server = server.clone();
let msg = msg.clone();
std::thread::spawn(move || {
if let Some(uuid) =
LOGGER.extract(server.wrap_err(&c, c.get_uuid()), "Unable send message")
{
if uuid != channel_id {
return;
}
LOGGER.extract(
server.wrap_err(&c, c.send(types::ServerMessage::MessageCreate(msg))),
"Failed to send message",
);
}
});
}
LOGGER.extract(
server.wrap_err(
&client,
client.send(types::ServerMessage::MessageCreate(msg)),
),
"Failed to send message",
);
Ok(())
}
pub fn edit(
_server: &Arc<Server>,
_client: &Client,
message_id: usize,
new_contents: &str,
) -> crate::Result<()> {
LOGGER.info(format!("EditMessage {message_id}: {new_contents}"));
Ok(())
}
pub fn delete(_server: &Arc<Server>, _client: &Client, message_id: usize) -> crate::Result<()> {
LOGGER.info(format!("DeleteMessage {message_id}"));
Ok(())
}
+48
View File
@@ -0,0 +1,48 @@
pub mod auth;
pub mod message;
use std::sync::Arc;
use crate::{
server::Server,
types::{ClientMessage, WsMessage},
utils::client::Client,
};
impl Server {
pub fn call_request(
self: &Arc<Self>,
req: &WsMessage<ClientMessage>,
client: &Client,
) -> crate::Result<()> {
match req {
WsMessage::Message(req) => match req {
ClientMessage::SendMessage {
channel_id,
contents,
} => {
message::send(self, client, *channel_id, contents)?;
}
ClientMessage::EditMessage {
message_id,
new_contents,
} => message::edit(self, client, *message_id, new_contents)?,
ClientMessage::DeleteMessage { message_id } => {
message::delete(self, client, *message_id)?
}
},
WsMessage::Binary(b) => {
Self::LOGGER.info(format!("Binary message: {b:?}"));
}
WsMessage::String(s) => {
Self::LOGGER.info(format!("String message: {s}"));
}
}
Ok(())
}
}
+172
View File
@@ -0,0 +1,172 @@
use std::{
collections::HashSet,
net::{TcpListener, TcpStream},
path::{Path, PathBuf},
sync::{Arc, Mutex},
};
use crate::{
requests, types,
utils::{self, client::Client, database},
};
#[derive(serde::Serialize, serde::Deserialize)]
pub struct ServerConfig {
server_name: String,
server_id: String,
port: u16,
}
#[allow(dead_code)]
pub struct Server {
pub root: PathBuf,
pub config: ServerConfig,
pub clients: Mutex<HashSet<Client>>,
pub db: utils::database::Database,
}
impl Default for ServerConfig {
fn default() -> Self {
Self {
port: 7090,
server_name: format!("Server Name"),
server_id: format!("offline-server"),
}
}
}
impl ServerConfig {
pub fn build(self, root: &Path) -> Arc<Server> {
Server::new_config(root, self)
}
}
impl Server {
crate::logger!(LOGGER "Server");
pub fn new(root: &Path) -> Arc<Self> {
Self::new_config(root, ServerConfig::default())
}
pub fn new_config(root: &Path, config: ServerConfig) -> Arc<Self> {
Arc::new(Self {
db: database::Database::new(&config).unwrap(),
root: root.to_path_buf(),
config,
clients: Mutex::new(HashSet::new()),
})
}
pub fn run(self: &Arc<Self>) -> crate::Result<()> {
// Start server
let listener = TcpListener::bind(format!("0.0.0.0:{}", self.config.port))?;
Self::LOGGER.info(format!("Server listening at 0.0.0.0:{}", self.config.port));
for stream in listener.incoming() {
match stream {
Ok(stream) => {
std::thread::spawn({
let srv = self.clone();
move || {
let Some(client) = Self::LOGGER
.extract(srv.init_client(stream), "Failed to initialize client")
else {
return;
};
Self::LOGGER.extract(
srv.wrap_err(&client, srv.handle_client(&client)),
"Client handler failed",
);
}
});
}
Err(e) => {
Self::LOGGER.error(format!("Connection failed: {e}"));
}
}
}
Ok(())
}
fn init_client(self: &Arc<Self>, stream: TcpStream) -> anyhow::Result<Client> {
Self::LOGGER.info(format!("New connection: {}", stream.peer_addr()?));
// Initialize client
let mut client = Client::new(stream)?;
// Initialize handshake
self.wrap_err(
&client,
client.send(types::handshake::ServerDetails {
name: self.config.server_name.clone(),
id: self.config.server_id.clone(),
version: format!("0.0.1"),
}),
)?;
match self.wrap_err(&client, client.read_t::<types::handshake::ClientDetails>())? {
Some(types::WsMessage::Message(types::handshake::ClientDetails {
auth_token,
last_message,
..
})) => {
let auth_res = requests::auth::auth(self, &mut client, &auth_token);
let uuid = self.wrap_err(&client, auth_res)?;
self.wrap_err(
&client,
client.send(types::ServerMessage::Authenticated {
uuid,
messages: if let Some(i) = last_message {
self.wrap_err(&client, self.db.get_messages_after_id(uuid, i))?
} else {
self.wrap_err(&client, self.db.get_messages_after_id(uuid, 0))?
},
}),
)?;
}
Some(v) => {
self.wrap_err(
&client,
client.send(types::ResponseError::InvalidHandshake(format!(
"Invalid handshake: {v:?}"
))),
)?;
}
None => {}
}
// Insert to the set of all connected clients
self.clients.lock().unwrap().insert(client.clone());
Ok(client)
}
fn handle_client(self: &Arc<Self>, client: &Client) -> anyhow::Result<()> {
// The main req/res loop
loop {
let req = client.read()?;
if let Some(r) = &req {
self.wrap_err(&client, self.call_request(r, &client))?;
}
}
}
/// When there is a error it removes the client
pub fn wrap_err<T, E: std::fmt::Display>(
self: &Arc<Self>,
client: &Client,
res: std::result::Result<T, E>,
) -> std::result::Result<T, E> {
if let Err(e) = &res {
self.clients.lock().unwrap().remove(&client);
if client
.send(types::ResponseError::InternalError(e.to_string()))
.is_err()
{}
}
res
}
}
+124
View File
@@ -0,0 +1,124 @@
use serde::{Deserialize, Serialize};
/// Messages sent *from the client* (user’s app) to the server
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", content = "params", rename_all = "snake_case")]
pub enum ClientMessage {
/// Send a message to a channel
SendMessage {
channel_id: u32,
contents: String,
},
/// Edit a message (if allowed)
EditMessage {
message_id: usize,
new_contents: String,
},
/// Delete a message (if allowed)
DeleteMessage { message_id: usize },
}
/// Messages sent *from the server* to the client
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", content = "params", rename_all = "snake_case")]
pub enum ServerMessage {
/// Successful authentication
Authenticated {
uuid: u32,
messages: Vec<data::Message>,
},
TempMessage {
message: String,
},
/// A new message in a channel
MessageCreate(data::Message),
/// A message was edited
MessageUpdate(data::Message),
/// A message was deleted
MessageDelete {
channel_id: u32,
message_id: usize,
},
/// Presence updates
PresenceUpdate {
user_id: String,
status: String,
},
/// Typing indicator
Typing {
user_id: String,
channel_id: u32,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "error", content = "message", rename_all = "snake_case")]
pub enum ResponseError {
InvalidRequest(String),
InvalidHandshake(String),
Unauthorized(String),
NotFound(String),
InternalError(String),
}
/// WebSocket wrapper
#[derive(Debug, Clone)]
pub enum WsMessage<T: Serialize + for<'de> Deserialize<'de>> {
Message(T),
Binary(Vec<u8>),
String(String),
}
/// Shared data structures
pub mod data {
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Message {
pub id: i64,
pub channel_id: u32,
pub from: u32,
pub contents: String,
pub timestamp: i64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Channel {
pub id: String,
pub name: String,
pub kind: ChannelKind,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ChannelKind {
Text,
Voice,
}
}
pub mod handshake {
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServerDetails {
pub version: String,
pub name: String,
pub id: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ClientDetails {
pub version: String,
pub auth_token: String,
pub last_message: Option<usize>,
}
}
+361
View File
@@ -0,0 +1,361 @@
use std::{
hash::{Hash, Hasher},
io::{self, Read, Write},
net::{SocketAddr, TcpStream},
time::Duration,
};
use anyhow::anyhow;
use serde::{Deserialize, Serialize};
use crate::types::{ClientMessage, WsMessage};
pub mod handshake {
use base64::Engine;
use base64::engine::general_purpose::STANDARD as Base64;
use sha1::{Digest, Sha1};
use std::collections::HashMap;
use std::io::{BufRead, BufReader, Write};
use std::net::TcpStream;
const WS_GUID: &str = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
pub fn handle_websocket_handshake(stream: &mut TcpStream) -> std::io::Result<()> {
let mut reader = BufReader::new(stream.try_clone()?);
let mut request_line = String::new();
reader.read_line(&mut request_line)?;
if !request_line.starts_with("GET") {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"Invalid HTTP method",
));
}
let mut headers = HashMap::new();
let mut line = String::new();
loop {
line.clear();
let bytes = reader.read_line(&mut line)?;
if bytes == 0 || line == "\r\n" {
break;
}
if let Some((k, v)) = line.split_once(':') {
headers.insert(k.trim().to_lowercase(), v.trim().to_string());
}
}
let key = headers.get("sec-websocket-key").ok_or_else(|| {
std::io::Error::new(std::io::ErrorKind::InvalidData, "Missing Sec-WebSocket-Key")
})?;
if headers.get("upgrade").map(|v| v.to_lowercase()) != Some("websocket".to_string()) {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"Missing or invalid Upgrade header",
));
}
if !headers
.get("connection")
.map(|v| v.to_lowercase().contains("upgrade"))
.unwrap_or(false)
{
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"Missing or invalid Connection header",
));
}
// Optional: validate Sec-WebSocket-Version == 13 (most common)
if let Some(ver) = headers.get("sec-websocket-version") {
if ver.trim() != "13" {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"Unsupported Sec-WebSocket-Version",
));
}
}
// Compute accept key
let mut hasher = Sha1::new();
hasher.update(key.as_bytes());
hasher.update(WS_GUID.as_bytes());
let hash = hasher.finalize();
let accept_key = Base64.encode(hash);
// Note: include Sec-WebSocket-Protocol handling if you support subprotocols
let response = format!(
"HTTP/1.1 101 Switching Protocols\r\n\
Upgrade: websocket\r\n\
Connection: Upgrade\r\n\
Sec-WebSocket-Accept: {}\r\n\
Sec-WebSocket-Version: 13\r\n\r\n",
accept_key
);
stream.write_all(response.as_bytes())?;
stream.flush()?;
Ok(())
}
}
pub struct Client(TcpStream, Option<u32>);
impl Client {
/// Create a client with no timeouts
pub fn new(mut stream: TcpStream) -> crate::Result<Self> {
handshake::handle_websocket_handshake(&mut stream)?;
Ok(Client(stream, None))
}
/// Create a client and set read/write timeouts (useful in prod)
pub fn with_timeouts(
mut stream: TcpStream,
read_timeout: Option<Duration>,
write_timeout: Option<Duration>,
) -> crate::Result<Self> {
if let Some(t) = read_timeout {
stream.set_read_timeout(Some(t))?;
}
if let Some(t) = write_timeout {
stream.set_write_timeout(Some(t))?;
}
handshake::handle_websocket_handshake(&mut stream)?;
Ok(Client(stream, None))
}
/// Send a close frame and flush. `code` is a WebSocket close code (e.g., 1000 normal).
pub fn send_close(&self, code: u16, reason: &str) -> crate::Result<()> {
let mut stream = self.0.try_clone()?;
// control frames must be <= 125 bytes
let mut payload = Vec::new();
payload.extend_from_slice(&code.to_be_bytes());
payload.extend_from_slice(reason.as_bytes());
if payload.len() > 125 {
return Err(anyhow!("close reason too long").into());
}
let mut frame = Vec::with_capacity(2 + payload.len());
frame.push(0x88); // FIN=1, opcode=0x8 (Close)
frame.push(payload.len() as u8); // server->client MUST NOT mask
frame.extend_from_slice(&payload);
stream.write_all(&frame)?;
stream.flush()?;
Ok(())
}
/// Send a pong with given payload (control frames must be <=125)
fn send_pong(&self, payload: &[u8]) -> crate::Result<()> {
let mut stream = self.0.try_clone()?;
if payload.len() > 125 {
return Err(anyhow!("pong payload too long").into());
}
let mut frame = Vec::with_capacity(2 + payload.len());
frame.push(0x8A); // FIN=1, opcode=0xA (Pong)
frame.push(payload.len() as u8);
frame.extend_from_slice(payload);
stream.write_all(&frame)?;
stream.flush()?;
Ok(())
}
/// Send a text/binary frame (server->client must NOT mask)
pub fn send<T: Serialize>(&self, m: T) -> crate::Result<()> {
let mut stream = self.0.try_clone()?;
let payload = serde_json::to_string(&m)?;
let payload_bytes = payload.as_bytes();
let len = payload_bytes.len();
let mut header = Vec::new();
header.push(0x81); // FIN=1, opcode=0x1 (text)
if len < 126 {
header.push(len as u8);
} else if len <= 65535 {
header.push(126);
header.extend_from_slice(&(len as u16).to_be_bytes());
} else {
header.push(127);
header.extend_from_slice(&(len as u64).to_be_bytes());
}
stream.write_all(&header)?;
stream.write_all(payload_bytes)?;
stream.flush()?;
Ok(())
}
/// Read a full WebSocket message, handling fragmentation and control frames.
///
/// Returns:
/// - Ok(Some(WsMessage)) on an application message (text/binary)
/// - Ok(None) if the connection should be closed (close received / read EOF)
/// - Err on protocol or IO errors.
pub fn read_t<T: Serialize + for<'de> Deserialize<'de>>(
&self,
) -> crate::Result<Option<WsMessage<T>>> {
let mut stream = self.0.try_clone()?;
let mut message_payload = Vec::new();
loop {
// read the 2-byte header
let mut header = [0u8; 2];
if let Err(e) = stream.read_exact(&mut header) {
if e.kind() == io::ErrorKind::UnexpectedEof || e.kind() == io::ErrorKind::BrokenPipe
{
return Ok(None); // treat EOF as closed
}
return Err(e.into());
}
let fin = header[0] & 0x80 != 0;
let opcode = header[0] & 0x0F;
let masked = header[1] & 0x80 != 0;
let mut payload_len = (header[1] & 0x7F) as u64;
// Extended payload lengths
if payload_len == 126 {
let mut ext_len = [0u8; 2];
stream.read_exact(&mut ext_len)?;
payload_len = u16::from_be_bytes(ext_len) as u64;
} else if payload_len == 127 {
let mut ext_len = [0u8; 8];
stream.read_exact(&mut ext_len)?;
payload_len = u64::from_be_bytes(ext_len);
}
// Mask key (client→server MUST be masked)
let mut mask = [0u8; 4];
if masked {
stream.read_exact(&mut mask)?;
} else {
let _ = self.send_close(1002, "Client frames must be masked");
return Ok(None);
}
// Control frame checks
if matches!(opcode, 0x8 | 0x9 | 0xA) {
if payload_len > 125 {
let _ = self.send_close(1002, "Control frame too large");
return Ok(None);
}
if !fin {
let _ = self.send_close(1002, "Control frames must not be fragmented");
return Ok(None);
}
}
// Read payload + unmask
let mut payload = vec![0u8; payload_len as usize];
if payload_len > 0 {
stream.read_exact(&mut payload)?;
for i in 0..payload.len() {
payload[i] ^= mask[i % 4];
}
}
match opcode {
0x0 | 0x1 | 0x2 => {
// Continuation / Text / Binary
message_payload.extend(payload);
if fin {
break; // got full message
} else {
continue; // wait for more fragments
}
}
0x8 => {
// Close
let (code, reason) = if payload.len() >= 2 {
let code = u16::from_be_bytes([payload[0], payload[1]]);
let reason = if payload.len() > 2 {
String::from_utf8_lossy(&payload[2..]).into_owned()
} else {
String::new()
};
(code, reason)
} else {
(1000, String::new())
};
let _ = self.send_close(code, &reason);
return Ok(None);
}
0x9 => {
// Ping → respond with Pong
let _ = self.send_pong(&payload);
continue;
}
0xA => {
// Pong → ignore
continue;
}
_ => {
let _ = self.send_close(1002, "Unsupported opcode");
return Ok(None);
}
}
}
// Try parsing JSON into ClientMessage
let message = match String::from_utf8(message_payload.clone()) {
Ok(text) => match serde_json::from_str(&text) {
Ok(msg) => WsMessage::Message(msg),
Err(_) => WsMessage::String(text),
},
Err(_) => WsMessage::Binary(message_payload),
};
Ok(Some(message))
}
/// Read a full WebSocket message, handling fragmentation and control frames.
///
/// Returns:
/// - Ok(Some(WsMessage)) on an application message (text/binary)
/// - Ok(None) if the connection should be closed (close received / read EOF)
/// - Err on protocol or IO errors.
pub fn read(&self) -> crate::Result<Option<WsMessage<ClientMessage>>> {
self.read_t()
}
pub fn get_uuid(&self) -> crate::Result<u32> {
self.1.ok_or(anyhow!("Invalid UUID"))
}
pub fn set_uuid(&mut self, uuid: u32) {
self.1 = Some(uuid)
}
pub fn addr(&self) -> crate::Result<SocketAddr> {
Ok(self.0.peer_addr().unwrap_or(self.0.local_addr()?))
}
}
impl Clone for Client {
fn clone(&self) -> Self {
Client(
self.0.try_clone().expect("failed to clone TcpStream"),
self.1.clone(),
)
}
}
impl PartialEq for Client {
fn eq(&self, other: &Self) -> bool {
self.addr().unwrap() == other.addr().unwrap()
}
}
impl Eq for Client {}
impl Hash for Client {
fn hash<H: Hasher>(&self, state: &mut H) {
self.addr().unwrap().hash(state);
}
}
+140
View File
@@ -0,0 +1,140 @@
use crate::{server::ServerConfig, types::data::Message};
use rusqlite::{Connection, Result, params};
pub struct Database(Connection);
// General use case
impl Database {
pub fn new(_config: &ServerConfig) -> Option<Self> {
let conn = Connection::open("main.db").ok()?;
conn.execute(
"CREATE TABLE IF NOT EXISTS chat (
id INTEGER PRIMARY KEY AUTOINCREMENT,
channel_id INTEGER NOT NULL,
user_id INTEGER NOT NULL,
contents TEXT NOT NULL,
timestamp INTEGER NOT NULL
)",
[],
)
.ok()?;
Some(Database(conn))
}
}
// For chat messages
impl Database {
/// Insert a message into the DB
pub fn insert_message(
&self,
channel_id: u32,
user_id: u32,
contents: &str,
timestamp: i64,
) -> Result<Message> {
self.0.execute(
"INSERT INTO chat (channel_id, user_id, contents, timestamp)
VALUES (?1, ?2, ?3, ?4)",
params![channel_id, user_id, contents, timestamp],
)?;
let id = self.0.last_insert_rowid();
Ok(Message {
id,
channel_id,
from: user_id,
contents: contents.to_string(),
timestamp,
})
}
/// Delete a message from the DB
pub fn delete_message(&self, message_id: usize) -> Result<()> {
self.0
.execute("DELETE FROM chat WHERE id = ?1;", params![message_id])?;
Ok(())
}
/// Delete a message from the DB
pub fn edit_message(&self, message_id: usize, contents: &str) -> Result<()> {
self.0.execute(
"UPDATE table_name
SET contents = ?2
WHERE id = ?1;
",
params![message_id, contents],
)?;
Ok(())
}
/// Get a message by its ID
pub fn get_message_by_id(&self, message_id: usize) -> Result<Option<Message>> {
let mut stmt = self.0.prepare(
"SELECT id, channel_id, user_id, contents, timestamp
FROM chat
WHERE id = ?1",
)?;
let mut rows = stmt.query_map(params![message_id], |row| {
Ok((
row.get::<_, i64>(0)?, // id
row.get::<_, u32>(1)?, // channel_id
row.get::<_, u32>(2)?, // user_id
row.get::<_, String>(3)?, // contents
row.get::<_, i64>(4)?, // timestamp
))
})?;
if let Some(row) = rows.next() {
let (id, channel_id, user_id, contents, timestamp) = row?;
return Ok(Some(Message {
id,
channel_id,
from: user_id,
contents,
timestamp,
}));
}
Ok(None)
}
/// Get all messages with an ID greater than the given one
pub fn get_messages_after_id(
&self,
channel_id: u32,
message_id: usize,
) -> Result<Vec<Message>> {
let mut stmt = self.0.prepare(
"SELECT id, channel_id, user_id, contents, timestamp
FROM chat
WHERE (channel_id = ?2 OR user_id = ?2)
AND id > ?1
ORDER BY id ASC;",
)?;
let rows = stmt.query_map(params![message_id, channel_id], |row| {
Ok(Message {
id: row.get::<_, i64>(0)?,
channel_id: row.get::<_, u32>(1)?,
from: row.get::<_, u32>(2)?,
contents: row.get::<_, String>(3)?,
timestamp: row.get::<_, i64>(4)?,
})
})?;
let mut messages = Vec::new();
for row in rows {
messages.push(row?);
}
Ok(messages)
}
}
unsafe impl Send for Database {}
unsafe impl Sync for Database {}
+45
View File
@@ -0,0 +1,45 @@
use std::fmt::Display;
pub struct Logger {
name: String,
}
impl Logger {
pub fn new<T: Display>(name: T) -> Self {
Logger {
name: name.to_string(),
}
}
pub fn info<T: Display>(&self, message: T) {
println!("\x1b[32mINFO\x1b[0m ({}) › {}", self.name, message);
}
pub fn warn<T: Display>(&self, message: T) {
println!("\x1b[33mWARN\x1b[0m ({}) › {}", self.name, message);
}
pub fn error<T: Display>(&self, message: T) {
println!("\x1b[31mERROR\x1b[0m ({}) › {}", self.name, message);
}
pub fn extract<T, E: Display, D: Display>(&self, v: Result<T, E>, m: D) -> Option<T> {
match v {
Ok(a) => Some(a),
Err(e) => {
self.error(format!("{m}: {e}"));
None
}
}
}
pub fn extract_panic<T, E: Display, D: Display>(&self, v: Result<T, E>, m: D) -> T {
match v {
Ok(a) => a,
Err(e) => {
self.error(format!("{m}: {e}"));
panic!()
}
}
}
}
+4
View File
@@ -0,0 +1,4 @@
pub mod client;
pub mod database;
pub mod logger;
pub mod vfs;
+84
View File
@@ -0,0 +1,84 @@
use std::{fs, path::Path};
logger! {
const LOGGER "File"
}
use serde::{Deserialize, Serialize};
use crate::logger;
pub fn dir(path: &Path) -> crate::Result<()> {
if !path.exists() {
LOGGER.info(format!("Directory {path:?} does not exist, creating it"));
fs::create_dir_all(path)?;
}
Ok(())
}
pub fn read(path: &Path, default_content: &str) -> crate::Result<String> {
if !path.exists() {
LOGGER.info(format!(
"File {path:?} does not exist, creating it with default contents"
));
write(path, default_content)?;
return Ok(default_content.to_string());
}
Ok(fs::read_to_string(path)?)
}
pub fn read_bytes<'a>(path: &Path, default_content: Vec<u8>) -> crate::Result<Vec<u8>> {
if !path.exists() {
LOGGER.info(format!(
"File {path:?} does not exist, creating it with default contents"
));
write_bytes(path, &default_content)?;
return Ok(default_content);
}
Ok(fs::read(path)?)
}
pub fn read_config<T: Default + Serialize + for<'de> Deserialize<'de>>(
path: &Path,
) -> crate::Result<T> {
if !path.exists() {
LOGGER.info(format!(
"File {path:?} does not exist, creating it with default contents"
));
let default = T::default();
write_config(path, &default)?;
return Ok(default);
}
let read = fs::read_to_string(path)?;
Ok(serde_json::from_str::<T>(&read)?)
}
pub fn write(path: &Path, content: &str) -> crate::Result<()> {
dir(path.parent().ok_or(std::io::Error::new(
std::io::ErrorKind::InvalidFilename,
"File doesn't have a parent assigned, example: `config/config.json`",
))?)?;
fs::write(path, content)?;
Ok(())
}
pub fn write_bytes(path: &Path, content: &[u8]) -> crate::Result<()> {
dir(path.parent().ok_or(std::io::Error::new(
std::io::ErrorKind::InvalidFilename,
"File doesn't have a parent assigned, example: `config/config.json`",
))?)?;
fs::write(path, content)?;
Ok(())
}
pub fn write_config<T: Serialize>(path: &Path, content: &T) -> crate::Result<()> {
dir(path.parent().ok_or(std::io::Error::new(
std::io::ErrorKind::InvalidFilename,
"File doesn't have a parent assigned, example: `config/config.json`",
))?)?;
fs::write(path, &serde_json::to_string_pretty(content)?)?;
Ok(())
}