Compare commits

...
17 Commits
Author SHA1 Message Date
selimaj-dev b685ceeb04 Ping pong 2025-10-31 16:15:36 +01:00
selimaj-dev 61679da20b Using .env config 2025-10-30 12:45:22 +01:00
selimaj-dev edf8ef2b2d Improved handshake 2025-10-30 12:15:17 +01:00
selimaj-dev 59beb35d6a Fixed message broadcasting 2025-10-15 19:03:15 +02:00
selimaj-dev bc4e329af4 Fixed message broadcasting 2025-10-15 19:02:37 +02:00
selimaj-dev 2d0acdf67b Working auth 2025-10-14 00:07:23 +02:00
selimaj-dev e425461a29 Authentication 2025-10-13 22:25:50 +02:00
selimaj-dev 721a79cf74 Working supabase structure 2025-10-13 12:30:42 +02:00
selimaj-dev 3911274bc1 Fixed client overload 2025-10-05 11:10:17 +02:00
Klesti Selimaj c6a959d4d1 Merge pull request #1 from kleo-dev/working-node
Working node system
2025-10-04 22:23:40 +02:00
selimaj-dev 2910db9e8f Removed all dead code warnings 2025-10-04 22:01:34 +02:00
selimaj-dev 4bd671de4c Improved messaging and fixed direct sending problems as well as uuid 2025-10-04 21:59:35 +02:00
selimaj-dev f5162cceeb Actual DM structure 2025-10-04 16:49:29 +02:00
selimaj-dev 5fa7fd224f Simple working node server 2025-10-04 14:37:14 +02:00
selimaj-dev 480963e162 Simple working server (from voxa-server) 2025-10-04 11:23:55 +02:00
selimaj-dev 82876a3560 Simple working server (from voxa-server) 2025-10-04 11:23:50 +02:00
selimaj-dev c0878e21ba Readme 2025-10-04 11:02:57 +02:00
16 changed files with 2162 additions and 0 deletions
+10
View File
@@ -19,3 +19,13 @@ 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
.env
*.db
config.json
Generated
+915
View File
@@ -0,0 +1,915 @@
# 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 0.11.1+wasi-snapshot-preview1",
]
[[package]]
name = "getrandom"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
dependencies = [
"cfg-if",
"libc",
"r-efi",
"wasi 0.14.7+wasi-0.2.4",
]
[[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 = "ppv-lite86"
version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
dependencies = [
"zerocopy",
]
[[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 = "r-efi"
version = "5.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
[[package]]
name = "rand"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
dependencies = [
"rand_chacha",
"rand_core",
]
[[package]]
name = "rand_chacha"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
dependencies = [
"getrandom 0.3.3",
]
[[package]]
name = "ring"
version = "0.17.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
dependencies = [
"cc",
"cfg-if",
"getrandom 0.2.16",
"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",
"rand",
"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 = "wasi"
version = "0.14.7+wasi-0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c"
dependencies = [
"wasip2",
]
[[package]]
name = "wasip2"
version = "1.0.1+wasi-0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7"
dependencies = [
"wit-bindgen",
]
[[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 = "wit-bindgen"
version = "0.46.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
[[package]]
name = "zerocopy"
version = "0.8.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "zeroize"
version = "1.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
+16
View File
@@ -0,0 +1,16 @@
[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"
rand = "0.9.2"
+63
View File
@@ -1,2 +1,65 @@
# voxa-node # voxa-node
The server for the DM and notification system of Voxa The server for the DM and notification system of Voxa
## Setup
### Using `config.json` (Usually when you self host)
`cargo run`
### Using `.env`
```python
#.env
VX_CONFIG='{"server_name":"Server Name","server_id":"<required>","server_key":"<required>","port":7090,"channels":[]}'
```
If your env is automatically loaded
```bash
cargo run
```
Run this if you are running on self host or when the .env isn't automatically loaded
```bash
set -a; source .env; set +a
cargo run
```
### 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)
};
}
+30
View File
@@ -0,0 +1,30 @@
#![allow(dead_code)]
mod macros;
mod requests;
mod server;
mod types;
mod utils;
use std::env;
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 = if let Ok(env_config) = env::var("VX_CONFIG") {
serde_json::from_str(&env_config)?
} else {
vfs::read_config(&root.join("config.json"))?
};
config.build(&root).run()?;
Ok(())
}
+26
View File
@@ -0,0 +1,26 @@
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: String,
}
pub fn auth(server: &Arc<Server>, client: &mut Client, token: &str) -> crate::Result<String> {
let mut res = ureq::get(format!(
"https://vxchat.netlify.app/api/auth?token={token}&key={}",
server.config.server_key
))
.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)
}
+71
View File
@@ -0,0 +1,71 @@
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: &str,
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(),
)?;
if msg.from != msg.channel_id {
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 to get uuid")
{
if uuid != msg.channel_id && uuid != msg.from {
return;
}
LOGGER.extract(
server.wrap_err(&c, c.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(())
}
}
+174
View File
@@ -0,0 +1,174 @@
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 {
pub server_name: String,
pub server_id: String,
pub server_key: String,
pub 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"),
server_key: format!(""),
}
}
}
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: uuid.clone(),
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
}
}
+128
View File
@@ -0,0 +1,128 @@
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: String,
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: Author,
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: String,
message_id: usize,
},
/// Presence updates
PresenceUpdate {
user_id: Author,
status: String,
},
/// Typing indicator
Typing {
user_id: Author,
channel_id: String,
},
}
#[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),
}
pub type Author = String;
/// Shared data structures
pub mod data {
use serde::{Deserialize, Serialize};
use crate::types::Author;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Message {
pub id: i64,
pub channel_id: String,
pub from: Author,
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>,
}
}
+378
View File
@@ -0,0 +1,378 @@
use std::{
hash::{Hash, Hasher},
io::{self, Read, Write},
net::{SocketAddr, TcpStream},
};
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)?;
// Trim CRLF to make sure comparisons are clean
let request_line = request_line.trim_end();
// Allow HEAD (used by Render for health checks)
if request_line.starts_with("HEAD") {
let response = "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n";
stream.write_all(response.as_bytes())?;
stream.flush()?;
return Ok(());
}
// Only proceed if it’s a GET
if !request_line.starts_with("GET") {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
format!("Invalid HTTP method: {request_line}"),
));
}
// Read headers
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());
}
}
// Check if it's actually a WebSocket upgrade request
let is_websocket_upgrade = headers
.get("upgrade")
.map(|v| v.eq_ignore_ascii_case("websocket"))
.unwrap_or(false);
if !is_websocket_upgrade {
// Not a WebSocket request — probably a normal HTTP GET (e.g. health check)
let response =
"HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: 2\r\n\r\nOK";
stream.write_all(response.as_bytes())?;
stream.flush()?;
return Ok(());
}
// Validate "Connection: Upgrade"
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",
));
}
// Validate WebSocket key
let key = headers.get("sec-websocket-key").ok_or_else(|| {
std::io::Error::new(std::io::ErrorKind::InvalidData, "Missing Sec-WebSocket-Key")
})?;
// Validate version
if let Some(ver) = headers.get("sec-websocket-version") {
if ver.trim() != "13" {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
format!("Unsupported Sec-WebSocket-Version: {}", ver),
));
}
}
// 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);
// Send response
let response = format!(
"HTTP/1.1 101 Switching Protocols\r\n\
Upgrade: websocket\r\n\
Connection: Upgrade\r\n\
Sec-WebSocket-Accept: {}\r\n\r\n",
accept_key
);
stream.write_all(response.as_bytes())?;
stream.flush()?;
Ok(())
}
}
pub struct Client(TcpStream, Option<String>, u64);
impl Client {
/// Create a client
pub fn new(mut stream: TcpStream) -> crate::Result<Self> {
handshake::handle_websocket_handshake(&mut stream)?;
stream.set_read_timeout(Some(std::time::Duration::from_secs(10)))?;
stream.set_write_timeout(Some(std::time::Duration::from_secs(10)))?;
Ok(Client(stream, None, rand::random()))
}
/// 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 ping (no payload)
fn send_ping(&self) -> crate::Result<()> {
let mut stream = self.0.try_clone()?;
// FIN + opcode (ping = 0x89), payload length = 0x00
stream.write_all(&[0x89, 0x00])?;
stream.flush()?;
Ok(())
}
/// Send a pong (no payload)
fn send_pong(&self) -> crate::Result<()> {
let mut stream = self.0.try_clone()?;
// FIN + opcode (pong = 0x8A), payload length = 0x00
stream.write_all(&[0x8A, 0x00])?;
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::WouldBlock || e.kind() == io::ErrorKind::TimedOut {
self.send_ping()?;
continue;
}
if e.kind() == io::ErrorKind::UnexpectedEof || e.kind() == io::ErrorKind::BrokenPipe
{
return Ok(None);
}
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;
} else {
continue;
}
}
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 => {
self.send_pong()?;
continue;
}
0xA => {
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<String> {
match &self.1 {
Some(v) => Ok(v.clone()),
None => Err(anyhow!("Client ({}) UUID not set", self.2).into()),
}
}
pub fn set_uuid(&mut self, uuid: &str) {
self.1 = Some(uuid.to_string())
}
#[deprecated]
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(),
self.2.clone(),
)
}
}
impl PartialEq for Client {
fn eq(&self, other: &Self) -> bool {
self.2 == other.2
}
}
impl Eq for Client {}
impl Hash for Client {
fn hash<H: Hasher>(&self, state: &mut H) {
self.2.hash(state);
}
}
+140
View File
@@ -0,0 +1,140 @@
use crate::{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 TEXT NOT NULL,
user_id TEXT 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: &str,
user_id: &str,
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: channel_id.to_string(),
from: user_id.to_string(),
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: &str) -> 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::<_, String>(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.to_string(),
contents,
timestamp,
}));
}
Ok(None)
}
/// Get all messages with an ID greater than the given one
pub fn get_messages_after_id(
&self,
channel_id: &str,
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::<_, String>(1)?,
from: row.get::<_, String>(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(())
}