Compare commits

..

No commits in common. "e8ad5b5c231ff8da6cc3a1582c8f244098e195c0" and "97a3f86ef9d57b8336af8a53a4a865675ede956a" have entirely different histories.

65 changed files with 510 additions and 911 deletions

View file

@ -1,10 +1,8 @@
{
"protoc": {
"compile_on_save": true,
"options": [
"--proto_path=${workspaceRoot}/tripod-id/proto",
"--proto_path=${workspaceRoot}/core/proto",
"--java_out=${workspaceRoot}/.tmp"
"--proto_path=${workspaceRoot}/core/proto"
]
}
}

View file

@ -53,7 +53,7 @@ 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"

View file

@ -3,10 +3,10 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
.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/caretta_sync/authorization_request.proto",
"proto/caretta_sync/authorized_node.proto",
"proto/caretta_sync/remote_node.proto",
"proto/caretta_sync/common.proto"
],
&["proto", "../tripod-id/proto"]
)?;

View file

@ -0,0 +1,77 @@
syntax = "proto3";
package caretta_sync;
import "caretta_sync/common.proto";
import "caretta_sync/authorized_node.proto";
import "caretta_sync/remote_node.proto";
import "tripod_id.proto";
import "google/protobuf/timestamp.proto";
import "google/protobuf/duration.proto";
service AuthorizationRequest {
rpc Send(AuthorizationRequestSendRequest) returns (AuthorizationRequestSendResponse);
rpc Accept(AuthorizationRequestAcceptRequest) returns (AuthorizationRequestAcceptResponse);
rpc Reject(AuthorizationRequestRejectRequest) returns (AuthorizationRequestRejectResponse);
rpc Info(AuthorizationRequestInfoRequest) returns (AuthorizationRequestInfoResponse);
rpc InfoStream(stream AuthorizationRequestInfoStreamRequest) returns (stream AuthorizationRequestInfoStreamResponse);
}
enum AuthorizationRequestKind {
SENT = 0;
RECEIVED = 1;
}
message AuthorizationRequestSendRequest {
caretta_sync.RemoteNodeIdentifier remote_node = 1;
}
message AuthorizationRequestSendResponse {
caretta_sync.RemoteNodeInfo remote_node_info = 1;
string passcode = 2;
}
message AuthorizationRequestInfo {
tripod_id.Double id = 1;
caretta_sync.PublicKey public_key = 2;
google.protobuf.Timestamp created_at = 3;
AuthorizationRequestKind kind = 4;
string node_info = 5;
}
message AuthorizationRequestIdentifier {
oneof identifier {
caretta_sync.Uuid uuid = 1;
tripod_id.Double id = 2;
}
}
message AuthorizationRequestAcceptRequest {
AuthorizationRequestIdentifier authorization_request = 1;
string passcode = 2;
}
message AuthorizationRequestAcceptResponse {
caretta_sync.AuthorizedNodeInfo authorized_node_info = 1;
}
message AuthorizationRequestRejectRequest {
AuthorizationRequestIdentifier authorization_request = 1;
}
message AuthorizationRequestRejectResponse {
AuthorizationRequestInfo request_info = 1;
}
message AuthorizationRequestInfoRequest {
AuthorizationRequestIdentifier request = 1;
}
message AuthorizationRequestInfoResponse{
AuthorizationRequestInfo info = 1;
}
message AuthorizationRequestInfoStreamRequest {}
message AuthorizationRequestInfoStreamResponse {
AuthorizationRequestInfo request_info = 1;
}

View file

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

View file

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

View file

@ -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);
}

View file

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

View file

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

View file

@ -1,8 +0,0 @@
syntax = "proto3";
package caretta_sync.authorization_request;
import "caretta_sync/authorization_request/identifier.proto";
message InfoRequest {
Identifier request = 1;
}

View file

@ -1,9 +0,0 @@
syntax = "proto3";
package caretta_sync.authorization_request;
import "caretta_sync/authorization_request/info.proto";
message InfoResponse{
Info info = 1;
}

View file

@ -1,4 +0,0 @@
syntax = "proto3";
package caretta_sync.authorization_request;
message ListRequest {}

View file

@ -1,8 +0,0 @@
syntax = "proto3";
package caretta_sync.authorization_request;
import "caretta_sync/authorization_request/info.proto";
message ListResponse {
Info request_info = 1;
}

View file

@ -1,8 +0,0 @@
syntax = "proto3";
package caretta_sync.authorization_request;
import "caretta_sync/authorization_request/identifier.proto";
message RejectRequest {
Identifier authorization_request = 1;
}

View file

@ -1,8 +0,0 @@
syntax = "proto3";
package caretta_sync.authorization_request;
import "caretta_sync/authorization_request/info.proto";
message RejectResponse {
Info request_info = 1;
}

View file

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

View file

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

View file

@ -1,10 +0,0 @@
syntax = "proto3";
package caretta_sync.authorization_request;
enum Status {
UNSPECIFIED = 0;
SENT = 1;
RECEIVED = 2;
ACCEPTED = 3;
REJECTED = 4;
}

View file

@ -0,0 +1,40 @@
syntax = "proto3";
// The package contain service and messages about node already authorized.
// The authorized node includes not only remote authorized peers but also local node itself.
package caretta_sync;
import "tripod_id.proto";
import "caretta_sync/remote_node.proto";
import "caretta_sync/common.proto";
service AuthorizadNode {
rpc Info(AuthorizedNodeInfoRequest) returns (AuthorizedNodeInfoResponse);
rpc InfoStream(stream AuthorizedNodeInfoStreamRequest) returns (stream AuthorizedNodeInfoStreamResponse);
}
message AuthorizedNodeIdentifier {
oneof identifier {
tripod_id.Single id = 1;
caretta_sync.PublicKey public_key = 2;
}
}
message AuthorizedNodeInfo {
tripod_id.Single id = 1;
caretta_sync.PublicKey public_key = 2;
string note = 3;
}
message AuthorizedNodeInfoRequest {
AuthorizedNodeIdentifier node = 1;
}
message AuthorizedNodeInfoResponse {
AuthorizedNodeInfo node_info = 1;
}
message AuthorizedNodeInfoStreamRequest{}
message AuthorizedNodeInfoStreamResponse {
AuthorizedNodeInfo node_info = 1;
}

View file

@ -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);
}

View file

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

View file

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

View file

@ -1,9 +0,0 @@
syntax = "proto3";
package caretta_sync.authorized_node;
import "caretta_sync/authorized_node/identifier.proto";
message InfoRequest {
Identifier node = 1;
}

View file

@ -1,8 +0,0 @@
syntax = "proto3";
package caretta_sync.authorized_node;
import "caretta_sync/authorized_node/info.proto";
message InfoResponse {
Info node_info = 1;
}

View file

@ -1,5 +0,0 @@
syntax = "proto3";
package caretta_sync.authorized_node;
message ListRequest{}

View file

@ -1,9 +0,0 @@
syntax = "proto3";
package caretta_sync.authorized_node;
import "caretta_sync/authorized_node/info.proto";
message ListResponse {
Info node_info = 1;
}

View file

@ -0,0 +1,41 @@
syntax = "proto3";
package caretta_sync;
message PublicKey {
bytes key = 1;
}
message Uuid {
uint64 high_bits = 1;
uint64 low_bits = 2;
}
message Url {
string url = 1;
}
message SocketAddr {
oneof socket_addr {
SocketAddrV4 v4 = 1;
SocketAddrV6 v6 = 2;
}
}
message SocketAddrV4 {
Ipv4Addr ip = 1;
uint32 port = 2;
}
message Ipv4Addr {
uint32 bits = 1;
}
message SocketAddrV6 {
Ipv6Addr ip = 1;
uint32 port = 2;
}
message Ipv6Addr {
uint64 high_bits = 1;
uint64 low_bits = 2;
}

View file

@ -1,8 +0,0 @@
syntax = "proto3";
package caretta_sync.common;
// protobuf message of url::Url.
message Url {
string url = 1;
}

View file

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

View file

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

View file

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

View file

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

View file

@ -1,8 +0,0 @@
syntax = "proto3";
package caretta_sync.iroh;
// protobuf message of iroh::PublicKey.
message PublicKey {
bytes key = 1;
}

View file

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

View file

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

View file

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

View file

@ -1,7 +0,0 @@
syntax = "proto3";
package caretta_sync.net;
message Ipv4Addr {
uint32 bits = 1;
}

View file

@ -1,7 +0,0 @@
syntax = "proto3";
package caretta_sync.net;
message Ipv6Addr {
uint64 high_bits = 1;
uint64 low_bits = 2;
}

View file

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

View file

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

View file

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

View file

@ -0,0 +1,107 @@
syntax = "proto3";
package caretta_sync;
import "caretta_sync/common.proto";
import "tripod_id.proto";
import "google/protobuf/timestamp.proto";
import "google/protobuf/duration.proto";
import "google/protobuf/empty.proto";
service RemoteNode {
rpc Info(RemoteNodeInfoRequest) returns (RemoteNodeInfoResponse);
rpc List(stream RemoteNodeListRequest) returns (stream RemoteNodeListResponse);
}
message RemoteNodeIdentifier {
oneof identifier {
tripod_id.Double id = 1;
caretta_sync.PublicKey public_key = 2;
}
}
message RemoteNodeInfoRequest {
RemoteNodeIdentifier remote_node = 1;
}
message RemoteNodeListRequest {}
message RemoteNodeInfoResponse {
RemoteNodeInfo remote_node_info = 1;
}
message RemoteNodeListResponse {
RemoteNodeInfo remote_node_info = 1;
}
message RemoteNodeInfo {
// A messege of iroh::RemoteInfo.
message RemoteInfo {
message ConnectionType {
message Mixed {
caretta_sync.SocketAddr socket_addr = 1;
caretta_sync.Url relay_url = 2;
}
oneof conn_type {
caretta_sync.SocketAddr direct = 1;
caretta_sync.Url relay = 2;
Mixed mixed = 3;
google.protobuf.Empty none = 4;
}
}
message DirectAddrInfo {
message LastControl {
enum ControlMsg {
PING = 0;
PONG = 1;
CALL_ME_MAYBE = 2;
}
google.protobuf.Duration duration = 1;
ControlMsg control_msg = 2;
}
message SourceDuration {
oneof source {
google.protobuf.Empty saved = 1;
google.protobuf.Empty udp = 2;
google.protobuf.Empty Relay = 3;
google.protobuf.Empty App = 4;
string discovery = 5;
string named_app = 6;
};
google.protobuf.Duration duration = 7;
}
caretta_sync.SocketAddr addr = 1;
google.protobuf.Duration latency = 2;
LastControl last_control = 3;
google.protobuf.Duration last_payload = 4;
google.protobuf.Duration last_alive = 5;
repeated SourceDuration sources = 6;
}
message RelayUrlInfo {
caretta_sync.Url relay_url = 1;
google.protobuf.Duration last_alive = 2;
google.protobuf.Duration latency = 3;
}
bool authorized = 2;
caretta_sync.PublicKey public_key = 3;
RelayUrlInfo relay_url = 4;
repeated DirectAddrInfo addrs = 5;
ConnectionType conn_type = 6;
google.protobuf.Duration latency = 7;
google.protobuf.Duration last_used = 8;
}
tripod_id.Double public_id = 1;
RemoteInfo remote_info = 2;
}

View file

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

View file

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

View file

@ -1,16 +0,0 @@
syntax = "proto3";
package caretta_sync.remote_node;
import "caretta_sync/remote_node/identifier.proto";
message InfoRequest {
Identifier remote_node = 1;
}

View file

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

View file

@ -1,4 +0,0 @@
syntax = "proto3";
package caretta_sync.remote_node;
message ListRequest{}

View file

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

View file

@ -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);
}

View file

@ -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!()
}
}

View file

@ -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!()
}
}

View file

@ -1,9 +1,22 @@
use super::*;
tonic::include_proto!("caretta_sync.common");
use crate::proto::{error::{ProtoDeserializeError, ProtoSerializeError}};
use crate::proto::{error::{ProtoDeserializeError, ProtoSerializeError}, socket_addr};
impl From<uuid::Uuid> for Uuid {
impl From<iroh::PublicKey> for PublicKeyMessage {
fn from(value: iroh::PublicKey) -> Self {
Self{ key: Vec::from(value.as_bytes()) }
}
}
impl TryFrom<PublicKeyMessage> for iroh::PublicKey {
type Error = ProtoDeserializeError;
fn try_from(value: PublicKeyMessage) -> Result<Self, Self::Error> {
let slice: [u8; 32] = value.key[0..32].try_into()?;
Ok(iroh::PublicKey::from_bytes(&slice)?)
}
}
impl From<uuid::Uuid> for UuidMessage {
fn from(value: uuid::Uuid) -> Self {
let (first_half, second_half) = value.as_u64_pair();
Self {
@ -13,34 +26,123 @@ impl From<uuid::Uuid> for Uuid {
}
}
impl From<Uuid> for uuid::Uuid {
fn from(value: Uuid) -> Self {
impl From<UuidMessage> for uuid::Uuid {
fn from(value: UuidMessage) -> Self {
uuid::Uuid::from_u64_pair(value.high_bits, value.low_bits)
}
}
impl From<url::Url> for Url {
impl From<url::Url> for UrlMessage {
fn from(value: url::Url) -> Self {
todo!()
}
}
impl TryFrom<Url> for url::Url {
impl TryFrom<UrlMessage> for url::Url {
type Error = ProtoDeserializeError;
fn try_from(value: Url) -> Result<Self, Self::Error> {
fn try_from(value: UrlMessage) -> Result<Self, Self::Error> {
todo!()
}
}
impl From<std::net::SocketAddr> for SocketAddrMessage {
fn from(value: std::net::SocketAddr) -> Self {
Self{
socket_addr: Some(match value {
std::net::SocketAddr::V4(x) => socket_addr::SocketAddr::V4(SocketAddrV4Message::from(x)),
std::net::SocketAddr::V6(x) => socket_addr::SocketAddr::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.ok_or(Self::Error::MissingField("SocketAddr.socket_addr"))? {
socket_addr::SocketAddr::V4(x) => std::net::SocketAddr::V4(x.try_into()?),
socket_addr::SocketAddr::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};
use super::*;
fn validate_uuid_conversion(uuid: uuid::Uuid) -> bool{
let message = Uuid::from(uuid);
let message = UuidMessage::from(uuid);
uuid == uuid::Uuid::from(message)
}
@ -51,4 +153,17 @@ mod tests {
assert!(validate_uuid_conversion(uuid::Uuid::now_v7()));
}
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() {
assert!(validate_socket_addr_conversion(net::SocketAddr::new(net::IpAddr::V4(net::Ipv4Addr::new(0, 0, 0, 0)),u16::MIN)).unwrap());
assert!(validate_socket_addr_conversion(net::SocketAddr::new(net::IpAddr::V4(net::Ipv4Addr::BROADCAST),u16::MAX)).unwrap());
assert!(validate_socket_addr_conversion(net::SocketAddr::new(net::IpAddr::V6(net::Ipv6Addr::new(0,0,0,0,0,0,0,0)), u16::MAX)).unwrap());
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::MIN)).unwrap());
}
}

View file

@ -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 }),
}) }
}
}

View file

@ -1,11 +1,25 @@
mod authorization_request;
pub mod authorization_request;
mod authorized_node;
mod remote_node;
mod common;
mod error;
mod iroh;
mod net;
//mod server;
mod generated{
tonic::include_proto!("caretta_sync");
}
pub use generated::{
*,
PublicKey as PublicKeyMessage,
Uuid as UuidMessage,
Url as UrlMessage,
SocketAddr as SocketAddrMessage,
SocketAddrV6 as SocketAddrV6Message,
SocketAddrV4 as SocketAddrV4Message,
Ipv4Addr as Ipv4AddrMessage,
Ipv6Addr as Ipv6AddrMessage,
};
pub use common::*;
pub use error::*;

View file

@ -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());
}
}

View file

@ -1,3 +1,4 @@
use super::*;
use std::{pin::Pin, time::Duration};
@ -9,21 +10,82 @@ 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");
impl TryFrom<(iroh::endpoint::Source, Duration)> for remote_node_info::remote_info::direct_addr_info::Source {
type Error = ProtoSerializeError;
fn try_from(src: (iroh::endpoint::Source, Duration)) -> Result<Self, Self::Error> {
let (source, duration )= src;
Ok(Self {
source: source.to_string(),
duration: Some(duration.try_into()?),
})
}
}
impl TryFrom<RemoteNodeIdentifier> for RemoteNodeRecord<LocalRecordId> {
type Error = Error;
fn try_from(value: RemoteNodeIdentifier) -> Result<Self, Self::Error> {
Ok(match value.identifier.ok_or(ProtoDeserializeError::MissingField("RemoteNodeIdentifier.identifier"))? {
remote_node_identifier::Identifier::PublicKey(x) => Self::get_or_insert_by_public_key(&x.try_into()?)?,
remote_node_identifier::Identifier::Id(x) => Self::get_by_public_id(&x.try_into()?)?
})
}
}
impl TryFrom<(tripod_id::Double, RemoteInfo)> for RemoteNodeInfo {
type Error = ProtoSerializeError;
fn try_from(value: (tripod_id::Double, RemoteInfo)) -> Result<Self, Self::Error> {
Ok(Self {
id: Some(value.0.into()),
public_key: Some(value.1.node_id.into()),
relay_url: value.1.relay_url.map_or(String::from(""), |x| x.relay_url.to_string()),
addrs: value.1.addrs.into_iter()
.map(|x| DirectAddrInfoMessage::try_from(x))
.collect::<Result<Vec<DirectAddrInfoMessage>,Self::Error>>()?,
conn_type: value.1.conn_type.to_string(),
latency: value.1.latency.map(|x| x.try_into()).transpose()?,
last_used: value.1.last_used.map(|x| x.try_into()).transpose()?,
})
}
}
impl TryFrom<DirectAddrInfo> for RemoteNodeDirectAddrInfo {
type Error = ProtoSerializeError;
fn try_from(value: DirectAddrInfo) -> Result<Self, Self::Error> {
Ok(RemoteNodeDirectAddrInfo {
addr: value.addr.to_string(),
latency: value.latency.map(|x| x.try_into()).transpose()?,
last_control: value.last_control.map(|x| LastControlMessage::try_from(x)).transpose()?,
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(|x| SourceMessage::try_from(x)).collect::<Result<Vec<SourceMessage>, Self::Error>>()?
})
}
}
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!()
type ListStream = Pin<Box<dyn Stream<Item = Result<RemoteNodeListResponse, Status>> + Send>>;
async fn info(&self, request: Request<RemoteNodeInfoRequest>) -> Result<Response<RemoteNodeInfoResponse>, Status> {
let request = request.into_inner();
let remote_node: RemoteNodeRecord<LocalRecordId> = request.remote_node.ok_or(Status::invalid_argument("remote_node is required."))?.try_into()?;
let remote_info: RemoteNodeInfo = IROH_ENDPOINT.get_unchecked().remote_info(remote_node.public_key).ok_or(Status::not_found(format!("node {:?} is not found", remote_node)))?.into();
Ok(Response::new(RemoteNodeInfoResponse{
remote_node_info: Some(remote_info)
}))
}
async fn list(&self, request: Request<Streaming<ListRequest>>)
async fn list(&self, request: Request<Streaming<RemoteNodeListRequest>>)
-> Result<Response<Self::ListStream>, Status> {
let iter = IROH_ENDPOINT.get_unchecked().remote_info_iter()
.map(|x| {
todo!();
RemoteNodeInfo::try_from(x).map(|x| RemoteNodeListResponse {
remote_node: x.into()
}).or_else(|e| {
Err(Status::from_error(Box::new(e)))
})
});
let stream = futures::stream::iter(iter);
Ok(Response::new(Box::pin(stream)))

View file

@ -1,11 +1,7 @@
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/tripod_id.proto"],
&["proto/"]
)?;
Ok(())

View file

@ -0,0 +1,17 @@
syntax = "proto3";
package tripod_id;
// Single size tripod id message
message Single {
uint32 id = 1;
}
// Double size tripod id message
message Double {
uint32 id = 1;
}
// Triple size tripod id message
message Triple {
uint64 id = 1;
}

View file

@ -1,7 +0,0 @@
syntax = "proto3";
package tripod_id;
// Double size tripod id message
message Double {
uint32 id = 1;
}

View file

@ -1,7 +0,0 @@
syntax = "proto3";
package tripod_id;
// Single size tripod id message
message Single {
uint32 id = 1;
}

View file

@ -1,7 +0,0 @@
syntax = "proto3";
package tripod_id;
// Triple size tripod id message
message Triple {
uint64 id = 1;
}

View file

@ -1,6 +1,6 @@
use prost::Name;
use crate::{prost::{Double, TripodIdMessage}, Error};
use crate::{prost::{Double, TripodIdMessage}, Error, TripodId};
impl Name for Double {
const NAME: &'static str = "Double";
@ -27,27 +27,3 @@ impl TryFrom<Double> for crate::Double {
)
}
}
#[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();
}
}

View file

@ -1,20 +1,17 @@
pub mod generated {
include!(concat!(env!("OUT_DIR"), "/tripod_id.rs"));
}
mod single;
mod double;
mod triple;
pub use generated::*;
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> {

View file

@ -1,6 +1,6 @@
use prost::Name;
use crate::{prost::{Triple, TripodIdMessage}, Error};
use crate::{prost::{Triple, TripodIdMessage}, Error, TripodId};
impl Name for Triple {
const NAME: &'static str = "Triple";
@ -27,27 +27,3 @@ impl TryFrom<Triple> for crate::Triple {
)
}
}
#[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();
}
}

View file

@ -1,3 +1,7 @@
use std::str::FromStr;
use crate::Single;
/// Test if the character is valid delimiter.
pub fn is_delimiter(c: char) -> bool {
match c {