✨♻️➖➕ bevy_vanth, vanth, vanth_derive, vanth_transport: Many changes in preparation for networking
- Created new crate `bevy_vanth` with basic plugin structure for Bevy integration. - Refactored `Id` generation in `vanth` to use `OsRng` and removed redundant `to_u128_pair`/`from_u128_pair` methods. - Moved networking functionality into new `net` module with `Node`, `Packet`, and `Message` types. - Updated `vanth_derive` to use `proc-macro-crate` for reliable crate path resolution. - Added `rand` dependency to replace custom ID generation logic. - Updated `Cargo.toml`/`Cargo.lock` with new dependencies: `bevy_app`, `nix`, `cfg_aliases`, `proc-macro-crate`. - Modified `README.md` with improved project description. - Added commented clippy check in `flake.nix`. - Added `smol`, `async-process`, and `async-trait` dependencies in root and `vanth` crate. - Integrated `vanth` crate into `bevy_vanth` and added serde dependency. - Reorganized test files into module structure for `cli` and `vanth` crates. - Created new modules `compress` and `ecc` in `vanth`. - Implemented `Node` with async `run` method and `Backend` trait for networking in `vanth`. - Renamed `Memory` backend to `InMemoryStore` in `vanth` store module.
This commit is contained in:
parent
5262a266c0
commit
5afe3b61fb
34 changed files with 1887 additions and 483 deletions
774
Cargo.lock
generated
774
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
13
Cargo.toml
13
Cargo.toml
|
@ -12,6 +12,7 @@ name = "vanth"
|
|||
[workspace.dependencies]
|
||||
clap = { version = "4.5.42", features = ["derive"] }
|
||||
bevy_ecs = "0.16.1"
|
||||
bevy_app = "0.16.1"
|
||||
bincode = "2.0.1"
|
||||
serde = { version = "1.0.219", features = ["derive"] }
|
||||
serde_json = "1.0.140"
|
||||
|
@ -22,8 +23,16 @@ syn = { version = "2.0", features = ["full"] }
|
|||
proc-macro2 = "1.0"
|
||||
rusqlite = { version = "0.32.1", features = ["bundled"] }
|
||||
tempfile = "3.12.0"
|
||||
rand_core = "0.6.4"
|
||||
rand_chacha = { version = "0.3.1", features = ["serde1"] }
|
||||
rand = "0.9.2"
|
||||
rand_chacha = { version = "0.9.0", features = ["serde"] }
|
||||
rand_core = "0.9.0"
|
||||
tracing = "0.1.41"
|
||||
tracing-subscriber = "0.3.19"
|
||||
assert_cmd = "2.0.17"
|
||||
proc-macro-crate = "3.3.0"
|
||||
smol = "2.0.2"
|
||||
async-process = "2.4.0"
|
||||
async-trait = "0.1.89"
|
||||
chacha20poly1305 = "0.10.1"
|
||||
serde_bytes = "0.11.17"
|
||||
serde_arrays = "0.2.0"
|
||||
|
|
88
README.md
88
README.md
|
@ -1,89 +1,9 @@
|
|||
# Vanth
|
||||
|
||||
Vanth is a content-addressed database as a library designed for entity-component-system (ECS) applications.
|
||||
Vanth is a content-addressed data storage and task memoization framework designed for entity-component-system (ECS) applications.
|
||||
|
||||
Inspired by [Salsa](https://github.com/salsa-rs/salsa) and [Bevy](https://bevy.org/).
|
||||
|
||||
It is currently experimental and should not be used for anything.
|
||||
|
||||
## Library usage
|
||||
|
||||
Any type that implements `serde::Serialize` can be hashed using `vanth::hash` to produce a `vanth::ContentHash`.
|
||||
|
||||
```rust
|
||||
let x = "hello";
|
||||
assert_eq!(vanth::hash(&x).hex(), "ea8f163db38682925e4491c5e58d4bb3506ef8c14eb78a86e908c5624a67200f");
|
||||
```
|
||||
|
||||
Derive or implement the `vanth::Vanth` trait for types you want to store in the database.
|
||||
|
||||
```rust
|
||||
use serde::{Deserialize, Serialize};
|
||||
use vanth::Vanth;
|
||||
|
||||
#[derive(Deserialize, Serialize, Vanth)]
|
||||
struct Data {
|
||||
value: u32,
|
||||
}
|
||||
```
|
||||
|
||||
This generates a method `Vanth::ty()` which returns a `vanth::Ty`. This should represent the type's fully qualified name - its module path followed by the type itself and any generics it has. E.g. `Data::ty().to_string()` could return `"my::crate::module::Data"`.
|
||||
|
||||
The derive macro only works for basic types right now and is not implemented for `std` types. Moving or renaming types or modules will change the type name, necessitating a database migration. This is not supported yet.
|
||||
|
||||
This should be used with caution. There are good reasons why `std::any::TypeId` is opaque.
|
||||
|
||||
### Database storage
|
||||
|
||||
```rust
|
||||
use vanth::store::{Store, StoreParams};
|
||||
|
||||
// Or use `Store::in_memory`.
|
||||
let mut store = Store::sqlite_from_path(
|
||||
"path/to/my_database.sqlite".into(),
|
||||
StoreParams { create_if_not_exists: true, ..Default::default() },
|
||||
);
|
||||
let hash = store.write(Data { value: 5 }).unwrap();
|
||||
let my_data: Data = store.get_from_hash(hash).unwrap();
|
||||
```
|
||||
|
||||
## CLI usage
|
||||
|
||||
You can run the Vanth CLI with Nix using `nix run git+https://git.mascully.com/mascully/vanth.git`.
|
||||
|
||||
Use `--` to pass arguments, e.g. `nix run git+https://git.mascully.com/mascully/vanth.git -- --help`.
|
||||
|
||||
### Syntax
|
||||
|
||||
Write a component to the database:
|
||||
|
||||
```bash
|
||||
$ vanth write --db /path/to/db.sqlite --ty my::type::Name --value '{ "field": "value" }'
|
||||
a1cf81d8afe4e72604ea5c13bbd9b6cce14bd98c3a2f036f7156c4464a88ec09
|
||||
```
|
||||
|
||||
Get a component from the database:
|
||||
|
||||
```bash
|
||||
$ vanth get --db /path/to/db.sqlite --ty my::type::Name a1cf81d8afe4e72604ea5c13bbd9b6cce14bd98c3a2f036f7156c4464a88ec09
|
||||
{"field":"value"}
|
||||
```
|
||||
|
||||
Get all components of a type from the database:
|
||||
|
||||
```bash
|
||||
$ vanth get-all --db /path/to/db.sqlite --ty my::type::Name
|
||||
{"field":"value1"}
|
||||
{"field":"value2"}
|
||||
{"field":"value3"}
|
||||
```
|
||||
|
||||
Delete a component by its content hash:
|
||||
|
||||
```bash
|
||||
$ vanth delete --db /path/to/db.sqlite ea8f163db38682925e4491c5e58d4bb3506ef8c14eb78a86e908c5624a67200f
|
||||
```
|
||||
|
||||
Delete all components of a type:
|
||||
|
||||
```bash
|
||||
$ vanth delete-all --db /path/to/db.sqlite --ty my::type::Name
|
||||
```
|
||||
See the [crate-level documentation](crates/vanth/README.md) for more details.
|
||||
|
|
10
crates/bevy_vanth/Cargo.toml
Normal file
10
crates/bevy_vanth/Cargo.toml
Normal file
|
@ -0,0 +1,10 @@
|
|||
[package]
|
||||
name = "bevy_vanth"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
bevy_ecs.workspace = true
|
||||
bevy_app.workspace = true
|
||||
serde.workspace = true
|
||||
vanth = { path = "../vanth" }
|
56
crates/bevy_vanth/src/lib.rs
Normal file
56
crates/bevy_vanth/src/lib.rs
Normal file
|
@ -0,0 +1,56 @@
|
|||
use bevy_ecs::{component::Component, system::Query, world::World as BevyWorld};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use vanth::{ActiveTask, Module, Root, Task, Vanth, entity::TaskId};
|
||||
|
||||
// TODO: Should there be one plugin per root, or one global plugin with many root components?
|
||||
// #[derive(Component)]
|
||||
// pub struct VanthRoot {}
|
||||
|
||||
pub struct BevyVanthPlugin {
|
||||
root: Root,
|
||||
}
|
||||
|
||||
impl BevyVanthPlugin {
|
||||
pub fn from_root(root: Root) -> Self {
|
||||
Self { root }
|
||||
}
|
||||
}
|
||||
|
||||
// impl bevy_app::Plugin for BevyVanthPlugin {
|
||||
// fn build(&self, app: &mut bevy_app::App) {
|
||||
// app.add_systems(bevy_app::Update, process_active_tasks);
|
||||
// }
|
||||
// }
|
||||
|
||||
pub struct BevyPluginModule<M: Module>(M);
|
||||
|
||||
impl<M> From<M> for BevyPluginModule<M>
|
||||
where
|
||||
M: Module + Send + Sync,
|
||||
{
|
||||
fn from(value: M) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<M> bevy_app::Plugin for BevyPluginModule<M>
|
||||
where
|
||||
M: Module + Send + Sync + 'static,
|
||||
{
|
||||
fn build(&self, app: &mut bevy_app::App) {
|
||||
// app.add_systems(bevy_app::FixedPreUpdate, run_vanth).finish();
|
||||
}
|
||||
}
|
||||
|
||||
fn process_active_tasks(tasks: Query<()>) {}
|
||||
|
||||
pub fn run_task<I, O, T: Task<Input = I, Output = O>>(world: &mut BevyWorld, task: T) -> TaskId {
|
||||
todo!()
|
||||
}
|
||||
|
||||
pub fn creation_bevy_plugin<M>(module: M) -> impl bevy_app::Plugin
|
||||
where
|
||||
M: Module + Send + Sync + 'static,
|
||||
{
|
||||
BevyPluginModule(module)
|
||||
}
|
42
crates/bevy_vanth/tests/test.rs
Normal file
42
crates/bevy_vanth/tests/test.rs
Normal file
|
@ -0,0 +1,42 @@
|
|||
use bevy_app::App;
|
||||
use bevy_vanth::BevyPluginModule;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use vanth::{Module, Vanth};
|
||||
|
||||
mod bevy_tests {
|
||||
use bevy_app::Plugin;
|
||||
use vanth::Root;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[derive(Deserialize, Serialize, Vanth)]
|
||||
struct TestData {
|
||||
number: u32,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Vanth)]
|
||||
struct TestTask {
|
||||
a: TestData,
|
||||
b: TestData,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Vanth)]
|
||||
struct TestModule {}
|
||||
|
||||
impl Module for TestModule {
|
||||
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bevy() {
|
||||
let root = Root::local();
|
||||
let module = TestModule {};
|
||||
let module_bevy_plugin = bevy_vanth::creation_bevy_plugin(module);
|
||||
let bevy_vanth_plugin = bevy_vanth::BevyVanthPlugin::from_root(root);
|
||||
|
||||
let mut app = App::new();
|
||||
|
||||
app.add_plugins(module_bevy_plugin);
|
||||
// app.add_plugins(bevy_vanth_plugin);
|
||||
}
|
||||
}
|
0
crates/cli/tests/nu/mod.rs
Normal file
0
crates/cli/tests/nu/mod.rs
Normal file
|
@ -7,6 +7,8 @@ use serde_json::{Value, json};
|
|||
use tempfile::tempdir;
|
||||
use vanth::{ContentHash, Vanth, hash as vanth_hash};
|
||||
|
||||
pub mod nu;
|
||||
|
||||
fn run_vanth(args: &[&str], input: Option<&str>) -> (String, String, i32) {
|
||||
let mut cmd = Command::cargo_bin("vanth").unwrap();
|
||||
let output = cmd.args(args).write_stdin(input.unwrap_or("")).output().unwrap();
|
|
@ -16,6 +16,10 @@ blake3.workspace = true
|
|||
vanth_derive = { path = "../vanth_derive" }
|
||||
rusqlite.workspace = true
|
||||
tracing.workspace = true
|
||||
rand = { workspace = true }
|
||||
smol.workspace = true
|
||||
async-process.workspace = true
|
||||
async-trait.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = { workspace = true }
|
||||
|
|
85
crates/vanth/README.md
Normal file
85
crates/vanth/README.md
Normal file
|
@ -0,0 +1,85 @@
|
|||
# Vanth
|
||||
|
||||
## Library usage
|
||||
|
||||
Any type that implements `serde::Serialize` can be hashed using `vanth::hash` to produce a `vanth::ContentHash`.
|
||||
|
||||
```rust
|
||||
let x = "hello";
|
||||
assert_eq!(vanth::hash(&x).hex(), "ea8f163db38682925e4491c5e58d4bb3506ef8c14eb78a86e908c5624a67200f");
|
||||
```
|
||||
|
||||
Derive or implement the `vanth::Vanth` trait for types you want to store in the database.
|
||||
|
||||
```rust
|
||||
use serde::{Deserialize, Serialize};
|
||||
use vanth::Vanth;
|
||||
|
||||
#[derive(Deserialize, Serialize, Vanth)]
|
||||
struct Data {
|
||||
value: u32,
|
||||
}
|
||||
```
|
||||
|
||||
This generates a method `Vanth::ty()` which returns a `vanth::Ty`. This should represent the type's fully qualified name - its module path followed by the type itself and any generics it has. E.g. `Data::ty().to_string()` could return `"my::crate::module::Data"`.
|
||||
|
||||
The derive macro only works for basic types right now and is not implemented for `std` types. Moving or renaming types or modules will change the type name, necessitating a database migration. This is not supported yet.
|
||||
|
||||
This should be used with caution. There are good reasons why `std::any::TypeId` is opaque.
|
||||
|
||||
### Database storage
|
||||
|
||||
```rust
|
||||
use vanth::store::{Store, StoreParams};
|
||||
|
||||
// Or use `Store::in_memory`.
|
||||
let mut store = Store::sqlite_from_path(
|
||||
"path/to/my_database.sqlite",
|
||||
StoreParams { create_if_not_exists: true, ..Default::default() },
|
||||
);
|
||||
let hash = store.write(Data { value: 5 }).unwrap();
|
||||
let my_data: Data = store.get_from_hash(hash).unwrap();
|
||||
```
|
||||
|
||||
## CLI usage
|
||||
|
||||
You can run the Vanth CLI with Nix using `nix run git+https://git.mascully.com/mascully/vanth.git`.
|
||||
|
||||
Use `--` to pass arguments, e.g. `nix run git+https://git.mascully.com/mascully/vanth.git -- --help`.
|
||||
|
||||
### Syntax
|
||||
|
||||
Write a component to the database:
|
||||
|
||||
```bash
|
||||
$ vanth write --db /path/to/db.sqlite --ty my::type::Name --value '{ "field": "value" }'
|
||||
a1cf81d8afe4e72604ea5c13bbd9b6cce14bd98c3a2f036f7156c4464a88ec09
|
||||
```
|
||||
|
||||
Get a component from the database:
|
||||
|
||||
```bash
|
||||
$ vanth get --db /path/to/db.sqlite --ty my::type::Name a1cf81d8afe4e72604ea5c13bbd9b6cce14bd98c3a2f036f7156c4464a88ec09
|
||||
{"field":"value"}
|
||||
```
|
||||
|
||||
Get all components of a type from the database:
|
||||
|
||||
```bash
|
||||
$ vanth get-all --db /path/to/db.sqlite --ty my::type::Name
|
||||
{"field":"value1"}
|
||||
{"field":"value2"}
|
||||
{"field":"value3"}
|
||||
```
|
||||
|
||||
Delete a component by its content hash:
|
||||
|
||||
```bash
|
||||
$ vanth delete --db /path/to/db.sqlite ea8f163db38682925e4491c5e58d4bb3506ef8c14eb78a86e908c5624a67200f
|
||||
```
|
||||
|
||||
Delete all components of a type:
|
||||
|
||||
```bash
|
||||
$ vanth delete-all --db /path/to/db.sqlite --ty my::type::Name
|
||||
```
|
|
@ -1,12 +1,12 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
use vanth::{hash, Vanth};
|
||||
// use serde::{Deserialize, Serialize};
|
||||
// use vanth::{hash, Vanth};
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, Vanth)]
|
||||
struct Foo {
|
||||
value: i32,
|
||||
}
|
||||
// #[derive(Clone, Debug, Deserialize, Serialize, Vanth)]
|
||||
// struct Foo {
|
||||
// value: i32,
|
||||
// }
|
||||
|
||||
fn main() {
|
||||
let x = "hello";
|
||||
println!("Hash: {:?}", hash(&x).hex());
|
||||
// let x = "hello";
|
||||
// println!("Hash: {:?}", hash(&x).hex());
|
||||
}
|
||||
|
|
11
crates/vanth/src/compress.rs
Normal file
11
crates/vanth/src/compress.rs
Normal file
|
@ -0,0 +1,11 @@
|
|||
pub trait Compress {
|
||||
type Output;
|
||||
|
||||
fn compress(&self) -> Self::Output;
|
||||
}
|
||||
|
||||
pub trait Decompress {
|
||||
type Output;
|
||||
|
||||
fn decompress(&self) -> Self::Output;
|
||||
}
|
1
crates/vanth/src/ecc.rs
Normal file
1
crates/vanth/src/ecc.rs
Normal file
|
@ -0,0 +1 @@
|
|||
//! Error-correcting codes or something.
|
|
@ -1,11 +1,13 @@
|
|||
use crate::hashing_serializer::{self, HashingSerializer};
|
||||
use crate::{hash, ComponentMarker, ContentHash, ModuleMarker, Task, TaskMarker, Ty, Vanth};
|
||||
use rand::TryRngCore;
|
||||
use rand::rngs::OsRng;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::{self, Debug, Display};
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use crate::hash;
|
||||
use crate::hashing_serializer::{self, HashingSerializer};
|
||||
|
||||
#[derive(Clone, Copy, Deserialize)]
|
||||
pub struct EntityId([u8; 32]);
|
||||
|
||||
|
@ -15,8 +17,14 @@ impl From<String> for EntityId {
|
|||
}
|
||||
}
|
||||
|
||||
pub type TaskId = Id<TaskMarker>;
|
||||
|
||||
pub type ModuleId = Id<ModuleMarker>;
|
||||
|
||||
pub type ComponentId = Id<ComponentMarker>;
|
||||
|
||||
/// A generic identifier type that can be used for different entity types
|
||||
#[derive(Clone, Copy, Serialize, Deserialize)]
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Id<T: ?Sized> {
|
||||
/// The raw identifier value
|
||||
pub value: [u8; 32],
|
||||
|
@ -25,6 +33,20 @@ pub struct Id<T: ?Sized> {
|
|||
_marker: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T: ?Sized> Copy for Id<T> {}
|
||||
|
||||
impl<T: ?Sized> Clone for Id<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self::new(self.value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized> std::fmt::Debug for Id<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "Id<{}>", std::any::type_name::<T>())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized> Id<T> {
|
||||
/// Create a new ID with the given value
|
||||
pub fn new(value: [u8; 32]) -> Self {
|
||||
|
@ -34,17 +56,15 @@ impl<T: ?Sized> Id<T> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Create an ID from a u64 (fills only first 8 bytes)
|
||||
pub fn zero() -> Self {
|
||||
Self::new([0; 32])
|
||||
}
|
||||
|
||||
/// Generate a random ID
|
||||
pub fn random() -> Self {
|
||||
let mut value = [0u8; 32];
|
||||
// Simple random generation for demonstration
|
||||
for i in 0..32 {
|
||||
value[i] = (std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_nanos() as u8)
|
||||
.wrapping_add(i as u8);
|
||||
}
|
||||
OsRng.try_fill_bytes(&mut value).unwrap();
|
||||
Self::new(value)
|
||||
}
|
||||
|
||||
|
@ -67,42 +87,6 @@ impl<T: ?Sized> Id<T> {
|
|||
}
|
||||
Self::new(bytes)
|
||||
}
|
||||
|
||||
/// Convert the ID to a pair of u128 values for handling larger values
|
||||
/// Returns (high_bits, low_bits)
|
||||
pub fn to_u128_pair(&self) -> (u128, u128) {
|
||||
let mut high = 0u128;
|
||||
let mut low = 0u128;
|
||||
|
||||
// First 16 bytes go to low
|
||||
for i in 0..16 {
|
||||
low |= (self.value[i] as u128) << (i * 8);
|
||||
}
|
||||
|
||||
// Next 16 bytes go to high
|
||||
for i in 0..16 {
|
||||
high |= (self.value[i + 16] as u128) << (i * 8);
|
||||
}
|
||||
|
||||
(high, low)
|
||||
}
|
||||
|
||||
/// Create an ID from a pair of u128 values
|
||||
pub fn from_u128_pair(high: u128, low: u128) -> Self {
|
||||
let mut bytes = [0u8; 32];
|
||||
|
||||
// Low bits fill first 16 bytes
|
||||
for i in 0..16 {
|
||||
bytes[i] = ((low >> (i * 8)) & 0xFF) as u8;
|
||||
}
|
||||
|
||||
// High bits fill next 16 bytes
|
||||
for i in 0..16 {
|
||||
bytes[i + 16] = ((high >> (i * 8)) & 0xFF) as u8;
|
||||
}
|
||||
|
||||
Self::new(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized> PartialEq for Id<T> {
|
||||
|
@ -119,34 +103,28 @@ impl<T: ?Sized> Hash for Id<T> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized> Debug for Id<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let (high, low) = self.to_u128_pair();
|
||||
write!(f, "Id<{}>({:016x}{:016x})", std::any::type_name::<T>(), high, low)
|
||||
}
|
||||
#[derive(Debug)]
|
||||
pub struct Entity {
|
||||
pub id: Id<Entity>,
|
||||
pub components: HashMap<Ty, Box<dyn EntityComponent>>,
|
||||
}
|
||||
|
||||
impl<T: ?Sized> Display for Id<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let (high, low) = self.to_u128_pair();
|
||||
write!(f, "{:016x}{:016x}", high, low)
|
||||
}
|
||||
pub trait EntityComponent: Debug {}
|
||||
|
||||
impl<T> EntityComponent for T where T: Component + Debug {}
|
||||
|
||||
pub trait Component: Clone + Vanth {
|
||||
fn component_id(&self) -> &'static str;
|
||||
}
|
||||
|
||||
pub struct ContentHash {
|
||||
value: [u8; 32],
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, Vanth)]
|
||||
pub struct EntityContents {
|
||||
components: Vec<ComponentContents>,
|
||||
}
|
||||
|
||||
impl ContentHash {}
|
||||
|
||||
pub trait Entity {
|
||||
fn entity_id() -> Id<dyn Entity>
|
||||
where
|
||||
Self: Sized;
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, Vanth)]
|
||||
pub struct ComponentContents {
|
||||
pub content_hash: ContentHash,
|
||||
pub ty: Ty,
|
||||
pub data: Vec<u8>,
|
||||
}
|
||||
|
||||
pub trait Component: Send + Sync + 'static {
|
||||
fn component_id() -> &'static str;
|
||||
}
|
||||
|
||||
// impl<'de, T> Component for T where T: Serialize + Deserialize<'de> {}
|
||||
|
|
|
@ -1,57 +1,44 @@
|
|||
use std::marker::PhantomData;
|
||||
#![doc = include_str!("../README.md")]
|
||||
|
||||
use bevy_ecs::{prelude::*, query::QueryData};
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
marker::PhantomData,
|
||||
};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use bevy_ecs::{
|
||||
prelude::{Component as BevyComponent, Entity as BevyEntity},
|
||||
query::QueryData,
|
||||
};
|
||||
use serde::{Deserialize, Serialize, de::DeserializeOwned};
|
||||
|
||||
use crate::entity::EntityId;
|
||||
use crate::{
|
||||
entity::{Entity, EntityId, Id, TaskId},
|
||||
store::Store,
|
||||
};
|
||||
|
||||
pub mod compress;
|
||||
pub mod ecc;
|
||||
pub mod entity;
|
||||
pub mod hashing_serializer;
|
||||
pub mod network;
|
||||
pub mod nix;
|
||||
pub mod std_impls;
|
||||
pub mod store;
|
||||
|
||||
pub use hashing_serializer::hash;
|
||||
pub use network::Node;
|
||||
pub use vanth_derive::Vanth;
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub enum Error {
|
||||
Other(String),
|
||||
}
|
||||
|
||||
|
||||
/// A view of all of the [`Node`]s in a cluster.
|
||||
pub struct Network {
|
||||
// TODO
|
||||
}
|
||||
|
||||
/// A Vanth server.
|
||||
pub struct Node {
|
||||
// TODO
|
||||
}
|
||||
|
||||
impl Node {
|
||||
pub fn new() -> Self {
|
||||
Self {}
|
||||
impl From<smol::io::Error> for Error {
|
||||
fn from(value: smol::io::Error) -> Self {
|
||||
Self::Other(value.to_string())
|
||||
}
|
||||
|
||||
pub fn entity_count(&self) -> usize {
|
||||
todo!()
|
||||
}
|
||||
|
||||
pub fn run() {
|
||||
todo!()
|
||||
}
|
||||
|
||||
pub fn save(entity_id: impl Into<EntityId>) -> Result<()> {
|
||||
// TODO
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// pub fn load(entity_id: impl Into<EntityId>) -> Result<Option<EntityContents>> {
|
||||
// // TODO
|
||||
// Ok(None)
|
||||
// }
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
|
@ -75,65 +62,57 @@ pub struct Value {
|
|||
data: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Hash)]
|
||||
pub struct EventTy(Ty);
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Hash)]
|
||||
pub struct TaskTy(Ty);
|
||||
|
||||
/// A wrapper for the fully-qualified name of a Rust type. This should be univerisally unique for a given type within a
|
||||
/// given project.
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, Eq, Hash)]
|
||||
#[derive(Clone, Deserialize, Serialize, PartialEq, Eq, Hash)]
|
||||
pub struct Ty {
|
||||
pub path: Vec<String>,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for Ty {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl ToString for Ty {
|
||||
fn to_string(&self) -> String {
|
||||
self.path.join("::")
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Ty {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.path == other.path
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AsRef<str>> PartialEq<T> for Ty {
|
||||
fn eq(&self, other: &T) -> bool {
|
||||
self.to_string() == *other.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Deserialize, BevyComponent, Serialize, PartialEq, Eq, Hash, Vanth)]
|
||||
pub struct VanthVersion {
|
||||
pub version_number: u64,
|
||||
pub extra_data: u64,
|
||||
}
|
||||
|
||||
/// All types stored in the Vanth database should implement this trait.
|
||||
pub trait Vanth {
|
||||
/// Get the [`Ty`] representing this type.
|
||||
fn ty() -> Ty;
|
||||
}
|
||||
|
||||
macro_rules! impl_vanth {
|
||||
// TODO
|
||||
() => {};
|
||||
}
|
||||
|
||||
// impl_vanth!(std::string::String)
|
||||
|
||||
// TODO: Impl for different tuple sizes
|
||||
pub trait VanthTuple {}
|
||||
|
||||
// #[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
// pub struct EntityContents {
|
||||
// components: Vec<ComponentContents>
|
||||
// }
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ComponentContents<T: Vanth> {
|
||||
content_hash: ContentHash,
|
||||
data: Vec<u8>,
|
||||
_marker: PhantomData<T>,
|
||||
}
|
||||
|
||||
// use a macro to implement VanthTuiple here.
|
||||
|
||||
/// A 32 byte BLAKE3 hash representing the contents of some value.
|
||||
///
|
||||
/// This can be generated with the [`hash`] function.
|
||||
#[derive(Copy, Clone, Debug, Deserialize, Component, Serialize, PartialEq, Eq, Hash)]
|
||||
#[derive(Copy, Clone, Debug, Deserialize, BevyComponent, Serialize, PartialEq, Eq, Hash, Vanth)]
|
||||
pub struct ContentHash {
|
||||
pub hash: [u8; 32],
|
||||
}
|
||||
|
@ -144,13 +123,13 @@ impl ContentHash {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Component, Serialize)]
|
||||
#[derive(Clone, Debug, Deserialize, BevyComponent, Serialize)]
|
||||
pub struct Reference<T: Clone + Serialize> {
|
||||
value: ReferenceValue,
|
||||
_marker: PhantomData<T>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Component, Serialize)]
|
||||
#[derive(Clone, Debug, Deserialize, BevyComponent, Serialize)]
|
||||
pub enum ReferenceValue {
|
||||
Absent,
|
||||
Retrieving(ReferenceRetrievalTask),
|
||||
|
@ -167,7 +146,7 @@ impl<T: Clone + Serialize> Reference<T> {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Component, Clone, Debug, Deserialize, Serialize)]
|
||||
#[derive(BevyComponent, Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct ReferenceRetrievalTask {}
|
||||
|
||||
impl Future for ReferenceRetrievalTask {
|
||||
|
@ -182,5 +161,157 @@ pub struct Handle<T> {
|
|||
_marker: PhantomData<T>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait Task: Vanth + Serialize + DeserializeOwned {
|
||||
type Input: Vanth + Serialize;
|
||||
type Output: Vanth + Serialize + DeserializeOwned;
|
||||
type Resource: Vanth;
|
||||
}
|
||||
|
||||
pub struct TaskMarker;
|
||||
pub struct ModuleMarker;
|
||||
pub struct ComponentMarker;
|
||||
|
||||
#[derive(BevyComponent)]
|
||||
pub enum ActiveTask<T: Task> {
|
||||
Requested(T::Input),
|
||||
// Running(Box<dyn Future<Output = T::Output> + Send + Sync>),
|
||||
// TODO
|
||||
Running(TaskId),
|
||||
Completed(T::Output),
|
||||
}
|
||||
|
||||
pub struct TaskCache {
|
||||
pub values: HashMap<TaskId, Vec<u8>>,
|
||||
}
|
||||
|
||||
/// A representation of the approximate computational cost of completing a task.
|
||||
pub struct Cost {
|
||||
value: f64,
|
||||
}
|
||||
|
||||
/// A world which Vanth entities live in. Lifetimes `'v` of [`Vanth<'v>`] types are tied to the lifetime of the `Root`.
|
||||
pub struct Root {}
|
||||
#[derive(Debug)]
|
||||
pub struct Root {
|
||||
authority: Authority,
|
||||
current_epoch: u64,
|
||||
store: Store,
|
||||
registered_components: HashSet<Ty>,
|
||||
registered_modules: HashMap<Ty, BoxedModule>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct BoxedModule {}
|
||||
|
||||
impl Root {
|
||||
pub fn local() -> Self {
|
||||
Self::new(Authority::Local, Store::in_memory().unwrap())
|
||||
}
|
||||
|
||||
pub fn new(authority: Authority, store: Store) -> Self {
|
||||
Self {
|
||||
authority,
|
||||
current_epoch: 0,
|
||||
store,
|
||||
registered_components: HashSet::new(),
|
||||
registered_modules: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn register_module<M: Module>(&mut self) {
|
||||
let boxed_module = BoxedModule {};
|
||||
self.registered_modules.insert(M::ty(), boxed_module);
|
||||
}
|
||||
|
||||
pub async fn run_module_task<R, M, T>(&mut self, module: &mut M, input: T::Input) -> T::Output
|
||||
where
|
||||
M: Module + ProvideResource<R> + ExecuteTask<T>,
|
||||
T: Task<Resource = R>,
|
||||
{
|
||||
let mut resources = module.get_resource();
|
||||
module.execute_task(input, &mut resources).await
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Module: Vanth + Serialize + DeserializeOwned {}
|
||||
|
||||
#[async_trait]
|
||||
pub trait ExecuteTask<T>
|
||||
where
|
||||
T: Task,
|
||||
{
|
||||
async fn execute_task(&self, input: T::Input, resource: &mut T::Resource) -> T::Output;
|
||||
}
|
||||
|
||||
pub trait ProvideResource<R>: Module {
|
||||
fn get_resource(&self) -> R;
|
||||
}
|
||||
|
||||
// TODO: Implement for other tuple sizes.
|
||||
impl<M, R1, R2> ProvideResource<(R1, R2)> for M
|
||||
where
|
||||
M: Module + ProvideResource<R1> + ProvideResource<R2>,
|
||||
{
|
||||
fn get_resource(&self) -> (R1, R2) {
|
||||
(self.get_resource(), self.get_resource())
|
||||
}
|
||||
}
|
||||
|
||||
impl<M, R1, R2, R3> ProvideResource<(R1, R2, R3)> for M
|
||||
where
|
||||
M: Module + ProvideResource<R1> + ProvideResource<R2> + ProvideResource<R3>,
|
||||
{
|
||||
fn get_resource(&self) -> (R1, R2, R3) {
|
||||
(self.get_resource(), self.get_resource(), self.get_resource())
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Shell: Module {
|
||||
// <Self as Module>::Monads
|
||||
}
|
||||
|
||||
pub struct ModuleRegistration {
|
||||
// types: Vec<Ty>,
|
||||
// events: Vec<EventTy>,
|
||||
handler: Box<dyn ModuleRegistrationHandler>,
|
||||
}
|
||||
|
||||
impl ModuleRegistration {
|
||||
fn add_type<T: Vanth>(&mut self) {
|
||||
self.handler.add_type(T::ty());
|
||||
}
|
||||
|
||||
fn add_task<T: Task>(&mut self) -> TaskId {
|
||||
todo!()
|
||||
// self.handler.add_task(T::Input::ty(), T::Output::ty())
|
||||
}
|
||||
|
||||
fn add_resource<R: Vanth>(&mut self) {
|
||||
self.handler.add_resource(R::ty())
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ModuleRegistrationHandler {
|
||||
fn add_type(&self, ty: Ty);
|
||||
fn add_task(&self, input_ty: Ty, output_ty: Ty) -> TaskId;
|
||||
fn add_resource(&self, resource_ty: Ty);
|
||||
}
|
||||
|
||||
pub trait TaskSystem {}
|
||||
|
||||
// impl <F> TaskSystem for F where F: Fn()
|
||||
|
||||
impl ModuleRegistration {}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, Vanth)]
|
||||
pub enum Authority {
|
||||
/// No networking or synchronization.
|
||||
Local,
|
||||
Master(MasterAuthority),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, Vanth)]
|
||||
pub struct MasterAuthority {
|
||||
// TODO
|
||||
public_key: Vec<u8>,
|
||||
}
|
||||
|
|
145
crates/vanth/src/network.rs
Normal file
145
crates/vanth/src/network.rs
Normal file
|
@ -0,0 +1,145 @@
|
|||
use std::collections::HashMap;
|
||||
use std::net::Ipv6Addr;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use smol::net::UdpSocket;
|
||||
|
||||
use crate::entity::{Entity, EntityContents};
|
||||
use crate::{ContentHash, Error};
|
||||
use crate::{
|
||||
Vanth,
|
||||
entity::{EntityId, Id},
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum NetworkError {
|
||||
Timeout {
|
||||
duration_waited_secs: f64,
|
||||
},
|
||||
Other(String)
|
||||
}
|
||||
|
||||
pub struct Packet {
|
||||
pub source_node: Id<Node>,
|
||||
pub signature: Vec<u8>,
|
||||
pub message: Message,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, Vanth)]
|
||||
pub struct Message {
|
||||
pub events: Vec<Event>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, Vanth)]
|
||||
pub enum Event {
|
||||
RequestValue(ContentHash),
|
||||
UpsertEntity(EntityContents),
|
||||
DeleteEntity(EntityContents),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, Vanth)]
|
||||
pub enum Address {
|
||||
Uri(String),
|
||||
Id(Id<Address>),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, Vanth)]
|
||||
pub struct NetworkParams {
|
||||
id: Id<Network>,
|
||||
initial_peers: Vec<Address>,
|
||||
}
|
||||
|
||||
/// A view of all of the [`Node`]s in a cluster.
|
||||
#[derive(Debug)]
|
||||
pub struct Network {
|
||||
// TODO
|
||||
id: Id<Network>,
|
||||
peers: HashMap<Id<Node>, Peer>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, Vanth)]
|
||||
pub struct Peer {
|
||||
id: Id<Node>,
|
||||
last_confirmed_epoch: u64,
|
||||
}
|
||||
|
||||
/// A Vanth server.
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, Vanth)]
|
||||
pub struct Node {
|
||||
id: Id<Node>,
|
||||
config: NodeConfig,
|
||||
ip_address: Option<Ipv6Addr>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, Vanth)]
|
||||
pub struct NodeConfig {
|
||||
pub port: u16,
|
||||
networks: Vec<NetworkParams>,
|
||||
}
|
||||
|
||||
impl Default for NodeConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
// Sidereal orbital period of Vanth in 100's of seconds.
|
||||
port: 8241,
|
||||
networks: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Node {
|
||||
pub fn from_config(config: impl Into<NodeConfig>) -> Self {
|
||||
Self {
|
||||
id: Id::random(),
|
||||
config: config.into(),
|
||||
ip_address: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_id(id: Id<Node>, config: impl Into<NodeConfig>) -> Self {
|
||||
Self {
|
||||
id,
|
||||
config: config.into(),
|
||||
ip_address: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn entity_count(&self) -> usize {
|
||||
todo!()
|
||||
}
|
||||
|
||||
pub async fn run(self) -> Result<(), Error> {
|
||||
let socket = UdpSocket::bind(("127.0.0.1", self.config.port)).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn save(entity_id: impl Into<EntityId>) -> Result<(), Error> {
|
||||
// TODO
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Attempt to open a UDP connection to the provided [`Node`].
|
||||
pub async fn connect_to(network: Id<Network>, node: &Node) -> Result<(), Error> {
|
||||
// TODO
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// pub fn load(entity_id: impl Into<EntityId>) -> Result<Option<EntityContents>> {
|
||||
// // TODO
|
||||
// Ok(None)
|
||||
// }
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait Backend {
|
||||
type ConnectionHandle;
|
||||
type Error;
|
||||
|
||||
// TODO: Improve efficiency or something.
|
||||
async fn connect<'a>(&mut self, address: &Address) -> Result<Self::ConnectionHandle, Self::Error>;
|
||||
async fn close(&mut self, connection: Self::ConnectionHandle) -> Result<(), Self::Error>;
|
||||
async fn poll(&mut self) -> impl Iterator<Item = Result<(Self::ConnectionHandle, Vec<u8>), Self::Error>>;
|
||||
async fn send(&mut self, data: &[u8]) -> Result<(), Self::Error>;
|
||||
}
|
68
crates/vanth/src/std_impls.rs
Normal file
68
crates/vanth/src/std_impls.rs
Normal file
|
@ -0,0 +1,68 @@
|
|||
use super::{Ty, Vanth};
|
||||
|
||||
impl Vanth for () {
|
||||
fn ty() -> Ty {
|
||||
Ty {
|
||||
path: vec!["()".into()],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_tuple_vanth {
|
||||
($($T:ident),+ $(,)?) => {
|
||||
impl<$($T),+> Vanth for ($($T,)+)
|
||||
where
|
||||
$($T: Vanth),+
|
||||
{
|
||||
fn ty() -> Ty {
|
||||
// Join the inner type strings with ", " and wrap in parentheses.
|
||||
Ty {
|
||||
path: vec![format!(
|
||||
"({})",
|
||||
vec![$($T::ty().to_string()),+].join(", ")
|
||||
)],
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_tuple_vanth!(T1);
|
||||
impl_tuple_vanth!(T1, T2);
|
||||
impl_tuple_vanth!(T1, T2, T3);
|
||||
impl_tuple_vanth!(T1, T2, T3, T4);
|
||||
impl_tuple_vanth!(T1, T2, T3, T4, T5);
|
||||
impl_tuple_vanth!(T1, T2, T3, T4, T5, T6);
|
||||
impl_tuple_vanth!(T1, T2, T3, T4, T5, T6, T7);
|
||||
impl_tuple_vanth!(T1, T2, T3, T4, T5, T6, T7, T8);
|
||||
impl_tuple_vanth!(T1, T2, T3, T4, T5, T6, T7, T8, T9);
|
||||
impl_tuple_vanth!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10);
|
||||
impl_tuple_vanth!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11);
|
||||
impl_tuple_vanth!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12);
|
||||
|
||||
impl<T> Vanth for Option<T>
|
||||
where
|
||||
T: Vanth,
|
||||
{
|
||||
fn ty() -> Ty {
|
||||
Ty {
|
||||
path: vec![format!("Option<{}>", T::ty().to_string())],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, E> Vanth for Result<T, E>
|
||||
where
|
||||
T: Vanth,
|
||||
E: Vanth,
|
||||
{
|
||||
fn ty() -> Ty {
|
||||
Ty {
|
||||
path: vec![format!(
|
||||
"Result<{}, {}>",
|
||||
T::ty().to_string(),
|
||||
E::ty().to_string()
|
||||
)],
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,8 @@
|
|||
use std::{collections::HashMap, marker::PhantomData, path::PathBuf};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
marker::PhantomData,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use rusqlite::{Connection, named_params, params};
|
||||
|
||||
|
@ -6,7 +10,7 @@ use bevy_ecs::prelude::*;
|
|||
use serde::{Deserialize, Serialize, de::DeserializeOwned};
|
||||
use tracing::trace;
|
||||
|
||||
use crate::{ComponentContents, ContentHash, Ty, Vanth, hash};
|
||||
use crate::{ContentHash, Ty, Vanth, entity::ComponentContents, hash};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Store {
|
||||
|
@ -58,7 +62,7 @@ impl Default for StoreParams {
|
|||
|
||||
impl Store {
|
||||
/// Use an SQLite backend with a database file at the provided path.
|
||||
pub fn sqlite_from_path(path: PathBuf, params: StoreParams) -> Result<Self> {
|
||||
pub fn sqlite_from_path(path: impl AsRef<Path>, params: StoreParams) -> Result<Self> {
|
||||
use rusqlite::OpenFlags;
|
||||
// Base flags for URI handling and disabling mutexes.
|
||||
let mut flags = OpenFlags::SQLITE_OPEN_URI | OpenFlags::SQLITE_OPEN_NO_MUTEX;
|
||||
|
@ -76,7 +80,7 @@ impl Store {
|
|||
}
|
||||
|
||||
// Open the SQLite connection with the computed flags.
|
||||
let connection = rusqlite::Connection::open_with_flags(path, flags)?;
|
||||
let connection = rusqlite::Connection::open_with_flags(path.as_ref(), flags)?;
|
||||
Ok(Self {
|
||||
backend: Box::new(Sqlite { connection }),
|
||||
})
|
||||
|
@ -85,7 +89,7 @@ impl Store {
|
|||
/// Use an in-memory backend.
|
||||
pub fn in_memory() -> Result<Self> {
|
||||
Ok(Self {
|
||||
backend: Box::new(Memory::new()),
|
||||
backend: Box::new(InMemoryStore::new()),
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -102,14 +106,14 @@ impl Store {
|
|||
self.backend.get_from_hash(ty, content_hash)
|
||||
}
|
||||
|
||||
pub fn get_all_of_type<T: Vanth>(&mut self) -> Result<Vec<ComponentContents<T>>> {
|
||||
pub fn get_all_of_type<T: Vanth>(&mut self) -> Result<Vec<ComponentContents>> {
|
||||
let raw_items = self.backend.get_all_of_ty(T::ty())?;
|
||||
let mut results = Vec::new();
|
||||
for (content_hash, data) in raw_items {
|
||||
results.push(ComponentContents {
|
||||
_marker: PhantomData,
|
||||
content_hash,
|
||||
data,
|
||||
ty: T::ty(),
|
||||
});
|
||||
}
|
||||
Ok(results)
|
||||
|
@ -153,6 +157,7 @@ pub struct Cache {
|
|||
// backend: Backend,
|
||||
}
|
||||
|
||||
// TODO: Change to storing entities (groups of components) instead of components.
|
||||
pub trait Backend: std::fmt::Debug + Send {
|
||||
fn get_from_hash(&mut self, ty: Ty, content_hash: ContentHash) -> Result<Option<Vec<u8>>>;
|
||||
|
||||
|
@ -229,7 +234,7 @@ impl Backend for Sqlite {
|
|||
|
||||
drop(statement);
|
||||
transaction.commit()?;
|
||||
|
||||
|
||||
Ok(results)
|
||||
}
|
||||
|
||||
|
@ -272,17 +277,17 @@ impl Backend for Sqlite {
|
|||
|
||||
/// In-memory storage with one table per type.
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct Memory {
|
||||
pub struct InMemoryStore {
|
||||
tables: HashMap<Ty, HashMap<ContentHash, Vec<u8>>>,
|
||||
}
|
||||
|
||||
impl Memory {
|
||||
impl InMemoryStore {
|
||||
pub fn new() -> Self {
|
||||
Self { tables: HashMap::new() }
|
||||
}
|
||||
}
|
||||
|
||||
impl Backend for Memory {
|
||||
impl Backend for InMemoryStore {
|
||||
fn get_from_hash(&mut self, ty: Ty, content_hash: ContentHash) -> Result<Option<Vec<u8>>> {
|
||||
Ok(self.tables.get(&ty).and_then(|table| table.get(&content_hash)).cloned())
|
||||
}
|
||||
|
@ -315,3 +320,13 @@ impl Backend for Memory {
|
|||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// One table per type. Keys and values are both blobs.
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct FsStore {
|
||||
path: PathBuf,
|
||||
}
|
||||
|
||||
// impl Backend for FsStore {
|
||||
|
||||
// }
|
||||
|
|
|
@ -1,60 +0,0 @@
|
|||
/// A 192-bit hash value.
|
||||
pub struct Hash([u8; 24]);
|
||||
|
||||
impl Hash {
|
||||
pub fn from_bytes(value: [u8; 24]) -> Self {
|
||||
Hash(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Hash {
|
||||
/// The Base52 representation of the hash. This will always be 34 ASCII characters long.
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let mut result = String::with_capacity(34);
|
||||
for chunk in self.0.chunks(4) {
|
||||
let mut value = 0u32;
|
||||
for (i, &byte) in chunk.iter().enumerate() {
|
||||
value |= (byte as u32) << (8 * i);
|
||||
}
|
||||
|
||||
for _ in 0..6 {
|
||||
let digit = value % 52;
|
||||
value /= 52;
|
||||
let c = if digit < 26 {
|
||||
(b'A' + digit as u8) as char
|
||||
} else {
|
||||
(b'a' + (digit - 26) as u8) as char
|
||||
};
|
||||
result.push(c);
|
||||
}
|
||||
}
|
||||
write!(f, "{}", result)
|
||||
}
|
||||
}
|
||||
|
||||
impl Hash {
|
||||
pub fn to_string_truncated(&self, length: usize) -> String {
|
||||
self.to_string()[0..length].to_string()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_hash_to_string() {
|
||||
let pairs = [(0u8, "AAAAAA"), (255u8, "ZZZZZZ"), (1u8, "AAAAAB")];
|
||||
let input = [0u8; 24];
|
||||
let hash = Hash::from_bytes(input);
|
||||
let result = hash.to_string();
|
||||
|
||||
assert_eq!(result.len(), 34);
|
||||
|
||||
// String should only contain A-Z and a-z
|
||||
assert!(result.chars().all(|c| c.is_ascii_alphabetic()));
|
||||
|
||||
// With all zero bytes, should start with 'AAAAAA'
|
||||
assert_eq!(&result[0..6], "AAAAAA");
|
||||
}
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
use bevy_ecs::component::Component;
|
||||
use serde::{Deserialize, Serialize, de::DeserializeOwned};
|
||||
use vanth::Vanth;
|
||||
|
||||
// TODO: derive `Vanth`
|
||||
#[derive(Debug, Deserialize, Component, Serialize)]
|
||||
struct Foo {}
|
||||
|
||||
#[test]
|
||||
fn test_derive() {
|
||||
#[derive(Deserialize, Serialize, Vanth)]
|
||||
struct Foo<T: Vanth> {
|
||||
field_a: i32,
|
||||
field_b: String,
|
||||
inner: T,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Vanth)]
|
||||
struct Bar {
|
||||
field_a: i32,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Vanth)]
|
||||
struct Qux<T: Vanth, S: Vanth> {
|
||||
field_a: i32,
|
||||
field_b: String,
|
||||
inner: T,
|
||||
inner_2: S,
|
||||
}
|
||||
|
||||
let base = "integration::derive::";
|
||||
|
||||
assert_eq!(Bar::ty(), format!("{base}Bar"));
|
||||
assert_eq!(Foo::<Bar>::ty(), format!("{base}Foo<{base}Bar>"));
|
||||
assert_eq!(
|
||||
Qux::<Bar, Foo<Bar>>::ty(),
|
||||
format!("{base}Qux<{base}Bar,{base}Foo<{base}Bar>>")
|
||||
);
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
use vanth::{Node, Reference};
|
||||
|
||||
mod derive;
|
||||
mod fs;
|
||||
mod reference;
|
||||
mod store;
|
47
crates/vanth/tests/net/mod.rs
Normal file
47
crates/vanth/tests/net/mod.rs
Normal file
|
@ -0,0 +1,47 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use smol::channel::{self, Receiver, Sender};
|
||||
use vanth::{
|
||||
entity::Id,
|
||||
network::{self, Address, Packet},
|
||||
};
|
||||
|
||||
struct TestNetBackend {
|
||||
sockets: HashMap<Id<Address>, (Sender<Packet>, Receiver<Packet>)>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl network::Backend for TestNetBackend {
|
||||
type ConnectionHandle = Id<Address>;
|
||||
type Error = String;
|
||||
|
||||
async fn connect<'a>(&mut self, address: &Address) -> Result<Self::ConnectionHandle, Self::Error> {
|
||||
let id = match address {
|
||||
Address::Id(id) => id,
|
||||
_ => unimplemented!(),
|
||||
};
|
||||
let socket = channel::unbounded();
|
||||
assert!(self.sockets.insert(*id, socket).is_none());
|
||||
Ok(*id)
|
||||
}
|
||||
|
||||
async fn close(&mut self, connection: Self::ConnectionHandle) -> Result<(), Self::Error> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
async fn poll(&mut self) -> impl Iterator<Item = Result<(Self::ConnectionHandle, Vec<u8>), Self::Error>> {
|
||||
todo!();
|
||||
[].into_iter()
|
||||
}
|
||||
|
||||
async fn send(&mut self, data: &[u8]) -> Result<(), Self::Error> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_network() {
|
||||
// let id_1 = Id::random();
|
||||
// let address_1 = Address::Id(id);
|
||||
}
|
138
crates/vanth/tests/test.rs
Normal file
138
crates/vanth/tests/test.rs
Normal file
|
@ -0,0 +1,138 @@
|
|||
use bevy_ecs::component::Component;
|
||||
use serde::{Deserialize, Serialize, de::DeserializeOwned};
|
||||
use vanth::{Node, Reference, Vanth};
|
||||
|
||||
mod fs;
|
||||
mod net;
|
||||
mod reference;
|
||||
mod store;
|
||||
|
||||
// TODO: derive `Vanth`
|
||||
#[derive(Debug, Deserialize, Component, Serialize)]
|
||||
struct Foo {}
|
||||
|
||||
#[test]
|
||||
fn test_derive() {
|
||||
mod vanth {}
|
||||
|
||||
use ::vanth::Vanth;
|
||||
|
||||
#[derive(Deserialize, Serialize, Vanth)]
|
||||
struct Foo<T: ::vanth::Vanth> {
|
||||
field_a: i32,
|
||||
field_b: String,
|
||||
inner: T,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Vanth)]
|
||||
struct Bar {
|
||||
field_a: i32,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Vanth)]
|
||||
struct Qux<T: Vanth, S: Vanth> {
|
||||
field_a: i32,
|
||||
field_b: String,
|
||||
inner: T,
|
||||
inner_2: S,
|
||||
}
|
||||
|
||||
let base = "test::";
|
||||
|
||||
assert_eq!(Bar::ty(), format!("{base}Bar"));
|
||||
assert_eq!(Foo::<Bar>::ty(), format!("{base}Foo<{base}Bar>"));
|
||||
assert_eq!(
|
||||
Qux::<Bar, Foo<Bar>>::ty(),
|
||||
format!("{base}Qux<{base}Bar,{base}Foo<{base}Bar>>")
|
||||
);
|
||||
}
|
||||
|
||||
mod task_tests {
|
||||
use async_trait::async_trait;
|
||||
use smol::block_on;
|
||||
use vanth::{ExecuteTask, Module, Root, Task};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[derive(Deserialize, Serialize, Vanth)]
|
||||
struct TestData(u32);
|
||||
|
||||
#[derive(Deserialize, Serialize, Vanth)]
|
||||
struct TestTask {
|
||||
a: TestData,
|
||||
b: TestData,
|
||||
}
|
||||
|
||||
impl Task for TestTask {
|
||||
type Input = (TestData, TestData);
|
||||
type Output = TestData;
|
||||
type Resource = ();
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Vanth)]
|
||||
struct TestModule {}
|
||||
|
||||
impl Module for TestModule {}
|
||||
|
||||
#[async_trait]
|
||||
impl ExecuteTask<TestTask> for TestModule {
|
||||
async fn execute_task(&self, input: (TestData, TestData), resource: &mut ()) -> TestData {
|
||||
let (TestData(a), TestData(b)) = input;
|
||||
TestData(a + b)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_task() {
|
||||
let root = Root::local();
|
||||
let module = TestModule {};
|
||||
let input = (TestData(2), TestData(3));
|
||||
let mut resources = ();
|
||||
|
||||
let result = block_on(module.execute_task(input, &mut resources));
|
||||
assert_eq!(result.0, 5);
|
||||
}
|
||||
}
|
||||
|
||||
mod module_tests {
|
||||
use async_trait::async_trait;
|
||||
use smol::block_on;
|
||||
use vanth::{ExecuteTask, Module, Root, Task};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[derive(Deserialize, Serialize, Vanth)]
|
||||
struct TestData(u32);
|
||||
|
||||
#[derive(Deserialize, Serialize, Vanth)]
|
||||
struct TestTask {
|
||||
a: TestData,
|
||||
b: TestData,
|
||||
}
|
||||
|
||||
impl Task for TestTask {
|
||||
type Input = (TestData, TestData);
|
||||
type Output = TestData;
|
||||
type Resource = ();
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Vanth)]
|
||||
struct TestModule {}
|
||||
|
||||
impl Module for TestModule {}
|
||||
|
||||
#[async_trait]
|
||||
impl ExecuteTask<TestTask> for TestModule {
|
||||
async fn execute_task(&self, input: (TestData, TestData), resource: &mut ()) -> TestData {
|
||||
let (TestData(a), TestData(b)) = input;
|
||||
TestData(a + b)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_task() {
|
||||
let mut root = Root::local();
|
||||
let module = TestModule {};
|
||||
// let module_id = root.register_module(module);
|
||||
}
|
||||
}
|
|
@ -9,4 +9,5 @@ proc-macro = true
|
|||
[dependencies]
|
||||
quote.workspace = true
|
||||
syn.workspace = true
|
||||
proc-macro2.workspace = true
|
||||
proc-macro2.workspace = true
|
||||
proc-macro-crate.workspace = true
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
use proc_macro::TokenStream;
|
||||
use proc_macro_crate::{crate_name, FoundCrate};
|
||||
use proc_macro2::Span;
|
||||
use quote::quote;
|
||||
use syn::parse_quote;
|
||||
use syn::{DeriveInput, GenericParam, Generics, parse_macro_input};
|
||||
|
@ -11,6 +13,15 @@ pub fn vanth_derive(input: TokenStream) -> TokenStream {
|
|||
|
||||
let mut generics = input.generics.clone();
|
||||
|
||||
let vanth_path = match crate_name("vanth") {
|
||||
Ok(FoundCrate::Itself) => quote!(crate),
|
||||
Ok(FoundCrate::Name(name)) => {
|
||||
let ident = syn::Ident::new(&name, Span::call_site());
|
||||
quote!(::#ident)
|
||||
}
|
||||
Err(_) => quote!(vanth),
|
||||
};
|
||||
|
||||
let type_params: Vec<syn::Ident> = generics
|
||||
.params
|
||||
.iter()
|
||||
|
@ -25,7 +36,7 @@ pub fn vanth_derive(input: TokenStream) -> TokenStream {
|
|||
|
||||
let mut where_clause = generics.where_clause.clone().unwrap_or_else(|| parse_quote!(where));
|
||||
for tp in &type_params {
|
||||
where_clause.predicates.push(parse_quote!(#tp : vanth::Vanth));
|
||||
where_clause.predicates.push(parse_quote!(#tp : #vanth_path::Vanth));
|
||||
}
|
||||
|
||||
let (impl_generics, ty_generics, _) = generics.split_for_impl();
|
||||
|
@ -34,19 +45,19 @@ pub fn vanth_derive(input: TokenStream) -> TokenStream {
|
|||
quote! { String::new() }
|
||||
} else {
|
||||
quote! {
|
||||
format!("<{}>", [#(#type_params::ty().path.join("::")),*].join(","))
|
||||
format!("<{}>", [#( <#type_params as #vanth_path::Vanth>::ty().path.join("::") ),*].join(","))
|
||||
}
|
||||
};
|
||||
|
||||
let expanded = quote! {
|
||||
impl #impl_generics vanth::Vanth for #name #ty_generics #where_clause {
|
||||
fn ty() -> vanth::Ty {
|
||||
impl #impl_generics #vanth_path::Vanth for #name #ty_generics #where_clause {
|
||||
fn ty() -> #vanth_path::Ty {
|
||||
let module_path = module_path!();
|
||||
let mut path: Vec<String> = module_path.split("::").map(|s| s.to_string()).collect();
|
||||
let base_name = stringify!(#name);
|
||||
let generics_str = #generics_code;
|
||||
path.push(format!("{}{}", base_name, generics_str));
|
||||
vanth::Ty { path }
|
||||
#vanth_path::Ty { path }
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
25
crates/vanth_transport/Cargo.toml
Normal file
25
crates/vanth_transport/Cargo.toml
Normal file
|
@ -0,0 +1,25 @@
|
|||
[package]
|
||||
name = "vanth_transport"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
[lib]
|
||||
path = "src/lib.rs"
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
digest.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
blake3.workspace = true
|
||||
vanth = { path = "../vanth" }
|
||||
vanth_derive = { path = "../vanth_derive" }
|
||||
tracing.workspace = true
|
||||
rand = { workspace = true }
|
||||
smol.workspace = true
|
||||
async-process.workspace = true
|
||||
async-trait.workspace = true
|
||||
ntrulp = { path = "/home/asraelite/dev/ntrulp", features = ["ntrup1277"] }
|
||||
chacha20poly1305.workspace = true
|
||||
serde_bytes.workspace = true
|
||||
serde_arrays.workspace = true
|
26
crates/vanth_transport/src/crypto.rs
Normal file
26
crates/vanth_transport/src/crypto.rs
Normal file
|
@ -0,0 +1,26 @@
|
|||
use serde::{Serialize, de::DeserializeOwned};
|
||||
use vanth::Vanth;
|
||||
|
||||
pub mod ntru;
|
||||
|
||||
pub enum CryptoError {
|
||||
RngGenerationFailed,
|
||||
InvalidKey,
|
||||
}
|
||||
|
||||
pub trait Asymmetric {
|
||||
type PublicKey: Vanth + Serialize + DeserializeOwned;
|
||||
type PrivateKey: Vanth + Serialize + DeserializeOwned;
|
||||
type Seed;
|
||||
|
||||
fn generate_default_public_key(&mut self, private_key: &Self::PrivateKey) -> Result<Self::PublicKey, CryptoError>;
|
||||
fn generate_private_key(&mut self, seed: Self::Seed) -> Result<Self::PrivateKey, CryptoError>;
|
||||
}
|
||||
|
||||
pub trait MultiKeyAsymmetric: Asymmetric {
|
||||
fn generate_public_key(
|
||||
&mut self,
|
||||
seed: Self::Seed,
|
||||
private_key: &Self::PrivateKey,
|
||||
) -> Result<Self::PublicKey, CryptoError>;
|
||||
}
|
151
crates/vanth_transport/src/crypto/ntru.rs
Normal file
151
crates/vanth_transport/src/crypto/ntru.rs
Normal file
|
@ -0,0 +1,151 @@
|
|||
use super::{Asymmetric, CryptoError};
|
||||
use ntrulp::{
|
||||
key::{priv_key::PrivKey, pub_key::PubKey},
|
||||
poly::{r3::R3, rq::Rq},
|
||||
};
|
||||
use serde::{Deserialize, Serialize, ser::SerializeStruct as _};
|
||||
use vanth::Vanth;
|
||||
|
||||
const NTRU_COEFFS_LEN: usize = 1277;
|
||||
|
||||
pub struct Ntru {}
|
||||
|
||||
#[derive(Vanth)]
|
||||
pub struct PrivateKey(PrivKey);
|
||||
|
||||
impl From<PrivKey> for PrivateKey {
|
||||
fn from(value: PrivKey) -> Self {
|
||||
PrivateKey(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for PrivateKey {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
let mut s = serializer.serialize_struct("PrivateKey", 2)?;
|
||||
s.serialize_field("f", &self.0.0.coeffs.to_vec())?;
|
||||
s.serialize_field("g", &self.0.1.coeffs.to_vec())?;
|
||||
s.end()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for PrivateKey {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
#[derive(Deserialize)]
|
||||
struct PrivateKeyHelper {
|
||||
f: Vec<i8>,
|
||||
g: Vec<i8>,
|
||||
}
|
||||
|
||||
let helper = PrivateKeyHelper::deserialize(deserializer)?;
|
||||
|
||||
let f = {
|
||||
let slice = helper.f.as_slice();
|
||||
if slice.len() != NTRU_COEFFS_LEN {
|
||||
return Err(serde::de::Error::invalid_length(
|
||||
slice.len(),
|
||||
&"invalid coeffs length for PrivateKey.f",
|
||||
));
|
||||
}
|
||||
let mut coeffs = [0i8; NTRU_COEFFS_LEN];
|
||||
coeffs.copy_from_slice(slice);
|
||||
R3 { coeffs }
|
||||
};
|
||||
let g = {
|
||||
let slice = helper.g.as_slice();
|
||||
if slice.len() != NTRU_COEFFS_LEN {
|
||||
return Err(serde::de::Error::invalid_length(
|
||||
slice.len(),
|
||||
&"invalid coeffs length for PrivateKey.g",
|
||||
));
|
||||
}
|
||||
let mut coeffs = [0i8; NTRU_COEFFS_LEN];
|
||||
coeffs.copy_from_slice(slice);
|
||||
R3 { coeffs }
|
||||
};
|
||||
|
||||
Ok(PrivateKey(PrivKey(f, g)))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Vanth)]
|
||||
pub struct PublicKey(PubKey);
|
||||
|
||||
impl From<PubKey> for PublicKey {
|
||||
fn from(value: PubKey) -> Self {
|
||||
PublicKey(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for PublicKey {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
let mut s = serializer.serialize_struct("PublicKey", 1)?;
|
||||
s.serialize_field("coeffs", &self.0.coeffs.to_vec())?;
|
||||
s.end()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for PublicKey {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
#[derive(Deserialize)]
|
||||
struct PublicKeyHelper {
|
||||
coeffs: Vec<i16>,
|
||||
}
|
||||
|
||||
let helper = PublicKeyHelper::deserialize(deserializer)?;
|
||||
let slice = helper.coeffs.as_slice();
|
||||
if slice.len() != NTRU_COEFFS_LEN {
|
||||
return Err(serde::de::Error::invalid_length(
|
||||
slice.len(),
|
||||
&"invalid coeffs length for PublicKey.coeffs",
|
||||
));
|
||||
}
|
||||
let mut coeffs = [0i16; NTRU_COEFFS_LEN];
|
||||
coeffs.copy_from_slice(slice);
|
||||
Ok(PublicKey(Rq { coeffs }))
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Use seed during generation, or in some other way make the random number generator more generic.
|
||||
impl Asymmetric for Ntru {
|
||||
type PrivateKey = PrivateKey;
|
||||
type PublicKey = PublicKey;
|
||||
// TODO: Use correct type
|
||||
type Seed = u64;
|
||||
|
||||
fn generate_default_public_key(&mut self, private_key: &Self::PrivateKey) -> Result<Self::PublicKey, CryptoError> {
|
||||
let pubkey = PubKey::from_sk(&private_key.0).map_err(|_| CryptoError::InvalidKey)?;
|
||||
Ok(PublicKey(pubkey))
|
||||
}
|
||||
|
||||
fn generate_private_key(&mut self, seed: Self::Seed) -> Result<Self::PrivateKey, CryptoError> {
|
||||
let mut rng = rand::rng();
|
||||
let f: Rq = Rq::from(ntrulp::rng::short_random(&mut rng).unwrap());
|
||||
let mut g: R3;
|
||||
let mut attempt_count = 0;
|
||||
let sk: PrivKey = loop {
|
||||
g = R3::from(ntrulp::rng::random_small(&mut rng));
|
||||
|
||||
if let Ok(s) = PrivKey::compute(&f, &g) {
|
||||
break s;
|
||||
};
|
||||
|
||||
attempt_count += 1;
|
||||
if attempt_count > 100 {
|
||||
return Err(CryptoError::RngGenerationFailed);
|
||||
}
|
||||
};
|
||||
Ok(PrivateKey(sk))
|
||||
}
|
||||
}
|
44
crates/vanth_transport/src/lib.rs
Normal file
44
crates/vanth_transport/src/lib.rs
Normal file
|
@ -0,0 +1,44 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use smol::net::UdpSocket;
|
||||
use vanth::{
|
||||
entity::Id,
|
||||
network::{Address, Backend},
|
||||
};
|
||||
|
||||
pub mod crypto;
|
||||
|
||||
pub struct UdpBackend {
|
||||
open_sockets: HashMap<Id<UdpSocket>, UdpSocket>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Backend for UdpBackend {
|
||||
type ConnectionHandle = Id<UdpSocket>;
|
||||
type Error = String;
|
||||
|
||||
async fn connect<'a>(&mut self, address: &Address) -> Result<Self::ConnectionHandle, Self::Error> {
|
||||
let socket = match address {
|
||||
Address::Uri(uri) => UdpSocket::bind(uri).await.map_err(|e| e.to_string())?,
|
||||
_ => return Err(format!("Address {:?} is not supported for the UDP backend.", address)),
|
||||
};
|
||||
let id = Id::random();
|
||||
assert!(self.open_sockets.insert(id, socket).is_none());
|
||||
Ok(id)
|
||||
}
|
||||
|
||||
async fn close(&mut self, connection: Self::ConnectionHandle) -> Result<(), Self::Error> {
|
||||
todo!();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn poll(&mut self) -> impl Iterator<Item = Result<(Self::ConnectionHandle, Vec<u8>), Self::Error>> {
|
||||
todo!();
|
||||
[].into_iter()
|
||||
}
|
||||
|
||||
async fn send(&mut self, data: &[u8]) -> Result<(), Self::Error> {
|
||||
todo!()
|
||||
}
|
||||
}
|
50
crates/vanth_transport/tests/ntru_serde_roundtrip.rs
Normal file
50
crates/vanth_transport/tests/ntru_serde_roundtrip.rs
Normal file
|
@ -0,0 +1,50 @@
|
|||
use serde_json::{from_value, to_value, Value};
|
||||
use vanth_transport::crypto::ntru::{PrivateKey, PublicKey};
|
||||
|
||||
use ntrulp::{
|
||||
key::{priv_key::PrivKey as NtruPrivKey, pub_key::PubKey as NtruPubKey},
|
||||
poly::{r3::R3, rq::Rq},
|
||||
};
|
||||
|
||||
fn generate_keypair() -> (NtruPrivKey, NtruPubKey) {
|
||||
let mut rng = rand::rng();
|
||||
let f: Rq = Rq::from(ntrulp::rng::short_random(&mut rng).expect("failed to sample short_random"));
|
||||
let mut g: R3;
|
||||
|
||||
let sk = loop {
|
||||
g = R3::from(ntrulp::rng::random_small(&mut rng));
|
||||
match NtruPrivKey::compute(&f, &g) {
|
||||
Ok(s) => break s,
|
||||
Err(_) => continue,
|
||||
};
|
||||
};
|
||||
|
||||
let pk = NtruPubKey::from_sk(&sk).expect("failed to derive public key from secret key");
|
||||
(sk, pk)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_private_key_serde_roundtrip() {
|
||||
let (sk, _) = generate_keypair();
|
||||
|
||||
// Round-trip via serde_json Value without touching internal fields.
|
||||
let wrapped: PrivateKey = sk.into();
|
||||
let json1: Value = to_value(&wrapped).expect("failed to serialize PrivateKey");
|
||||
let reparsed: PrivateKey = from_value(json1.clone()).expect("failed to deserialize PrivateKey");
|
||||
let json2: Value = to_value(&reparsed).expect("failed to serialize PrivateKey again");
|
||||
|
||||
assert_eq!(json2, json1, "PrivateKey JSON did not round-trip losslessly.");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_public_key_serde_roundtrip() {
|
||||
let (_, pk) = generate_keypair();
|
||||
|
||||
// Round-trip via serde_json Value without touching internal fields.
|
||||
let wrapped: PublicKey = pk.into();
|
||||
let json1: Value = to_value(&wrapped).expect("failed to serialize PublicKey");
|
||||
let reparsed: PublicKey = from_value(json1.clone()).expect("failed to deserialize PublicKey");
|
||||
let json2: Value = to_value(&reparsed).expect("failed to serialize PublicKey again");
|
||||
|
||||
assert_eq!(json2, json1, "PublicKey JSON did not round-trip losslessly.");
|
||||
}
|
16
flake.nix
16
flake.nix
|
@ -94,6 +94,18 @@
|
|||
'';
|
||||
}
|
||||
);
|
||||
|
||||
clippyCheck = craneLib.cargoClippy (
|
||||
commonArgs
|
||||
// craneArgs
|
||||
// {
|
||||
inherit cargoArtifacts;
|
||||
pname = "vanth-clippy";
|
||||
version = "0.1.0";
|
||||
# Fail on any lint warning and cover all targets/features.
|
||||
cargoClippyExtraArgs = "--all-targets --all-features -- -D warnings";
|
||||
}
|
||||
);
|
||||
in
|
||||
{
|
||||
packages = rec {
|
||||
|
@ -101,6 +113,10 @@
|
|||
default = vanth;
|
||||
};
|
||||
|
||||
checks = {
|
||||
# clippy = clippyCheck;
|
||||
};
|
||||
|
||||
devShells.default = craneLib.devShell {
|
||||
inputsFrom = [ vanth ];
|
||||
packages = packages;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue