Initial commit
This commit is contained in:
commit
36502625db
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/target
|
757
Cargo.lock
generated
Normal file
757
Cargo.lock
generated
Normal file
@ -0,0 +1,757 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[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 = "aho-corasick"
|
||||
version = "1.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "0.6.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d664a92ecae85fd0a7392615844904654d1d5f5514837f471ddef4a057aba1b6"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"anstyle-parse",
|
||||
"anstyle-query",
|
||||
"anstyle-wincon",
|
||||
"colorchoice",
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87"
|
||||
|
||||
[[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 = "autocfg"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||
|
||||
[[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 = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bytemuck"
|
||||
version = "1.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "374d28ec25809ee0e23827c2ab573d729e293f281dfe393500e7ad618baa61c6"
|
||||
dependencies = [
|
||||
"bytemuck_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bytemuck_derive"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "965ab7eb5f8f97d2a083c799f3a1b994fc397b2fe2da5d1da1626ce15a39f2b1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.48",
|
||||
]
|
||||
|
||||
[[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.4.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "52bdc885e4cacc7f7c9eedc1ef6da641603180c783c41a15c264944deeaab642"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.4.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fb7fb5e4e979aec3be7791562fcba452f94ad85e954da024396433e0e25a79e9"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
"clap_lex",
|
||||
"strsim",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.4.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.48",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1"
|
||||
|
||||
[[package]]
|
||||
name = "colorchoice"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
|
||||
|
||||
[[package]]
|
||||
name = "enum-repr"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bad30c9c0fa1aaf1ae5010dab11f1117b15d35faf62cda4bbbc53b9987950f18"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "env_logger"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95b3f3e67048839cb0d0781f445682a35113da7121f7c949db0e2be96a4fbece"
|
||||
dependencies = [
|
||||
"humantime",
|
||||
"is-terminal",
|
||||
"log",
|
||||
"regex",
|
||||
"termcolor",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gimli"
|
||||
version = "0.28.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
|
||||
|
||||
[[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.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7"
|
||||
|
||||
[[package]]
|
||||
name = "humantime"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
|
||||
|
||||
[[package]]
|
||||
name = "is-terminal"
|
||||
version = "0.4.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0bad00257d07be169d870ab665980b06cdb366d792ad690bf2e76876dc503455"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"rustix",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.151"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4"
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.4.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456"
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"scopeguard",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
|
||||
|
||||
[[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.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
|
||||
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 = "parking_lot"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
|
||||
dependencies = [
|
||||
"lock_api",
|
||||
"parking_lot_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot_core"
|
||||
version = "0.9.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"smallvec",
|
||||
"windows-targets 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.75"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "907a61bd0f64c2f29cd1cf1dc34d05176426a3f504a78010f08416ddb7b13708"
|
||||
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 = "redox_syscall"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.38.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316"
|
||||
dependencies = [
|
||||
"bitflags 2.4.1",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||
|
||||
[[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.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970"
|
||||
|
||||
[[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.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||
|
||||
[[package]]
|
||||
name = "stund"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bitflags 2.4.1",
|
||||
"bytemuck",
|
||||
"clap",
|
||||
"enum-repr",
|
||||
"env_logger",
|
||||
"log",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.109"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.48"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termcolor"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff1bc3d3f05aff0403e8ac0d92ced918ec05b666a43f83297ccef5bea8a3d449"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.56"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.56"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.48",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.35.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"bytes",
|
||||
"libc",
|
||||
"mio",
|
||||
"num_cpus",
|
||||
"parking_lot",
|
||||
"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 2.0.48",
|
||||
]
|
||||
|
||||
[[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 = "wasi"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.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"
|
16
Cargo.toml
Normal file
16
Cargo.toml
Normal file
@ -0,0 +1,16 @@
|
||||
[package]
|
||||
name = "stund"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
bitflags = { version = "2.4.1", features = ["bytemuck"] }
|
||||
bytemuck = { version = "1.14.0", features = ["derive"] }
|
||||
clap = { version = "4.4.13", features = ["derive"] }
|
||||
enum-repr = "0.2.6"
|
||||
env_logger = "0.10.1"
|
||||
log = "0.4.20"
|
||||
thiserror = "1.0.56"
|
||||
tokio = { version = "1.35.1", features = ["full"] }
|
17
src/error.rs
Normal file
17
src/error.rs
Normal file
@ -0,0 +1,17 @@
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Error {
|
||||
#[error("I/O error: {0}")]
|
||||
IoError(#[from] std::io::Error),
|
||||
#[error("Malformed messaged content was received")]
|
||||
MalformedMessage,
|
||||
#[error("Truncated message was received")]
|
||||
TruncatedMessage,
|
||||
#[error("Message size exceeds limits")]
|
||||
MessageTooLarge,
|
||||
#[error("Could not map IPvN socket address to IPv4")]
|
||||
MappingError,
|
||||
#[error("Unrecognized comprehension-required attributes")]
|
||||
UnrecognizedComprehensionAttributes,
|
||||
#[error("Incorrect parameters supplied")]
|
||||
InvalidConfiguration,
|
||||
}
|
169
src/main.rs
Normal file
169
src/main.rs
Normal file
@ -0,0 +1,169 @@
|
||||
use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6};
|
||||
|
||||
use clap::Parser;
|
||||
use protocol::{
|
||||
convert::{deserialize_message, DeserializeResult, WireSerialize},
|
||||
Attribute, Class, Message, Method, Response,
|
||||
};
|
||||
use tokio::{
|
||||
io::AsyncReadExt,
|
||||
net::{TcpListener, UdpSocket},
|
||||
select,
|
||||
signal::ctrl_c,
|
||||
};
|
||||
use util::AsyncSendTo;
|
||||
|
||||
use crate::{error::Error, protocol::ErrorCode};
|
||||
|
||||
pub mod error;
|
||||
pub mod protocol;
|
||||
pub mod util;
|
||||
|
||||
const DEFAULT_PORT: u16 = 3478;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
struct Args {
|
||||
#[arg(long, help = "Listen for IPv4 requests only")]
|
||||
no_ipv6: bool,
|
||||
listen_address: Option<SocketAddr>,
|
||||
}
|
||||
|
||||
async fn send_bad_request<S: AsyncSendTo>(
|
||||
socket: &mut S,
|
||||
source: SocketAddr,
|
||||
resp_buffer: &mut [u8],
|
||||
message: Option<&Message>,
|
||||
) -> Result<(), Error> {
|
||||
// Dummy message in case the message failed to parse at all
|
||||
let dummy = Message::invalid();
|
||||
let message = message.unwrap_or(&dummy);
|
||||
let mut response = Response::new(false);
|
||||
response.add_attribute(Attribute::ErrorCode(ErrorCode::BadRequest));
|
||||
let size = response.wire_serialize(&message, resp_buffer)?;
|
||||
socket.send_to(&resp_buffer[..size], source).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn handle_message_parse<S: AsyncSendTo>(
|
||||
socket: &mut S,
|
||||
source: SocketAddr,
|
||||
message: &[u8],
|
||||
resp_buffer: &mut [u8],
|
||||
) -> Result<Message, Error> {
|
||||
match deserialize_message(message) {
|
||||
DeserializeResult::Ok(message) => Ok(message),
|
||||
DeserializeResult::Error(err) => {
|
||||
send_bad_request(socket, source, resp_buffer, None).await?;
|
||||
Err(err)
|
||||
}
|
||||
DeserializeResult::UnknownComprehensionAttributes(message, ids)
|
||||
if message.class == Class::Request =>
|
||||
{
|
||||
let mut response = Response::new(false);
|
||||
response.add_attribute(Attribute::ErrorCode(ErrorCode::UnknownAttribute));
|
||||
response.add_attribute(Attribute::UnknownAttributes(ids));
|
||||
let size = response.wire_serialize(&message, resp_buffer)?;
|
||||
socket.send_to(&resp_buffer[..size], source).await?;
|
||||
Err(Error::UnrecognizedComprehensionAttributes)
|
||||
}
|
||||
DeserializeResult::UnknownComprehensionAttributes(_, _) => Err(Error::MalformedMessage),
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_message<S: AsyncSendTo>(
|
||||
socket: &mut S,
|
||||
source: SocketAddr,
|
||||
message: &[u8],
|
||||
) -> Result<(), Error> {
|
||||
let mut resp_buffer = [0; protocol::MESSAGE_SIZE_LIMIT];
|
||||
|
||||
let message = handle_message_parse(socket, source, message, &mut resp_buffer).await?;
|
||||
|
||||
log::trace!("{:?} {:?} from {}", message.method, message.class, source);
|
||||
|
||||
match (message.class, message.method) {
|
||||
(Class::Request, Method::Binding) => {
|
||||
let address = util::map_sockaddr(source);
|
||||
|
||||
let mut response = Response::new(true);
|
||||
response.add_attribute(Attribute::XorMappedAddress(address));
|
||||
let size = response.wire_serialize(&message, &mut resp_buffer)?;
|
||||
socket.send_to(&resp_buffer[..size], source).await?;
|
||||
}
|
||||
// Unrecognized request
|
||||
(Class::Request, _) => {
|
||||
send_bad_request(socket, source, &mut resp_buffer, Some(&message)).await?;
|
||||
}
|
||||
// 6.3.2: No response is generated for indication
|
||||
(Class::Indication, _) => {}
|
||||
// We're the server side, so ignore that
|
||||
(Class::SuccessResponse | Class::ErrorResponse, _) => {}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn handle_and_log_message<S: AsyncSendTo>(
|
||||
socket: &mut S,
|
||||
source: SocketAddr,
|
||||
message: &[u8],
|
||||
) {
|
||||
if let Err(error) = handle_message(socket, source, message).await {
|
||||
log::warn!("{}: {}", source, error);
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Error> {
|
||||
env_logger::init();
|
||||
|
||||
let args = Args::parse();
|
||||
|
||||
let listen_address = args.listen_address.unwrap_or_else(|| {
|
||||
if !args.no_ipv6 {
|
||||
SocketAddr::V6(SocketAddrV6::new(Ipv6Addr::UNSPECIFIED, DEFAULT_PORT, 0, 0))
|
||||
} else {
|
||||
SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, DEFAULT_PORT))
|
||||
}
|
||||
});
|
||||
|
||||
if args.no_ipv6 && listen_address.is_ipv6() {
|
||||
log::error!("--no-ipv6 specified, but IPv6 port supplied");
|
||||
return Err(Error::InvalidConfiguration);
|
||||
}
|
||||
|
||||
let mut udp_listener = UdpSocket::bind(listen_address).await?;
|
||||
let tcp_listener = TcpListener::bind(listen_address).await?;
|
||||
let mut buffer = [0; protocol::MESSAGE_SIZE_LIMIT];
|
||||
|
||||
log::info!("Listening on {}", listen_address);
|
||||
|
||||
loop {
|
||||
let udp_receive_fut = udp_listener.recv_from(&mut buffer);
|
||||
let tcp_accept_fut = tcp_listener.accept();
|
||||
let ctrl_c_fut = ctrl_c();
|
||||
|
||||
select! {
|
||||
res = tcp_accept_fut => {
|
||||
let (mut socket, source) = res?;
|
||||
|
||||
let Ok(len) = socket.read(&mut buffer).await else {
|
||||
continue;
|
||||
};
|
||||
|
||||
handle_and_log_message(&mut socket, source, &buffer[..len]).await;
|
||||
}
|
||||
res = udp_receive_fut => {
|
||||
let (len, source) = res?;
|
||||
|
||||
handle_and_log_message(&mut udp_listener, source, &buffer[..len]).await;
|
||||
}
|
||||
_ = ctrl_c_fut => {
|
||||
log::info!("Ctrl+C received, stopping...");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
265
src/protocol/convert.rs
Normal file
265
src/protocol/convert.rs
Normal file
@ -0,0 +1,265 @@
|
||||
use std::{mem::size_of, net::SocketAddr};
|
||||
|
||||
use bytemuck::Pod;
|
||||
|
||||
use crate::{
|
||||
error::Error,
|
||||
protocol::rfc8489::{RawAttributeHeader, RawAttributeType, FAMILY_IPV4},
|
||||
};
|
||||
|
||||
use super::{
|
||||
rfc8489::{
|
||||
RawErrorCodeAttribute, RawMessageHeader, RawXorMappedAddressAttributeV4,
|
||||
RawXorMappedAddressAttributeV6, FAMILY_IPV6, MESSAGE_COOKIE,
|
||||
},
|
||||
Attribute, Class, Message, Method, Response, MESSAGE_SIZE_LIMIT,
|
||||
};
|
||||
|
||||
pub enum DeserializeResult {
|
||||
Ok(Message),
|
||||
UnknownComprehensionAttributes(Message, Vec<u16>),
|
||||
Error(Error),
|
||||
}
|
||||
|
||||
pub trait WireSerialize {
|
||||
fn wire_serialize(&self, request: &Message, output: &mut [u8]) -> Result<usize, Error>;
|
||||
}
|
||||
|
||||
fn put_struct<T: Pod>(buffer: &mut [u8], value: &T) -> usize {
|
||||
buffer[..size_of::<T>()].copy_from_slice(bytemuck::bytes_of(value));
|
||||
size_of::<T>()
|
||||
}
|
||||
|
||||
fn try_put_struct<T: Pod>(buffer: &mut [u8], value: &T) -> Result<usize, Error> {
|
||||
if size_of::<T>() <= buffer.len() {
|
||||
Ok(put_struct(buffer, value))
|
||||
} else {
|
||||
Err(Error::MessageTooLarge)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_struct<T: Pod>(buffer: &[u8]) -> &T {
|
||||
bytemuck::from_bytes(&buffer[..size_of::<T>()])
|
||||
}
|
||||
|
||||
impl WireSerialize for Attribute {
|
||||
fn wire_serialize(&self, request: &Message, output: &mut [u8]) -> Result<usize, Error> {
|
||||
match self {
|
||||
Self::XorMappedAddress(SocketAddr::V4(v4)) => {
|
||||
let raw = RawXorMappedAddressAttributeV4::new(*v4).wrap();
|
||||
try_put_struct(output, &raw)
|
||||
}
|
||||
Self::XorMappedAddress(SocketAddr::V6(v6)) => {
|
||||
let raw = RawXorMappedAddressAttributeV6::new(&request.transaction_id, *v6).wrap();
|
||||
try_put_struct(output, &raw)
|
||||
}
|
||||
&Self::ErrorCode(code) => {
|
||||
let raw = RawErrorCodeAttribute::from(code).wrap();
|
||||
try_put_struct(output, &raw)
|
||||
}
|
||||
Self::UnknownAttributes(attrs) => {
|
||||
// NOTE Not really fancy, but there's no nice way of getting DSTs working
|
||||
// Align to 32 bits
|
||||
let len_unpadded = attrs.len() * size_of::<u16>();
|
||||
let len = (len_unpadded + 3) & !3;
|
||||
let need_padding = attrs.len() % 2 != 0;
|
||||
try_put_struct(
|
||||
output,
|
||||
&RawAttributeHeader {
|
||||
ty: RawAttributeType::UNKNOWN_ATTRIBUTES.0.to_be(),
|
||||
len: u16::try_from(len_unpadded).unwrap().to_be(),
|
||||
},
|
||||
)?;
|
||||
let mut offset = size_of::<RawAttributeHeader>();
|
||||
for attr in attrs {
|
||||
try_put_struct(&mut output[offset..], &attr.to_be())?;
|
||||
offset += size_of::<u16>();
|
||||
}
|
||||
if need_padding {
|
||||
try_put_struct(&mut output[offset..], &0u16.to_be())?;
|
||||
}
|
||||
|
||||
Ok(size_of::<RawAttributeHeader>() + len)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl WireSerialize for Response {
|
||||
fn wire_serialize(&self, request: &Message, output: &mut [u8]) -> Result<usize, Error> {
|
||||
// Serialize attributes first
|
||||
let mut attr_len = 0;
|
||||
for attr in self.attributes.iter() {
|
||||
attr_len += attr.wire_serialize(
|
||||
request,
|
||||
&mut output[size_of::<RawMessageHeader>() + attr_len..],
|
||||
)?;
|
||||
}
|
||||
|
||||
// Serialize the header
|
||||
let class = if self.success {
|
||||
Class::SuccessResponse
|
||||
} else {
|
||||
Class::ErrorResponse
|
||||
};
|
||||
let ty = serialize_ty(request.method, class);
|
||||
|
||||
let raw = RawMessageHeader {
|
||||
ty: ty.to_be(),
|
||||
len: u16::try_from(attr_len).unwrap().to_be(),
|
||||
cookie: MESSAGE_COOKIE.to_be(),
|
||||
transaction_id: request.transaction_id.map(u32::to_be),
|
||||
};
|
||||
Ok(attr_len + try_put_struct(output, &raw)?)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn deserialize_message(data: &[u8]) -> DeserializeResult {
|
||||
use DeserializeResult as DR;
|
||||
|
||||
if data.len() < size_of::<RawMessageHeader>() {
|
||||
return DR::Error(Error::TruncatedMessage);
|
||||
}
|
||||
|
||||
// Receive the header first
|
||||
let header: &RawMessageHeader = get_struct(data);
|
||||
|
||||
if u32::from_be(header.cookie) != MESSAGE_COOKIE {
|
||||
return DR::Error(Error::MalformedMessage);
|
||||
}
|
||||
|
||||
let len = u16::from_be(header.len) as usize;
|
||||
|
||||
if len + size_of::<RawMessageHeader>() > MESSAGE_SIZE_LIMIT {
|
||||
return DR::Error(Error::MessageTooLarge);
|
||||
}
|
||||
if len + size_of::<RawMessageHeader>() > data.len() {
|
||||
return DR::Error(Error::TruncatedMessage);
|
||||
}
|
||||
|
||||
let (method, class) = match parse_ty(u16::from_be(header.ty)) {
|
||||
Ok(r) => r,
|
||||
Err(e) => return DR::Error(e),
|
||||
};
|
||||
let transaction_id = header.transaction_id.map(u32::from_be);
|
||||
let mut unknown_attributes = vec![];
|
||||
let mut message = Message {
|
||||
method,
|
||||
class,
|
||||
transaction_id,
|
||||
attributes: vec![],
|
||||
};
|
||||
|
||||
// Receive the attributes, starting from header end
|
||||
let mut offset = size_of::<RawMessageHeader>();
|
||||
loop {
|
||||
let attr_data = &data[offset..];
|
||||
|
||||
if attr_data.len() == 0 {
|
||||
break;
|
||||
}
|
||||
|
||||
if attr_data.len() < size_of::<RawAttributeHeader>() {
|
||||
return DR::Error(Error::TruncatedMessage);
|
||||
}
|
||||
|
||||
let header: &RawAttributeHeader = get_struct(attr_data);
|
||||
let ty = RawAttributeType(u16::from_be(header.ty));
|
||||
let len = u16::from_be(header.len) as usize;
|
||||
|
||||
if len + size_of::<RawAttributeHeader>() > attr_data.len() {
|
||||
return DR::Error(Error::TruncatedMessage);
|
||||
}
|
||||
|
||||
let attr_data = &attr_data[size_of::<RawAttributeHeader>()..];
|
||||
|
||||
let attr = match ty {
|
||||
RawAttributeType::XOR_MAPPED_ADDRESS => match attr_data[2] {
|
||||
FAMILY_IPV4 => {
|
||||
let raw: &RawXorMappedAddressAttributeV4 = get_struct(attr_data);
|
||||
Some(Attribute::XorMappedAddress(raw.to_sockaddr()))
|
||||
}
|
||||
FAMILY_IPV6 => {
|
||||
let raw: &RawXorMappedAddressAttributeV6 = get_struct(attr_data);
|
||||
Some(Attribute::XorMappedAddress(
|
||||
raw.to_sockaddr(&transaction_id),
|
||||
))
|
||||
}
|
||||
_ => return DR::Error(Error::MalformedMessage),
|
||||
},
|
||||
ty if ty.is_comprehension_required() => {
|
||||
unknown_attributes.push(ty.0);
|
||||
None
|
||||
}
|
||||
// Just ignore it
|
||||
_ => None,
|
||||
};
|
||||
|
||||
if let Some(attr) = attr {
|
||||
message.attributes.push(attr);
|
||||
}
|
||||
// RFC8489, section 14: "Length" field contains the length of the Value prior to padding
|
||||
offset += ((len + 3) & !3) + size_of::<RawAttributeHeader>();
|
||||
}
|
||||
|
||||
if unknown_attributes.is_empty() {
|
||||
DR::Ok(message)
|
||||
} else {
|
||||
DR::UnknownComprehensionAttributes(message, unknown_attributes)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_ty(ty: u16) -> Result<(Method, Class), Error> {
|
||||
if ty & 0b1100000000000000 != 0 {
|
||||
return Err(Error::MalformedMessage);
|
||||
}
|
||||
|
||||
let m = (ty & 0b1111) | ((ty & 0b11100000) >> 1) | ((ty & 0b11111000000000) >> 2);
|
||||
let c = ((ty & 0b10000) >> 4) | ((ty & 0b100000000) >> 7);
|
||||
|
||||
let method = Method::from_repr(m).ok_or(Error::MalformedMessage)?;
|
||||
let class = Class::from_repr(c).ok_or(Error::MalformedMessage)?;
|
||||
|
||||
Ok((method, class))
|
||||
}
|
||||
|
||||
pub fn serialize_ty(method: Method, class: Class) -> u16 {
|
||||
let method = method.repr();
|
||||
let class = class.repr();
|
||||
|
||||
let m = (method & 0b1111) | ((method & 0b1110000) << 1) | ((method & 0b1111100000) << 2);
|
||||
let c = ((class & 0b1) << 4) | ((class & 0b10) << 7);
|
||||
|
||||
m | c
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::protocol::{
|
||||
convert::{parse_ty, serialize_ty},
|
||||
Class, Method,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn parse_message_ty_field() {
|
||||
let ty = 0x0001;
|
||||
assert_eq!(parse_ty(ty).unwrap(), (Method::Binding, Class::Request));
|
||||
|
||||
let ty = 0x0101;
|
||||
assert_eq!(
|
||||
parse_ty(ty).unwrap(),
|
||||
(Method::Binding, Class::SuccessResponse)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialize_ty_field() {
|
||||
let class = Class::Request;
|
||||
let method = Method::Binding;
|
||||
assert_eq!(serialize_ty(method, class), 0x0001);
|
||||
|
||||
let class = Class::SuccessResponse;
|
||||
let method = Method::Binding;
|
||||
assert_eq!(serialize_ty(method, class), 0x0101);
|
||||
}
|
||||
}
|
76
src/protocol/mod.rs
Normal file
76
src/protocol/mod.rs
Normal file
@ -0,0 +1,76 @@
|
||||
use std::net::SocketAddr;
|
||||
|
||||
use enum_repr::EnumRepr;
|
||||
|
||||
pub mod convert;
|
||||
pub mod rfc8489;
|
||||
|
||||
pub const MESSAGE_SIZE_LIMIT: usize = 512;
|
||||
|
||||
#[EnumRepr(type = "u16")]
|
||||
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||
#[non_exhaustive]
|
||||
pub enum Method {
|
||||
Reserved = 0,
|
||||
Binding = 1,
|
||||
}
|
||||
|
||||
#[EnumRepr(type = "u16")]
|
||||
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||
pub enum Class {
|
||||
Request = 0,
|
||||
Indication = 1,
|
||||
SuccessResponse = 2,
|
||||
ErrorResponse = 3,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||
pub enum ErrorCode {
|
||||
UnknownAttribute,
|
||||
BadRequest,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Attribute {
|
||||
XorMappedAddress(SocketAddr),
|
||||
ErrorCode(ErrorCode),
|
||||
UnknownAttributes(Vec<u16>),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Message {
|
||||
pub method: Method,
|
||||
pub class: Class,
|
||||
pub transaction_id: [u32; 3],
|
||||
pub attributes: Vec<Attribute>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct Response {
|
||||
success: bool,
|
||||
attributes: Vec<Attribute>,
|
||||
}
|
||||
|
||||
impl Message {
|
||||
pub fn invalid() -> Self {
|
||||
Self {
|
||||
method: Method::Reserved,
|
||||
class: Class::Request,
|
||||
attributes: vec![],
|
||||
transaction_id: [0; 3],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Response {
|
||||
pub fn new(success: bool) -> Self {
|
||||
Self {
|
||||
success,
|
||||
attributes: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_attribute(&mut self, attr: Attribute) {
|
||||
self.attributes.push(attr);
|
||||
}
|
||||
}
|
196
src/protocol/rfc8489.rs
Normal file
196
src/protocol/rfc8489.rs
Normal file
@ -0,0 +1,196 @@
|
||||
use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6};
|
||||
|
||||
use bytemuck::{Pod, Zeroable};
|
||||
|
||||
use super::ErrorCode;
|
||||
|
||||
macro_rules! impl_attrubute {
|
||||
($number:expr, $ty:ident, $inner_ty:ty) => {
|
||||
#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
#[repr(C)]
|
||||
pub struct $ty {
|
||||
header: RawAttributeHeader,
|
||||
inner: $inner_ty,
|
||||
}
|
||||
|
||||
impl $inner_ty {
|
||||
pub fn wrap(self) -> $ty {
|
||||
let size_of_self: u16 = std::mem::size_of::<Self>().try_into().unwrap();
|
||||
$ty {
|
||||
header: RawAttributeHeader {
|
||||
ty: $number.0.to_be(),
|
||||
len: size_of_self.to_be(),
|
||||
},
|
||||
inner: self,
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub const MESSAGE_COOKIE: u32 = 0x2112A442;
|
||||
|
||||
pub const FAMILY_IPV4: u8 = 1;
|
||||
pub const FAMILY_IPV6: u8 = 2;
|
||||
|
||||
#[derive(Clone, Copy, Pod, Zeroable)]
|
||||
#[repr(C)]
|
||||
pub struct RawMessageHeader {
|
||||
pub ty: u16,
|
||||
pub len: u16,
|
||||
pub cookie: u32,
|
||||
pub transaction_id: [u32; 3],
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Pod, Zeroable)]
|
||||
#[repr(C)]
|
||||
pub struct RawAttributeType(pub u16);
|
||||
|
||||
#[derive(Clone, Copy, Pod, Zeroable)]
|
||||
#[repr(C)]
|
||||
pub struct RawAttributeHeader {
|
||||
pub ty: u16,
|
||||
pub len: u16,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Pod, Zeroable)]
|
||||
#[repr(C)]
|
||||
pub struct RawXorMappedAddressAttributeV4 {
|
||||
_0: u8,
|
||||
family: u8,
|
||||
x_port: u16,
|
||||
x_address: u32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Pod, Zeroable)]
|
||||
#[repr(C)]
|
||||
pub struct RawXorMappedAddressAttributeV6 {
|
||||
_0: u8,
|
||||
family: u8,
|
||||
x_port: u16,
|
||||
x_address: [u8; 16],
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Pod, Zeroable)]
|
||||
#[repr(C)]
|
||||
pub struct RawErrorCodeAttribute {
|
||||
_0: u16,
|
||||
class_number: u16,
|
||||
// No reason is provided
|
||||
}
|
||||
|
||||
impl RawAttributeType {
|
||||
pub const XOR_MAPPED_ADDRESS: Self = Self(0x20);
|
||||
pub const ERROR_CODE: Self = Self(0x09);
|
||||
pub const UNKNOWN_ATTRIBUTES: Self = Self(0x0A);
|
||||
|
||||
pub const fn is_comprehension_required(&self) -> bool {
|
||||
self.0 < 0x8000
|
||||
}
|
||||
}
|
||||
|
||||
impl RawXorMappedAddressAttributeV4 {
|
||||
pub fn new(address: SocketAddrV4) -> Self {
|
||||
let x_port = address.port() ^ (MESSAGE_COOKIE >> 16) as u16;
|
||||
let x_address = u32::from(*address.ip()) ^ MESSAGE_COOKIE;
|
||||
|
||||
Self {
|
||||
_0: 0,
|
||||
family: FAMILY_IPV4,
|
||||
x_port: x_port.to_be(),
|
||||
x_address: x_address.to_be(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_sockaddr(&self) -> SocketAddr {
|
||||
let x_port = u16::from_be(self.x_port) ^ (MESSAGE_COOKIE >> 16) as u16;
|
||||
let x_address = u32::from_be(self.x_address) ^ MESSAGE_COOKIE;
|
||||
|
||||
SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::from(x_address), x_port))
|
||||
}
|
||||
}
|
||||
|
||||
impl RawXorMappedAddressAttributeV6 {
|
||||
pub fn new(transation_id: &[u32], address: SocketAddrV6) -> Self {
|
||||
let xor_mask = Self::xor_mask(transation_id);
|
||||
|
||||
let x_port = address.port() ^ (MESSAGE_COOKIE >> 16) as u16;
|
||||
let x_address = address
|
||||
.ip()
|
||||
.octets()
|
||||
.into_iter()
|
||||
.zip(xor_mask)
|
||||
.map(|(x, y)| x ^ y)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Self {
|
||||
_0: 0,
|
||||
family: FAMILY_IPV6,
|
||||
x_port: x_port.to_be(),
|
||||
x_address: x_address.try_into().unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_sockaddr(&self, transation_id: &[u32]) -> SocketAddr {
|
||||
let xor_mask = Self::xor_mask(transation_id);
|
||||
|
||||
let x_port = u16::from_be(self.x_port) ^ (MESSAGE_COOKIE >> 16) as u16;
|
||||
let x_address = self
|
||||
.x_address
|
||||
.into_iter()
|
||||
.zip(xor_mask)
|
||||
.map(|(x, y)| x ^ y)
|
||||
.collect::<Vec<_>>();
|
||||
let x_address: [u8; 16] = x_address.try_into().unwrap();
|
||||
|
||||
SocketAddr::V6(SocketAddrV6::new(Ipv6Addr::from(x_address), x_port, 0, 0))
|
||||
}
|
||||
|
||||
fn xor_mask(transation_id: &[u32]) -> impl Iterator<Item = u8> + '_ {
|
||||
MESSAGE_COOKIE.to_be_bytes().into_iter().chain(
|
||||
transation_id
|
||||
.into_iter()
|
||||
.flat_map(|word| word.to_be_bytes()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl RawErrorCodeAttribute {
|
||||
pub const UNKNOWN_ATTRIBUTE: Self = Self {
|
||||
_0: 0,
|
||||
class_number: Self::make_class_number(420).to_be(),
|
||||
};
|
||||
pub const BAD_REQUEST: Self = Self {
|
||||
_0: 0,
|
||||
class_number: Self::make_class_number(400).to_be(),
|
||||
};
|
||||
|
||||
const fn make_class_number(code: u16) -> u16 {
|
||||
(code % 100) | ((code / 100) << 8)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ErrorCode> for RawErrorCodeAttribute {
|
||||
fn from(value: ErrorCode) -> Self {
|
||||
match value {
|
||||
ErrorCode::UnknownAttribute => Self::UNKNOWN_ATTRIBUTE,
|
||||
ErrorCode::BadRequest => Self::BAD_REQUEST,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl_attrubute!(
|
||||
RawAttributeType::XOR_MAPPED_ADDRESS,
|
||||
XorMappedAddressV4Wrapper,
|
||||
RawXorMappedAddressAttributeV4
|
||||
);
|
||||
impl_attrubute!(
|
||||
RawAttributeType::XOR_MAPPED_ADDRESS,
|
||||
XorMappedAddressV6Wrapper,
|
||||
RawXorMappedAddressAttributeV6
|
||||
);
|
||||
impl_attrubute!(
|
||||
RawAttributeType::ERROR_CODE,
|
||||
ErrorCodeWrapper,
|
||||
RawErrorCodeAttribute
|
||||
);
|
52
src/util.rs
Normal file
52
src/util.rs
Normal file
@ -0,0 +1,52 @@
|
||||
use std::{
|
||||
future::Future,
|
||||
io,
|
||||
net::{IpAddr, SocketAddr},
|
||||
};
|
||||
|
||||
use tokio::{
|
||||
io::AsyncWriteExt,
|
||||
net::{TcpStream, UdpSocket},
|
||||
};
|
||||
|
||||
pub trait AsyncSendTo {
|
||||
fn send_to<'a>(
|
||||
&'a mut self,
|
||||
data: &'a [u8],
|
||||
dest: SocketAddr,
|
||||
) -> impl Future<Output = Result<usize, io::Error>> + 'a;
|
||||
}
|
||||
|
||||
impl AsyncSendTo for UdpSocket {
|
||||
fn send_to<'a>(
|
||||
&'a mut self,
|
||||
data: &'a [u8],
|
||||
dest: SocketAddr,
|
||||
) -> impl Future<Output = Result<usize, io::Error>> + 'a {
|
||||
UdpSocket::send_to(self, data, dest)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncSendTo for TcpStream {
|
||||
fn send_to<'a>(
|
||||
&'a mut self,
|
||||
data: &'a [u8],
|
||||
_dest: SocketAddr,
|
||||
) -> impl Future<Output = Result<usize, io::Error>> + 'a {
|
||||
// Already associated, so ignore the destination
|
||||
self.write(data)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn map_sockaddr(source: SocketAddr) -> SocketAddr {
|
||||
match source {
|
||||
SocketAddr::V4(_) => source,
|
||||
SocketAddr::V6(v6) => {
|
||||
if let Some(v6_to_v4) = v6.ip().to_ipv4_mapped() {
|
||||
SocketAddr::new(IpAddr::V4(v6_to_v4), v6.port())
|
||||
} else {
|
||||
source
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user