initial commit

This commit is contained in:
core 2023-08-09 23:47:54 -04:00
commit 379ae045d0
Signed by: core
GPG Key ID: FDBF740DADDCEECF
20 changed files with 1796 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
target

537
Cargo.lock generated Normal file
View File

@ -0,0 +1,537 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "arrayref"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545"
[[package]]
name = "base64"
version = "0.21.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d"
[[package]]
name = "bitflags"
version = "2.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42"
[[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 = "cc"
version = "1.0.82"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "305fe645edc1442a0fa8b6726ba61d422798d37a52e12eaecf4b022ebbb88f01"
dependencies = [
"libc",
]
[[package]]
name = "cfg-if"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "colored"
version = "2.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2674ec482fbc38012cf31e6c42ba0177b431a0cb6f15fe40efa5aab1bda516f6"
dependencies = [
"is-terminal",
"lazy_static",
"windows-sys 0.48.0",
]
[[package]]
name = "cpufeatures"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1"
dependencies = [
"libc",
]
[[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 = "deranged"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7684a49fb1af197853ef7b2ee694bc1f5b4179556f1e5710e1760c5db6f5e929"
[[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 = "errno"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b30f669a7961ef1631673d2766cc92f52d64f7ef354d4fe0ddfd30ed52f0f4f"
dependencies = [
"errno-dragonfly",
"libc",
"windows-sys 0.48.0",
]
[[package]]
name = "errno-dragonfly"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
dependencies = [
"cc",
"libc",
]
[[package]]
name = "form_urlencoded"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652"
dependencies = [
"percent-encoding",
]
[[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.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427"
dependencies = [
"cfg-if 1.0.0",
"libc",
"wasi",
]
[[package]]
name = "hermit-abi"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b"
[[package]]
name = "hornbeam"
version = "0.1.0"
dependencies = [
"base64",
"log",
"radix64",
"rand",
"sha1",
"simple_logger",
"url",
]
[[package]]
name = "hornbeam-client"
version = "0.1.0"
[[package]]
name = "idna"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c"
dependencies = [
"unicode-bidi",
"unicode-normalization",
]
[[package]]
name = "is-terminal"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b"
dependencies = [
"hermit-abi",
"rustix",
"windows-sys 0.48.0",
]
[[package]]
name = "itoa"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38"
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.147"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3"
[[package]]
name = "linux-raw-sys"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503"
[[package]]
name = "log"
version = "0.4.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4"
[[package]]
name = "num_threads"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44"
dependencies = [
"libc",
]
[[package]]
name = "percent-encoding"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94"
[[package]]
name = "ppv-lite86"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
[[package]]
name = "radix64"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "999718fa65c3be3a74f3f6dae5a98526ff436ea58a82a574f0de89eecd342bee"
dependencies = [
"arrayref",
"cfg-if 0.1.10",
]
[[package]]
name = "rand"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"libc",
"rand_chacha",
"rand_core",
]
[[package]]
name = "rand_chacha"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"getrandom",
]
[[package]]
name = "rustix"
version = "0.38.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "172891ebdceb05aa0005f533a6cbfca599ddd7d966f6f5d4d9b2e70478e70399"
dependencies = [
"bitflags",
"errno",
"libc",
"linux-raw-sys",
"windows-sys 0.48.0",
]
[[package]]
name = "serde"
version = "1.0.183"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32ac8da02677876d532745a130fc9d8e6edfa81a269b107c5b00829b91d8eb3c"
[[package]]
name = "sha1"
version = "0.10.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3"
dependencies = [
"cfg-if 1.0.0",
"cpufeatures",
"digest",
]
[[package]]
name = "simple_logger"
version = "4.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2230cd5c29b815c9b699fb610b49a5ed65588f3509d9f0108be3a885da629333"
dependencies = [
"colored",
"log",
"time",
"windows-sys 0.42.0",
]
[[package]]
name = "time"
version = "0.3.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0fdd63d58b18d663fbdf70e049f00a22c8e42be082203be7f26589213cd75ea"
dependencies = [
"deranged",
"itoa",
"libc",
"num_threads",
"serde",
"time-core",
"time-macros",
]
[[package]]
name = "time-core"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb"
[[package]]
name = "time-macros"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb71511c991639bb078fd5bf97757e03914361c48100d52878b8e52b46fb92cd"
dependencies = [
"time-core",
]
[[package]]
name = "tinyvec"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
dependencies = [
"tinyvec_macros",
]
[[package]]
name = "tinyvec_macros"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "typenum"
version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
[[package]]
name = "unicode-bidi"
version = "0.3.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460"
[[package]]
name = "unicode-normalization"
version = "0.1.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921"
dependencies = [
"tinyvec",
]
[[package]]
name = "url"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb"
dependencies = [
"form_urlencoded",
"idna",
"percent-encoding",
]
[[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.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7"
dependencies = [
"windows_aarch64_gnullvm 0.42.2",
"windows_aarch64_msvc 0.42.2",
"windows_i686_gnu 0.42.2",
"windows_i686_msvc 0.42.2",
"windows_x86_64_gnu 0.42.2",
"windows_x86_64_gnullvm 0.42.2",
"windows_x86_64_msvc 0.42.2",
]
[[package]]
name = "windows-sys"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.48.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f"
dependencies = [
"windows_aarch64_gnullvm 0.48.0",
"windows_aarch64_msvc 0.48.0",
"windows_i686_gnu 0.48.0",
"windows_i686_msvc 0.48.0",
"windows_x86_64_gnu 0.48.0",
"windows_x86_64_gnullvm 0.48.0",
"windows_x86_64_msvc 0.48.0",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc"
[[package]]
name = "windows_aarch64_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
[[package]]
name = "windows_aarch64_msvc"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3"
[[package]]
name = "windows_i686_gnu"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
[[package]]
name = "windows_i686_gnu"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241"
[[package]]
name = "windows_i686_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
[[package]]
name = "windows_i686_msvc"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00"
[[package]]
name = "windows_x86_64_gnu"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
[[package]]
name = "windows_x86_64_gnu"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953"
[[package]]
name = "windows_x86_64_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
[[package]]
name = "windows_x86_64_msvc"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"

5
Cargo.toml Normal file
View File

@ -0,0 +1,5 @@
[workspace]
members = [
"hornbeam",
"hornbeam-client"
]

1
clippy.toml Normal file
View File

@ -0,0 +1 @@
doc-valid-idents = ["WebSocket", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "DirectX", "ECMAScript", "GPLv2", "GPLv3", "GitHub", "GitLab", "IPv4", "IPv6", "ClojureScript", "CoffeeScript", "JavaScript", "PureScript", "TypeScript", "NaN", "NaNs", "OAuth", "GraphQL", "OCaml", "OpenGL", "OpenMP", "OpenSSH", "OpenSSL", "OpenStreetMap", "OpenDNS", "WebGL", "TensorFlow", "TrueType", "iOS", "macOS", "FreeBSD", "TeX", "LaTeX", "BibTeX", "BibLaTeX", "MinGW", "CamelCase"]

View File

@ -0,0 +1,8 @@
[package]
name = "hornbeam-client"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]

View File

@ -0,0 +1,14 @@
pub fn add(left: usize, right: usize) -> usize {
left + right
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
let result = add(2, 2);
assert_eq!(result, 4);
}
}

1
hornbeam/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target

30
hornbeam/Cargo.toml Normal file
View File

@ -0,0 +1,30 @@
[package]
name = "hornbeam"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
url = "2.3"
sha1 = "0.10"
rand = { version = "0.8", optional = true } # feature: csprng-rand
base64 = { version = "0.21", optional = true } # feature: base64-base64
radix64 = { version = "0.6", optional = true } # feature: base64-radix64
log = { version = "0.4", optional = true } # feature: logging
[dev-dependencies]
simple_logger = "4.1"
[features]
default = ["csprng-rand", "base64-radix64", "logging"]
csprng-rand = ["rand"]
base64-base64 = ["base64"]
base64-radix64 = ["radix64"]
logging = ["log"]

View File

@ -0,0 +1,10 @@
use base64::Engine;
use crate::b64::b64_common::DecodeError;
pub(crate) fn base64_encode(b: &[u8]) -> String {
base64::engine::general_purpose::STANDARD.encode(b)
}
pub(crate) fn base64_decode(b: &str) -> Result<Vec<u8>, DecodeError> {
Ok(base64::engine::general_purpose::STANDARD.decode(b)?)
}

View File

@ -0,0 +1,54 @@
use std::fmt::{Display, Formatter};
/// Errors that can occur during decoding.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DecodeError {
/// An invalid byte was found in the input. The offending byte is provided.
InvalidByte(u8),
/// The length of the input is invalid.
InvalidLength,
/// The last non-padding byte of input has discarded bits and those bits are
/// not zero. While this could be decoded it likely represents a corrupted or
/// invalid encoding.
InvalidTrailingBits,
/// invalid padding
InvalidPadding
}
impl Display for DecodeError {
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
match self {
DecodeError::InvalidByte(byte) => write!(f, "invalid byte {}", byte),
DecodeError::InvalidLength => write!(f, "encoded text cannot have a 6-bit remainder"),
DecodeError::InvalidTrailingBits => {
write!(f, "last byte has unnecessary trailing bits")
},
DecodeError::InvalidPadding => {
write!(f, "invalid padding")
}
}
}
}
#[cfg(feature = "base64-radix64")]
impl From<radix64::DecodeError> for DecodeError {
fn from(value: radix64::DecodeError) -> Self {
match value {
radix64::DecodeError::InvalidByte(b) => Self::InvalidByte(b),
radix64::DecodeError::InvalidLength => Self::InvalidLength,
radix64::DecodeError::InvalidTrailingBits => Self::InvalidTrailingBits
}
}
}
#[cfg(feature = "base64-base64")]
impl From<base64::DecodeError> for DecodeError {
fn from(value: base64::DecodeError) -> Self {
match value {
base64::DecodeError::InvalidByte(_, b) => Self::InvalidByte(b),
base64::DecodeError::InvalidLength => Self::InvalidLength,
base64::DecodeError::InvalidLastSymbol(_, _) => Self::InvalidTrailingBits,
base64::DecodeError::InvalidPadding => Self::InvalidPadding
}
}
}

View File

@ -0,0 +1,9 @@
use crate::b64::b64_common::DecodeError;
pub(crate) fn base64_encode(b: &[u8]) -> String {
radix64::STD.encode(b)
}
pub(crate) fn base64_decode(b: &str) -> Result<Vec<u8>, DecodeError> {
Ok(radix64::STD.decode(b)?)
}

15
hornbeam/src/b64/mod.rs Normal file
View File

@ -0,0 +1,15 @@
#[cfg(not(any(feature = "base64-base64", feature = "base64-radix64")))]
compile_error!("You need to select one Base64 implementation");
#[cfg(all(feature = "base64-base64", feature = "base64-radix64"))]
compile_error!("You can only select one Base64 implementation");
#[cfg(feature = "base64-base64")]
#[path = "b64_base64.rs"]
pub(crate) mod impl_b64;
#[cfg(feature = "base64-radix64")]
#[path = "b64_radix64.rs"]
pub(crate) mod impl_b64;
pub(crate) mod b64_common;

View File

@ -0,0 +1,331 @@
use std::error::Error;
use std::fmt::{Display, Formatter};
use std::io;
use std::io::{Read, Write};
use std::string::FromUtf8Error;
use url::Url;
use crate::handshake_common::{HeaderMap, WEBSOCKET_PROTOCOL_VERSION};
use crate::random::websocket_client_key;
/// Contains the information needed to perform the WebSocket client handshake. Create from a URL with `ClientConnectionInfo::from(url)`,
/// or build from it's components with `ClientConnectionInfo::build()`.
pub struct ClientConnectionInfo {
url: Url,
websocket_key: [u8; 16],
origin: Option<String>,
extra_headers: HeaderMap
}
#[derive(Debug)]
/// Errors that can happen while sending the client handshake
pub enum ClientHandshakeSendError {
/// The URL scheme on the URL provided was invalid (not ws or wss)
InvalidUrlScheme {
/// The scheme provided
got_scheme: String
},
/// The URL provided does not have a host
UrlMissingHost,
/// There was an IO error while trying to write the handshake
IoError(io::Error)
}
impl Display for ClientHandshakeSendError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
ClientHandshakeSendError::InvalidUrlScheme { got_scheme } => {
write!(f, "invalid url scheme: expected one of 'ws', 'wss', got '{}'", got_scheme)
},
ClientHandshakeSendError::UrlMissingHost => {
write!(f, "invalid url: url missing host")
}
ClientHandshakeSendError::IoError(e) => {
write!(f, "io error: {}", e)
}
}
}
}
impl Error for ClientHandshakeSendError {}
impl From<io::Error> for ClientHandshakeSendError {
fn from(value: io::Error) -> Self {
Self::IoError(value)
}
}
#[derive(Debug)]
/// Errors that can happen while receiving the client handshake response
pub enum ClientHandshakeRecvError {
/// There was an IO error while trying to read the handshake response
IoError(io::Error),
/// Reached an EOF while trying to read the handshake
UnexpectedEOF,
/// Received invalid UTF-8 data while trying to parse the handshake
InvalidUTF8(FromUtf8Error),
/// Server did not respond with a HTTP/1.1 response
IncorrectHTTPVersion {
/// The version that the server actually sent
server_response: String
},
/// WebSocket handshake responses must be 4 lines at minimum, but this response was too short
ResponseTooShort,
/// WebSocket handshake responses must be valid HTTP responses, but the header was improperly formatted
IncorrectHTTPHeader,
/// The HTTP response indicated failure
HttpErrorCode {
/// The error code returned by the server
code: String,
/// The error code description returned by the server
message: String
},
/// The handshake response was missing the `Connection: Upgrade` header
MissingConnectionUpgrade,
/// The handshake response was missing the `Upgrade: websocket` header
MissingUpgradeWebsocket,
/// The handshake response was missing the `Sec-WebSocket-Accept` header
MissingSecWebsocketAccept,
/// The handshake response had the `Sec-WebSocket-Accept` header, but it's value was incorrect
IncorrectSecWebsocketAccept
}
impl Display for ClientHandshakeRecvError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Self::IoError(e) => {
write!(f, "io error: {e}")
},
Self::UnexpectedEOF => {
write!(f, "unexpected EOF")
},
Self::InvalidUTF8(e) => {
write!(f, "invalid UTF-8: {e}")
},
Self::IncorrectHTTPVersion { server_response } => {
write!(f, "incorrect HTTP version while reading response, expected HTTP/1.1, got '{server_response}'")
},
Self::ResponseTooShort => {
write!(f, "websocket response invalid (too short)")
},
Self::IncorrectHTTPHeader => {
write!(f, "websocket response invalid (improper http response)")
},
Self::HttpErrorCode { code, message } => {
write!(f, "websocket error: HTTP {code} {message}")
},
Self::MissingConnectionUpgrade => write!(f, "websocket response invalid (missing Connection: Upgrade)"),
Self::MissingUpgradeWebsocket => write!(f, "websocket response invalid (missing Upgrade: websocket)"),
Self::MissingSecWebsocketAccept => write!(f, "websocket response invalid (missing Sec-WebSocket-Accept)"),
Self::IncorrectSecWebsocketAccept => write!(f, "websocket response invalid (Sec-WebSocket-Accept incorrect)")
}
}
}
impl Error for ClientHandshakeRecvError {}
impl From<io::Error> for ClientHandshakeRecvError {
fn from(value: io::Error) -> Self {
Self::IoError(value)
}
}
impl From<FromUtf8Error> for ClientHandshakeRecvError {
fn from(value: FromUtf8Error) -> Self {
Self::InvalidUTF8(value)
}
}
impl ClientConnectionInfo {
/// Creates a new `ClientConnectionInfo` with the connection target URL, an optional Origin header value, and
/// any headers you wish to set in the initial request (cookies, authentication, etc)
pub fn build(url: Url, origin: Option<String>, extra_headers: HeaderMap) -> Self {
Self {
url,
websocket_key: websocket_client_key(),
origin,
extra_headers
}
}
/// Send the WebSocket client handshake over the given stream.
/// # Errors
/// This function will return an error if the handshake fails for any reason.
/// See `ClientHandshakeError` for more details.
pub fn send_handshake<W: Write>(&self, w: &mut W) -> Result<(), ClientHandshakeSendError> {
let resource_url = self.url.path().to_string() + self.url.query().unwrap_or("");
// send the handshake:
// 1. HTTP GET
write!(w, "GET {resource_url} HTTP/1.1\r\n")?;
write!(w, "Host: {}\r\n", self.url.host_str().ok_or(ClientHandshakeSendError::UrlMissingHost)?)?;
write!(w, "Upgrade: websocket\r\n")?;
write!(w, "Connection: upgrade\r\n")?;
write!(w, "Sec-WebSocket-Key: {}\r\n", crate::b64::impl_b64::base64_encode(&self.websocket_key))?;
if let Some(origin) = &self.origin {
write!(w, "Origin: {origin}\r\n")?;
}
write!(w, "Sec-WebSocket-Version: {WEBSOCKET_PROTOCOL_VERSION}\r\n")?;
// TODO: permessage-deflate (7692)
for (key, val) in &self.extra_headers {
write!(w, "{key}: {val}\r\n")?;
}
write!(w, "\r\n")?;
Ok(())
}
/// Read the WebSocket handshake response from the given stream.
/// # Errors
/// This function will return an error in many circumstances. See `ClientHandshakeRecvError` for details.
pub fn read_handshake<R: Read>(&self, r: &mut R) -> Result<(), ClientHandshakeRecvError> {
// read until we see the double \r\n
let mut read_bytes = vec![];
let mut read_buf = [0u8; 1024];
loop {
let read = r.read(&mut read_buf)?;
if read == 0 {
return Err(ClientHandshakeRecvError::UnexpectedEOF);
}
read_bytes.extend_from_slice(&read_buf[0..read-1]);
if read_bytes.ends_with(&[0x0d, 0x0a, 0x0d]) || read_bytes.ends_with(&[0x0d, 0x0a, 0x0d, 0x0a]) {
// we have hit the end of the server ws handshake
break;
}
}
debug!("read {:?} bytes from server, parsing as handshake", read_bytes);
let handshake_response = String::from_utf8(read_bytes)?;
trace!("{}", handshake_response);
let lines = handshake_response.lines().collect::<Vec<_>>();
if lines.len() < 4 {
return Err(ClientHandshakeRecvError::ResponseTooShort);
}
let http_header_parts = lines[0].split(' ').collect::<Vec<_>>();
if http_header_parts.len() < 3 {
return Err(ClientHandshakeRecvError::IncorrectHTTPHeader);
}
if http_header_parts[0] != "HTTP/1.1" {
return Err(ClientHandshakeRecvError::IncorrectHTTPVersion {
server_response: http_header_parts[0].to_string()
});
}
if http_header_parts[1] != "101" {
let error_code = http_header_parts[1].to_string();
let error_response = http_header_parts[2..].join(" ");
return Err(ClientHandshakeRecvError::HttpErrorCode {
code: error_code,
message: error_response
});
}
let mut has_connection = false;
let mut has_upgrade = false;
let mut has_sec_accept = false;
for line in &lines[1..] {
if line.to_lowercase() == "connection: upgrade" {
has_connection = true;
} else if line.to_lowercase() == "upgrade: websocket" {
has_upgrade = true;
} else if line.to_lowercase().starts_with("sec-websocket-accept: ") {
let accept_key = line.split(' ').nth(1).unwrap();
}
}
if !has_connection {
return Err(ClientHandshakeRecvError::MissingConnectionUpgrade);
}
if !has_upgrade {
return Err(ClientHandshakeRecvError::MissingUpgradeWebsocket);
}
if !has_sec_accept {
return Err(ClientHandshakeRecvError::MissingSecWebsocketAccept);
}
Ok(())
}
}
impl From<Url> for ClientConnectionInfo {
fn from(value: Url) -> Self {
Self::build(
value,
None,
HeaderMap::new()
)
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use std::collections::HashMap;
use std::io::{Cursor};
use std::net::TcpStream;
use std::str::FromStr;
use std::sync::Once;
use url::Url;
use crate::handshake_client::ClientConnectionInfo;
static LOG: Once = Once::new();
fn setup_logger() {
LOG.call_once(|| simple_logger::init().unwrap());
}
#[test]
fn client_handshake_test() {
setup_logger();
let mut client = ClientConnectionInfo::build(
Url::from_str("ws://somesite.com").unwrap(),
Some("whatever".to_string()),
HashMap::from([
("Authorization".to_string(), "Bearer 12345".to_string())
])
);
client.websocket_key = [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10];
let mut fakesock = Cursor::new(Vec::new());
client.send_handshake(&mut fakesock).unwrap();
assert_eq!(
String::from_utf8(fakesock.into_inner()).unwrap(),
"GET / HTTP/1.1\r\n\
Host: somesite.com\r\n\
Upgrade: websocket\r\n\
Connection: upgrade\r\n\
Sec-WebSocket-Key: AQIDBAUGBwgJCgsMDQ4PEA==\r\n\
Origin: whatever\r\n\
Sec-WebSocket-Version: 13\r\n\
Authorization: Bearer 12345\r\n\r\n"
);
}
#[test]
fn live_client_test() {
setup_logger();
let client = ClientConnectionInfo::from(Url::from_str("ws://10.16.1.1:3204/ws").unwrap());
let mut tcpstream = TcpStream::connect("10.16.1.1:3204").unwrap();
client.send_handshake(&mut tcpstream).unwrap();
client.read_handshake(&mut tcpstream).unwrap();
panic!();
}
}

View File

@ -0,0 +1,15 @@
use std::collections::HashMap;
use sha1::{Sha1, Digest};
/// The WebSocket protocol version, as defined by RFC 6455 to be 13.
pub const WEBSOCKET_PROTOCOL_VERSION: i32 = 13;
/// A type alias for a key-value header map
pub type HeaderMap = HashMap<String, String>;
pub(crate) fn derive_handshake_response(input: [u8; 16]) -> String {
let mut hasher = Sha1::new();
hasher.update(input);
hasher.update(b"258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
crate::b64::impl_b64::base64_encode(&hasher.finalize())
}

View File

84
hornbeam/src/lib.rs Normal file
View File

@ -0,0 +1,84 @@
//! # Hornbeam
//! Hornbeam is a simple WebSocket protocol library. For the client or server crates, check out
//! `hornbeam-client` or `hornbeam-server`.
#![deny(clippy::correctness)]
#![warn(clippy::suspicious)]
#![warn(clippy::style)]
#![warn(clippy::complexity)]
#![deny(clippy::perf)]
#![warn(clippy::pedantic)]
#![warn(clippy::nursery)]
//#![deny(clippy::unwrap_used)]
#![warn(clippy::expect_used)]
#![deny(missing_docs)]
#![allow(clippy::must_use_candidate)] // This gets annoying
#[allow(unused)]
#[macro_use]
pub(crate) mod logging {
macro_rules! trace {
($($x:tt)*) => (
#[cfg(feature = "logging")] {
log::trace!($($x)*)
}
)
}
macro_rules! debug {
($($x:tt)*) => (
#[cfg(feature = "logging")] {
log::debug!($($x)*)
}
)
}
macro_rules! info {
($($x:tt)*) => (
#[cfg(feature = "logging")] {
log::info!($($x)*)
}
)
}
macro_rules! warn {
($($x:tt)*) => (
#[cfg(feature = "logging")] {
log::warn!($($x)*)
}
)
}
macro_rules! error {
($($x:tt)*) => (
#[cfg(feature = "logging")] {
log::error!($($x)*)
}
)
}
}
/// Contains the state definitions of the WebSocket state machine
pub mod state;
/// Contains the common code for the client and server handshakes
pub mod handshake_common;
/// Contains the code for the client handshake
pub mod handshake_client;
/// Contains the code for the server handshake
pub mod handshake_server;
/// Contains the generic WebSocket stream implementation, that streams are upgraded into after completing the client or server handshake
pub mod stream;
#[cfg(not(feature = "csprng-rand"))]
compile_error!("You need to select one CSPRNG implementation");
/// Pluggable CSPRNG implementation
#[cfg(feature = "csprng-rand")]
#[path = "random_rand.rs"]
pub mod random;
pub(crate) mod b64;

View File

@ -0,0 +1,6 @@
use rand::Rng;
/// Generates a random WebSocket client key
pub fn websocket_client_key() -> [u8; 16] {
rand::thread_rng().gen()
}

11
hornbeam/src/state.rs Normal file
View File

@ -0,0 +1,11 @@
/// The state that the websocket stream is currently in
pub enum StreamState {
/// The stream is connecting - the handshake has not finished
Connecting,
/// The stream is open - data can be sent
Open,
/// The stream is closing - no more data can be sent
Closing,
/// The stream is closed
Closed
}

4
hornbeam/src/stream.rs Normal file
View File

@ -0,0 +1,4 @@
/// An upgraded TCP stream, used to send WebSocket frames.
pub struct WebsocketStream {
enable_masking: bool
}

660
tarpaulin-report.html Normal file

File diff suppressed because one or more lines are too long