diff --git a/depell/src/main.rs b/depell/src/main.rs index 2ee3417..ef0c554 100644 --- a/depell/src/main.rs +++ b/depell/src/main.rs @@ -1,11 +1,11 @@ -#![feature(array_windows)] +#![feature(array_chunks)] #![feature(write_all_vectored)] use { aes_gcm::{ aead::{self, AeadMutInPlace}, AeadCore, Aes256Gcm, KeyInit, }, - ed25519_dalek::ed25519::signature::Signer, + ed25519_dalek::{self as ed, ed25519::signature::Signer}, rand_core::OsRng, std::{ collections::{HashMap, HashSet}, @@ -16,88 +16,51 @@ use { path::PathBuf, slice, str::FromStr, - sync::{atomic, OnceLock}, + sync::{ + atomic::{self, AtomicUsize}, + Arc, Mutex, + }, time, }, - x25519_dalek::{EphemeralSecret, SharedSecret}, + x25519_dalek::{self as x, EphemeralSecret, SharedSecret}, }; -static CONN_COUNT: atomic::AtomicUsize = atomic::AtomicUsize::new(0); -static USER_DATA_DIR: OnceLock = OnceLock::new(); -static SERVER_SECRET: OnceLock = OnceLock::new(); +type Subcommand<'a, T> = (&'a str, &'a str, T); +type BaseSubcommand<'a> = Subcommand<'a, fn(&Cli) -> io::Result<()>>; +type ConsumeSubcommand<'a> = Subcommand<'a, fn(&Cli, EncriptedStream) -> io::Result<()>>; -#[derive(Default)] -struct Cli { - program: String, - args: Vec, - flags: HashSet, - options: HashMap, -} +type Username = [u8; 32]; +type Postname = [u8; 64]; +type Pk = [u8; 32]; +type Nonce = u64; -impl Cli { - pub fn parse() -> Self { - let mut s = Self::default(); - let mut args = std::env::args(); - s.program = args.next().unwrap(); - - for arg in args { - if let Some(arg) = arg.strip_prefix("--") { - match arg.split_once('=') { - Some((name, value)) => _ = s.options.insert(name.to_owned(), value.to_owned()), - None => _ = s.flags.insert(arg.to_string()), - } - } else { - s.args.push(arg); - } - } - - s - } - - pub fn arg(&self, index: usize) -> &str { - self.args.get(index).map_or("", String::as_str) - } - - pub fn expect_option(&self, name: &str) -> &str { - self.options.get(name).unwrap_or_else(|| panic!("--{name} is mandatory")) - } - - pub fn expect_poption>(&self, name: &str) -> T { - self.expect_option(name).parse::().unwrap_or_else(|e| { - panic!("failed to parse --{name} as {}: {e}", std::any::type_name::()) - }) - } -} - -type Subcommand<'a> = (&'a str, &'a str, fn(&Cli) -> io::Result<()>); - -fn help(subs: &[(&str, &str, T)]) -> io::Result<()> { - for (name, desc, _) in subs { - eprintln!("{name} - {desc}"); - } - Err(io::ErrorKind::NotFound.into()) -} - -const SUBCOMMANDS: &[Subcommand] = &[ +const SUBCOMMANDS: &[BaseSubcommand] = &[ ("help", "print command descriptions", |_| help(SUBCOMMANDS)), ("serve", "run the server", |cli| { let port = cli.expect_poption::("port"); - let max_conns = cli.expect_poption::("max-conns"); - USER_DATA_DIR.set(cli.expect_poption("user-data-path")).unwrap(); - SERVER_SECRET.set(cli.expect_poption::("secret").0).unwrap(); + + let config = Arc::new(ServerState { + user_data_dir: cli.expect_poption("user-data-path"), + secret: cli.expect_poption::("secret").0, + active_ips: Default::default(), + max_conns: cli.expect_poption::("max-conns"), + conn_count: Default::default(), + }); let listener = TcpListener::bind((Ipv4Addr::UNSPECIFIED, port)).unwrap(); for incoming in listener.incoming() { match incoming { Ok(c) => { - if CONN_COUNT.fetch_add(1, atomic::Ordering::Relaxed) >= max_conns { - CONN_COUNT.fetch_sub(1, atomic::Ordering::Relaxed); + let Ok(std::net::SocketAddr::V4(addr)) = + c.peer_addr().ctx("obtaining socket addr") + else { continue; - } + }; - std::thread::spawn(move || { - _ = handle_client(c); - CONN_COUNT.fetch_sub(1, atomic::Ordering::Relaxed); - }); + let Some(guard) = ConnectionGuard::new(config.clone(), *addr.ip()) else { + continue; + }; + + std::thread::spawn(move || _ = guard.config.handle_client(c)); } Err(e) => { eprintln!("accepting conn conn: {e}") @@ -106,13 +69,24 @@ const SUBCOMMANDS: &[Subcommand] = &[ } Ok(()) }), + ("make-secret", "creates secret usable by the server", |_| { + println!("{}", DisplayHex(ed::SigningKey::generate(&mut OsRng).to_bytes())); + Ok(()) + }), + ("make-ping-command", "create a ping command to handshake with the server", |cli| { + let HexSk(secret) = cli.expect_poption::("secret"); + let addr = cli.expect_poption::("addr"); + let id = DisplayHex(ed::VerifyingKey::from(&secret).to_bytes()); + println!("depell consume --server-identity={id} --addr={addr} ping"); + Ok(()) + }), ("make-profile", "create profile file (private key + name)", |cli| { let name = cli.expect_option("name"); let name = str_as_username(name) .ok_or(io::ErrorKind::InvalidData) .ctx("name is limmited to 32 characters")?; - let &key = ed25519_dalek::SigningKey::generate(&mut rand_core::OsRng).as_bytes(); + let &key = ed::SigningKey::generate(&mut rand_core::OsRng).as_bytes(); let profile = UserProfile { name, key }; let out_file = cli.expect_option("out-file"); @@ -123,7 +97,7 @@ const SUBCOMMANDS: &[Subcommand] = &[ let profile_path = cli.expect_option("profile"); let mut profile_file = fs::File::open(profile_path).ctx("opening profile file")?; let profile: UserProfile = read_struct(&mut profile_file).ctx("reading the profile")?; - let sx = x25519_dalek::EphemeralSecret::random_from_rng(OsRng); + let sx = x::EphemeralSecret::random_from_rng(OsRng); let auth = UserAuth::sign(profile, &sx); let addr = cli.expect_poption::("addr"); @@ -133,7 +107,7 @@ const SUBCOMMANDS: &[Subcommand] = &[ let HexPk(server_identity) = cli.expect_poption("server-identity"); let sauth: ServerAuth = read_struct(&mut stream).ctx("reading server auth")?; let secret = sauth - .verify(auth.pk, server_identity, sx) + .verify(auth.x, server_identity, sx) .map_err(|_| io::ErrorKind::PermissionDenied) .ctx("authenticating server")?; let stream = EncriptedStream::new(stream, secret); @@ -142,94 +116,112 @@ const SUBCOMMANDS: &[Subcommand] = &[ }), ]; -type ConsumeSubcommand<'a> = (&'a str, &'a str, fn(&Cli, EncriptedStream) -> io::Result<()>); - const CONSUME_SUBCOMMAND: &[ConsumeSubcommand] = &[ ("help", "this help message", |_, _| help(CONSUME_SUBCOMMAND)), ("ping", "ping the server to check the connection", |_, mut stream| { - write_struct(&mut stream, &Qid::Ping) - }), // + let now = time::Instant::now(); + write_struct(&mut stream, &Qid::Ping)?; + if !matches!(Aid::try_from(read_struct::(&mut stream)?)?, Aid::Pong) { + eprintln!("server did not respond with ping"); + } + println!("{:?}", now.elapsed()); + Ok(()) + }), ]; -fn hex_to_array(s: &str) -> Result<[u8; SIZE], &'static str> { - let mut buf = [0u8; SIZE]; - - if s.len() != SIZE * 2 { - return Err("expected 64 character hex string"); - } - - fn byte_to_hex(val: u8) -> Result { - Ok(match val { - b'0'..=b'9' => val - b'0', - b'a'..=b'f' => val - b'a' + 10, - b'A'..=b'F' => val - b'A' + 10, - _ => return Err("invalid hex char"), - }) - } - - for (dst, &[a, b]) in buf.iter_mut().zip(s.as_bytes().array_windows()) { - *dst = byte_to_hex(a)? | (byte_to_hex(b)? << 4); - } - - Ok(buf) -} - -struct HexPk(ed25519_dalek::VerifyingKey); - -impl std::str::FromStr for HexPk { - type Err = &'static str; - - fn from_str(s: &str) -> Result { - ed25519_dalek::VerifyingKey::from_bytes(&hex_to_array(s)?) - .map_err(|_| "hex code does not represent the valid key") - .map(Self) - } -} - -struct HexSk(ed25519_dalek::SigningKey); - -impl std::str::FromStr for HexSk { - type Err = &'static str; - - fn from_str(s: &str) -> Result { - Ok(Self(ed25519_dalek::SigningKey::from_bytes(&hex_to_array(s)?))) - } -} - -fn select_subcommand<'a, T>(depth: usize, list: &'a [(&str, &str, T)], cli: &Cli) -> &'a T { - &list.iter().find(|&&(name, ..)| name == cli.arg(depth)).unwrap_or(&list[0]).2 -} - fn main() -> io::Result<()> { let cli = Cli::parse(); select_subcommand(0, SUBCOMMANDS, &cli)(&cli) } -fn handle_client(mut stream: TcpStream) -> io::Result<()> { - let (user, sec) = { - let user_auth: UserAuth = read_struct(&mut stream).ctx("reading auth packet")?; - let sx = x25519_dalek::EphemeralSecret::random_from_rng(OsRng); - let pk = x25519_dalek::PublicKey::from(&sx); - let user = UserData::load(&user_auth, sx).ctx("loading user data")?; - let sauth = ServerAuth::sign(&user_auth, SERVER_SECRET.get().unwrap(), pk); - write_struct(&mut stream, &sauth).ctx("sending handshare response")?; - user - }; +fn help(subs: &[Subcommand]) -> io::Result<()> { + for (name, desc, _) in subs { + eprintln!("{name} - {desc}"); + } + Err(io::ErrorKind::NotFound.into()) +} - let mut stream = EncriptedStream::new(stream, sec); +fn select_subcommand<'a, T>(depth: usize, list: &'a [Subcommand], cli: &Cli) -> &'a T { + &list.iter().find(|&&(name, ..)| name == cli.arg(depth)).unwrap_or(&list[0]).2 +} - loop { - match Qid::try_from(read_struct::(&mut stream)?)? { - Qid::Ping => write_struct(&mut stream, &Aid::Pong)?, +struct ServerState { + user_data_dir: PathBuf, + secret: ed::SigningKey, + max_conns: usize, + active_ips: Mutex>, + conn_count: AtomicUsize, +} + +impl ServerState { + fn handle_client(&self, mut stream: TcpStream) -> io::Result<()> { + let (user, sec) = { + let user_auth: UserAuth = read_struct(&mut stream).ctx("reading auth packet")?; + let sx = x::EphemeralSecret::random_from_rng(OsRng); + let pk = x::PublicKey::from(&sx); + let user = UserData::load(&user_auth, sx, self).ctx("loading user data")?; + let sauth = ServerAuth::sign(&user_auth, &self.secret, pk); + write_struct(&mut stream, &sauth).ctx("sending handshare response")?; + user + }; + + let mut stream = EncriptedStream::new(stream, sec); + + loop { + match Qid::try_from(read_struct::(&mut stream)?)? { + Qid::Ping => write_struct(&mut stream, &Aid::Pong)?, + } } } } +struct ConnectionGuard { + ip: Ipv4Addr, + config: Arc, +} + +impl ConnectionGuard { + fn new(config: Arc, ip: Ipv4Addr) -> Option { + if config.conn_count.fetch_add(1, atomic::Ordering::Relaxed) >= config.max_conns { + eprintln!("max connection cap reached"); + config.conn_count.fetch_sub(1, atomic::Ordering::Relaxed); + return None; + } + + if !config.active_ips.lock().unwrap().insert(ip) { + eprintln!("ip already connected, dropping connection"); + config.conn_count.fetch_sub(1, atomic::Ordering::Relaxed); + return None; + } + + Some(Self { ip, config }) + } +} + +impl Drop for ConnectionGuard { + fn drop(&mut self) { + self.config.active_ips.lock().unwrap().remove(&self.ip); + self.config.conn_count.fetch_sub(1, atomic::Ordering::Relaxed); + } +} + #[repr(u16)] enum Aid { Pong, } +impl TryFrom for Aid { + type Error = io::ErrorKind; + + fn try_from(value: u16) -> Result { + if value <= Self::Pong as u16 { + Ok(unsafe { mem::transmute::(value) }) + } else { + Err(io::ErrorKind::NotFound) + } + } +} + #[repr(u16)] enum Qid { Ping, @@ -240,7 +232,7 @@ impl TryFrom for Qid { fn try_from(value: u16) -> Result { if value <= Self::Ping as u16 { - Ok(unsafe { mem::transmute::(value) }) + Ok(unsafe { mem::transmute::(value) }) } else { Err(io::ErrorKind::NotFound) } @@ -260,7 +252,191 @@ impl Ctx for Result { } } -const ASOC_DATA: &[u8] = b"testicle torsion vizard"; +fn username_as_str(name: &Username) -> Option<&str> { + let len = name.iter().rposition(|&b| b != 0xff)? + 1; + std::str::from_utf8(&name[..len]).ok() +} + +fn str_as_username(name: &str) -> Option { + if name.len() > mem::size_of::() { + return None; + } + let mut buff = [0xffu8; mem::size_of::()]; + buff[..name.len()].copy_from_slice(name.as_bytes()); + Some(buff) +} + +#[repr(packed)] +struct UserProfile { + name: Username, + key: ed::SecretKey, +} + +#[repr(packed)] +struct UserAuth { + signature: ed::Signature, + pk: Pk, + x: x::PublicKey, + name: Username, + nonce: Nonce, +} + +impl UserAuth { + fn sign(UserProfile { name, key }: UserProfile, sx: &x::EphemeralSecret) -> Self { + let nonce = + time::SystemTime::now().duration_since(time::SystemTime::UNIX_EPOCH).unwrap().as_secs(); + let mut message = [0; mem::size_of::() + mem::size_of::()]; + message[..mem::size_of::()].copy_from_slice(&name); + message[mem::size_of::()..].copy_from_slice(&nonce.to_le_bytes()); + + let signing_key = ed::SigningKey::from_bytes(&key); + let signature = signing_key.sign(&message); + let pk = ed::VerifyingKey::from(&signing_key).to_bytes(); + let x = x::PublicKey::from(sx); + + Self { signature, pk, x, name, nonce } + } + + fn verify( + &self, + pk: Pk, + nonce: Nonce, + sx: x::EphemeralSecret, + ) -> Result { + if nonce >= self.nonce { + eprintln!("invalid auth nonce"); + return Err(ed::SignatureError::default()); + } + + let pk = ed::VerifyingKey::from_bytes(&pk)?; + + let mut message = [0; mem::size_of::() + mem::size_of::()]; + message[..mem::size_of::()].copy_from_slice(&self.name); + message[mem::size_of::()..].copy_from_slice(&self.nonce.to_le_bytes()); + + pk.verify_strict(&message, &self.signature)?; + + Ok(sx.diffie_hellman(&self.x)) + } +} + +#[repr(packed)] +struct ServerAuth { + signature: ed::Signature, + x: x::PublicKey, +} + +impl ServerAuth { + fn sign(user_auth: &UserAuth, sk: &ed::SigningKey, x: x::PublicKey) -> Self { + let signature = sk.sign(user_auth.x.as_bytes()); + Self { signature, x } + } + + fn verify( + &self, + x: x::PublicKey, + pk: ed::VerifyingKey, + sx: EphemeralSecret, + ) -> Result { + pk.verify_strict(x.as_bytes(), &self.signature)?; + Ok(sx.diffie_hellman(&self.x)) + } +} + +struct UserData { + header: UserHeader, + post_headers: fs::File, + posts: fs::File, +} + +impl UserData { + fn load( + auth: &UserAuth, + sx: x::EphemeralSecret, + config: &ServerState, + ) -> io::Result<(Self, SharedSecret)> { + const HEADER_PATH: &str = "header.bin"; + const POST_HEADERS_PATH: &str = "post-headers.bin"; + const POST_PATH: &str = "posts.bin"; + + let mut path = PathBuf::from_iter([ + config.user_data_dir.as_path(), + username_as_str(&auth.name).ok_or(io::ErrorKind::InvalidData)?.as_ref(), + ]); + + if path.exists() { + let mut opts = fs::OpenOptions::new(); + opts.write(true).read(true); + + path.push(HEADER_PATH); + let mut header_file = opts.open(&path).ctx("opening user header file")?; + let mut header: UserHeader = + read_struct(&mut header_file).ctx("reading the user header")?; + path.pop(); + + let secret = auth + .verify(header.pk, header.nonce, sx) + .map_err(|_| io::ErrorKind::PermissionDenied) + .ctx("authenticating user")?; + + header.nonce = auth.nonce; + write_struct(&mut header_file, &header).ctx("saving user nonce")?; + + path.push(POST_HEADERS_PATH); + let post_headers = opts.open(&path).ctx("opening user post header file")?; + path.pop(); + + path.push(POST_PATH); + let posts = opts.open(&path).ctx("opening user post file")?; + path.pop(); + + Ok((Self { header, post_headers, posts }, secret)) + } else { + let secret = auth + .verify(auth.pk, 0, sx) + .map_err(|_| io::ErrorKind::PermissionDenied) + .ctx("verifiing registratio signature")?; + + fs::create_dir_all(&path).ctx("creating new user directory")?; + path.push(HEADER_PATH); + let header = + UserHeader { pk: auth.pk, nonce: auth.nonce, post_count: 0, runs: 0, imports: 0 }; + fs::write(&path, as_bytes(&header)).ctx("writing new user header")?; + path.pop(); + + path.push(POST_HEADERS_PATH); + let post_headers = fs::File::create_new(&path).ctx("creating new user post headers")?; + path.pop(); + + path.push(POST_PATH); + let posts = fs::File::create_new(&path).ctx("creating new user posts")?; + path.pop(); + + Ok((Self { header, post_headers, posts }, secret)) + } + } +} + +#[repr(packed)] +struct UserHeader { + pk: Pk, + nonce: Nonce, + post_count: u32, + imports: u32, + runs: u32, +} + +#[repr(packed)] +struct PostHeader { + name: Postname, + timestamp: u64, + size: u32, + offset: u32, + imports: u32, + runs: u32, +} + +const ASOC_DATA: &[u8] = b"testicle torsion wizard"; struct EncriptedStream { inner: TcpStream, @@ -339,7 +515,7 @@ impl Write for EncriptedStream { fn read_struct(stream: &mut impl Read) -> io::Result { let mut res = mem::MaybeUninit::uninit(); stream.read_exact(as_mut_bytes(&mut res))?; - unsafe { res.assume_init() } + Ok(unsafe { res.assume_init() }) } fn write_struct(stream: &mut impl Write, value: &T) -> io::Result<()> { @@ -354,191 +530,112 @@ fn as_bytes(value: &T) -> &[u8] { unsafe { slice::from_raw_parts(value as *const _ as *const u8, mem::size_of::()) } } -type Username = [u8; 32]; -type Postname = [u8; 64]; -type Pk = [u8; 32]; -type Nonce = u64; - -fn username_as_str(name: &Username) -> Option<&str> { - let len = name.iter().rposition(|&b| b != 0xff)? + 1; - std::str::from_utf8(&name[..len]).ok() +#[derive(Default)] +struct Cli { + program: String, + args: Vec, + flags: HashSet, + options: HashMap, } -fn str_as_username(name: &str) -> Option { - if name.len() > mem::size_of::() { - return None; - } - let mut buff = [0xffu8; mem::size_of::()]; - buff[..name.len()].copy_from_slice(name.as_bytes()); - Some(buff) -} +impl Cli { + pub fn parse() -> Self { + let mut s = Self::default(); + let mut args = std::env::args(); + s.program = args.next().unwrap(); -#[repr(packed)] -struct UserProfile { - name: Username, - key: ed25519_dalek::SecretKey, -} - -#[repr(packed)] -struct UserAuth { - signature: ed25519_dalek::Signature, - pk: Pk, - x: x25519_dalek::PublicKey, - name: Username, - nonce: Nonce, -} - -impl UserAuth { - fn sign(UserProfile { name, key }: UserProfile, sx: &x25519_dalek::EphemeralSecret) -> Self { - let nonce = - time::SystemTime::now().duration_since(time::SystemTime::UNIX_EPOCH).unwrap().as_secs(); - let mut message = [0; mem::size_of::() + mem::size_of::()]; - message[..mem::size_of::()].copy_from_slice(&name); - message[mem::size_of::()..].copy_from_slice(&nonce.to_le_bytes()); - - let signing_key = ed25519_dalek::SigningKey::from_bytes(&key); - let signature = signing_key.sign(&message); - let pk = ed25519_dalek::VerifyingKey::from(&signing_key).to_bytes(); - let x = x25519_dalek::PublicKey::from(sx); - - Self { signature, pk, x, name, nonce } - } - - fn verify( - &self, - pk: Pk, - nonce: Nonce, - sx: x25519_dalek::EphemeralSecret, - ) -> Result { - if nonce >= self.nonce { - eprintln!("invalid auth nonce"); - return Err(ed25519_dalek::SignatureError::default()); + for arg in args { + if let Some(arg) = arg.strip_prefix("--") { + match arg.split_once('=') { + Some((name, value)) => _ = s.options.insert(name.to_owned(), value.to_owned()), + None => _ = s.flags.insert(arg.to_string()), + } + } else { + s.args.push(arg); + } } - let pk = ed25519_dalek::VerifyingKey::from_bytes(&pk)?; + s + } - let mut message = [0; mem::size_of::() + mem::size_of::()]; - message[..mem::size_of::()].copy_from_slice(&self.name); - message[mem::size_of::()..].copy_from_slice(&nonce.to_le_bytes()); + pub fn arg(&self, index: usize) -> &str { + self.args.get(index).map_or("", String::as_str) + } - pk.verify_strict(&message, &self.signature)?; + pub fn expect_option(&self, name: &str) -> &str { + self.options.get(name).unwrap_or_else(|| panic!("--{name}= is mandatory")) + } - Ok(sx.diffie_hellman(&self.x)) + pub fn expect_poption>(&self, name: &str) -> T { + self.expect_option(name).parse::().unwrap_or_else(|e| { + panic!("failed to parse --{name}= as {}: {e}", std::any::type_name::()) + }) } } -#[repr(packed)] -struct ServerAuth { - signature: ed25519_dalek::Signature, - x: x25519_dalek::PublicKey, -} +fn hex_to_array(s: &str) -> Result<[u8; SIZE], &'static str> { + let mut buf = [0u8; SIZE]; -impl ServerAuth { - fn sign( - user_auth: &UserAuth, - sk: &ed25519_dalek::SigningKey, - x: x25519_dalek::PublicKey, - ) -> Self { - let signature = sk.sign(user_auth.x.as_bytes()); - Self { signature, x } + if s.len() != SIZE * 2 { + return Err("expected 64 character hex string"); } - fn verify( - &self, - x: Pk, - pk: ed25519_dalek::VerifyingKey, - sx: EphemeralSecret, - ) -> Result { - pk.verify_strict(&x, &self.signature)?; - Ok(sx.diffie_hellman(&self.x)) + fn byte_to_hex(val: u8) -> Result { + Ok(match val { + b'0'..=b'9' => val - b'0', + b'a'..=b'f' => val - b'a' + 10, + b'A'..=b'F' => val - b'A' + 10, + _ => return Err("invalid hex char"), + }) + } + + for (dst, &[a, b]) in buf.iter_mut().zip(s.as_bytes().array_chunks()) { + *dst = byte_to_hex(b)? | (byte_to_hex(a)? << 4); + } + + Ok(buf) +} + +struct HexPk(ed::VerifyingKey); + +impl std::str::FromStr for HexPk { + type Err = &'static str; + + fn from_str(s: &str) -> Result { + ed::VerifyingKey::from_bytes(&hex_to_array(s)?) + .map_err(|_| "hex code does not represent the valid key") + .map(Self) } } -struct UserData { - header: UserHeader, - post_headers: fs::File, - posts: fs::File, +struct HexSk(ed::SigningKey); + +impl std::str::FromStr for HexSk { + type Err = &'static str; + + fn from_str(s: &str) -> Result { + Ok(Self(ed::SigningKey::from_bytes(&hex_to_array(s)?))) + } } -impl UserData { - fn load( - auth: &UserAuth, - sx: x25519_dalek::EphemeralSecret, - ) -> io::Result<(Self, SharedSecret)> { - const HEADER_PATH: &str = "header.bin"; - const POST_HEADERS_PATH: &str = "post-headers.bin"; - const POST_PATH: &str = "posts.bin"; +struct DisplayHex([u8; 32]); - let mut path = PathBuf::from_iter([ - USER_DATA_DIR.get().unwrap().as_path(), - username_as_str(&auth.name).ok_or(io::ErrorKind::InvalidData)?.as_ref(), - ]); - - if path.exists() { - path.push(HEADER_PATH); - let mut header_file = fs::File::open(&path).ctx("opening user header file")?; - let mut header: UserHeader = - read_struct(&mut header_file).ctx("reading the user header")?; - path.pop(); - - let secret = auth - .verify(header.pk, header.nonce, sx) - .map_err(|_| io::ErrorKind::PermissionDenied) - .ctx("authenticating user")?; - - header.nonce = auth.nonce; - write_struct(&mut header_file, &header).ctx("saving user nonce")?; - - path.push(POST_HEADERS_PATH); - let post_headers = fs::File::open(&path).ctx("opening user post header file")?; - path.pop(); - - path.push(POST_PATH); - let posts = fs::File::open(&path).ctx("opening user post file")?; - path.pop(); - - Ok((Self { header, post_headers, posts }, secret)) - } else { - let secret = auth - .verify(auth.pk, 0, sx) - .map_err(|_| io::ErrorKind::PermissionDenied) - .ctx("verifiing registratio signature")?; - - fs::create_dir_all(&path).ctx("creating new user directory")?; - path.push(HEADER_PATH); - let header = - UserHeader { pk: auth.pk, nonce: auth.nonce, post_count: 0, runs: 0, imports: 0 }; - fs::write(&path, as_bytes(&header)).ctx("writing new user header")?; - path.pop(); - - path.push(POST_HEADERS_PATH); - let post_headers = fs::File::create_new(&path).ctx("creating new user post headers")?; - path.pop(); - - path.push(POST_PATH); - let posts = fs::File::create_new(&path).ctx("creating new user posts")?; - path.pop(); - - Ok((Self { header, post_headers, posts }, secret)) +impl fmt::Display for DisplayHex { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + for b in self.0 { + write!(f, "{b:02x}")?; } + Ok(()) } } -#[repr(packed)] -struct UserHeader { - pk: Pk, - nonce: Nonce, - post_count: u32, - imports: u32, - runs: u32, -} +#[cfg(test)] +#[test] +fn test_hex() { + let expected = [1u8; 32]; -#[repr(packed)] -struct PostHeader { - name: Postname, - timestamp: u64, - size: u32, - offset: u32, - imports: u32, - runs: u32, + let hex = dbg!(DisplayHex(expected).to_string()); + let got: [u8; 32] = hex_to_array(&hex).unwrap(); + + assert_eq!(got, expected); } diff --git a/mks.profile b/mks.profile new file mode 100644 index 0000000..2fdcff2 --- /dev/null +++ b/mks.profile @@ -0,0 +1,2 @@ +mlokisѯ=$ +H֜v z IsJUw