Add caretta-id

This commit is contained in:
fluo10 2025-09-12 09:27:14 +09:00
parent f2e79d4cd0
commit 99fdb12712
18 changed files with 241 additions and 30 deletions

View file

@ -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

View file

@ -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<String>,
#[command(flatten)]
config: ConfigArgs
}
impl Runnable for DeviceAddCommandArgs {
fn run(self, app_name: &'static str) {
todo!()
}
}

View file

View file

View file

View file

View file

@ -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

View file

@ -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<String>,
passcode: Option<String>,
created_at: DateTime<Local>,
updated_at: DateTime<Local>,
}
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<Self, rusqlite::Error> {
connection.query_row(
"SELECT node_id, passcode, created_at, updated_at FROM authorizaation WHRE node_id=(?1)",

13
id/Cargo.toml Normal file
View file

@ -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

21
id/README.md Normal file
View file

@ -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.

3
id/src/builder.rs Normal file
View file

@ -0,0 +1,3 @@
pub struct IdBuilder {
}

174
id/src/chunk.rs Normal file
View file

@ -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<u8> {
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<u16, Error> {
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<String, Error> {
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<R: Rng + ?Sized>(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<Self, Self::Err> {
todo!()
}
}
impl TryFrom<u16> for IdChunk {
type Error = Error;
fn try_from(value: u16) -> Result<Self, Self::Error> {
todo!()
}
}
impl From<IdChunk> 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<R>(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);
}
}
}

4
id/src/config.rs Normal file
View file

@ -0,0 +1,4 @@
pub struct Config {
pub enable_nil: bool,
pub enable_max: bool,
}

0
id/src/double.rs Normal file
View file

8
id/src/error.rs Normal file
View file

@ -0,0 +1,8 @@
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("Outside of range: {0}")]
OutsideOfRange(u16),
#[error("Invalid chunk: {0}")]
InvalidChunk(String),
}

6
id/src/lib.rs Normal file
View file

@ -0,0 +1,6 @@
mod chunk;
mod double;
mod error;
mod single;
mod triple;

0
id/src/single.rs Normal file
View file

0
id/src/triple.rs Normal file
View file