最小可用版

This commit is contained in:
pointer-to-bios 2024-02-21 20:46:15 +08:00
commit 8efd10d765
13 changed files with 1323 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/target
/southbay.sqlite3

654
Cargo.lock generated Normal file
View File

@ -0,0 +1,654 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "accounts"
version = "0.1.0"
dependencies = [
"clap",
"openssl",
"rusqlite",
"tokio",
]
[[package]]
name = "addr2line"
version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb"
dependencies = [
"gimli",
]
[[package]]
name = "adler"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "ahash"
version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42cd52102d3df161c77a887b608d7a4897d7cc112886a9537b738a887a03aaff"
dependencies = [
"cfg-if",
"once_cell",
"version_check",
"zerocopy",
]
[[package]]
name = "allocator-api2"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5"
[[package]]
name = "anstream"
version = "0.6.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96b09b5178381e0874812a9b157f7fe84982617e48f71f4e3235482775e5b540"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"utf8parse",
]
[[package]]
name = "anstyle"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc"
[[package]]
name = "anstyle-parse"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648"
dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7"
dependencies = [
"anstyle",
"windows-sys 0.52.0",
]
[[package]]
name = "backtrace"
version = "0.3.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837"
dependencies = [
"addr2line",
"cc",
"cfg-if",
"libc",
"miniz_oxide",
"object",
"rustc-demangle",
]
[[package]]
name = "bitflags"
version = "2.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf"
[[package]]
name = "bytes"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223"
[[package]]
name = "cc"
version = "1.0.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0"
dependencies = [
"libc",
]
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clap"
version = "4.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c918d541ef2913577a0f9566e9ce27cb35b6df072075769e0b26cb5a554520da"
dependencies = [
"clap_builder",
"clap_derive",
]
[[package]]
name = "clap_builder"
version = "4.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f3e7391dad68afb0c2ede1bf619f579a3dc9c2ec67f089baa397123a2f3d1eb"
dependencies = [
"anstream",
"anstyle",
"clap_lex",
"strsim",
]
[[package]]
name = "clap_derive"
version = "4.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "307bc0538d5f0f83b8248db3087aa92fe504e4691294d0c96c0eabc33f47ba47"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "clap_lex"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce"
[[package]]
name = "colorchoice"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
[[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 = "foreign-types"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
dependencies = [
"foreign-types-shared",
]
[[package]]
name = "foreign-types-shared"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
[[package]]
name = "gimli"
version = "0.28.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
[[package]]
name = "hashbrown"
version = "0.14.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
dependencies = [
"ahash",
"allocator-api2",
]
[[package]]
name = "hashlink"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "692eaaf7f7607518dd3cef090f1474b61edc5301d8012f09579920df68b725ee"
dependencies = [
"hashbrown",
]
[[package]]
name = "heck"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
[[package]]
name = "hermit-abi"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d3d0e0f38255e7fa3cf31335b3a56f05febd18025f4db5ef7a0cfb4f8da651f"
[[package]]
name = "libc"
version = "0.2.153"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
[[package]]
name = "libsqlite3-sys"
version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c10584274047cb335c23d3e61bcef8e323adae7c5c8c760540f73610177fc3f"
dependencies = [
"pkg-config",
"vcpkg",
]
[[package]]
name = "memchr"
version = "2.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"
[[package]]
name = "miniz_oxide"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7"
dependencies = [
"adler",
]
[[package]]
name = "mio"
version = "0.8.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09"
dependencies = [
"libc",
"wasi",
"windows-sys 0.48.0",
]
[[package]]
name = "num_cpus"
version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
dependencies = [
"hermit-abi",
"libc",
]
[[package]]
name = "object"
version = "0.32.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441"
dependencies = [
"memchr",
]
[[package]]
name = "once_cell"
version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
[[package]]
name = "openssl"
version = "0.10.63"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15c9d69dd87a29568d4d017cfe8ec518706046a05184e5aea92d0af890b803c8"
dependencies = [
"bitflags",
"cfg-if",
"foreign-types",
"libc",
"once_cell",
"openssl-macros",
"openssl-sys",
]
[[package]]
name = "openssl-macros"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "openssl-sys"
version = "0.9.99"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22e1bf214306098e4832460f797824c05d25aacdf896f64a985fb0fd992454ae"
dependencies = [
"cc",
"libc",
"pkg-config",
"vcpkg",
]
[[package]]
name = "pin-project-lite"
version = "0.2.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58"
[[package]]
name = "pkg-config"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec"
[[package]]
name = "proc-macro2"
version = "1.0.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rusqlite"
version = "0.31.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b838eba278d213a8beaf485bd313fd580ca4505a00d5871caeb1457c55322cae"
dependencies = [
"bitflags",
"fallible-iterator",
"fallible-streaming-iterator",
"hashlink",
"libsqlite3-sys",
"smallvec",
]
[[package]]
name = "rustc-demangle"
version = "0.1.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
[[package]]
name = "signal-hook-registry"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1"
dependencies = [
"libc",
]
[[package]]
name = "smallvec"
version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7"
[[package]]
name = "socket2"
version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9"
dependencies = [
"libc",
"windows-sys 0.48.0",
]
[[package]]
name = "strsim"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01"
[[package]]
name = "syn"
version = "2.0.49"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "915aea9e586f80826ee59f8453c1101f9d1c4b3964cd2460185ee8e299ada496"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "tokio"
version = "1.36.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931"
dependencies = [
"backtrace",
"bytes",
"libc",
"mio",
"num_cpus",
"pin-project-lite",
"signal-hook-registry",
"socket2",
"tokio-macros",
"windows-sys 0.48.0",
]
[[package]]
name = "tokio-macros"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "unicode-ident"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
[[package]]
name = "utf8parse"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
[[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.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "windows-sys"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
dependencies = [
"windows-targets 0.48.5",
]
[[package]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets 0.52.0",
]
[[package]]
name = "windows-targets"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
dependencies = [
"windows_aarch64_gnullvm 0.48.5",
"windows_aarch64_msvc 0.48.5",
"windows_i686_gnu 0.48.5",
"windows_i686_msvc 0.48.5",
"windows_x86_64_gnu 0.48.5",
"windows_x86_64_gnullvm 0.48.5",
"windows_x86_64_msvc 0.48.5",
]
[[package]]
name = "windows-targets"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd"
dependencies = [
"windows_aarch64_gnullvm 0.52.0",
"windows_aarch64_msvc 0.52.0",
"windows_i686_gnu 0.52.0",
"windows_i686_msvc 0.52.0",
"windows_x86_64_gnu 0.52.0",
"windows_x86_64_gnullvm 0.52.0",
"windows_x86_64_msvc 0.52.0",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea"
[[package]]
name = "windows_aarch64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef"
[[package]]
name = "windows_i686_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
[[package]]
name = "windows_i686_gnu"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313"
[[package]]
name = "windows_i686_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
[[package]]
name = "windows_i686_msvc"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a"
[[package]]
name = "windows_x86_64_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e"
[[package]]
name = "windows_x86_64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
[[package]]
name = "zerocopy"
version = "0.7.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.7.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
dependencies = [
"proc-macro2",
"quote",
"syn",
]

12
Cargo.toml Normal file
View File

@ -0,0 +1,12 @@
[package]
name = "accounts"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
clap = { version = "4.5.1", features = ["derive"] }
openssl = "0.10.63"
rusqlite = "0.31.0"
tokio = { version = "1.36.0", features = ["net", "io-std", "sync", "time", "signal", "rt", "rt-multi-thread", "macros", "io-util"] }

61
README.md Normal file
View File

@ -0,0 +1,61 @@
# SouthBay Accounts
南湾用户登录服务。
## 登录流程
用户只能每3秒发送一次登录请求。每3秒发送超过30次登录请求视为攻击行为禁止请求的ip`3600`秒。
客户端依次发送以下三个请求。
每个请求都先发送一个字节表示请求编号。
* 编号1`prelogin`客户端向服务端请求tcp连接并发送私钥加密的`southbayhub login:{id}`
服务端回复一个公钥加密的`southbayhub server`、`already logged`或`unexist`,连接断开。
服务端账户由`unlinked`状态进入`logining`状态10秒超过10秒视为登录失败。
客户端通过解密字符串验证服务端的有效性,并选择停止登录或继续登录。
* 编号2`login`客户端向服务端请求tcp连接并发送私钥加密的`{id}|{passwd}`
服务端根据是否超时以及账号和密码的正确性回复公钥加密的`unprelogged`、`logged in`、
`passwd incorrect`,连接断开。
服务端账户进入`logged`状态60秒超过60秒进入`nonavailable`状态。
* 编号3`heartbeat`客户端向服务端请求tcp连接并发送私钥加密的`{id}`后服务端的`logged`状态计时归零,
处于`unavailable`状态的换回`logged`状态并将计时归零。处于其它状态的什么都不做。
* 编号4`logout`客户端向服务端请求tcp连接并发送私钥加密的`{id}`。所有的用户数据项重新写回数据库。
## 数据库
使用`sqlite3`。
### 用户数据项
项目|说明
:-:|:-
`id`|用户唯一标识符用户名的md5前8字节。
`name`|用户名,不可以更改。
`passwd`|用户密码的sha512值字符串的md5值客户端应发送sha512数据表应存储sha512值的md5值16进制字符串。
`email`|用户邮箱。
`avatar`|用户头像。一个服务端资源id。
```sql
CREATE TABLE users (
id BIGINT PRIMARY KEY,
name VARCHAR(128),
passwd CHAR(32),
email TEXT,
avatar INT
);
```
### 资源数据项
项目|说明
:-:|:-
`id`|资源id
`path`|资源路径
```sql
CREATE TABLE resource (
id INT PRIMARY KEY,
path TEXT
);
```

12
src/config.rs Normal file
View File

@ -0,0 +1,12 @@
use clap::Parser;
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
pub struct Config {
/// 监听端口
#[arg(short, long, default_value_t = 8848)]
pub port: usize,
/// 并行数
#[arg(short, long, default_value_t = 4)]
pub concurrent: usize,
}

39
src/crypt.rs Normal file
View File

@ -0,0 +1,39 @@
use openssl::{error::ErrorStack, pkey::Private, rsa::Rsa};
static mut PRIKEY: Option<Box<Rsa<Private>>> = None;
pub struct Crypt {}
impl Crypt {
pub fn init() -> Result<(), ErrorStack> {
let prikey = include_str!("id_rsa");
let prikey = Rsa::private_key_from_pem(prikey.as_bytes())?;
unsafe { PRIKEY = Some(Box::new(prikey)) };
Ok(())
}
pub fn encrypt(text: &str) -> Vec<u8> {
let mut ciphertext = vec![0; unsafe { PRIKEY.as_ref().unwrap() }.size() as usize];
let encrypted_len = unsafe { PRIKEY.as_ref().unwrap() }
.public_encrypt(
text.as_bytes(),
&mut ciphertext,
openssl::rsa::Padding::PKCS1,
)
.unwrap();
ciphertext.truncate(encrypted_len);
ciphertext
}
pub fn decrypt(data: &[u8]) -> String {
let mut ciphertext = vec![0; unsafe { PRIKEY.as_ref().unwrap() }.size() as usize];
let encrypted_len = unsafe { PRIKEY.as_ref().unwrap() }
.public_decrypt(data, &mut ciphertext, openssl::rsa::Padding::PKCS1)
.unwrap();
ciphertext.truncate(encrypted_len);
ciphertext
.into_iter()
.map(|x| x as char)
.collect::<String>()
}
}

67
src/db.rs Normal file
View File

@ -0,0 +1,67 @@
use std::{
cell::RefCell,
io::{self, ErrorKind},
};
use rusqlite::Connection;
use crate::userctl::{OnlineState, User};
thread_local! {
static CONNECTION: RefCell<Connection> = RefCell::new(Connection::open("southbay.sqlite3").unwrap());
}
pub async fn read_user(id: usize) -> Result<User, Box<io::Error>> {
let res = CONNECTION.with(|conn| -> Result<User, Box<io::Error>> {
let conn = conn.borrow();
let mut statement = conn
.prepare("SELECT name, passwd, email, avatar FROM users IF id = ?1;")
.unwrap();
let mut q = statement.query([id]).unwrap();
let row = if let Ok(row) = q.next() {
row
} else {
return Err(Box::new(io::Error::new(
io::ErrorKind::NotFound,
format!("{}", id),
)));
}
.unwrap();
Ok(User {
id,
name: row.get(0).unwrap(),
passwd: row.get(1).unwrap(),
email: row.get(2).unwrap(),
avatar: row.get(3).unwrap(),
state: OnlineState::Logging,
})
})?;
Ok(res)
}
pub async fn save_user(user: &User) -> io::Result<()> {
CONNECTION.with(|conn| -> io::Result<()> {
let conn = conn.borrow();
let mut statement = conn
.prepare("INSERT INTO users (name, passwd, email, avatar) VALUES (?1, ?2, ?3, ?4);")
.unwrap();
let mut q = statement
.query([
user.name.as_bytes(),
&user.passwd,
user.email.as_bytes(),
&user.avatar.to_le_bytes(),
])
.unwrap();
if let Ok(_) = q.next() {
()
} else {
return Err(io::Error::new(
ErrorKind::NotFound,
format!("Unexist user {}", user.id),
));
}
Ok(())
})?;
Ok(())
}

28
src/id_rsa Normal file
View File

@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC0zG3gOS43O72S
bL/l3RWtP3eHlxAbn0fT13tfzrFXa3IyWSg4b1rtrEUqgO8ZLaYDpqeYuszLqcW9
6r45l5CGqY3Iuf7RSmlmM3SI3R5x6QefsR0KWj7pdTJZ6uZzJb49e9PcQffQWQuk
tFH0Y6YpSOfH7frBTj/ipv51NkniRgh6pTUeBv9vFsvmm4HIhPhtle7L+wlTdRuQ
X+MVPH1kYqRBb7PioQONNEuefKRG4MnPtIb9mqtJ92EanzELnal9ypG8ZtajFFpx
uE18wIT+/n4CTR0q4dQyrbphVfEW4fwvtcJeQoKLG5m8QjrDqcQPv3chvZyaHfmz
S63A4eQjAgMBAAECggEAEQGxzDJUwJP5WblVC7WSK8EZPe3dxrC4PEW8Bd/BcBcG
n5R3Y2ebcVk6YFcke9p+upUwlVNCSbNnNbLLwKBwfdpGdf0uP62fiyIynLUqqfhg
49/werV7ztCxXc0/btFmuw3XeJPT+wsv5zGhtpAGZJfGWwNttO/ms9aVf50Nsh+l
LKfBe87gOcfEz4706lXtVHpyBtsRg+pZSIA4tF6TH3PgUHWUAj7dsC9+jd7MGHnl
z+dOemTNtjqqk7d7j71QiKe+ChP6bCxdv6fUQom0Y3XkeSKwHxVF2JATh7JN1c0e
KTJdSRliw8Snd6ccEQPOII5k05sPhY70Ijq28CnBIQKBgQDb7iBH68mCvb+OJTC9
ijYA1qvGm5IJqeaN+Ac2q4Hft4kDl1DiS2j4+l291MQ7FH3szMuV07AqXkDHWzUQ
i/5wlP5Y6EFlJXyryfTM8Mz9v8XFMGkJXx3EU9nRpCkMEOhQc4grngnCMEayz2F+
g+Y6I100eqqu6smA38Yap0l2ywKBgQDSc1fLd34Ew2QzZtevnroQtdkG4sO7y0hL
iCvikTy6KVykHHZkvfDsE6k7JeG+2wqXuZUKrHqgIkOXvQ8TDGPFobhCm9zVV8Ur
30Lh+z68mi6CUkysUfqBvoXj0g52AOWoCjocKGYawK4QhciwZRaLmN2CqNm2e9EX
6JUOpdhFCQKBgCbZrNgnncQ7sq/wf+irlpiVU2jVKHoOiky/6L5Ok7WqXUjGHSKs
gZGh4JddE4EAQLJpBvmjmHYTikvtH7WZlUvb9gZMmOi+M4TBOz5Mt1mH2+7We2eN
Ztu8mDjBUALypac4kXBrDXxBPOvvWGCZ+jyA7MENfkNB9HCQlxU0QfT7AoGATamF
F6/0EFvuyU1FgSjJL0t8WJ8sbkv5VO5ei5DfYS/MYT+jrU+u7d/fjtVO9nis8wro
ZnT6F8gtO3zoaDYlR5SDhAkyuIRYXfVZID3wi/c1/J/Uba8fC+w8SrOE5KU2GwoT
IbsC2xKJ6c0FH5tec4upCwO2vDH7GqCZCTvIkMkCgYEA1KavY6TOdj5V8f6nMiLj
PnUuAR2pluzFtpmTrY30DM4IwqA1Nx6awuaLcrhUsoH/uFi+gvr7oItwM7AwF4Al
q0YKwDXCDBOO9K5WSirqUsHoALsZxODWeBoxRytca8sfjm/J7WcEAk3UxY729d+H
G44/LXMaBEtVltDybw9boPs=
-----END PRIVATE KEY-----

132
src/lib.rs Normal file
View File

@ -0,0 +1,132 @@
use std::{
borrow::{Borrow, BorrowMut},
process::exit,
sync::Arc,
time::Duration,
};
use config::Config;
use crypt::Crypt;
use tokio::{
net::TcpListener,
signal::unix::{signal, SignalKind},
task,
time::sleep,
};
use userctl::UserCtl;
use crate::{thrpool::AsyncThreadPool, userctl::OnlineState};
pub mod config;
pub mod crypt;
pub mod db;
pub mod service;
pub mod thrpool;
pub mod userctl;
pub async fn run(config: Config) -> ! {
Crypt::init().expect("Failed to load the public key.");
let userctler = UserCtl::new();
let passedinto_ctler = Arc::clone(&userctler);
task::spawn(async move {
loop {
for _ in 0..3600 {
sleep(Duration::from_secs(1)).await;
{
passedinto_ctler
.read()
.await
.borrow_mut()
.update_timecounts();
}
}
{
passedinto_ctler
.write()
.await
.borrow_mut()
.users
.write()
.unwrap()
.retain(|_id, user| user.write().unwrap().state != OnlineState::Unlinked);
}
}
});
let listener = TcpListener::bind(&format!("0.0.0.0:{}", config.port))
.await
.unwrap();
println!("Listening on 0.0.0.0:{}", config.port);
let mut thread_pool = AsyncThreadPool::new(config.concurrent);
task::spawn(async move {
loop {
let (socket, _) = listener.accept().await.unwrap();
let userctl = Arc::clone(&userctler);
thread_pool
.send(async move {
let mut buf = [0u8; 1];
socket.readable().await.unwrap();
let ip = socket.peer_addr().unwrap();
if let Ok(1) = socket.try_read(&mut buf) {
match buf[0] {
1 => {
if let Err(e) =
service::prelogin(socket, userctl.read().await.borrow()).await
{
println!("From {:?} : {}", ip, e.to_string());
} else {
println!("{:?} logging in.", ip);
}
}
2 => {
match service::login(socket, userctl.read().await.borrow()).await {
Ok(n) => println!("{:?} login succeded as user {}.", ip, n),
Err(e) => println!("From {:?}: {}", ip, e.to_string()),
}
}
3 => {
if let Err(e) =
service::heartbeat(socket, userctl.read().await.borrow()).await
{
println!("From {:?} : {}", ip, e.to_string());
} else {
println!("{:?} heartbeats.", ip);
}
}
4 => {
if let Err(e) =
service::logout(socket, userctl.read().await.borrow()).await
{
println!("From {:?} : {}", ip, e.to_string());
} else {
println!("{:?} logout.", ip);
}
}
_ => (),
}
};
})
.await
.unwrap();
}
});
let mut sigterm = signal(SignalKind::terminate()).unwrap();
let mut sigint = signal(SignalKind::interrupt()).unwrap();
tokio::select! {
_ = sigint.recv() => {
println!("\nReceived SIGINT, exiting...");
exit(0);
}
_ = sigterm.recv() => {
println!("Received SIGTERM, exiting...");
exit(0);
}
}
}

11
src/main.rs Normal file
View File

@ -0,0 +1,11 @@
use accounts::{config::Config, run};
use clap::Parser;
use tokio::io;
#[tokio::main]
async fn main() -> io::Result<()> {
let config = Config::parse();
run(config).await;
Ok(())
}

217
src/service.rs Normal file
View File

@ -0,0 +1,217 @@
use std::{
io::{self, Error, ErrorKind},
sync::RwLock,
};
use openssl::hash::{Hasher, MessageDigest};
use tokio::net::TcpStream;
use crate::{
crypt::Crypt,
db,
userctl::{OnlineState, User, UserCtl},
};
async fn receive_decrypt_from(socket: &TcpStream) -> io::Result<String> {
let mut buf = [0u8; 1024];
let mut secret = Vec::new();
loop {
socket.readable().await?;
let n = socket.try_read(&mut buf)?;
secret.append(&mut buf[..n].to_vec());
if buf[n - 1] == b'\n' {
break;
}
}
Ok(Crypt::decrypt(&secret[..(secret.len() - 1)]))
}
pub async fn prelogin(socket: TcpStream, userctl: &UserCtl) -> io::Result<()> {
let text = receive_decrypt_from(&socket).await?;
let text = text.split(":").collect::<Vec<&str>>();
if text[0] == "southbayhub login" {
let id = usize::from_str_radix(text[0], 16).unwrap();
if userctl.users.read().unwrap().contains_key(&id)
&& userctl
.users
.read()
.unwrap()
.get(&id)
.unwrap()
.read()
.unwrap()
.state
!= OnlineState::Unlinked
&& userctl
.users
.read()
.unwrap()
.get(&id)
.unwrap()
.read()
.unwrap()
.state
!= OnlineState::Unavailable
{
socket.try_write(&Crypt::encrypt("already logged\n"))?;
} else {
match db::read_user(id).await {
Ok(user) => {
socket.try_write(&Crypt::encrypt("southbayhub server\n"))?;
userctl.users.write().unwrap().insert(id, RwLock::new(user));
userctl
.state_timecount
.write()
.unwrap()
.insert(id, RwLock::new((0, 10, OnlineState::Unlinked)));
}
Err(e) => {
if let ErrorKind::NotFound = e.kind() {
socket.try_write(&Crypt::encrypt("unexist\n"))?;
}
}
}
}
Ok(())
} else {
Err(Error::new(
io::ErrorKind::InvalidData,
format!(
"Need \"southbayhub login:{{id}}\" but received \"{}\".",
text[0]
),
))
}
}
// 登录成功返回登录用户的id
pub async fn login(socket: TcpStream, userctl: &UserCtl) -> io::Result<usize> {
let text = receive_decrypt_from(&socket).await?;
let text = text.split(":").collect::<Vec<&str>>();
let (id, passwd) = (usize::from_str_radix(text[0], 16).unwrap(), text[1]);
if !userctl.users.read().unwrap().contains_key(&id) {
socket.try_write(&Crypt::encrypt("unprelogged\n"))?;
Err(io::Error::new(
ErrorKind::NotFound,
format!("{} didn't prelogged.", id),
))
} else {
let guard = userctl.users.read().unwrap();
let mut guard = guard.get(&id).unwrap().write().unwrap();
let User {
passwd: passident,
state,
..
} = &mut *guard;
let passident = passident.iter().map(|x| *x as char).collect::<String>();
if *state == OnlineState::Logged || *state == OnlineState::Unavailable {
socket.try_write(&Crypt::encrypt("logged in\n"))?;
Err(io::Error::new(
ErrorKind::AlreadyExists,
format!("{} had login.", id),
))
} else if *state == OnlineState::Logging {
let sha512str = {
let sha512str = passwd
.chars()
.map(|x| format!("{:x}", x as u8))
.collect::<Vec<String>>();
let mut ss = String::new();
for s in sha512str.into_iter() {
ss += &s;
}
ss
};
let md5 = {
let mut hasher = Hasher::new(MessageDigest::md5())?;
hasher.update(sha512str.as_bytes())?;
let res = hasher.finish()?;
let res = res
.iter()
.map(|x| format!("{:x}", *x))
.collect::<Vec<String>>();
let mut ss = String::new();
for s in res.into_iter() {
ss += &s;
}
ss
};
if passident == md5 {
*state = OnlineState::Logged;
*userctl
.state_timecount
.read()
.unwrap()
.get(&id)
.unwrap()
.write()
.unwrap() = (0, 60, OnlineState::Unavailable);
socket.try_write(&Crypt::encrypt("ok\n"))?;
Ok(id)
} else {
socket.try_write(&Crypt::encrypt("passwd incorrect\n"))?;
Err(io::Error::new(
ErrorKind::ConnectionRefused,
format!("{}'s passwd incorrect.", id),
))
}
} else {
socket.try_write(&Crypt::encrypt("unprelogged\n"))?;
Err(io::Error::new(
ErrorKind::NotFound,
format!("{} didn't prelogged.", id),
))
}
}
}
pub async fn heartbeat(socket: TcpStream, userctl: &UserCtl) -> io::Result<()> {
let text = receive_decrypt_from(&socket).await?;
let id = usize::from_str_radix(&text, 16).unwrap();
if !userctl.users.read().unwrap().contains_key(&id) {
return Ok(());
} else if {
userctl
.users
.read()
.unwrap()
.get(&id)
.unwrap()
.read()
.unwrap()
.state
== OnlineState::Unavailable
} {
userctl
.users
.read()
.unwrap()
.get(&id)
.unwrap()
.write()
.unwrap()
.state = OnlineState::Logged;
} else {
return Ok(());
}
userctl
.state_timecount
.read()
.unwrap()
.get(&id)
.unwrap()
.write()
.unwrap()
.0 = 0;
Ok(())
}
pub async fn logout(socket: TcpStream, userctl: &UserCtl) -> io::Result<()> {
let text = receive_decrypt_from(&socket).await?;
let id = usize::from_str_radix(&text, 16).unwrap();
let user = userctl.users.write().unwrap().remove(&id).unwrap();
let user = user.read().unwrap();
let _ = db::save_user(&*user);
let _ = userctl.state_timecount.write().unwrap().remove(&id);
Ok(())
}

38
src/thrpool.rs Normal file
View File

@ -0,0 +1,38 @@
use std::{future::Future, sync::Arc};
use tokio::{
sync::{
mpsc::{self, error::SendError, Sender},
Mutex,
},
task,
};
pub struct AsyncThreadPool<F: Future<Output = ()> + Send> {
sender: Sender<F>,
}
impl<F: Future<Output = ()> + Send + 'static> AsyncThreadPool<F> {
pub fn new(threads: usize) -> Self {
let (sender, receiver) = mpsc::channel::<F>(64);
let receiver = Arc::new(Mutex::new(receiver));
for _ in 0..threads {
let inrc = Arc::clone(&receiver);
task::spawn(async move {
loop {
let f = {
let mut f = inrc.lock().await;
f.recv().await.unwrap()
};
f.await;
}
});
}
Self { sender }
}
pub async fn send(&mut self, f: F) -> Result<(), SendError<F>> {
self.sender.send(f).await?;
Ok(())
}
}

50
src/userctl.rs Normal file
View File

@ -0,0 +1,50 @@
use std::{
collections::HashMap,
sync::{self, Arc},
};
use tokio::sync::RwLock;
pub struct UserCtl {
pub users: sync::RwLock<HashMap<usize, sync::RwLock<User>>>,
pub state_timecount: sync::RwLock<HashMap<usize, sync::RwLock<(usize, usize, OnlineState)>>>,
}
impl UserCtl {
pub fn new() -> Arc<RwLock<Self>> {
Arc::new(RwLock::new(UserCtl {
users: sync::RwLock::new(HashMap::new()),
state_timecount: sync::RwLock::new(HashMap::new()),
}))
}
pub fn update_timecounts(&self) {
for (id, counts) in self.state_timecount.read().unwrap().iter() {
{
counts.write().unwrap().0 += 1
};
if counts.read().unwrap().1 != 0 && counts.read().unwrap().0 == counts.read().unwrap().1
{
self.users.read().unwrap().get(&id).unwrap().write().unwrap().state = counts.read().unwrap().2;
}
}
}
}
pub struct User {
pub id: usize,
pub name: String,
pub passwd: Vec<u8>,
pub email: String,
pub avatar: u32,
pub state: OnlineState,
}
#[derive(Clone, Copy, PartialEq)]
pub enum OnlineState {
Logging,
Logged,
Unavailable,
Unlinked,
}