diff --git a/Cargo.toml b/Cargo.toml index c7818bb..c0234e9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,7 +26,7 @@ caretta-sync-macros = { path="macros", optional = true} caretta-sync-core = {workspace = true, features = ["test"]} [workspace] -members = [ ".", "core", "macros", "cli", "mobile", "examples/*" , "bevy"] +members = [ ".", "core", "macros", "cli", "mobile", "examples/*" , "bevy", "id"] resolver = "3" [workspace.package] @@ -43,6 +43,7 @@ ciborium = "0.2.2" clap = { version = "4.5.38", features = ["derive"] } caretta-sync-core.path = "core" futures = { version = "0.3.31", features = ["executor"] } +rand = "0.8.5" serde = { version = "1.0.219", features = ["derive"] } thiserror = "2.0.12" tokio = { version = "1.45.0", features = ["macros", "rt", "rt-multi-thread"] } @@ -56,6 +57,7 @@ prost-types = "0.14.1" tonic-prost-build = "0.14.0" tonic-prost = "0.14.0" + [profile.dev] opt-level = 1 diff --git a/cli/src/cli/device/add.rs b/cli/src/cli/device/add.rs deleted file mode 100644 index 58d5f45..0000000 --- a/cli/src/cli/device/add.rs +++ /dev/null @@ -1,24 +0,0 @@ -use clap::Args; -use caretta_sync_core::utils::runnable::Runnable; - -use crate::cli::ConfigArgs; - -use crate::cli::PeerArgs; - -#[derive(Debug, Args)] -pub struct DeviceAddCommandArgs { - #[command(flatten)] - peer: PeerArgs, - #[arg(short, long)] - passcode: Option, - #[command(flatten)] - config: ConfigArgs -} - -impl Runnable for DeviceAddCommandArgs { - fn run(self, app_name: &'static str) { - todo!() - } -} - - diff --git a/cli/src/cli/device/auth/approve.rs b/cli/src/cli/device/auth/approve.rs new file mode 100644 index 0000000..e69de29 diff --git a/cli/src/cli/device/auth/list.rs b/cli/src/cli/device/auth/list.rs new file mode 100644 index 0000000..e69de29 diff --git a/cli/src/cli/device/auth/mod.rs b/cli/src/cli/device/auth/mod.rs new file mode 100644 index 0000000..e69de29 diff --git a/cli/src/cli/device/auth/request.rs b/cli/src/cli/device/auth/request.rs new file mode 100644 index 0000000..e69de29 diff --git a/core/Cargo.toml b/core/Cargo.toml index 3b0051d..7985d1b 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -36,7 +36,7 @@ tracing-subscriber = { version = "0.3.19", features = ["env-filter"] } uuid.workspace = true url.workspace = true whoami = "1.6.1" -rand = "0.8.5" +rand.workspace = true ed25519-dalek = { version = "2.2.0", features = ["signature"] } tokio-stream.workspace = true diff --git a/core/src/data/local/authorization/mod.rs b/core/src/data/local/authorization/mod.rs index 15c8f0c..79a14ff 100644 --- a/core/src/data/local/authorization/mod.rs +++ b/core/src/data/local/authorization/mod.rs @@ -10,26 +10,29 @@ use iroh::NodeId; pub use request::*; pub use response::*; use rusqlite::{params, types::FromSqlError, Connection}; +use uuid::Uuid; use crate::data::local::RusqliteRecord; /// On going authorization pub struct Authorization { + request_id: Uuid, node_id: NodeId, - passcode: String, + node_info: Option, + passcode: Option, created_at: DateTime, updated_at: DateTime, } static TABLE_NAME: &str = "authorization"; -static DEFAULT_COLUMNS: [&str;4] = [ +static DEFAULT_COLUMNS: [&str;5] = [ + "request_id", "node_id", - "passcode", "created_at", "updated_at" ]; impl Authorization { - pub fn new(node_id: NodeId, passcode: String) -> Self { + pub fn new_sent(node_id: NodeId, passcode: String) -> Self { let timestamp = Local::now(); Self { node_id: node_id, @@ -38,6 +41,7 @@ impl Authorization { updated_at: timestamp } } + pub fn new_received(node_id:) pub fn get_by_node_id(node_id: NodeId, connection: &Connection) -> Result { connection.query_row( "SELECT node_id, passcode, created_at, updated_at FROM authorizaation WHRE node_id=(?1)", diff --git a/id/Cargo.toml b/id/Cargo.toml new file mode 100644 index 0000000..9ffa4e0 --- /dev/null +++ b/id/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "caretta-id" +edition.workspace = true +version = "0.1.0-alpha" +description.workspace = true +license.workspace = true +repository.workspace = true + +[features] + +[dependencies] +rand.workspace = true +thiserror.workspace = true diff --git a/id/README.md b/id/README.md new file mode 100644 index 0000000..c0056fe --- /dev/null +++ b/id/README.md @@ -0,0 +1,21 @@ +# Carreta ID +Random user-friendly id for distubuted system for personal data. +## Examples + +- `123` : shortest version +- `456-789` : default size, still user freindly and sufficient randomness (for personal data) +- `abc-def-ghj` : long version. alphabets except i, l and o are also valid  +## Specs +### Characters + + + +## Perpose +When I considering implementing IDs for users(not for internal system) to specify items, such as GitHub commit hashes or issue numbers, the following issues arose. + +- Sequential numbers like Git issues are difficult to implement in distributes systems because collitions are unavoidable. +- Random number like UUID is too long for users +- Short random number like 7-digit commit hash seems good but is is not standardized specification. + +So I decided to make my own ID specifications. + diff --git a/id/src/builder.rs b/id/src/builder.rs new file mode 100644 index 0000000..678f383 --- /dev/null +++ b/id/src/builder.rs @@ -0,0 +1,3 @@ +pub struct IdBuilder { + +} \ No newline at end of file diff --git a/id/src/chunk.rs b/id/src/chunk.rs new file mode 100644 index 0000000..5e7c37c --- /dev/null +++ b/id/src/chunk.rs @@ -0,0 +1,174 @@ +use std::{fmt::Display, str::FromStr}; + +use rand::Rng; + +use crate::error::Error; + +static CHARACTERS: &[u8;33] = b"0123456789abcdefghjkmnpqrstuvwxyz"; + +static BASE: u16 = 33; +static SQUARED_BASE: u16 = 1089; +static CUBED_BASE: u16 = 35937; + +pub static NIL: IdChunk = IdChunk(0); +pub static MAX: IdChunk = IdChunk(CUBED_BASE-1); + +fn char_to_u8(c: char) -> Option { + Some(match c { + '0' => 0, + '1' => 1, + '2' => 2, + '3' => 3, + '4' => 4, + '5' => 5, + '6' => 6, + '7' => 7, + '8' => 8, + '9' => 9, + 'a' => 10, + 'b' => 11, + 'c' => 12, + 'd' => 13, + 'e' => 14, + 'f' => 15, + 'g' => 16, + 'h' => 17, + 'i' => 1, + 'j' => 18, + 'k' => 19, + 'l' => 1, + 'm' => 20, + 'n' => 21, + 'o' => 0, + 'p' => 22, + 'q' => 23, + 'r' => 24, + 's' => 25, + 't' => 26, + 'u' => 27, + 'v' => 28, + 'w' => 29, + 'x' => 30, + 'y' => 31, + 'z' => 32, + 'A' => 10, + 'B' => 11, + 'C' => 12, + 'D' => 13, + 'E' => 14, + 'F' => 15, + 'G' => 16, + 'H' => 17, + 'I' => 1, + 'J' => 18, + 'K' => 19, + 'L' => 1, + 'M' => 20, + 'N' => 21, + 'O' => 0, + 'P' => 22, + 'Q' => 23, + 'R' => 24, + 'S' => 25, + 'T' => 26, + 'U' => 27, + 'V' => 28, + 'W' => 29, + 'X' => 30, + 'Y' => 31, + 'Z' => 32, + _ => return None + }) +} + +fn str_to_u16(s: &str) -> Result { + if s.len() != 3 { + return Err(Error::InvalidChunk(format!("Chunk '{}' is not 3 characters", s))) + } + let mut buf : [u16;3] = [0;3]; + for (i, c) in s.chars().enumerate() { + buf[i] = BASE.pow((2 - i) as u32) * (char_to_u8(c).ok_or(Error::InvalidChunk(format!("Invalid char: {}", c)))? as u16); + } + + Ok(buf.iter().sum()) +} +fn u16_to_string(int: u16) -> Result { + if int >= CUBED_BASE { + return Err(Error::OutsideOfRange(int)) + } + let first_char = char::from(CHARACTERS[usize::try_from(int / SQUARED_BASE).unwrap()]); + let second_char = char::from(CHARACTERS[usize::try_from((int % SQUARED_BASE)/ BASE).unwrap()]); + let third_char = char::from(CHARACTERS[usize::try_from(int % BASE).unwrap()]); + Ok(format!("{}{}{}", first_char, second_char, third_char)) +} + +#[derive(Clone, Debug, PartialEq)] +pub struct IdChunk(u16); + +impl IdChunk { + pub fn new(rng: &mut R) -> Self { + Self(rng.gen_range(0..CUBED_BASE)) + + } +} + +impl Display for IdChunk { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + todo!() + } +} + +impl FromStr for IdChunk { + type Err = Error; + + fn from_str(s: &str) -> Result { + todo!() + } +} + +impl TryFrom for IdChunk { + type Error = Error; + + fn try_from(value: u16) -> Result { + todo!() + } +} + +impl From for u16 { + fn from(value: IdChunk) -> Self { + value.0 + } +} + +#[cfg(test)] +mod tests { + use super::*; + fn assert_eq_chunk(s: &str, i: u16) { + assert_eq!(s, &u16_to_string(i).unwrap()); + assert_eq!(i, str_to_u16(s).unwrap()) + + } + #[test] + fn test_nil() { + assert_eq_chunk("000", 0); + } + #[test] + fn test_max() { + assert_eq_chunk("zzz", CUBED_BASE-1); + } + fn assert_random(rand: &mut R) + where + R: Rng + { + let chunk = IdChunk::new(rand); + let s = chunk.to_string(); + assert_eq!(chunk,IdChunk::from_str(&s).unwrap()) + } + fn random_x10() { + let mut rng = rand::thread_rng(); + for _ in 0..10 { + assert_random(&mut rng); + } + + } +} \ No newline at end of file diff --git a/id/src/config.rs b/id/src/config.rs new file mode 100644 index 0000000..f2fe151 --- /dev/null +++ b/id/src/config.rs @@ -0,0 +1,4 @@ +pub struct Config { + pub enable_nil: bool, + pub enable_max: bool, +} \ No newline at end of file diff --git a/id/src/double.rs b/id/src/double.rs new file mode 100644 index 0000000..e69de29 diff --git a/id/src/error.rs b/id/src/error.rs new file mode 100644 index 0000000..3c8af2d --- /dev/null +++ b/id/src/error.rs @@ -0,0 +1,8 @@ +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("Outside of range: {0}")] + OutsideOfRange(u16), + #[error("Invalid chunk: {0}")] + InvalidChunk(String), +} + diff --git a/id/src/lib.rs b/id/src/lib.rs new file mode 100644 index 0000000..795d0b5 --- /dev/null +++ b/id/src/lib.rs @@ -0,0 +1,6 @@ +mod chunk; +mod double; +mod error; +mod single; +mod triple; + diff --git a/id/src/single.rs b/id/src/single.rs new file mode 100644 index 0000000..e69de29 diff --git a/id/src/triple.rs b/id/src/triple.rs new file mode 100644 index 0000000..e69de29