Compare commits
No commits in common. "main" and "feature/iroh" have entirely different histories.
main
...
feature/ir
97 changed files with 291 additions and 2544 deletions
5
.vscode/extensions.json
vendored
5
.vscode/extensions.json
vendored
|
|
@ -1,5 +0,0 @@
|
|||
{
|
||||
"recommendations": [
|
||||
"zxh404.vscode-proto3"
|
||||
]
|
||||
}
|
||||
10
.vscode/settings.json
vendored
10
.vscode/settings.json
vendored
|
|
@ -1,10 +0,0 @@
|
|||
{
|
||||
"protoc": {
|
||||
"compile_on_save": true,
|
||||
"options": [
|
||||
"--proto_path=${workspaceRoot}/tripod-id/proto",
|
||||
"--proto_path=${workspaceRoot}/core/proto",
|
||||
"--java_out=${workspaceRoot}/.tmp"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -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", "tripod-id"]
|
||||
members = [ ".", "core", "macros", "cli", "mobile", "examples/*" , "bevy"]
|
||||
resolver = "3"
|
||||
|
||||
[workspace.package]
|
||||
|
|
@ -38,14 +38,11 @@ repository = "https://forgejo.fireturlte.net/lazy-supplements"
|
|||
|
||||
[workspace.dependencies]
|
||||
bevy = { git = "https://github.com/bevyengine/bevy.git", rev="16ffdaea0daec11e4347d965f56c9c8e1122a488" }
|
||||
tripod-id = {path="./tripod-id", features=["prost", "rusqlite", "serde"]}
|
||||
chrono = "0.4.41"
|
||||
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"
|
||||
rusqlite = "0.37.0"
|
||||
serde = { version = "1.0.219", features = ["derive"] }
|
||||
thiserror = "2.0.12"
|
||||
tokio = { version = "1.45.0", features = ["macros", "rt", "rt-multi-thread"] }
|
||||
|
|
@ -53,13 +50,12 @@ tokio-stream = "0.1.17"
|
|||
tonic = "0.14.0"
|
||||
url = { version = "2.5.7", features = ["serde"] }
|
||||
uuid = { version = "1.17.0", features = ["v7"] }
|
||||
iroh = { version = "0.92.0", features = ["discovery-local-network", "discovery-pkarr-dht"] }
|
||||
iroh = { version = "0.91.2", features = ["discovery-local-network", "discovery-pkarr-dht"] }
|
||||
prost = "0.14.1"
|
||||
prost-types = "0.14.1"
|
||||
tonic-prost-build = "0.14.0"
|
||||
tonic-prost = "0.14.0"
|
||||
|
||||
|
||||
[profile.dev]
|
||||
opt-level = 1
|
||||
|
||||
|
|
|
|||
24
cli/src/cli/device/add.rs
Normal file
24
cli/src/cli/device/add.rs
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
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!()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -13,7 +13,6 @@ test = ["dep:tempfile", ]
|
|||
|
||||
[dependencies]
|
||||
base64 = "0.22.1"
|
||||
tripod-id.workspace = true
|
||||
chrono.workspace = true
|
||||
chrono-tz = "0.10.3"
|
||||
ciborium.workspace = true
|
||||
|
|
@ -23,7 +22,7 @@ futures.workspace = true
|
|||
iroh.workspace = true
|
||||
prost.workspace = true
|
||||
prost-types.workspace = true
|
||||
rusqlite = { workspace = true, features = ["bundled", "chrono", "uuid"] }
|
||||
rusqlite = { version = "0.37.0", features = ["bundled", "chrono"] }
|
||||
serde.workspace = true
|
||||
sysinfo = "0.37.0"
|
||||
tempfile = { version = "3.20.0", optional = true }
|
||||
|
|
@ -37,7 +36,7 @@ tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }
|
|||
uuid.workspace = true
|
||||
url.workspace = true
|
||||
whoami = "1.6.1"
|
||||
rand.workspace = true
|
||||
rand = "0.8.5"
|
||||
ed25519-dalek = { version = "2.2.0", features = ["signature"] }
|
||||
tokio-stream.workspace = true
|
||||
|
||||
|
|
|
|||
|
|
@ -1,14 +1,4 @@
|
|||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
tonic_prost_build::configure()
|
||||
.extern_path(".tripod_id", "::tripod_id::prost")
|
||||
.compile_protos(
|
||||
&[
|
||||
"proto/caretta_sync/authorization_request/authorization_request.proto",
|
||||
"proto/caretta_sync/authorized_node/authorized_node.proto",
|
||||
"proto/caretta_sync/remote_node/remote_node.proto",
|
||||
|
||||
],
|
||||
&["proto", "../tripod-id/proto"]
|
||||
)?;
|
||||
tonic_prost_build::compile_protos("proto/caretta_sync.proto")?;
|
||||
Ok(())
|
||||
}
|
||||
51
core/proto/caretta_sync.proto
Normal file
51
core/proto/caretta_sync.proto
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
syntax = "proto3";
|
||||
package caretta_sync;
|
||||
import "google/protobuf/timestamp.proto";
|
||||
import "google/protobuf/duration.proto";
|
||||
|
||||
service CarettaSync {
|
||||
rpc RemoteInfo(RemoteInfoRequest) returns (RemoteInfoResponse);
|
||||
rpc RemoteInfoIter(RemoteInfoIterRequest) returns (stream RemoteInfoResponse);
|
||||
}
|
||||
|
||||
message NodeIdMessage {
|
||||
bytes node_id = 1;
|
||||
}
|
||||
|
||||
message RemoteInfoRequest {
|
||||
NodeIdMessage node_id = 1;
|
||||
}
|
||||
|
||||
message RemoteInfoIterRequest {}
|
||||
|
||||
message RemoteInfoResponse {
|
||||
RemoteInfoMessage remote_info = 1;
|
||||
}
|
||||
|
||||
message RemoteInfoMessage {
|
||||
NodeIdMessage node_id = 1;
|
||||
string relay_url = 2;
|
||||
repeated DirectAddrInfoMessage addrs = 3;
|
||||
string conn_type = 4;
|
||||
google.protobuf.Duration latency = 5;
|
||||
google.protobuf.Duration last_used = 6;
|
||||
}
|
||||
|
||||
message DirectAddrInfoMessage {
|
||||
string addr = 1;
|
||||
google.protobuf.Duration latency = 2;
|
||||
LastControlMessage last_control = 3;
|
||||
google.protobuf.Duration last_payload = 4;
|
||||
google.protobuf.Duration last_alive = 5;
|
||||
repeated SourceMessage sources = 6;
|
||||
}
|
||||
|
||||
message LastControlMessage {
|
||||
google.protobuf.Duration duration = 1;
|
||||
string control_msg = 2;
|
||||
}
|
||||
|
||||
message SourceMessage {
|
||||
string source = 1;
|
||||
google.protobuf.Duration duration = 2;
|
||||
}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
syntax = "proto3";
|
||||
package caretta_sync.authorization_request;
|
||||
|
||||
import "caretta_sync/authorization_request/identifier.proto";
|
||||
|
||||
message AcceptRequest {
|
||||
Identifier authorization_request = 1;
|
||||
string passcode = 2;
|
||||
}
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
syntax = "proto3";
|
||||
package caretta_sync.authorization_request;
|
||||
|
||||
import "caretta_sync/authorized_node/info.proto";
|
||||
|
||||
message AcceptResponse {
|
||||
caretta_sync.authorized_node.Info authorized_node_info = 1;
|
||||
}
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
syntax = "proto3";
|
||||
package caretta_sync.authorization_request;
|
||||
|
||||
import "caretta_sync/authorization_request/accept_request.proto";
|
||||
import "caretta_sync/authorization_request/accept_response.proto";
|
||||
import "caretta_sync/authorization_request/info_request.proto";
|
||||
import "caretta_sync/authorization_request/info_response.proto";
|
||||
import "caretta_sync/authorization_request/list_request.proto";
|
||||
import "caretta_sync/authorization_request/list_response.proto";
|
||||
import "caretta_sync/authorization_request/reject_request.proto";
|
||||
import "caretta_sync/authorization_request/reject_response.proto";
|
||||
import "caretta_sync/authorization_request/send_request.proto";
|
||||
import "caretta_sync/authorization_request/send_response.proto";
|
||||
|
||||
|
||||
|
||||
service AuthorizationRequest {
|
||||
rpc Send(SendRequest) returns (SendResponse);
|
||||
rpc Accept(AcceptRequest) returns (AcceptResponse);
|
||||
rpc Reject(RejectRequest) returns (RejectResponse);
|
||||
rpc Info(InfoRequest) returns (InfoResponse);
|
||||
rpc List(stream ListRequest) returns (stream ListResponse);
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
syntax = "proto3";
|
||||
package caretta_sync.authorization_request;
|
||||
|
||||
import "caretta_sync/common/uuid.proto";
|
||||
import "tripod_id/double.proto";
|
||||
|
||||
message Identifier {
|
||||
oneof identifier_value {
|
||||
caretta_sync.common.Uuid uuid = 1;
|
||||
tripod_id.Double id = 2;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
syntax = "proto3";
|
||||
package caretta_sync.authorization_request;
|
||||
|
||||
import "caretta_sync/iroh/public_key.proto";
|
||||
import "caretta_sync/authorization_request/status.proto";
|
||||
import "tripod_id/double.proto";
|
||||
import "google/protobuf/timestamp.proto";
|
||||
|
||||
message Info {
|
||||
tripod_id.Double id = 1;
|
||||
caretta_sync.iroh.PublicKey public_key = 2;
|
||||
google.protobuf.Timestamp created_at = 3;
|
||||
google.protobuf.Timestamp closed_at = 6;
|
||||
Status status = 4;
|
||||
}
|
||||
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
syntax = "proto3";
|
||||
package caretta_sync.authorization_request;
|
||||
|
||||
import "caretta_sync/authorization_request/identifier.proto";
|
||||
|
||||
message InfoRequest {
|
||||
Identifier request = 1;
|
||||
}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
syntax = "proto3";
|
||||
package caretta_sync.authorization_request;
|
||||
|
||||
import "caretta_sync/authorization_request/info.proto";
|
||||
|
||||
|
||||
message InfoResponse{
|
||||
Info info = 1;
|
||||
}
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
syntax = "proto3";
|
||||
package caretta_sync.authorization_request;
|
||||
|
||||
message ListRequest {}
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
syntax = "proto3";
|
||||
package caretta_sync.authorization_request;
|
||||
|
||||
import "caretta_sync/authorization_request/info.proto";
|
||||
|
||||
message ListResponse {
|
||||
Info request_info = 1;
|
||||
}
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
syntax = "proto3";
|
||||
package caretta_sync.authorization_request;
|
||||
|
||||
import "caretta_sync/authorization_request/identifier.proto";
|
||||
|
||||
message RejectRequest {
|
||||
Identifier authorization_request = 1;
|
||||
}
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
syntax = "proto3";
|
||||
package caretta_sync.authorization_request;
|
||||
|
||||
import "caretta_sync/authorization_request/info.proto";
|
||||
|
||||
message RejectResponse {
|
||||
Info request_info = 1;
|
||||
}
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
syntax = "proto3";
|
||||
package caretta_sync.authorization_request;
|
||||
|
||||
import "caretta_sync/remote_node/identifier.proto";
|
||||
|
||||
message SendRequest {
|
||||
caretta_sync.remote_node.Identifier remote_node = 1;
|
||||
}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
syntax = "proto3";
|
||||
package caretta_sync.authorization_request;
|
||||
|
||||
import "caretta_sync/remote_node/info.proto";
|
||||
|
||||
message SendResponse {
|
||||
caretta_sync.remote_node.Info remote_node_info = 1;
|
||||
string passcode = 2;
|
||||
}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
syntax = "proto3";
|
||||
package caretta_sync.authorization_request;
|
||||
|
||||
enum Status {
|
||||
UNSPECIFIED = 0;
|
||||
SENT = 1;
|
||||
RECEIVED = 2;
|
||||
ACCEPTED = 3;
|
||||
REJECTED = 4;
|
||||
}
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
syntax = "proto3";
|
||||
|
||||
package caretta_sync.authorized_node;
|
||||
|
||||
import "caretta_sync/authorized_node/info_request.proto";
|
||||
import "caretta_sync/authorized_node/info_response.proto";
|
||||
import "caretta_sync/authorized_node/list_request.proto";
|
||||
import "caretta_sync/authorized_node/list_response.proto";
|
||||
|
||||
|
||||
service AuthorizedNode {
|
||||
rpc Info(InfoRequest) returns (InfoResponse);
|
||||
rpc List(stream ListRequest) returns (stream ListResponse);
|
||||
}
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
syntax = "proto3";
|
||||
|
||||
package caretta_sync.authorized_node;
|
||||
|
||||
import "tripod_id/single.proto";
|
||||
import "caretta_sync/iroh/public_key.proto";
|
||||
|
||||
|
||||
message Identifier {
|
||||
oneof identifier_value {
|
||||
tripod_id.Single id = 1;
|
||||
caretta_sync.iroh.PublicKey public_key = 2;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
syntax = "proto3";
|
||||
|
||||
package caretta_sync.authorized_node;
|
||||
|
||||
import "tripod_id/single.proto";
|
||||
import "caretta_sync/iroh/public_key.proto";
|
||||
|
||||
message Info {
|
||||
tripod_id.Single id = 1;
|
||||
caretta_sync.iroh.PublicKey public_key = 2;
|
||||
string note = 3;
|
||||
}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
syntax = "proto3";
|
||||
|
||||
package caretta_sync.authorized_node;
|
||||
|
||||
import "caretta_sync/authorized_node/identifier.proto";
|
||||
|
||||
message InfoRequest {
|
||||
Identifier node = 1;
|
||||
}
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
syntax = "proto3";
|
||||
|
||||
package caretta_sync.authorized_node;
|
||||
import "caretta_sync/authorized_node/info.proto";
|
||||
|
||||
message InfoResponse {
|
||||
Info node_info = 1;
|
||||
}
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
syntax = "proto3";
|
||||
|
||||
package caretta_sync.authorized_node;
|
||||
|
||||
message ListRequest{}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
syntax = "proto3";
|
||||
|
||||
package caretta_sync.authorized_node;
|
||||
|
||||
import "caretta_sync/authorized_node/info.proto";
|
||||
|
||||
message ListResponse {
|
||||
Info node_info = 1;
|
||||
}
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
syntax = "proto3";
|
||||
|
||||
package caretta_sync.common;
|
||||
|
||||
// protobuf message of url::Url.
|
||||
message Url {
|
||||
string url = 1;
|
||||
}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
syntax = "proto3";
|
||||
|
||||
package caretta_sync.common;
|
||||
|
||||
// protobuf message of uuid::Uuid
|
||||
message Uuid {
|
||||
uint64 high_bits = 1;
|
||||
uint64 low_bits = 2;
|
||||
}
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
syntax = "proto3";
|
||||
package caretta_sync.iroh;
|
||||
|
||||
import "caretta_sync/common/url.proto";
|
||||
import "caretta_sync/net/socket_addr.proto";
|
||||
|
||||
// A protobuf message of iroh::ConnectionType
|
||||
message ConnectionType {
|
||||
message Direct {
|
||||
caretta_sync.net.SocketAddr direct_value = 1;
|
||||
}
|
||||
message Relay {
|
||||
caretta_sync.common.Url relay_value = 1;
|
||||
}
|
||||
message Mixed {
|
||||
caretta_sync.net.SocketAddr socket_addr = 1;
|
||||
caretta_sync.common.Url relay_url = 2;
|
||||
}
|
||||
message None{}
|
||||
oneof connection_type_value {
|
||||
Direct direct = 1;
|
||||
Relay relay = 2;
|
||||
Mixed mixed = 3;
|
||||
None none = 4;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
syntax = "proto3";
|
||||
package caretta_sync.iroh;
|
||||
|
||||
// The message of iroh::endpoint::ControlMsg.
|
||||
// To ensure compatiility with irof::endpoint::ControlMsg,
|
||||
// it's implemented as a message rather than an enum to accommodate the posibility
|
||||
// that values may be added to the enum in rust in the future.
|
||||
message ControlMsg {
|
||||
message Ping {}
|
||||
message Pong {}
|
||||
message CallMeMayBe {}
|
||||
|
||||
oneof control_msg_vaue {
|
||||
Ping ping = 1;
|
||||
Pong pong = 2;
|
||||
CallMeMayBe call_me_maybe = 3;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
syntax = "proto3";
|
||||
package caretta_sync.iroh;
|
||||
|
||||
import "google/protobuf/duration.proto";
|
||||
import "caretta_sync/iroh/control_msg.proto";
|
||||
import "caretta_sync/iroh/source.proto";
|
||||
import "caretta_sync/net/socket_addr.proto";
|
||||
|
||||
// A protobuf message of iroh::endpoint::DirectAddrInfo
|
||||
message DirectAddrInfo {
|
||||
// A protobuf message of (Duration, ControlMsg)
|
||||
message DurationControlMsg {
|
||||
|
||||
google.protobuf.Duration duration = 1;
|
||||
ControlMsg control_msg = 2;
|
||||
}
|
||||
|
||||
// A protobuf message of (iroh::Source, Duration)
|
||||
message SourceDuration {
|
||||
Source source = 1;
|
||||
google.protobuf.Duration duration = 2;
|
||||
}
|
||||
|
||||
caretta_sync.net.SocketAddr addr = 1;
|
||||
google.protobuf.Duration latency = 2;
|
||||
DurationControlMsg last_control = 3;
|
||||
google.protobuf.Duration last_payload = 4;
|
||||
google.protobuf.Duration last_alive = 5;
|
||||
repeated SourceDuration sources = 6;
|
||||
}
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
syntax = "proto3";
|
||||
|
||||
package caretta_sync.iroh;
|
||||
|
||||
// protobuf message of iroh::PublicKey.
|
||||
message PublicKey {
|
||||
bytes key = 1;
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
syntax = "proto3";
|
||||
package caretta_sync.iroh;
|
||||
|
||||
import "google/protobuf/duration.proto";
|
||||
import "caretta_sync/common/url.proto";
|
||||
|
||||
// A protobuf message of iroh::RelayUrlInfo
|
||||
message RelayUrlInfo {
|
||||
caretta_sync.common.Url relay_url = 1;
|
||||
google.protobuf.Duration last_alive = 2;
|
||||
google.protobuf.Duration latency = 3;
|
||||
}
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
syntax = "proto3";
|
||||
package caretta_sync.iroh;
|
||||
|
||||
import "caretta_sync/iroh/public_key.proto";
|
||||
import "caretta_sync/iroh/relay_url_info.proto";
|
||||
import "caretta_sync/iroh/direct_addr_info.proto";
|
||||
import "caretta_sync/iroh/connection_type.proto";
|
||||
import "google/protobuf/duration.proto";
|
||||
|
||||
// A messege of iroh::RemoteInfo.
|
||||
message RemoteInfo {
|
||||
PublicKey node_id = 3;
|
||||
RelayUrlInfo relay_url = 4;
|
||||
repeated DirectAddrInfo addrs = 5;
|
||||
ConnectionType conn_type = 6;
|
||||
google.protobuf.Duration latency = 7;
|
||||
google.protobuf.Duration last_used = 8;
|
||||
}
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
syntax = "proto3";
|
||||
package caretta_sync.iroh;
|
||||
|
||||
message Source {
|
||||
message Saved{}
|
||||
message Udp{}
|
||||
message Relay{}
|
||||
message App{}
|
||||
message Discovery{
|
||||
string value = 1;
|
||||
}
|
||||
message NamedApp {
|
||||
string value = 1;
|
||||
}
|
||||
oneof source_value {
|
||||
Saved saved = 1;
|
||||
Udp udp = 2;
|
||||
Relay relay = 3;
|
||||
App app = 4;
|
||||
Discovery discovery = 5;
|
||||
NamedApp named_app = 6;
|
||||
};
|
||||
}
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
syntax = "proto3";
|
||||
package caretta_sync.net;
|
||||
|
||||
message Ipv4Addr {
|
||||
uint32 bits = 1;
|
||||
}
|
||||
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
syntax = "proto3";
|
||||
package caretta_sync.net;
|
||||
|
||||
message Ipv6Addr {
|
||||
uint64 high_bits = 1;
|
||||
uint64 low_bits = 2;
|
||||
}
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
syntax = "proto3";
|
||||
|
||||
package caretta_sync.net;
|
||||
|
||||
import "caretta_sync/net/socket_addr_v4.proto";
|
||||
import "caretta_sync/net/socket_addr_v6.proto";
|
||||
|
||||
// Protobuf message of std::net::SocketAddr.
|
||||
message SocketAddr {
|
||||
oneof socket_addr_value {
|
||||
SocketAddrV4 v4 = 1;
|
||||
SocketAddrV6 v6 = 2;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
syntax = "proto3";
|
||||
package caretta_sync.net;
|
||||
|
||||
import "caretta_sync/net/ipv4_addr.proto";
|
||||
|
||||
message SocketAddrV4 {
|
||||
Ipv4Addr ip = 1;
|
||||
uint32 port = 2;
|
||||
}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
syntax = "proto3";
|
||||
package caretta_sync.net;
|
||||
|
||||
import "caretta_sync/net/ipv6_addr.proto";
|
||||
|
||||
message SocketAddrV6 {
|
||||
Ipv6Addr ip = 1;
|
||||
uint32 port = 2;
|
||||
}
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
syntax = "proto3";
|
||||
package caretta_sync.remote_node;
|
||||
|
||||
import "caretta_sync/iroh/public_key.proto";
|
||||
import "tripod_id/double.proto";
|
||||
|
||||
|
||||
message Identifier {
|
||||
oneof identifier_value {
|
||||
tripod_id.Double id = 1;
|
||||
caretta_sync.iroh.PublicKey public_key = 2;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
syntax = "proto3";
|
||||
package caretta_sync.remote_node;
|
||||
|
||||
import "caretta_sync/iroh/remote_info.proto";
|
||||
import "tripod_id/double.proto";
|
||||
|
||||
message Info {
|
||||
tripod_id.Double public_id = 1;
|
||||
caretta_sync.iroh.RemoteInfo remote_info = 2;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
syntax = "proto3";
|
||||
package caretta_sync.remote_node;
|
||||
|
||||
import "caretta_sync/remote_node/identifier.proto";
|
||||
|
||||
message InfoRequest {
|
||||
Identifier remote_node = 1;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
syntax = "proto3";
|
||||
package caretta_sync.remote_node;
|
||||
|
||||
import "caretta_sync/remote_node/info.proto";
|
||||
|
||||
message InfoResponse {
|
||||
Info remote_node_info = 1;
|
||||
}
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
syntax = "proto3";
|
||||
package caretta_sync.remote_node;
|
||||
|
||||
message ListRequest{}
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
syntax = "proto3";
|
||||
package caretta_sync.remote_node;
|
||||
|
||||
import "caretta_sync/remote_node/info.proto";
|
||||
|
||||
message ListResponse {
|
||||
Info remote_node_info = 1;
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
syntax = "proto3";
|
||||
package caretta_sync.remote_node;
|
||||
|
||||
import "caretta_sync/remote_node/info_request.proto";
|
||||
import "caretta_sync/remote_node/info_response.proto";
|
||||
import "caretta_sync/remote_node/list_request.proto";
|
||||
import "caretta_sync/remote_node/list_response.proto";
|
||||
|
||||
service RemoteNode {
|
||||
rpc Info(InfoRequest) returns (InfoResponse);
|
||||
rpc List(stream ListRequest) returns (stream ListResponse);
|
||||
}
|
||||
87
core/src/data/local/authorization/mod.rs
Normal file
87
core/src/data/local/authorization/mod.rs
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
//! Structs about authorization.
|
||||
|
||||
mod request;
|
||||
mod response;
|
||||
|
||||
use std::os::unix::raw::time_t;
|
||||
|
||||
use chrono::{DateTime, Local, NaiveDateTime};
|
||||
use iroh::NodeId;
|
||||
pub use request::*;
|
||||
pub use response::*;
|
||||
use rusqlite::{params, types::FromSqlError, Connection};
|
||||
|
||||
use crate::data::local::RusqliteRecord;
|
||||
|
||||
/// On going authorization
|
||||
pub struct Authorization {
|
||||
node_id: NodeId,
|
||||
passcode: String,
|
||||
created_at: DateTime<Local>,
|
||||
updated_at: DateTime<Local>,
|
||||
}
|
||||
static TABLE_NAME: &str = "authorization";
|
||||
static DEFAULT_COLUMNS: [&str;4] = [
|
||||
"node_id",
|
||||
"passcode",
|
||||
"created_at",
|
||||
"updated_at"
|
||||
];
|
||||
|
||||
impl Authorization {
|
||||
pub fn new(node_id: NodeId, passcode: String) -> Self {
|
||||
let timestamp = Local::now();
|
||||
Self {
|
||||
node_id: node_id,
|
||||
passcode: passcode,
|
||||
created_at: timestamp.clone(),
|
||||
updated_at: timestamp
|
||||
}
|
||||
}
|
||||
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)",
|
||||
params![node_id.as_bytes()],
|
||||
Self::from_row
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
impl RusqliteRecord for Authorization {
|
||||
fn from_row(row: &rusqlite::Row<'_>) -> Result<Self, rusqlite::Error> {
|
||||
let created_at: NaiveDateTime = row.get(2)?;
|
||||
let updated_at: NaiveDateTime = row.get(3)?;
|
||||
let node_id: Vec<u8> = row.get(0)?;
|
||||
Ok(Self {
|
||||
node_id: NodeId::from_bytes(node_id[..32].try_into().or_else(|e| {
|
||||
Err(rusqlite::types::FromSqlError::InvalidBlobSize {
|
||||
expected_size: 32,
|
||||
blob_size: node_id.len()
|
||||
})
|
||||
})?).or(Err(FromSqlError::InvalidType))?,
|
||||
passcode: row.get(1)?,
|
||||
created_at: DateTime::from(created_at.and_utc()),
|
||||
updated_at: DateTime::from(updated_at.and_utc()),
|
||||
})
|
||||
}
|
||||
fn insert(&self, connection: &rusqlite::Connection) -> Result<(), rusqlite::Error> {
|
||||
connection.execute(
|
||||
"INSERT INTO authorization (node_id, passcode, created_at, updated_at) VALUES (?1, ?2, ?3, ?4)",
|
||||
(&self.node_id.as_bytes(), &self.passcode, &self.created_at.naive_utc(), &self.updated_at.naive_utc()),
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
fn get_all(connection: &rusqlite::Connection) -> Result<Vec<Self>, rusqlite::Error> {
|
||||
let mut stmt = connection.prepare(&(String::from("SELECT ") + &DEFAULT_COLUMNS.join(", ") + " FROM " + TABLE_NAME))?;
|
||||
let rows = stmt.query_map(
|
||||
[],
|
||||
Self::from_row
|
||||
)?;
|
||||
let mut result= Vec::new();
|
||||
for row in rows {
|
||||
result.push(row?);
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
8
core/src/data/local/authorization/request.rs
Normal file
8
core/src/data/local/authorization/request.rs
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
use iroh::NodeId;
|
||||
|
||||
/// Request of node authentication.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AuthorizationRequest {
|
||||
sender_id: NodeId,
|
||||
sender_info: String,
|
||||
}
|
||||
12
core/src/data/local/authorization/response.rs
Normal file
12
core/src/data/local/authorization/response.rs
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
use iroh::NodeId;
|
||||
|
||||
/// Response of node authentication.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AuthorizationResponse {
|
||||
sender_id: NodeId,
|
||||
passcode: String,
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -1,80 +0,0 @@
|
|||
//! Structs about authorization.
|
||||
|
||||
mod sent;
|
||||
mod received;
|
||||
|
||||
use std::os::unix::raw::time_t;
|
||||
|
||||
use tripod_id::Double;
|
||||
use chrono::{DateTime, Local, NaiveDateTime};
|
||||
use iroh::{NodeId, PublicKey};
|
||||
pub use sent::*;
|
||||
pub use received::*;
|
||||
use rusqlite::{params, types::FromSqlError, Connection};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::data::local::LocalRecord;
|
||||
|
||||
|
||||
/// Request of node authentication.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AuthorizationRequestRecord {
|
||||
id: u32,
|
||||
uuid: Uuid,
|
||||
public_id: Double,
|
||||
peer_id: u32,
|
||||
created_at: DateTime<Local>,
|
||||
closed_at: Option<DateTime<Local>>,
|
||||
}
|
||||
|
||||
impl LocalRecord for AuthorizationRequestRecord {
|
||||
|
||||
const TABLE_NAME: &str = "authorization_request";
|
||||
const SELECT_COLUMNS: &[&str] = &[
|
||||
"id",
|
||||
"uuid",
|
||||
"public_id",
|
||||
"peer_id",
|
||||
"created_at",
|
||||
"closed_at"
|
||||
];
|
||||
const INSERT_COLUMNS: &[&str] = &[
|
||||
"uuid",
|
||||
"public_id",
|
||||
"peer_id",
|
||||
"created_at"
|
||||
];
|
||||
|
||||
type InsertParams<'a> = (&'a Double, &'a [u8;32], &'a NaiveDateTime);
|
||||
type SelectValues = (u32, Uuid, Double, PublicKey, NaiveDateTime, Option<NaiveDateTime>);
|
||||
fn from_row(row: &rusqlite::Row<'_>) -> Result<Self, rusqlite::Error> {
|
||||
let created_at: NaiveDateTime = row.get(4)?;
|
||||
let closed_at: Option<NaiveDateTime> = row.get(5)?;
|
||||
Ok(Self {
|
||||
id: row.get(0)?,
|
||||
uuid: row.get(1)?,
|
||||
public_id: row.get(2)?,
|
||||
peer_id: row.get(3)?,
|
||||
created_at: created_at.and_utc().into(),
|
||||
closed_at: closed_at.map(|x| x.and_utc().into())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(u32, Uuid, Double, u32, NaiveDateTime, Option<NaiveDateTime>)> for AuthorizationRequestRecord {
|
||||
fn from(value: (u32, Uuid, Double, u32, NaiveDateTime, Option<NaiveDateTime>)) -> Self {
|
||||
Self {
|
||||
id: value.0,
|
||||
uuid: value.1,
|
||||
public_id: value.2,
|
||||
peer_id: value.3,
|
||||
created_at: value.4.and_utc().into(),
|
||||
closed_at: value.5.map(|x| x.and_utc().into())
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<'a> From<&'a rusqlite::Row<'_>> for AuthorizationRequestRecord {
|
||||
fn from(value: &'a rusqlite::Row<'_>) -> Self {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
use caretta_id::{DoubleId, SingleId};
|
||||
use chrono::{DateTime, Local, NaiveDateTime};
|
||||
use iroh::{NodeId, PublicKey};
|
||||
|
||||
use crate::{data::local::LocalRecord, global::LOCAL_DATABASE_CONNECTION};
|
||||
|
||||
/// Response of node authentication.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ReceivedAuthorizationRequestRecord {
|
||||
id: u32,
|
||||
authorization_request_id: u32,
|
||||
peer_note: String,
|
||||
}
|
||||
|
||||
impl LocalRecord for ReceivedAuthorizationRequestRecord {
|
||||
const TABLE_NAME: &str = "received_authorization_request";
|
||||
|
||||
const SELECT_COLUMNS: &[&str] = &[
|
||||
"id",
|
||||
"authorization_request_id",
|
||||
"peer_note"
|
||||
];
|
||||
|
||||
const INSERT_COLUMNS: &[&str] = &[
|
||||
"authorization_request_id",
|
||||
"peer_note"
|
||||
];
|
||||
|
||||
type InsertParams<'a> = (&'a u32, &'a str);
|
||||
|
||||
fn from_row(row: &rusqlite::Row<'_>) -> Result<Self, rusqlite::Error> {
|
||||
Ok(Self {
|
||||
id: row.get(0)?,
|
||||
authorization_request_id: row.get(1)?,
|
||||
peer_note: row.get(2)?
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
use caretta_id::SingleId;
|
||||
use chrono::{DateTime, Local, NaiveDateTime};
|
||||
use iroh::{NodeId, PublicKey};
|
||||
use rusqlite::types::FromSqlError;
|
||||
|
||||
use crate::{data::local::LocalRecord, global::LOCAL_DATABASE_CONNECTION};
|
||||
|
||||
/// Request of node authentication.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SentAuthorizationRequestRecord {
|
||||
id: u32,
|
||||
authorization_request_id: u32,
|
||||
passcode: String,
|
||||
}
|
||||
|
||||
impl LocalRecord for SentAuthorizationRequestRecord {
|
||||
|
||||
const TABLE_NAME: &str = "sent_authorization_request";
|
||||
const SELECT_COLUMNS: &[&str] = &[
|
||||
"id",
|
||||
"authorization_request_id",
|
||||
"passcode",
|
||||
];
|
||||
const INSERT_COLUMNS: &[&str] = &[
|
||||
"authorization_request_id",
|
||||
"passcode"
|
||||
];
|
||||
|
||||
type InsertParams<'a> = (&'a u32, &'a str);
|
||||
|
||||
fn from_row(row: &rusqlite::Row<'_>) -> Result<Self, rusqlite::Error> {
|
||||
Ok(Self{
|
||||
id: row.get(0)?,
|
||||
authorization_request_id: row.get(0)?,
|
||||
passcode: row.get(2)?
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -6,11 +6,8 @@ use tracing::{event, Level};
|
|||
pub fn migrate(con: &mut Connection) -> Result<(), Error>{
|
||||
let version: u32 = con.pragma_query_value(None,"user_version", |row| row.get(0)).expect("Failed to get user_version");
|
||||
if version < 1 {
|
||||
let tx = con.transaction()?;
|
||||
event!(Level::INFO, "Migrate local db to version 1");
|
||||
v1::migrate(&tx)?;
|
||||
tx.pragma_update(None, "user_version", 1)?;
|
||||
tx.commit()?;
|
||||
v1::migrate(con)?;
|
||||
event!(Level::INFO, "Migration done.");
|
||||
}
|
||||
Ok(())
|
||||
|
|
|
|||
|
|
@ -1,45 +1,27 @@
|
|||
use rusqlite::{Connection, Error, Transaction};
|
||||
use rusqlite::{Error, Connection};
|
||||
|
||||
pub fn migrate(tx: &Transaction) -> Result<(), Error>{
|
||||
pub fn migrate(con: &mut Connection) -> Result<(), Error>{
|
||||
let tx = con.transaction()?;
|
||||
tx.execute_batch(
|
||||
"CREATE TABLE remote_node (
|
||||
id INTEGER PRIMARY KEY,
|
||||
public_id INTEGER NOT NULL UNIQUE,
|
||||
public_key BLOB UNIQUE NOT NULL
|
||||
);
|
||||
CREATE TABLE authorization_request (
|
||||
id INTEGER PRIMARY KEY,
|
||||
uuid BLOB NOT NULL UNIQUE,
|
||||
public_id INTEGER NOT NULL UNIQUE,
|
||||
remote_node_id INTEGER NOT NULL UNIQUE,
|
||||
created_at TEXT NOT NULL,
|
||||
closed_at TEXT,
|
||||
FOREIGN KEY(remote_node_id) REFERENCES remote_node(id)
|
||||
);
|
||||
CREATE TABLE received_authorization_request (
|
||||
"BEGIN;
|
||||
CREATE TABLE authorized_peer (
|
||||
id INTEGER PRIMARY KEY,
|
||||
authorization_request_id INTEGER NOT NULL UNIQUE,
|
||||
node_note TEXT,
|
||||
FOREIGN KEY(authorization_request_id) REFERENCES authorization_request(id)
|
||||
);
|
||||
CREATE TABLE sent_authorization_request (
|
||||
id INTEGER PRIMARY KEY,
|
||||
authorization_request_id INTEGER NOT NULL UNIQUE,
|
||||
passcode TEXT NOT NULL,
|
||||
FOREIGN KEY(authorization_request_id) REFERENCES authorization_request(id)
|
||||
);
|
||||
CREATE TABLE authorized_remote_node (
|
||||
id INTEGER PRIMARY KEY,
|
||||
uuid BLOB UNIQUE NOT NULL,
|
||||
public_id INTEGER NOT NULL UNIQUE,
|
||||
public_key BLOB NOT NULL UNIQUE,
|
||||
note TEXT NOT NULL,
|
||||
node_id BLOB NOT NULL UNIQUE,
|
||||
last_synced_at TEXT,
|
||||
last_sent_version_vector BLOB,
|
||||
last_sent_version_vector BLOB
|
||||
created_at TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL
|
||||
);",
|
||||
updated_at TEXT NOT NULL,
|
||||
);
|
||||
CREATE TABLE authorization (
|
||||
id INTEGER PRIMARY KEY,
|
||||
node_id BLOB UNIQUE NOT NULL,
|
||||
passcode TEXT NOT NULL,
|
||||
created_at TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL,
|
||||
);
|
||||
COMMIT;",
|
||||
)?;
|
||||
|
||||
tx.pragma_update(None, "user_version", 1)?;
|
||||
tx.commit()?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -1,121 +1,27 @@
|
|||
// mod authorization_request;
|
||||
mod remote_node;
|
||||
pub use remote_node::RemoteNodeRecord;
|
||||
mod authorization;
|
||||
pub mod migration;
|
||||
|
||||
use std::{cell::OnceCell, convert::Infallible, iter::Map, path::Path, sync::{LazyLock, OnceLock}};
|
||||
use std::{cell::OnceCell, iter::Map, path::Path, sync::{LazyLock, OnceLock}};
|
||||
|
||||
use migration::migrate;
|
||||
use rusqlite::{ffi::Error, params, types::FromSql, Connection, MappedRows, OptionalExtension, Params, Row, ToSql};
|
||||
use rusqlite::{ffi::Error, Connection, MappedRows, Row};
|
||||
|
||||
use crate::{config::StorageConfig, global::{CONFIG, LOCAL_DATABASE_CONNECTION}};
|
||||
|
||||
// pub use authorization_request::*;
|
||||
pub type LocalRecordError = rusqlite::Error;
|
||||
pub use authorization::*;
|
||||
|
||||
|
||||
/// a struct of id for local database record.
|
||||
pub type LocalRecordId = u32;
|
||||
|
||||
/// a struct for the record without id before inserted
|
||||
pub type NoLocalRecordId = rusqlite::types::Null;
|
||||
|
||||
|
||||
|
||||
/// A id struct
|
||||
/// Model trait for local database data.
|
||||
/// use LOCAL_DATABASE_CONNECTION for database connection.
|
||||
pub trait LocalRecord: Sized {
|
||||
const TABLE_NAME: &str;
|
||||
const COLUMNS: &[&str];
|
||||
|
||||
/// Tuple form of the record.
|
||||
/// the order of field must be same as COLUMNS.
|
||||
type RowValues;
|
||||
}
|
||||
pub trait SelectableLocalRecord: LocalRecord<RowValues: TryInto<Self>> {
|
||||
|
||||
const SELECT_STATEMENT: LazyLock<String> = LazyLock::new(|| {
|
||||
String::from("SELECT ") + &Self::COLUMNS.join(", ") + " FROM " + Self::TABLE_NAME
|
||||
});
|
||||
|
||||
const SELECT_PLACEHOLDER: LazyLock<String> = LazyLock::new(|| {
|
||||
let mut result : Vec<String> = Vec::new();
|
||||
for i in 0..Self::COLUMNS.len() {
|
||||
result.push(String::from("?") + &(i+1).to_string());
|
||||
}
|
||||
result.join(", ")
|
||||
});
|
||||
|
||||
|
||||
|
||||
fn get_one_where<P>(where_statement: &str, params: P) -> Result<Self, rusqlite::Error>
|
||||
where P: Params
|
||||
{
|
||||
let connection = LOCAL_DATABASE_CONNECTION.get_unchecked();
|
||||
Ok(connection.query_row(
|
||||
&(String::new() + &Self::SELECT_STATEMENT + " " + where_statement),
|
||||
params,
|
||||
Self::from_row
|
||||
)?)
|
||||
}
|
||||
|
||||
fn get_one_by_field<T>(field_name: &str, field_value: T) -> Result<Self, rusqlite::Error>
|
||||
where
|
||||
T: ToSql
|
||||
{
|
||||
let connection = LOCAL_DATABASE_CONNECTION.get_unchecked();
|
||||
Ok(connection.query_row(
|
||||
&([&Self::SELECT_STATEMENT, "FROM", Self::TABLE_NAME, "WHERE", field_name,"= ?1"].join(" ")),
|
||||
params![field_value],
|
||||
Self::from_row
|
||||
)?)
|
||||
}
|
||||
fn get_one_by_id(id: u32) -> Result<Self, rusqlite::Error> {
|
||||
Self::get_one_by_field("id", id )
|
||||
}
|
||||
pub trait RusqliteRecord: Sized {
|
||||
fn insert(&self, connection: &Connection) -> Result<(), rusqlite::Error>;
|
||||
fn from_row(row: &Row<'_>) -> Result<Self, rusqlite::Error>;
|
||||
fn get_all() -> Result<Vec<Self>, rusqlite::Error> {
|
||||
let connection = LOCAL_DATABASE_CONNECTION.get_unchecked();
|
||||
let mut stmt = connection.prepare(&("SELECT ".to_string() + &Self::COLUMNS.join(", ") + " FROM " + Self::TABLE_NAME))?;
|
||||
let rows = stmt.query_map(
|
||||
[],
|
||||
Self::from_row
|
||||
)?;
|
||||
let mut result= Vec::new();
|
||||
for row in rows {
|
||||
result.push(row?);
|
||||
}
|
||||
Ok(result)
|
||||
|
||||
}
|
||||
fn get_all(connection: &Connection) -> Result<Vec<Self>, rusqlite::Error>;
|
||||
}
|
||||
|
||||
pub trait InsertableLocalRecord: LocalRecord<RowValues: From<Self> + Params> {
|
||||
type LocalRecord: Sized + SelectableLocalRecord;
|
||||
|
||||
/// Place holder for insertion.
|
||||
/// Generated from Columns
|
||||
const INSERT_PLACEHOLDER: LazyLock<String> = LazyLock::new(|| {
|
||||
let mut result : Vec<String> = Vec::new();
|
||||
for i in 0..Self::COLUMNS.len() {
|
||||
result.push(String::from("?") + &(i+1).to_string());
|
||||
}
|
||||
result.join(", ")
|
||||
});
|
||||
/// Insert and get the inserted record.
|
||||
fn insert(self) -> Result<Self::LocalRecord, rusqlite::Error>{
|
||||
let params= Self::RowValues::from(self);
|
||||
pub trait LocalRecord : RusqliteRecord{
|
||||
fn insert_global(&self) -> Result<(), rusqlite::Error> {
|
||||
self.insert(&LOCAL_DATABASE_CONNECTION.get_unchecked())
|
||||
}
|
||||
fn get_all_global() -> Result<Vec<Self>, rusqlite::Error> {
|
||||
let connection = LOCAL_DATABASE_CONNECTION.get_unchecked();
|
||||
|
||||
Ok(connection.query_row(
|
||||
&[
|
||||
"INSERT INTO ", Self::TABLE_NAME, "(" , &Self::COLUMNS.join(", "), ")",
|
||||
"VALUES (" , &*Self::INSERT_PLACEHOLDER , ")",
|
||||
"RETURNING", &Self::COLUMNS.join(", ")
|
||||
].join(" "),
|
||||
params,
|
||||
Self::LocalRecord::from_row
|
||||
)?)
|
||||
Self::get_all(&connection)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,120 +0,0 @@
|
|||
//! Structs about cached remote_node.
|
||||
|
||||
use std::os::unix::raw::time_t;
|
||||
|
||||
use tripod_id::Double;
|
||||
use chrono::{DateTime, Local, NaiveDateTime};
|
||||
use iroh::{NodeId, PublicKey};
|
||||
use rusqlite::{params, types::{FromSqlError, Null}, Connection};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{data::local::{self, InsertableLocalRecord, LocalRecord, LocalRecordId, NoLocalRecordId, SelectableLocalRecord}, global::LOCAL_DATABASE_CONNECTION};
|
||||
|
||||
/// RemoteNode information cached in local database.
|
||||
///
|
||||
/// - Currently this only contain local uid and public key (=node id) of iroh.
|
||||
/// - This is a junction table enable to use caretta-id to specify items in the UI, especially on the CLI.
|
||||
/// - Actual remote_node information is managed by iroh endpoint and not contained in this model.
|
||||
/// - Once a remote_node is authorized, it is assigned a global (=synced) ID as authorized_remote_node so essentially this local id targets unauthorized remote_nodes.
|
||||
///
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct RemoteNodeRecord<T> {
|
||||
|
||||
/// serial primary key.
|
||||
pub id: T,
|
||||
|
||||
/// public tripod id of remote_node.
|
||||
/// this id is use only the node itself and not synced so another node has different local_remote_node_id even if its public_key is same.
|
||||
pub public_id: Double,
|
||||
|
||||
/// Iroh public key
|
||||
pub public_key: PublicKey,
|
||||
}
|
||||
|
||||
impl RemoteNodeRecord<LocalRecordId> {
|
||||
pub fn get_or_insert_by_public_key(public_key: &PublicKey) -> Result<Self, rusqlite::Error> {
|
||||
match Self::get_by_public_key(public_key) {
|
||||
Ok(x) => Ok(x),
|
||||
Err(rusqlite::Error::QueryReturnedNoRows) => {
|
||||
let new = RemoteNodeRecord{
|
||||
id: NoLocalRecordId{},
|
||||
public_id: rand::random(),
|
||||
public_key: public_key.clone()
|
||||
};
|
||||
Ok(new.insert()?)
|
||||
},
|
||||
Err(e) => Err(e)
|
||||
}
|
||||
|
||||
}
|
||||
pub fn get_by_public_id(public_id: &Double) -> Result<Self, rusqlite::Error> {
|
||||
Self::get_one_where("WHERE public_id = ?1", (public_id,))
|
||||
}
|
||||
pub fn get_by_public_key(public_key: &PublicKey) -> Result<Self, rusqlite::Error> {
|
||||
Self::get_one_where("WHERE public_Key = ?1", (public_key.as_bytes(),))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> LocalRecord for RemoteNodeRecord<T> {
|
||||
const TABLE_NAME: &str = "remote_node";
|
||||
const COLUMNS: &[&str] = &[
|
||||
"id",
|
||||
"public_id",
|
||||
"public_key"
|
||||
];
|
||||
|
||||
type RowValues = (T, Double, [u8;32]);
|
||||
}
|
||||
|
||||
impl SelectableLocalRecord for RemoteNodeRecord<LocalRecordId> {
|
||||
fn from_row(row: &rusqlite::Row<'_>) -> Result<Self, rusqlite::Error> {
|
||||
Ok(Self {
|
||||
id: row.get(0)?,
|
||||
public_id: row.get(1)?,
|
||||
public_key: PublicKey::from_bytes(&row.get(2)?).map_err(|e| FromSqlError::Other(Box::new(e)))?
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<(LocalRecordId, Double, [u8;32])> for RemoteNodeRecord<LocalRecordId> {
|
||||
type Error = rusqlite::Error;
|
||||
fn try_from(value: (LocalRecordId, Double, [u8;32])) -> Result<Self, Self::Error> {
|
||||
Ok(Self {
|
||||
id: value.0,
|
||||
public_id: value.1,
|
||||
public_key: PublicKey::from_bytes(&value.2).map_err(|x| FromSqlError::Other(Box::new(x)))?
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl InsertableLocalRecord for RemoteNodeRecord<NoLocalRecordId> {
|
||||
type LocalRecord = RemoteNodeRecord<LocalRecordId>;
|
||||
|
||||
}
|
||||
|
||||
impl From<RemoteNodeRecord<NoLocalRecordId>> for (NoLocalRecordId, Double, [u8;32]){
|
||||
fn from(value: RemoteNodeRecord<NoLocalRecordId>) -> Self {
|
||||
(value.id, value.public_id, value.public_key.as_bytes().to_owned())
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use iroh::SecretKey;
|
||||
|
||||
use crate::tests::TEST_CONFIG;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn insert_get_remote_node_record() {
|
||||
LOCAL_DATABASE_CONNECTION.get_or_init(&TEST_CONFIG.storage.get_local_database_path());
|
||||
let key = SecretKey::generate(&mut rand::rngs::OsRng);
|
||||
let pubkey = key.public();
|
||||
let record = RemoteNodeRecord::get_or_insert_by_public_key(&pubkey).unwrap();
|
||||
assert_eq!(record, RemoteNodeRecord::get_by_public_id(&record.public_id).unwrap());
|
||||
assert_eq!(record, RemoteNodeRecord::get_by_public_key(&record.public_key).unwrap());
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,4 @@
|
|||
use std::{array::TryFromSliceError, ffi::OsString};
|
||||
use tonic::Status;
|
||||
|
||||
use crate::proto::ProtoDeserializeError;
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum Error {
|
||||
|
|
@ -34,30 +31,10 @@ pub enum Error {
|
|||
TomlDe(#[from] toml::de::Error),
|
||||
#[error("toml serialization error: {0}")]
|
||||
TomlSer(#[from] toml::ser::Error),
|
||||
#[error("protobuf serialization error: {0}")]
|
||||
ProtoSerialize(#[from] crate::proto::ProtoSerializeError),
|
||||
#[error("protobuf deserialization error: {0}")]
|
||||
ProtoDeserialize(#[from] crate::proto::ProtoDeserializeError),
|
||||
#[error("Local record error: {0}")]
|
||||
LocalRecord(#[from] crate::data::local::LocalRecordError),
|
||||
#[error("Tripod id error: {0}")]
|
||||
TripodId(#[from] tripod_id::Error),
|
||||
}
|
||||
|
||||
impl From<std::ffi::OsString> for Error {
|
||||
fn from(s: OsString) -> Error {
|
||||
Self::OsStringConvert(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Error> for Status {
|
||||
fn from(value: Error) -> Self {
|
||||
match value {
|
||||
Error::ProtoDeserialize(x) => { match x {
|
||||
ProtoDeserializeError::MissingField(x) => Self::invalid_argument(format!("{} is required", x)),
|
||||
_ => Status::unimplemented("Unimplemented protobuf deserialize error status")
|
||||
}},
|
||||
_ => Status::unimplemented("Unimplemented error status")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
use std::pin::Pin;
|
||||
|
||||
use futures::Stream;
|
||||
use tonic::{Request, Response, Streaming};
|
||||
|
||||
tonic::include_proto!("caretta_sync.authorization_request");
|
||||
pub struct AuthorizationRequestService {}
|
||||
|
||||
#[tonic::async_trait]
|
||||
impl authorization_request_server::AuthorizationRequest for AuthorizationRequestService {
|
||||
type ListStream = Pin<Box<dyn Stream<Item = Result<ListResponse, tonic::Status>> + Send>>;
|
||||
async fn send(&self, request: Request<SendRequest>) -> Result<Response<SendResponse>, tonic::Status> {
|
||||
todo!()
|
||||
}
|
||||
async fn accept(&self, request: Request<AcceptRequest>) -> Result<Response<AcceptResponse>, tonic::Status>{
|
||||
todo!()
|
||||
}
|
||||
async fn reject(&self, request: Request<RejectRequest>) -> Result<Response<RejectResponse>, tonic::Status>{
|
||||
todo!()
|
||||
}
|
||||
async fn info(&self, request: Request<InfoRequest>) -> Result<Response<InfoResponse>, tonic::Status>{
|
||||
todo!()
|
||||
}
|
||||
async fn list(&self, request: Request<Streaming<ListRequest>>) -> Result<Response<Self::ListStream>, tonic::Status> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
use std::pin::Pin;
|
||||
|
||||
use futures::Stream;
|
||||
use tonic::{Request, Response, Streaming, Status};
|
||||
|
||||
tonic::include_proto!("caretta_sync.authorized_node");
|
||||
pub struct AuthorizedNodeService {}
|
||||
|
||||
#[tonic::async_trait]
|
||||
impl authorized_node_server::AuthorizedNode for AuthorizedNodeService {
|
||||
type ListStream = Pin<Box<dyn Stream<Item = Result<ListResponse, tonic::Status>> + Send>>;
|
||||
|
||||
async fn info(&self, request: Request<InfoRequest>) -> Result<Response<InfoResponse>, tonic::Status>{
|
||||
todo!()
|
||||
}
|
||||
async fn list(&self, request: Request<Streaming<ListRequest>>) -> Result<Response<Self::ListStream>, tonic::Status> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,54 +0,0 @@
|
|||
use super::*;
|
||||
tonic::include_proto!("caretta_sync.common");
|
||||
|
||||
use crate::proto::{error::{ProtoDeserializeError, ProtoSerializeError}};
|
||||
|
||||
impl From<uuid::Uuid> for Uuid {
|
||||
fn from(value: uuid::Uuid) -> Self {
|
||||
let (first_half, second_half) = value.as_u64_pair();
|
||||
Self {
|
||||
high_bits: first_half,
|
||||
low_bits: second_half
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Uuid> for uuid::Uuid {
|
||||
fn from(value: Uuid) -> Self {
|
||||
uuid::Uuid::from_u64_pair(value.high_bits, value.low_bits)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
impl From<url::Url> for Url {
|
||||
fn from(value: url::Url) -> Self {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Url> for url::Url {
|
||||
type Error = ProtoDeserializeError;
|
||||
fn try_from(value: Url) -> Result<Self, Self::Error> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::{net::{self, Ipv4Addr}, u16};
|
||||
|
||||
use super::*;
|
||||
fn validate_uuid_conversion(uuid: uuid::Uuid) -> bool{
|
||||
let message = Uuid::from(uuid);
|
||||
uuid == uuid::Uuid::from(message)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn uuid_conversion() {
|
||||
assert!(validate_uuid_conversion(uuid::Uuid::nil()));
|
||||
assert!(validate_uuid_conversion(uuid::Uuid::max()));
|
||||
assert!(validate_uuid_conversion(uuid::Uuid::now_v7()));
|
||||
}
|
||||
|
||||
}
|
||||
17
core/src/proto/convert/node_id_message.rs
Normal file
17
core/src/proto/convert/node_id_message.rs
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
use iroh::NodeId;
|
||||
|
||||
use crate::proto::{error::{ProtoDeserializeError, ProtoSerializeError}, NodeIdMessage};
|
||||
|
||||
impl From<NodeId> for NodeIdMessage {
|
||||
fn from(value: NodeId) -> Self {
|
||||
NodeIdMessage { node_id: Vec::from(value.as_bytes()) }
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<NodeIdMessage> for NodeId {
|
||||
type Error = ProtoDeserializeError;
|
||||
fn try_from(value: NodeIdMessage) -> Result<Self, Self::Error> {
|
||||
let slice: [u8; 32] = value.node_id[0..32].try_into()?;
|
||||
Ok(NodeId::from_bytes(&slice)?)
|
||||
}
|
||||
}
|
||||
7
core/src/proto/convert/remote_info_iter_request.rs
Normal file
7
core/src/proto/convert/remote_info_iter_request.rs
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
use crate::proto::RemoteInfoIterRequest;
|
||||
|
||||
impl RemoteInfoIterRequest {
|
||||
pub fn new() -> Self {
|
||||
Self{}
|
||||
}
|
||||
}
|
||||
11
core/src/proto/convert/remote_info_request.rs
Normal file
11
core/src/proto/convert/remote_info_request.rs
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
use iroh::NodeId;
|
||||
|
||||
use crate::proto::{error::ProtoDeserializeError, NodeIdMessage, RemoteInfoRequest};
|
||||
|
||||
impl From<NodeIdMessage> for RemoteInfoRequest {
|
||||
fn from(value: NodeIdMessage) -> Self {
|
||||
Self {
|
||||
node_id : Some(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
16
core/src/proto/convert/remote_info_response.rs
Normal file
16
core/src/proto/convert/remote_info_response.rs
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
use crate::{ proto::{RemoteInfoMessage, RemoteInfoResponse}};
|
||||
|
||||
impl From<RemoteInfoMessage> for RemoteInfoResponse {
|
||||
fn from(value: RemoteInfoMessage) -> Self {
|
||||
Self {
|
||||
remote_info: Some(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
impl From<Option<RemoteInfoMessage>> for RemoteInfoResponse {
|
||||
fn from(value: Option<RemoteInfoMessage>) -> Self {
|
||||
Self{
|
||||
remote_info: value,
|
||||
}
|
||||
}
|
||||
}
|
||||
16
core/src/proto/convert/source_message.rs
Normal file
16
core/src/proto/convert/source_message.rs
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
use std::time::Duration;
|
||||
|
||||
use iroh::endpoint::Source;
|
||||
|
||||
use crate::{error::Error, proto::{error::ProtoSerializeError, SourceMessage}};
|
||||
|
||||
impl TryFrom<(Source, Duration)> for SourceMessage {
|
||||
type Error = ProtoSerializeError;
|
||||
fn try_from(src: (Source, Duration)) -> Result<Self, Self::Error> {
|
||||
let (source, duration )= src;
|
||||
Ok(Self {
|
||||
source: source.to_string(),
|
||||
duration: Some(duration.try_into()?),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -12,6 +12,4 @@ pub enum ProtoDeserializeError {
|
|||
Signature(#[from] ed25519_dalek::SignatureError),
|
||||
#[error("slice parse error: {0}")]
|
||||
SliceTryFrom(#[from] std::array::TryFromSliceError),
|
||||
#[error("Int parse error: {0}")]
|
||||
IntTryFrom(#[from] std::num::TryFromIntError),
|
||||
}
|
||||
|
|
@ -1,120 +0,0 @@
|
|||
use std::pin::Pin;
|
||||
|
||||
use futures::Stream;
|
||||
use tonic::{async_trait, Request, Response, Status};
|
||||
|
||||
use crate::proto::{net::SocketAddr, remote_node_server, ProtoDeserializeError, ProtoSerializeError, };
|
||||
|
||||
|
||||
tonic::include_proto!("caretta_sync.iroh");
|
||||
|
||||
impl From<iroh::endpoint::ConnectionType> for ConnectionType {
|
||||
fn from(value: iroh::endpoint::ConnectionType) -> Self {
|
||||
use connection_type::*;
|
||||
Self {
|
||||
connection_type_value: Some(match value {
|
||||
iroh::endpoint::ConnectionType::Direct(socket_addr) => {
|
||||
connection_type::ConnectionTypeValue::Direct(connection_type::Direct{direct_value: Some(SocketAddr::from(socket_addr))})
|
||||
},
|
||||
iroh::endpoint::ConnectionType::Relay(relay_url) => {
|
||||
connection_type::ConnectionTypeValue::Relay(connection_type::Relay { relay_value: Some(super::common::Url::from((*relay_url).clone()))})
|
||||
},
|
||||
iroh::endpoint::ConnectionType::Mixed(socket_addr, relay_url) => {
|
||||
connection_type::ConnectionTypeValue::Mixed(connection_type::Mixed { socket_addr: Some(SocketAddr::from(socket_addr)), relay_url: Some(super::common::Url::from((*relay_url).clone()))})
|
||||
},
|
||||
iroh::endpoint::ConnectionType::None => {
|
||||
ConnectionTypeValue::None(None{})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<iroh::endpoint::ControlMsg> for ControlMsg {
|
||||
fn from(value: iroh::endpoint::ControlMsg) -> Self {
|
||||
use control_msg::*;
|
||||
Self { control_msg_vaue: Some(match value {
|
||||
iroh::endpoint::ControlMsg::Ping => ControlMsgVaue::Ping(Ping{}),
|
||||
iroh::endpoint::ControlMsg::Pong => ControlMsgVaue::Pong(Pong {}),
|
||||
iroh::endpoint::ControlMsg::CallMeMaybe => ControlMsgVaue::CallMeMaybe(CallMeMayBe { }),
|
||||
}) }
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<iroh::endpoint::DirectAddrInfo> for DirectAddrInfo {
|
||||
type Error = ProtoSerializeError;
|
||||
fn try_from(value: iroh::endpoint::DirectAddrInfo) -> Result<Self, Self::Error> {
|
||||
use direct_addr_info::*;
|
||||
let last_control: Option<DurationControlMsg> = if let Some((duration, control_msg)) = value.last_control {
|
||||
Some(DurationControlMsg{
|
||||
control_msg: Some(control_msg.into()),
|
||||
duration: Some(duration.try_into()?)
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Ok(Self {
|
||||
addr: Some(value.addr.into()),
|
||||
latency: value.latency.map(|x| x.try_into()).transpose()?,
|
||||
last_control: last_control,
|
||||
last_payload: value.last_payload.map(|x| x.try_into()).transpose()?,
|
||||
last_alive: value.last_alive.map(|x| x.try_into()).transpose()?,
|
||||
sources: value.sources.into_iter().map(|(s, d)| {
|
||||
Ok::<SourceDuration, ProtoSerializeError>(SourceDuration{
|
||||
source: Some(s.into()),
|
||||
duration: Some(d.try_into()?)
|
||||
})
|
||||
}).collect::<Result<Vec<SourceDuration>, ProtoSerializeError>>()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<iroh::PublicKey> for PublicKey {
|
||||
fn from(value: iroh::PublicKey) -> Self {
|
||||
Self{ key: Vec::from(value.as_bytes()) }
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<PublicKey> for iroh::PublicKey {
|
||||
type Error = ProtoDeserializeError;
|
||||
fn try_from(value: PublicKey) -> Result<Self, Self::Error> {
|
||||
let slice: [u8; 32] = value.key[0..32].try_into()?;
|
||||
Ok(iroh::PublicKey::from_bytes(&slice)?)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<iroh::endpoint::RemoteInfo> for RemoteInfo {
|
||||
type Error = ProtoSerializeError;
|
||||
fn try_from(value: iroh::endpoint::RemoteInfo) -> Result<Self, Self::Error> {
|
||||
Ok(Self {
|
||||
node_id: Some(value.node_id.into()),
|
||||
relay_url: value.relay_url.map(|x| {
|
||||
Ok::<RelayUrlInfo, ProtoSerializeError>(RelayUrlInfo {
|
||||
relay_url: Some((*x.relay_url).clone().into()),
|
||||
last_alive: x.last_alive.map(|x| x.try_into()).transpose()?,
|
||||
latency: x.latency.map(|x| x.try_into()).transpose()?
|
||||
})}).transpose()?,
|
||||
addrs: value.addrs.into_iter().map(|x| {
|
||||
x.try_into()
|
||||
}).collect::<Result<Vec<DirectAddrInfo>, ProtoSerializeError>>()?,
|
||||
conn_type: Some(value.conn_type.into()),
|
||||
latency: value.latency.map(|x| x.try_into()).transpose()?,
|
||||
last_used:value.last_used.map(|x| x.try_into()).transpose()?
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<iroh::endpoint::Source> for Source {
|
||||
fn from(value: iroh::endpoint::Source) -> Self {
|
||||
use source::*;
|
||||
Self {
|
||||
source_value:Some(match value {
|
||||
iroh::endpoint::Source::Saved => SourceValue::Saved(Saved { }),
|
||||
iroh::endpoint::Source::Udp => SourceValue::Udp(Udp { }),
|
||||
iroh::endpoint::Source::Relay => SourceValue::Relay(Relay { }),
|
||||
iroh::endpoint::Source::App => SourceValue::App(App{}),
|
||||
iroh::endpoint::Source::Discovery { name } => SourceValue::Discovery(Discovery { value: name }),
|
||||
iroh::endpoint::Source::NamedApp { name } => SourceValue::NamedApp(NamedApp { value: name }),
|
||||
}) }
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +1,5 @@
|
|||
mod authorization_request;
|
||||
mod authorized_node;
|
||||
mod remote_node;
|
||||
mod common;
|
||||
mod convert;
|
||||
mod error;
|
||||
mod iroh;
|
||||
mod net;
|
||||
mod server;
|
||||
|
||||
|
||||
pub use common::*;
|
||||
pub use error::*;
|
||||
pub use remote_node::*;
|
||||
tonic::include_proto!("caretta_sync");
|
||||
|
|
@ -1,140 +0,0 @@
|
|||
tonic::include_proto!("caretta_sync.net");
|
||||
|
||||
use crate::proto::{error::{ProtoDeserializeError, ProtoSerializeError}};
|
||||
|
||||
type Ipv4AddrMessage = Ipv4Addr;
|
||||
type Ipv6AddrMessage = Ipv6Addr;
|
||||
type SocketAddrMessage = SocketAddr;
|
||||
type SocketAddrV4Message = SocketAddrV4;
|
||||
type SocketAddrV6Message = SocketAddrV6;
|
||||
|
||||
impl From<std::net::SocketAddr> for SocketAddrMessage {
|
||||
fn from(value: std::net::SocketAddr) -> Self {
|
||||
Self{
|
||||
socket_addr_value: Some(match value {
|
||||
std::net::SocketAddr::V4(x) => socket_addr::SocketAddrValue::V4(SocketAddrV4Message::from(x)),
|
||||
std::net::SocketAddr::V6(x) => socket_addr::SocketAddrValue::V6(SocketAddrV6Message::from(x)),
|
||||
})}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<SocketAddrMessage> for std::net::SocketAddr {
|
||||
type Error = ProtoDeserializeError;
|
||||
fn try_from(value: SocketAddrMessage) -> Result<Self, Self::Error> {
|
||||
Ok(match value.socket_addr_value.ok_or(Self::Error::MissingField("SocketAddr.socket_addr"))? {
|
||||
socket_addr::SocketAddrValue::V4(x) => std::net::SocketAddr::V4(x.try_into()?),
|
||||
socket_addr::SocketAddrValue::V6(x) => std::net::SocketAddr::V6(x.try_into()?),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::net::SocketAddrV4> for SocketAddrV4Message {
|
||||
fn from(value: std::net::SocketAddrV4) -> Self {
|
||||
Self {
|
||||
ip : Some(value.ip().clone().into()),
|
||||
port: value.port().into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<SocketAddrV4Message> for std::net::SocketAddrV4 {
|
||||
type Error = ProtoDeserializeError;
|
||||
fn try_from(value: SocketAddrV4Message) -> Result<Self, Self::Error> {
|
||||
Ok(Self::new(value.ip.ok_or(ProtoDeserializeError::MissingField("SocketAddrV4.ip"))?.into(), value.port.try_into()?))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::net::Ipv4Addr> for Ipv4AddrMessage {
|
||||
fn from(value: std::net::Ipv4Addr) -> Self {
|
||||
Self{
|
||||
bits: value.to_bits()
|
||||
}
|
||||
}
|
||||
}
|
||||
impl From<Ipv4AddrMessage> for std::net::Ipv4Addr {
|
||||
fn from(value: Ipv4AddrMessage) -> Self{
|
||||
Self::from_bits(value.bits)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::net::SocketAddrV6> for SocketAddrV6Message {
|
||||
fn from(value: std::net::SocketAddrV6) -> Self {
|
||||
Self{
|
||||
ip: Some(value.ip().clone().into()),
|
||||
port: value.port().into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<SocketAddrV6Message> for std::net::SocketAddrV6 {
|
||||
type Error = ProtoDeserializeError;
|
||||
fn try_from(value: SocketAddrV6Message) -> Result<Self, Self::Error> {
|
||||
Ok(Self::new(
|
||||
value.ip.ok_or(ProtoDeserializeError::MissingField("SocketAddrV6.ip"))?.into(),
|
||||
value.port.try_into()?,
|
||||
0,
|
||||
0
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::net::Ipv6Addr> for Ipv6AddrMessage {
|
||||
fn from(value: std::net::Ipv6Addr) -> Self {
|
||||
let bits = value.to_bits();
|
||||
|
||||
Self{
|
||||
high_bits: (bits >> 64) as u64,
|
||||
low_bits: bits as u64,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl From<Ipv6AddrMessage> for std::net::Ipv6Addr{
|
||||
|
||||
fn from(value: Ipv6AddrMessage) -> Self {
|
||||
Self::from_bits(
|
||||
((value.high_bits as u128) << 64) + (value.low_bits as u128)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::{net::{self, Ipv4Addr}, u16, u8};
|
||||
|
||||
use rand::random;
|
||||
|
||||
use super::*;
|
||||
|
||||
fn validate_socket_addr_conversion(socket_addr: net::SocketAddr) -> Result<bool, ProtoDeserializeError> {
|
||||
let message = SocketAddrMessage::from(socket_addr);
|
||||
Ok(socket_addr == message.try_into()?)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn socket_addr_conversion_ipv4_min() {
|
||||
assert!(validate_socket_addr_conversion(net::SocketAddr::new(net::IpAddr::V4(net::Ipv4Addr::new(0, 0, 0, 0)),u16::MIN)).unwrap());
|
||||
}
|
||||
#[test]
|
||||
fn socket_addr_conversion_ipv4_max() {
|
||||
assert!(validate_socket_addr_conversion(net::SocketAddr::new(net::IpAddr::V4(net::Ipv4Addr::new(u8::MAX, u8::MAX, u8::MAX, u8::MAX)),u16::MAX)).unwrap());
|
||||
}
|
||||
#[test]
|
||||
fn socket_addr_conversion_ipv4_random() {
|
||||
for _ in 0..10 {
|
||||
assert!(validate_socket_addr_conversion(net::SocketAddr::new(net::IpAddr::V4(
|
||||
net::Ipv4Addr::new(random(), random(), random(), random())
|
||||
),
|
||||
random()
|
||||
)).unwrap())
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
fn socket_addr_conversion_ipv6_min() {
|
||||
assert!(validate_socket_addr_conversion(net::SocketAddr::new(net::IpAddr::V6(net::Ipv6Addr::new(0,0,0,0,0,0,0,0)), u16::MIN)).unwrap());
|
||||
}
|
||||
#[test]
|
||||
fn socket_addr_conversion_ipv6_max() {
|
||||
assert!(validate_socket_addr_conversion(net::SocketAddr::new(net::IpAddr::V6(net::Ipv6Addr::new(u16::MAX, u16::MAX, u16::MAX, u16::MAX, u16::MAX, u16::MAX, u16::MAX, u16::MAX)), u16::MAX)).unwrap());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
use std::{pin::Pin, time::Duration};
|
||||
|
||||
|
||||
use futures::{future::Remote, Stream};
|
||||
use iroh::{endpoint::{DirectAddrInfo, RemoteInfo}, PublicKey};
|
||||
use tonic::{Request, Response, Status, Streaming};
|
||||
use tripod_id::Double;
|
||||
|
||||
use crate::{data::local::{LocalRecordId, RemoteNodeRecord}, error::Error, global::IROH_ENDPOINT, proto::{error::{ProtoDeserializeError, ProtoSerializeError}}};
|
||||
|
||||
|
||||
tonic::include_proto!("caretta_sync.remote_node");
|
||||
|
||||
pub struct RemoteNodeServer{}
|
||||
|
||||
#[tonic::async_trait]
|
||||
impl remote_node_server::RemoteNode for RemoteNodeServer {
|
||||
type ListStream = Pin<Box<dyn Stream<Item = Result<ListResponse, Status>> + Send>>;
|
||||
async fn info(&self, request: Request<InfoRequest>) -> Result<Response<InfoResponse>, Status> {
|
||||
todo!()
|
||||
}
|
||||
async fn list(&self, request: Request<Streaming<ListRequest>>)
|
||||
-> Result<Response<Self::ListStream>, Status> {
|
||||
let iter = IROH_ENDPOINT.get_unchecked().remote_info_iter()
|
||||
.map(|x| {
|
||||
todo!();
|
||||
});
|
||||
let stream = futures::stream::iter(iter);
|
||||
Ok(Response::new(Box::pin(stream)))
|
||||
}
|
||||
}
|
||||
|
|
@ -4,7 +4,7 @@ use caretta_sync::{
|
|||
config::P2pConfig,
|
||||
proto::cached_peer_service_server::CachedPeerServiceServer,
|
||||
server::ServerTrait,
|
||||
rpc::service::iroh::CachedPeerService
|
||||
rpc::service::cached_peer::CachedPeerService
|
||||
};
|
||||
use libp2p::{futures::StreamExt, noise, swarm::SwarmEvent, tcp, yamux};
|
||||
use tokio::net::UnixListener;
|
||||
|
|
|
|||
|
|
@ -1,27 +0,0 @@
|
|||
[package]
|
||||
name = "tripod-id"
|
||||
edition.workspace = true
|
||||
version = "0.1.0-alpha"
|
||||
description.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[features]
|
||||
default=[]
|
||||
prost = ["dep:prost", "dep:prost-build"]
|
||||
rusqlite = ["dep:rusqlite"]
|
||||
serde = ["dep:serde"]
|
||||
|
||||
[dependencies]
|
||||
prost = { workspace = true, optional = true }
|
||||
rand.workspace = true
|
||||
rusqlite = {workspace = true, optional = true}
|
||||
serde = {workspace = true, optional = true}
|
||||
thiserror.workspace = true
|
||||
|
||||
|
||||
[build-dependencies]
|
||||
prost-build = {version = "0.14.1", optional = true}
|
||||
|
||||
[dev-dependencies]
|
||||
serde_test = "1.0.177"
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
# Tripod ID
|
||||
Distributable user-friendly id.
|
||||
|
||||
## 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.
|
||||
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
#[cfg(feature="prost")]
|
||||
prost_build::compile_protos(
|
||||
&[
|
||||
"proto/tripod_id/single.proto",
|
||||
"proto/tripod_id/double.proto",
|
||||
"proto/tripod_id/triple.proto"
|
||||
],
|
||||
&["proto/"]
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
syntax = "proto3";
|
||||
package tripod_id;
|
||||
|
||||
// Double size tripod id message
|
||||
message Double {
|
||||
uint32 id = 1;
|
||||
}
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
syntax = "proto3";
|
||||
package tripod_id;
|
||||
|
||||
// Single size tripod id message
|
||||
message Single {
|
||||
uint32 id = 1;
|
||||
}
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
syntax = "proto3";
|
||||
package tripod_id;
|
||||
|
||||
// Triple size tripod id message
|
||||
message Triple {
|
||||
uint64 id = 1;
|
||||
}
|
||||
|
|
@ -1,168 +0,0 @@
|
|||
use std::{fmt::Display, str::FromStr};
|
||||
|
||||
use rand::{distributions::Standard, prelude::Distribution, Rng};
|
||||
|
||||
#[cfg(feature="prost")]
|
||||
use crate::DoubleMessage;
|
||||
use crate::{utils::is_delimiter, Error, TripodId, Single};
|
||||
|
||||
#[derive(Copy, Clone, Debug, Hash, PartialEq)]
|
||||
pub struct Double(u32);
|
||||
|
||||
impl TripodId for Double{
|
||||
type Tuple = (Single, Single);
|
||||
type Integer = u32;
|
||||
#[cfg(feature="prost")]
|
||||
type Message = DoubleMessage;
|
||||
const CAPACITY: Self::Integer = (Single::CAPACITY as u32).pow(2);
|
||||
|
||||
const NIL: Self = Self(0);
|
||||
|
||||
const MAX: Self = Self(Self::CAPACITY -1);
|
||||
|
||||
#[cfg(test)]
|
||||
fn validate_inner(self) -> bool {
|
||||
self.0 < Self::CAPACITY
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Double {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let tuple: (Single, Single) = (*self).into();
|
||||
write!(f, "{}-{}", tuple.0, tuple.1)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(Single, Single)> for Double {
|
||||
fn from(value: (Single, Single)) -> Self {
|
||||
Self(u32::from(u16::from(value.0)) * u32::from(Single::CAPACITY) + u32::from(u16::from(value.1)))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Double> for (Single, Single) {
|
||||
fn from(value: Double) -> Self {
|
||||
(
|
||||
Single::try_from(u16::try_from(value.0/(Single::CAPACITY as u32)).unwrap()).unwrap(),
|
||||
Single::try_from(u16::try_from(value.0 % (Single::CAPACITY as u32)).unwrap()).unwrap()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Double {
|
||||
type Err = Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let tuple = match s.len() {
|
||||
7 => {
|
||||
let delimiter = s[3..4].chars().next().unwrap();
|
||||
if is_delimiter(delimiter) {
|
||||
Ok((Single::from_str(&s[0..3])?,Single::from_str(&s[4..7])?))
|
||||
} else {
|
||||
Err(Error::InvalidDelimiter{
|
||||
found: vec![delimiter],
|
||||
raw: s.to_string()
|
||||
})
|
||||
}
|
||||
|
||||
},
|
||||
6 => {
|
||||
Ok((Single::from_str(&s[0..3])?,Single::from_str(&s[3..6])?))
|
||||
},
|
||||
x => {
|
||||
Err(Error::InvalidLength{
|
||||
expected: (6, 7),
|
||||
found: x,
|
||||
raw: s.to_string()
|
||||
})
|
||||
}
|
||||
}?;
|
||||
Ok(Self::from(tuple))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl Distribution<Double> for Standard {
|
||||
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Double {
|
||||
Double(rng.gen_range(0..Double::CAPACITY))
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<u32> for Double {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(value: u32) -> Result<Self, Self::Error> {
|
||||
if value < Self::CAPACITY {
|
||||
Ok(Self(value))
|
||||
} else {
|
||||
Err(Error::OutsideOfRange{
|
||||
expected: Self::CAPACITY as u64,
|
||||
found: value as u64
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Double> for u32 {
|
||||
fn from(value: Double) -> Self {
|
||||
value.0
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl PartialEq<u32> for Double {
|
||||
fn eq(&self, other: &u32) -> bool {
|
||||
&u32::from(*self) == other
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<String> for Double {
|
||||
fn eq(&self, other: &String) -> bool {
|
||||
match Self::from_str(other) {
|
||||
Ok(x) => *self == x,
|
||||
Err(_) => false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use super::*;
|
||||
#[test]
|
||||
fn nil() {
|
||||
assert!(Double::NIL.validate_all().unwrap());
|
||||
assert_eq!(Double::NIL, 0);
|
||||
assert_eq!(Double::NIL, "000000".to_string());
|
||||
assert_eq!(Double::NIL, "000-000".to_string());
|
||||
assert!(Double::NIL.is_nil());
|
||||
assert!(!Double::NIL.is_max());
|
||||
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn max() {
|
||||
assert!(Double::MAX.validate_all().unwrap());
|
||||
assert_eq!(Double::MAX, Double::CAPACITY-1);
|
||||
assert_eq!(Double::MAX, "zzzzzz".to_string());
|
||||
assert_eq!(Double::MAX, "ZZZ-ZZZ".to_string());
|
||||
assert!(Double::MAX.is_max());
|
||||
assert!(!Double::MAX.is_nil());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn over_sized() {
|
||||
Double::try_from(Double::CAPACITY).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn random() {
|
||||
let mut rng = rand::thread_rng();
|
||||
for _ in 0..10 {
|
||||
let id: Double = rng.r#gen();
|
||||
assert!(id.validate_all().unwrap());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Error {
|
||||
#[error("expected under {expected}, found {found}")]
|
||||
OutsideOfRange{
|
||||
expected: u64,
|
||||
found: u64,
|
||||
},
|
||||
#[error("Invalid chunk: {0}")]
|
||||
InvalidChunk(String),
|
||||
#[error("Length of id expected {} or {} but found {found}: {raw}", .expected.0, .expected.1 )]
|
||||
InvalidLength{
|
||||
expected: (u8, u8),
|
||||
found: usize,
|
||||
raw: String
|
||||
},
|
||||
#[error("Number of chunks expected {expected} but {found}: {raw}")]
|
||||
InvalidLengthOfChunks{
|
||||
expected: u8,
|
||||
found: usize,
|
||||
raw: String,
|
||||
},
|
||||
#[error("Delimiter expected '-' or '_' but '{found:?}' found: {raw}")]
|
||||
InvalidDelimiter{
|
||||
found: Vec<char>,
|
||||
raw: String,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,128 +0,0 @@
|
|||
mod single;
|
||||
mod double;
|
||||
mod error;
|
||||
mod triple;
|
||||
mod utils;
|
||||
#[cfg(feature="rusqlite")]
|
||||
mod rusqlite;
|
||||
#[cfg(feature="serde")]
|
||||
mod serde;
|
||||
|
||||
use std::{fmt::Display, ops::Sub, str::FromStr};
|
||||
|
||||
pub use single::*;
|
||||
pub use double::*;
|
||||
pub use triple::*;
|
||||
pub use error::*;
|
||||
|
||||
#[cfg(feature="prost")]
|
||||
pub mod prost;
|
||||
#[cfg(feature="prost")]
|
||||
pub use prost::{ SingleMessage, DoubleMessage, TripleMessage ,TripodIdMessage};
|
||||
|
||||
/// The main trait for the tripod id
|
||||
pub trait TripodId: Copy + Display + TryFrom<Self::Integer, Error=Error> + From<Self::Tuple> + FromStr<Err=Error> + PartialEq + PartialEq<String> {
|
||||
|
||||
/// An associated integer type.
|
||||
/// This type is used to store the actual value of id.
|
||||
type Integer: From<Self> + Sub;
|
||||
|
||||
/// An associated tuple type containing SingleId.
|
||||
/// This type is used to represent the id as the tuple of SingleId.
|
||||
type Tuple: From<Self>;
|
||||
|
||||
/// An associated protobuf message type.
|
||||
/// This type is used for conversion between the protobuf message.
|
||||
#[cfg(feature="prost")]
|
||||
type Message: From<Self> + TryInto<Self, Error=Error>;
|
||||
|
||||
/// The nil Tripod ID.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// Basic usage:
|
||||
///
|
||||
/// ```
|
||||
/// # use tripod_id::{TripodId, Single};
|
||||
/// let id = Single::NIL;
|
||||
///
|
||||
/// assert_eq!(id, 0);
|
||||
/// assert_eq!(id, "000".to_string());
|
||||
/// ```
|
||||
const NIL: Self;
|
||||
|
||||
|
||||
/// The max Tripod ID.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// Basic usage:
|
||||
///
|
||||
/// ```
|
||||
/// # use tripod_id::{TripodId, Double};
|
||||
/// let id = Double::MAX;
|
||||
///
|
||||
/// assert_eq!(id, Double::CAPACITY - 1);
|
||||
/// assert_eq!(id, "ZZZ-ZZZ".to_string())
|
||||
/// ```
|
||||
const MAX: Self;
|
||||
|
||||
/// The capacity of the Tripod ID.
|
||||
const CAPACITY: Self::Integer;
|
||||
|
||||
/// Test if the Tripod ID is nil (=0).
|
||||
fn is_nil(self) -> bool {
|
||||
self == Self::NIL
|
||||
}
|
||||
|
||||
/// Test if the id is max(=Self::CAPACITY-1)
|
||||
fn is_max(self) -> bool {
|
||||
self == Self::MAX
|
||||
}
|
||||
|
||||
/// Validate the internal value has not reached capacity.
|
||||
/// Fundamentally, the internal value are private, and unvalidated value should not be enterd,
|
||||
/// so this function is only for testing purpose.
|
||||
#[cfg(test)]
|
||||
fn validate_inner(self) -> bool;
|
||||
|
||||
#[cfg(test)]
|
||||
fn validate_parse_strings(self, strings: &[&str]) -> Result<bool, Error> {
|
||||
let mut result: bool = true;
|
||||
for string in strings {
|
||||
result = result && (self == string.to_string())
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn validate_string_convertion(self) -> Result<bool, Error> {
|
||||
Ok(self == Self::from_str(&self.to_string())?)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn validate_integer_conversion(self) -> Result<bool, Error> {
|
||||
Ok(self == Self::try_from(Self::Integer::from(self))?)
|
||||
}
|
||||
#[cfg(test)]
|
||||
fn validate_tuple_conversion(self) -> bool {
|
||||
self == Self::from(Self::Tuple::from(self))
|
||||
}
|
||||
#[cfg(all(test, feature="prost"))]
|
||||
fn validate_message_conversion(self) -> Result<bool, Error> {
|
||||
Ok(self == Self::Message::from(self).try_into()?)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn validate_all(self) -> Result<bool, Error> {
|
||||
let mut result = self.validate_inner()
|
||||
&& self.validate_string_convertion()?
|
||||
&& self.validate_integer_conversion()?;
|
||||
#[cfg(feature="prost")]
|
||||
{
|
||||
result = result && self.validate_message_conversion()?;
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,53 +0,0 @@
|
|||
use prost::Name;
|
||||
|
||||
use crate::{prost::{Double, TripodIdMessage}, Error};
|
||||
|
||||
impl Name for Double {
|
||||
const NAME: &'static str = "Double";
|
||||
const PACKAGE: &'static str = super::PACKAGE_NAME;
|
||||
}
|
||||
|
||||
impl TripodIdMessage for Double {
|
||||
type TripodId = crate::Double;
|
||||
}
|
||||
|
||||
impl From<crate::Double> for Double {
|
||||
fn from(value: crate::Double) -> Self {
|
||||
Self {
|
||||
id: u32::from(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
impl TryFrom<Double> for crate::Double {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(value: Double) -> Result<Self, Self::Error> {
|
||||
Self::try_from(
|
||||
value.id
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{Double, DoubleMessage, TripodId};
|
||||
|
||||
#[test]
|
||||
fn nil() {
|
||||
let nil = DoubleMessage{id: 0};
|
||||
assert_eq!(Double::NIL, Double::try_from(nil).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn max() {
|
||||
let max = DoubleMessage{id: u32::from(Double::CAPACITY)-1};
|
||||
assert_eq!(Double::MAX, Double::try_from(max).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn oversized () {
|
||||
let oversized = DoubleMessage{id: u32::from(Double::CAPACITY)};
|
||||
let _ = Double::try_from(oversized).unwrap();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
mod single;
|
||||
mod double;
|
||||
mod triple;
|
||||
|
||||
use crate::TripodId;
|
||||
|
||||
const PACKAGE_NAME: &'static str = "tripod_id";
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/tripod_id.rs"));
|
||||
|
||||
/// Alias of single tripod-id message
|
||||
pub type SingleMessage = Single;
|
||||
|
||||
/// Alias of double tripod-id message
|
||||
pub type DoubleMessage = Double;
|
||||
|
||||
/// Alias of triple tripod-id message
|
||||
pub type TripleMessage = Triple;
|
||||
|
||||
pub trait TripodIdMessage: From<Self::TripodId> {
|
||||
type TripodId: TripodId + TryFrom<Self>;
|
||||
|
||||
fn is_valid(self) -> bool {
|
||||
Self::TripodId::try_from(self).is_ok()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,57 +0,0 @@
|
|||
use prost::Name;
|
||||
|
||||
use crate::{prost::{Single, TripodIdMessage}, Error, TripodId};
|
||||
|
||||
impl Name for Single {
|
||||
const NAME: &'static str = "Single";
|
||||
const PACKAGE: &'static str = super::PACKAGE_NAME;
|
||||
}
|
||||
|
||||
impl TripodIdMessage for Single {
|
||||
type TripodId = crate::Single;
|
||||
|
||||
}
|
||||
|
||||
impl From<crate::Single> for Single {
|
||||
fn from(value: crate::Single) -> Self {
|
||||
Self {
|
||||
id: u32::from(u16::from(value))
|
||||
}
|
||||
}
|
||||
}
|
||||
impl TryFrom<Single> for crate::Single {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(value: Single) -> Result<Self, Self::Error> {
|
||||
Self::try_from(
|
||||
u16::try_from(value.id).or(Err(Error::OutsideOfRange {
|
||||
expected: u64::from(crate::Single::CAPACITY),
|
||||
found: u64::from(value.id)
|
||||
}))?
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{Single, SingleMessage, TripodId};
|
||||
|
||||
#[test]
|
||||
fn nil() {
|
||||
let nil = SingleMessage{id: 0};
|
||||
assert_eq!(Single::NIL, Single::try_from(nil).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn max() {
|
||||
let max = SingleMessage{id: u32::from(Single::CAPACITY)-1};
|
||||
assert_eq!(Single::MAX, Single::try_from(max).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn oversized () {
|
||||
let oversized = SingleMessage{id: u32::from(Single::CAPACITY)};
|
||||
let _ = Single::try_from(oversized).unwrap();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,53 +0,0 @@
|
|||
use prost::Name;
|
||||
|
||||
use crate::{prost::{Triple, TripodIdMessage}, Error};
|
||||
|
||||
impl Name for Triple {
|
||||
const NAME: &'static str = "Triple";
|
||||
const PACKAGE: &'static str = super::PACKAGE_NAME;
|
||||
}
|
||||
|
||||
impl TripodIdMessage for Triple{
|
||||
type TripodId = crate::Triple;
|
||||
}
|
||||
|
||||
impl From<crate::Triple> for Triple {
|
||||
fn from(value: crate::Triple) -> Self {
|
||||
Self {
|
||||
id: u64::from(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
impl TryFrom<Triple> for crate::Triple {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(value: Triple) -> Result<Self, Self::Error> {
|
||||
Self::try_from(
|
||||
value.id
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{Triple, TripleMessage, TripodId};
|
||||
|
||||
#[test]
|
||||
fn nil() {
|
||||
let nil = TripleMessage{id: 0};
|
||||
assert_eq!(Triple::NIL, Triple::try_from(nil).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn max() {
|
||||
let max = TripleMessage{id: u64::from(Triple::CAPACITY)-1};
|
||||
assert_eq!(Triple::MAX, Triple::try_from(max).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn oversized () {
|
||||
let oversized = TripleMessage{id: u64::from(Triple::CAPACITY)};
|
||||
let _ = Triple::try_from(oversized).unwrap();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
use rusqlite::{types::FromSql, Error, ToSql};
|
||||
|
||||
use crate::{Double, Single, Triple};
|
||||
|
||||
impl FromSql for Single {
|
||||
fn column_result(value: rusqlite::types::ValueRef<'_>) -> rusqlite::types::FromSqlResult<Self> {
|
||||
let int = u16::column_result(value)?;
|
||||
Self::try_from(int).or_else(|e| {
|
||||
Err(rusqlite::types::FromSqlError::Other(Box::new(e)))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ToSql for Single {
|
||||
fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput<'_>> {
|
||||
Ok(u16::from(*self).into())
|
||||
}
|
||||
}
|
||||
|
||||
impl FromSql for Double {
|
||||
fn column_result(value: rusqlite::types::ValueRef<'_>) -> rusqlite::types::FromSqlResult<Self> {
|
||||
let int = u32::column_result(value)?;
|
||||
Self::try_from(int).or_else(|e| {
|
||||
Err(rusqlite::types::FromSqlError::Other(Box::new(e)))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ToSql for Double {
|
||||
fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput<'_>> {
|
||||
Ok(u32::from(*self).into())
|
||||
}
|
||||
}
|
||||
|
||||
impl FromSql for Triple {
|
||||
fn column_result(value: rusqlite::types::ValueRef<'_>) -> rusqlite::types::FromSqlResult<Self> {
|
||||
let int = u64::column_result(value)?;
|
||||
Self::try_from(int).or_else(|e| {
|
||||
Err(rusqlite::types::FromSqlError::Other(Box::new(e)))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ToSql for Triple {
|
||||
fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput<'_>> {
|
||||
Ok(rusqlite::types::ToSqlOutput::Owned(rusqlite::types::Value::Integer(
|
||||
i64::try_from(u64::from(*self)).map_err(
|
||||
|err| Error::ToSqlConversionFailure(err.into())
|
||||
)?
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
|
@ -1,78 +0,0 @@
|
|||
use std::str::FromStr;
|
||||
|
||||
use serde::{Deserialize, Serialize, de::Error};
|
||||
|
||||
use crate::{Double, Single, Triple};
|
||||
|
||||
impl Serialize for Single {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer {
|
||||
serializer.serialize_str(&self.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for Single {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de> {
|
||||
let s = String::deserialize(deserializer)?;
|
||||
Single::from_str(&s).map_err(|e| D::Error::custom(e))
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for Double {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer {
|
||||
serializer.serialize_str(&self.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for Double {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de> {
|
||||
let s = String::deserialize(deserializer)?;
|
||||
Double::from_str(&s).map_err(|e| D::Error::custom(e))
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for Triple {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer {
|
||||
serializer.serialize_str(&self.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for Triple {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de> {
|
||||
let s = String::deserialize(deserializer)?;
|
||||
Triple::from_str(&s).map_err(|e| D::Error::custom(e))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use serde_test::{assert_tokens, Token};
|
||||
|
||||
use crate::TripodId;
|
||||
|
||||
|
||||
#[test]
|
||||
fn single() {
|
||||
assert_tokens(&crate::Single::NIL, &[Token::Str("000")]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn double() {
|
||||
assert_tokens(&crate::Double::NIL, &[Token::Str("000-000")]);
|
||||
}
|
||||
#[test]
|
||||
fn triple() {
|
||||
assert_tokens(&crate::Triple::NIL, &[Token::Str("000-000-000")]);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,244 +0,0 @@
|
|||
use std::{fmt::Display, str::FromStr};
|
||||
|
||||
use rand::{distributions::Standard, prelude::Distribution, Rng};
|
||||
|
||||
#[cfg(feature="prost")]
|
||||
use crate::SingleMessage;
|
||||
use crate::{error::Error, TripodId};
|
||||
|
||||
const CHARACTERS: &[u8;33] = b"0123456789abcdefghjkmnpqrstuvwxyz";
|
||||
const BASE: u16 = 33;
|
||||
const SQUARED_BASE: u16 = BASE.pow(2);
|
||||
const CUBED_BASE: u16 = BASE.pow(3);
|
||||
|
||||
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{
|
||||
expected: CUBED_BASE as u64,
|
||||
found: int as u64
|
||||
})
|
||||
}
|
||||
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))
|
||||
}
|
||||
|
||||
/// Single size tripod id.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use std::str::FromStr;
|
||||
/// use tripod_id::{TripodId,Single};
|
||||
///
|
||||
/// assert_eq!(Single::from_str("012").unwrap(), Single::try_from(35).unwrap());
|
||||
/// ```
|
||||
#[derive(Copy, Clone, Debug, Hash, PartialEq)]
|
||||
pub struct Single(u16);
|
||||
|
||||
|
||||
impl TripodId for Single {
|
||||
type Integer = u16;
|
||||
type Tuple = (Single,);
|
||||
#[cfg(feature="prost")]
|
||||
type Message = SingleMessage;
|
||||
|
||||
const CAPACITY: Self::Integer = CUBED_BASE;
|
||||
|
||||
const NIL: Single = Single(0);
|
||||
|
||||
const MAX: Single = Single(Self::CAPACITY-1);
|
||||
|
||||
#[cfg(test)]
|
||||
fn validate_inner(self) -> bool {
|
||||
self.0 < Self::CAPACITY
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Single {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", u16_to_string(self.0).unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Single {
|
||||
type Err = Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Ok(Self(str_to_u16(s)?))
|
||||
}
|
||||
}
|
||||
|
||||
impl Distribution<Single> for Standard {
|
||||
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Single {
|
||||
Single(rng.gen_range(0..Single::CAPACITY))
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<u16> for Single {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(value: u16) -> Result<Self, Self::Error> {
|
||||
if value < Self::CAPACITY {
|
||||
Ok(Self(value))
|
||||
} else {
|
||||
Err(Error::OutsideOfRange{
|
||||
expected: Self::CAPACITY as u64,
|
||||
found: value as u64
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Single> for u16 {
|
||||
fn from(value: Single) -> Self {
|
||||
value.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(Single,)> for Single {
|
||||
fn from(value: (Single,)) -> Self {
|
||||
value.0
|
||||
}
|
||||
}
|
||||
impl From<Single> for (Single,) {
|
||||
fn from(value: Single) -> Self {
|
||||
(value,)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<u16> for Single {
|
||||
fn eq(&self, other: &u16) -> bool {
|
||||
&u16::from(*self) == other
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<String> for Single {
|
||||
fn eq(&self, other: &String) -> bool {
|
||||
match Self::from_str(other) {
|
||||
Ok(x) => *self == x,
|
||||
Err(_) => false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
#[test]
|
||||
fn nil() {
|
||||
assert!(Single::NIL.validate_all().unwrap());
|
||||
assert_eq!(Single::NIL, 0);
|
||||
assert!(Single::NIL.validate_parse_strings(&["000"]).unwrap());
|
||||
assert!(Single::NIL.is_nil());
|
||||
assert!(!Single::NIL.is_max())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn max() {
|
||||
assert!(Single::MAX.validate_all().unwrap());
|
||||
assert_eq!(Single::MAX, Single::CAPACITY - 1);
|
||||
assert!(Single::MAX.validate_parse_strings(&["zzz", "ZZZ"]).unwrap());
|
||||
assert!(Single::MAX.is_max());
|
||||
assert!(!Single::MAX.is_nil());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn over_sized() {
|
||||
Single::try_from(Single::CAPACITY).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn random() {
|
||||
let mut rng = rand::thread_rng();
|
||||
for _ in 0..10 {
|
||||
let single: Single = rng.r#gen();
|
||||
assert!(single.validate_all().unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -1,184 +0,0 @@
|
|||
#[cfg(feature="prost")]
|
||||
use crate::TripleMessage;
|
||||
use crate::{utils::is_delimiter, Double, Error, Single};
|
||||
|
||||
use std::{fmt::Display, str::FromStr};
|
||||
|
||||
use rand::{distributions::Standard, prelude::Distribution, Rng};
|
||||
|
||||
use crate::TripodId;
|
||||
|
||||
/// Triple length tripod id.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # use tripod_id::{TripodId, Triple};
|
||||
/// # use std::str::FromStr;
|
||||
///
|
||||
/// let _ = Triple::from_str("012-abc-def");
|
||||
/// ```
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
pub struct Triple(u64);
|
||||
|
||||
impl TripodId for Triple{
|
||||
type Integer = u64;
|
||||
type Tuple = (Single, Single, Single);
|
||||
#[cfg(feature="prost")]
|
||||
type Message = TripleMessage;
|
||||
const CAPACITY: Self::Integer = (Single::CAPACITY as u64).pow(3);
|
||||
|
||||
const NIL: Self = Self(0);
|
||||
|
||||
const MAX: Self = Self(Self::CAPACITY - 1);
|
||||
|
||||
#[cfg(test)]
|
||||
fn validate_inner(self) -> bool {
|
||||
self.0 < Self::CAPACITY
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Triple {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
|
||||
let tuple: (Single, Single, Single) = (*self).into();
|
||||
write!(f, "{}-{}-{}", tuple.0, tuple.1, tuple.2)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Triple {
|
||||
type Err = Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Ok(match s.len() {
|
||||
11 => {
|
||||
let delimiter = [
|
||||
s[3..4].chars().next().unwrap(),
|
||||
s[7..8].chars().next().unwrap(),
|
||||
];
|
||||
if is_delimiter(delimiter[0]) && is_delimiter(delimiter[1]) {
|
||||
Ok(Self::from((Single::from_str(&s[0..3])?,Single::from_str(&s[4..7])?,Single::from_str(&s[8..11])?)))
|
||||
} else {
|
||||
Err(Error::InvalidDelimiter{
|
||||
found: Vec::from(delimiter),
|
||||
raw: s.to_string()
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
9 => {
|
||||
Ok(Self::from((Single::from_str(&s[0..3])?,Single::from_str(&s[3..6])?,Single::from_str(&s[6..9])?)))
|
||||
}
|
||||
x => {
|
||||
Err(Self::Err::InvalidLength{
|
||||
expected: (9, 11),
|
||||
found: x,
|
||||
raw: s.to_string()
|
||||
})
|
||||
}
|
||||
} ?
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl Distribution<Triple> for Standard {
|
||||
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Triple {
|
||||
Triple(rng.gen_range(0..Triple::CAPACITY))
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<u64> for Triple {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(value: u64) -> Result<Self, Self::Error> {
|
||||
if value < Self::CAPACITY {
|
||||
Ok(Self(value))
|
||||
} else {
|
||||
Err(Error::OutsideOfRange{
|
||||
expected: Self::CAPACITY as u64,
|
||||
found: value as u64
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Triple> for u64 {
|
||||
fn from(value: Triple) -> Self {
|
||||
value.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(Single, Single, Single)> for Triple {
|
||||
fn from(value: (Single, Single, Single)) -> Self {
|
||||
Self(
|
||||
(u16::from(value.0) as u64) * (Double::CAPACITY as u64)
|
||||
+ (u16::from(value.1) as u64) * (Single::CAPACITY as u64)
|
||||
+ (u16::from(value.2) as u64)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Triple> for (Single, Single, Single) {
|
||||
fn from(value: Triple) -> Self {
|
||||
(
|
||||
Single::try_from(u16::try_from(value.0 / (Double::CAPACITY as u64)).unwrap()).unwrap(),
|
||||
Single::try_from(u16::try_from((value.0 % (Double::CAPACITY as u64)) /(Single::CAPACITY as u64)).unwrap()).unwrap(),
|
||||
Single::try_from(u16::try_from(value.0 % (Single::CAPACITY as u64)).unwrap()).unwrap()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<u64> for Triple {
|
||||
fn eq(&self, other: &u64) -> bool {
|
||||
&u64::from(*self) == other
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<String> for Triple {
|
||||
fn eq(&self, other: &String) -> bool {
|
||||
match Self::from_str(other) {
|
||||
Ok(x) => *self == x,
|
||||
Err(_) => false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use super::*;
|
||||
#[test]
|
||||
fn nil() {
|
||||
assert!(Triple::NIL.validate_all().unwrap());
|
||||
assert_eq!(Triple::NIL, 0);
|
||||
assert_eq!(Triple::NIL, "000000000".to_string());
|
||||
assert_eq!(Triple::NIL, "000-000-000".to_string());
|
||||
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn max() {
|
||||
assert!(Triple::MAX.validate_all().unwrap());
|
||||
assert_eq!(Triple::MAX, Triple::CAPACITY-1);
|
||||
assert_eq!(Triple::MAX, "zzzzzzzzz".to_string());
|
||||
assert_eq!(Triple::MAX, "ZZZ-ZZZ-ZZZ".to_string());
|
||||
assert_eq!((Single::MAX, Single::MAX, Single::MAX), Triple::MAX.into())
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn over_sized() {
|
||||
Triple::try_from(Triple::CAPACITY).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn random() {
|
||||
let mut rng = rand::thread_rng();
|
||||
for _ in 0..10 {
|
||||
let id: Triple = rng.r#gen();
|
||||
assert!(id.validate_all().unwrap());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
/// Test if the character is valid delimiter.
|
||||
pub fn is_delimiter(c: char) -> bool {
|
||||
match c {
|
||||
'-' | '_' => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue