Backup
This commit is contained in:
parent
33a2e3156e
commit
7bb564f91e
16 changed files with 1877 additions and 89 deletions
1
.envrc
Normal file
1
.envrc
Normal file
|
@ -0,0 +1 @@
|
||||||
|
use nix
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1 +1,2 @@
|
||||||
target
|
target
|
||||||
|
.direnv
|
1288
Cargo.lock
generated
1288
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -8,17 +8,22 @@ license = "MIT License"
|
||||||
# Used by library
|
# Used by library
|
||||||
macaddr = "1.0.1"
|
macaddr = "1.0.1"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
|
actix-web = "4"
|
||||||
# Used by server runtime
|
# Used by server runtime
|
||||||
fern = { optional = true, version = "0.7" }
|
fern = { optional = true, version = "0.7" }
|
||||||
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
stopwatch = "0.0.7"
|
stopwatch = "0.0.7"
|
||||||
rand = "0.9"
|
rand = "0.9"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
default = ["client", "server"]
|
||||||
bin-deps = ["fern"]
|
bin-deps = ["fern"]
|
||||||
|
client = []
|
||||||
|
server = []
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "matchmaker_server"
|
name = "matchmaker_server"
|
||||||
path = "src/main.rs"
|
path = "src/main.rs"
|
||||||
required-features = []
|
required-features = ["server"]
|
49
docs/WEBAPI_V1.md
Normal file
49
docs/WEBAPI_V1.md
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
# Web API Documentation - Draft
|
||||||
|
|
||||||
|
## /api/info
|
||||||
|
|
||||||
|
### GET
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"supportedVersions": [
|
||||||
|
"<VERSION IDENTIFIER>"
|
||||||
|
],
|
||||||
|
"partitions": [
|
||||||
|
"us",
|
||||||
|
"uk"
|
||||||
|
// ...
|
||||||
|
],
|
||||||
|
"heartbeatTimeoutDuration": 7
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## /api/query
|
||||||
|
|
||||||
|
### POST
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
// if this is unspecified/null, infer a default partition
|
||||||
|
"partition": "us",
|
||||||
|
""
|
||||||
|
// Validated against a server-side maximum and minimum
|
||||||
|
"count": 7,
|
||||||
|
// Validated to prevent out-of-bounds issues.
|
||||||
|
// This allows reading a slice of the instances,
|
||||||
|
// allowing pagination.
|
||||||
|
"offset": 0
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## /api/connect
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## /api/heartbeat
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"token":
|
||||||
|
}
|
||||||
|
```
|
4
rust-toolchain.toml
Normal file
4
rust-toolchain.toml
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
[toolchain]
|
||||||
|
channel = "1.81"
|
||||||
|
components = ["rustfmt"]
|
||||||
|
profile = "minimal"
|
40
shell.nix
40
shell.nix
|
@ -1,4 +1,40 @@
|
||||||
{ pkgs ? import <nixpkgs> {} }:
|
{ pkgs ? import <nixpkgs> {} }:
|
||||||
pkgs.mkShell {
|
let
|
||||||
buildInputs = [ pkgs.cargo pkgs.rustc ];
|
overrides = (builtins.fromTOML (builtins.readFile ./rust-toolchain.toml));
|
||||||
|
libPath = with pkgs; lib.makeLibraryPath [
|
||||||
|
# load external libraries that you need in your rust project here
|
||||||
|
];
|
||||||
|
in
|
||||||
|
pkgs.mkShell rec {
|
||||||
|
buildInputs = with pkgs; [
|
||||||
|
clang
|
||||||
|
# Replace llvmPackages with llvmPackages_X, where X is the latest LLVM version (at the time of writing, 16)
|
||||||
|
llvmPackages.bintools
|
||||||
|
rustup
|
||||||
|
];
|
||||||
|
RUSTC_VERSION = overrides.toolchain.channel;
|
||||||
|
# https://github.com/rust-lang/rust-bindgen#environment-variables
|
||||||
|
LIBCLANG_PATH = pkgs.lib.makeLibraryPath [ pkgs.llvmPackages_latest.libclang.lib ];
|
||||||
|
shellHook = ''
|
||||||
|
export PATH=$PATH:''${CARGO_HOME:-~/.cargo}/bin
|
||||||
|
export PATH=$PATH:''${RUSTUP_HOME:-~/.rustup}/toolchains/$RUSTC_VERSION-x86_64-unknown-linux-gnu/bin/
|
||||||
|
'';
|
||||||
|
# Add precompiled library to rustc search path
|
||||||
|
RUSTFLAGS = (builtins.map (a: ''-L ${a}/lib'') [
|
||||||
|
# add libraries here (e.g. pkgs.libvmi)
|
||||||
|
]);
|
||||||
|
LD_LIBRARY_PATH = libPath;
|
||||||
|
# Add glibc, clang, glib, and other headers to bindgen search path
|
||||||
|
BINDGEN_EXTRA_CLANG_ARGS =
|
||||||
|
# Includes normal include path
|
||||||
|
(builtins.map (a: ''-I"${a}/include"'') [
|
||||||
|
# add dev libraries here (e.g. pkgs.libvmi.dev)
|
||||||
|
pkgs.glibc.dev
|
||||||
|
])
|
||||||
|
# Includes with special directory paths
|
||||||
|
++ [
|
||||||
|
''-I"${pkgs.llvmPackages_latest.libclang.lib}/lib/clang/${pkgs.llvmPackages_latest.libclang.version}/include"''
|
||||||
|
''-I"${pkgs.glib.dev}/include/glib-2.0"''
|
||||||
|
''-I${pkgs.glib.out}/lib/glib-2.0/include/''
|
||||||
|
];
|
||||||
}
|
}
|
|
@ -1,28 +0,0 @@
|
||||||
use auth::AuthGuard;
|
|
||||||
use util::Address;
|
|
||||||
|
|
||||||
pub mod util;
|
|
||||||
pub mod auth;
|
|
||||||
|
|
||||||
pub enum DataStorageError {
|
|
||||||
AlreadyExists,
|
|
||||||
AntiSpam(String)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait DataStorageItem {
|
|
||||||
type Id;
|
|
||||||
type Address: Address;
|
|
||||||
fn id(&self) -> Self::Id;
|
|
||||||
fn address_info(&self) -> Self::Address;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait DataStorage<Auth: AuthGuard, Item: DataStorageItem>: IntoIterator<Item = Item> {
|
|
||||||
fn update_instance(&mut self, address: &impl Address, guard: Option<Auth>)
|
|
||||||
-> Result<Item::Id, DataStorageError>;
|
|
||||||
|
|
||||||
fn send_heartbeat(&mut self, address: &impl Address)
|
|
||||||
-> Result<(),DataStorageError>;
|
|
||||||
|
|
||||||
fn peek_instance(&self, id: Item::Id);
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,33 +0,0 @@
|
||||||
use std::net::IpAddr;
|
|
||||||
|
|
||||||
use macaddr::MacAddr;
|
|
||||||
|
|
||||||
pub trait Address: PartialEq {
|
|
||||||
fn mac(&self) -> MacAddr;
|
|
||||||
fn ip(&self) -> IpAddr;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(PartialEq, Debug)]
|
|
||||||
struct SimpleAddress {
|
|
||||||
mac: MacAddr,
|
|
||||||
ip: IpAddr
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SimpleAddress {
|
|
||||||
fn new(ip: IpAddr, mac: MacAddr) -> SimpleAddress {
|
|
||||||
return SimpleAddress {
|
|
||||||
ip,
|
|
||||||
mac
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Address for SimpleAddress {
|
|
||||||
fn mac(&self) -> MacAddr {
|
|
||||||
self.mac
|
|
||||||
}
|
|
||||||
|
|
||||||
fn ip(&self) -> IpAddr {
|
|
||||||
self.ip
|
|
||||||
}
|
|
||||||
}
|
|
14
src/lib.rs
14
src/lib.rs
|
@ -1,9 +1,15 @@
|
||||||
pub mod data;
|
pub mod server;
|
||||||
|
|
||||||
|
pub enum MatchmakerError {
|
||||||
|
AlreadyExists,
|
||||||
|
InternalError(String),
|
||||||
|
LimitReached(String),
|
||||||
|
Unauthorized,
|
||||||
|
AntiSpam(String),
|
||||||
|
}
|
||||||
|
|
||||||
pub trait Matchmaker {
|
pub trait Matchmaker {
|
||||||
fn run(&mut self);
|
fn run(&mut self);
|
||||||
}
|
}
|
||||||
|
|
||||||
struct DefaultMatchmaker {
|
struct DefaultMatchmaker {}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,3 +1 @@
|
||||||
fn main() {
|
fn main() {}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
33
src/server/access.rs
Normal file
33
src/server/access.rs
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
/* Interfaces */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use super::util::Address;
|
||||||
|
|
||||||
|
pub struct AccessInfo {
|
||||||
|
address: Address,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait AccessGuard {
|
||||||
|
fn check(&self, info: &AccessInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum AccessAction {
|
||||||
|
Allow,
|
||||||
|
Block,
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
/* Rate Limiting */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
struct RateLimitAccessor {}
|
||||||
|
|
||||||
|
pub struct RateLimitAccessGuard {
|
||||||
|
max_read: i32,
|
||||||
|
max_write: i32,
|
||||||
|
max_accessors: i32,
|
||||||
|
accessors: HashMap<Address, RateLimitAccessor>,
|
||||||
|
}
|
|
@ -185,7 +185,7 @@ mod tests {
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct CodeAuthKey {
|
pub struct CodeAuthKey {
|
||||||
key: u64
|
key: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CodeAuthKey {
|
impl CodeAuthKey {
|
||||||
|
@ -197,7 +197,7 @@ impl CodeAuthKey {
|
||||||
|
|
||||||
pub fn new<T: Hash + ?Sized>(code: &T) -> CodeAuthKey {
|
pub fn new<T: Hash + ?Sized>(code: &T) -> CodeAuthKey {
|
||||||
CodeAuthKey {
|
CodeAuthKey {
|
||||||
key: Self::_generate_key(code)
|
key: Self::_generate_key(code),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -211,14 +211,12 @@ impl PartialEq for CodeAuthKey {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct CodeAuthGuard {
|
pub struct CodeAuthGuard {
|
||||||
reference_key: CodeAuthKey
|
reference_key: CodeAuthKey,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CodeAuthGuard {
|
impl CodeAuthGuard {
|
||||||
fn new(key: CodeAuthKey) -> Self {
|
fn new(key: CodeAuthKey) -> Self {
|
||||||
Self {
|
Self { reference_key: key }
|
||||||
reference_key: key
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -233,15 +231,14 @@ impl AuthGuard for CodeAuthGuard {
|
||||||
/* ------------------------------- Unit Tests ------------------------------- */
|
/* ------------------------------- Unit Tests ------------------------------- */
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod code_authguard_tests {
|
mod code_authguard_tests {
|
||||||
|
use super::*;
|
||||||
use rand::{distr::Alphanumeric, prelude::*};
|
use rand::{distr::Alphanumeric, prelude::*};
|
||||||
use stopwatch::Stopwatch;
|
use stopwatch::Stopwatch;
|
||||||
use super::*;
|
|
||||||
|
|
||||||
fn _create_rng_string() -> String {
|
fn _create_rng_string() -> String {
|
||||||
let mut rng = rand::rng();
|
let mut rng = rand::rng();
|
||||||
let count = rng.random_range(5..100);
|
let count = rng.random_range(5..100);
|
||||||
rng
|
rng.sample_iter(Alphanumeric)
|
||||||
.sample_iter(Alphanumeric)
|
|
||||||
.take(count)
|
.take(count)
|
||||||
.map(char::from)
|
.map(char::from)
|
||||||
.collect()
|
.collect()
|
25
src/server/mod.rs
Normal file
25
src/server/mod.rs
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
use auth::AuthGuard;
|
||||||
|
use util::Address;
|
||||||
|
|
||||||
|
use crate::MatchmakerError;
|
||||||
|
|
||||||
|
pub mod access;
|
||||||
|
pub mod auth;
|
||||||
|
pub mod storage;
|
||||||
|
pub mod util;
|
||||||
|
|
||||||
|
pub trait DataStorageItem {
|
||||||
|
type Id;
|
||||||
|
fn id(&self) -> Self::Id;
|
||||||
|
fn address_info(&self) -> Address;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait DataStorage<Auth: AuthGuard, Item: DataStorageItem>: IntoIterator<Item = Item> {
|
||||||
|
fn reserve_instance(
|
||||||
|
&mut self,
|
||||||
|
address: &Address,
|
||||||
|
guard: Option<Auth>,
|
||||||
|
) -> Result<Item::Id, MatchmakerError>;
|
||||||
|
|
||||||
|
fn remove_instance(&mut self, id: Item::Id);
|
||||||
|
}
|
375
src/server/storage.rs
Normal file
375
src/server/storage.rs
Normal file
|
@ -0,0 +1,375 @@
|
||||||
|
use std::{
|
||||||
|
collections::{BTreeMap, HashMap}, iter::Map, net::IpAddr, sync::RwLock
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::util::Address;
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
/* Instance Storage */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum InstanceStorageError {
|
||||||
|
NotFound,
|
||||||
|
Full
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Instance {
|
||||||
|
type Id;
|
||||||
|
fn id(&self) -> Self::Id;
|
||||||
|
fn ip(&self) -> IpAddr;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait InstanceStorage<Item: Instance>: IntoIterator<Item = Item> {
|
||||||
|
// Create
|
||||||
|
|
||||||
|
// Request
|
||||||
|
fn get_by_address(&self, address: &Address) -> Result<&Item, InstanceStorageError>;
|
||||||
|
fn get_by_address_mut(
|
||||||
|
&mut self,
|
||||||
|
address: &Address,
|
||||||
|
) -> Result<&mut Item, InstanceStorageError>;
|
||||||
|
fn get_by_id(&self, id: Item::Id) -> Result<&Item, InstanceStorageError>;
|
||||||
|
fn get_by_id_mut(&mut self, id: Item::Id) -> Result<&mut Item, InstanceStorageError>;
|
||||||
|
// Delete
|
||||||
|
fn remove_by_address(&mut self, address: &Address) -> Result<(), InstanceStorageError>;
|
||||||
|
fn remove_by_id(&mut self, id: Item::Id) -> Result<(), InstanceStorageError>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------- Default Implementation ------------------------- */
|
||||||
|
pub struct BasicInstance {
|
||||||
|
id: u32,
|
||||||
|
ip: IpAddr
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Instance for BasicInstance {
|
||||||
|
type Id = u32;
|
||||||
|
fn id(&self) -> Self::Id {
|
||||||
|
self.id
|
||||||
|
}
|
||||||
|
fn ip(&self) -> IpAddr {
|
||||||
|
self.ip
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct BasicInstanceStorage {
|
||||||
|
max_instances: u32,
|
||||||
|
instances: BTreeMap<Address, BasicInstance>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BasicInstanceStorage {
|
||||||
|
fn new(max_instances: u32) -> Self {
|
||||||
|
Self {
|
||||||
|
instances: BTreeMap::new(),
|
||||||
|
max_instances
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InstanceStorage<BasicInstance> for BasicInstanceStorage {
|
||||||
|
fn get_by_address(&self, address: &Address) -> Result<&BasicInstance, InstanceStorageError> {
|
||||||
|
match self.instances.get(address) {
|
||||||
|
Some(v) => {
|
||||||
|
Ok(v)
|
||||||
|
},
|
||||||
|
None => Err(InstanceStorageError::NotFound)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_by_address_mut(
|
||||||
|
&mut self,
|
||||||
|
address: &Address,
|
||||||
|
) -> Result<&mut BasicInstance, InstanceStorageError> {
|
||||||
|
match self.instances.get_mut(address) {
|
||||||
|
Some(v) => {
|
||||||
|
Ok(v)
|
||||||
|
},
|
||||||
|
None => Err(InstanceStorageError::NotFound)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_by_id(&self, id: <BasicInstance as Instance>::Id) -> Result<&BasicInstance, InstanceStorageError> {
|
||||||
|
let search_result = self.instances
|
||||||
|
.iter()
|
||||||
|
.find(|v| v.1.id == id);
|
||||||
|
match search_result {
|
||||||
|
Some(v) => {
|
||||||
|
Ok(v.1)
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
Err(InstanceStorageError::NotFound)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_by_id_mut(&mut self, id: <BasicInstance as Instance>::Id) -> Result<&mut BasicInstance, InstanceStorageError> {
|
||||||
|
let search_result = self.instances
|
||||||
|
.iter_mut()
|
||||||
|
.find(|v| v.1.id == id);
|
||||||
|
match search_result {
|
||||||
|
Some(v) => {
|
||||||
|
Ok(v.1)
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
Err(InstanceStorageError::NotFound)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_by_address(&mut self, address: &Address) -> Result<(), InstanceStorageError> {
|
||||||
|
match self.instances.remove(address) {
|
||||||
|
Some(_) => {
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
None => Err(InstanceStorageError::NotFound)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_by_id(&mut self, id: <BasicInstance as Instance>::Id) -> Result<(), InstanceStorageError> {
|
||||||
|
let original_len = self.instances.len();
|
||||||
|
self.instances.retain(|_, v| v.id != id);
|
||||||
|
|
||||||
|
if self.instances.len() == original_len {
|
||||||
|
return Err(InstanceStorageError::NotFound);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoIterator for BasicInstanceStorage {
|
||||||
|
type Item = BasicInstance;
|
||||||
|
type IntoIter = std::vec::IntoIter<BasicInstance>;
|
||||||
|
|
||||||
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
|
self.instances
|
||||||
|
.into_iter()
|
||||||
|
.map(|(_address, instance)| instance)
|
||||||
|
.collect::<Vec<BasicInstance>>()
|
||||||
|
.into_iter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod basic_instance_test {
|
||||||
|
use std::net::{IpAddr, Ipv4Addr};
|
||||||
|
|
||||||
|
use macaddr::{MacAddr, MacAddr6};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
fn _create_sample_address(id: u8) -> Address {
|
||||||
|
let ip: IpAddr = IpAddr::V4(Ipv4Addr::new(127, 0, 0, id));
|
||||||
|
let mac: MacAddr = MacAddr::V6(MacAddr6::new(id, id, id, id, id, id));
|
||||||
|
|
||||||
|
Address::new(ip, mac)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_get_by_address_found() {
|
||||||
|
let mut storage = BasicInstanceStorage::new(10);
|
||||||
|
let instance = BasicInstance { id: 42, ip: IpAddr::V4(Ipv4Addr::LOCALHOST) };
|
||||||
|
let addr = _create_sample_address(1);
|
||||||
|
storage.instances.insert(addr.clone(), instance);
|
||||||
|
let fetched = storage.get_by_address(&addr).unwrap();
|
||||||
|
assert_eq!(fetched.id, 42);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_get_by_address_not_found() {
|
||||||
|
let storage = BasicInstanceStorage::new(10);
|
||||||
|
let addr = _create_sample_address(1);
|
||||||
|
let result = storage.get_by_address(&addr);
|
||||||
|
assert!(matches!(result, Err(InstanceStorageError::NotFound)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_get_by_address_mut() {
|
||||||
|
let mut storage = BasicInstanceStorage::new(10);
|
||||||
|
let addr = _create_sample_address(1);
|
||||||
|
let instance = BasicInstance { id: 42, ip: IpAddr::V4(Ipv4Addr::LOCALHOST) };
|
||||||
|
storage.instances.insert(addr.clone(), instance);
|
||||||
|
{
|
||||||
|
let fetched_mut = storage.get_by_address_mut(&addr).unwrap();
|
||||||
|
fetched_mut.id = 100;
|
||||||
|
}
|
||||||
|
let fetched = storage.get_by_address(&addr).unwrap();
|
||||||
|
assert_eq!(fetched.id, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_get_by_id_found() {
|
||||||
|
let mut storage = BasicInstanceStorage::new(10);
|
||||||
|
let addr = _create_sample_address(1);
|
||||||
|
let instance = BasicInstance { id: 42, ip: IpAddr::V4(Ipv4Addr::LOCALHOST) };
|
||||||
|
storage.instances.insert(addr, instance);
|
||||||
|
let fetched = storage.get_by_id(42).unwrap();
|
||||||
|
assert_eq!(fetched.id, 42);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_get_by_id_not_found() {
|
||||||
|
let storage = BasicInstanceStorage::new(10);
|
||||||
|
let result = storage.get_by_id(42);
|
||||||
|
assert!(matches!(result, Err(InstanceStorageError::NotFound)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_get_by_id_mut() {
|
||||||
|
let mut storage = BasicInstanceStorage::new(10);
|
||||||
|
let addr = _create_sample_address(1);
|
||||||
|
let instance = BasicInstance { id: 42, ip: IpAddr::V4(Ipv4Addr::LOCALHOST) };
|
||||||
|
storage.instances.insert(addr, instance);
|
||||||
|
{
|
||||||
|
let fetched_mut = storage.get_by_id_mut(42);
|
||||||
|
assert!(fetched_mut.is_ok());
|
||||||
|
let fetched_mut = fetched_mut.unwrap();
|
||||||
|
fetched_mut.id = 99;
|
||||||
|
}
|
||||||
|
let fetched = storage.get_by_id(42);
|
||||||
|
assert!(fetched.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_remove_by_address() {
|
||||||
|
let mut storage = BasicInstanceStorage::new(10);
|
||||||
|
let addr = _create_sample_address(1);
|
||||||
|
storage.instances.insert(addr.clone(), BasicInstance { id: 42, ip: IpAddr::V4(Ipv4Addr::LOCALHOST) });
|
||||||
|
assert!(storage.remove_by_address(&addr).is_ok());
|
||||||
|
assert!(storage.instances.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_remove_by_address_not_found() {
|
||||||
|
let mut storage = BasicInstanceStorage::new(10);
|
||||||
|
let addr = _create_sample_address(1);
|
||||||
|
let result = storage.remove_by_address(&addr);
|
||||||
|
assert!(matches!(result, Err(InstanceStorageError::NotFound)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_remove_by_id() {
|
||||||
|
let mut storage = BasicInstanceStorage::new(10);
|
||||||
|
let addr = _create_sample_address(1);
|
||||||
|
storage.instances.insert(addr, BasicInstance { id: 42, ip: IpAddr::V4(Ipv4Addr::LOCALHOST) });
|
||||||
|
assert!(storage.remove_by_id(42).is_ok());
|
||||||
|
assert!(storage.instances.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_remove_by_id_not_found() {
|
||||||
|
let mut storage = BasicInstanceStorage::new(10);
|
||||||
|
let result = storage.remove_by_id(42);
|
||||||
|
assert!(matches!(result, Err(InstanceStorageError::NotFound)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_into_iterator() {
|
||||||
|
let mut storage = BasicInstanceStorage::new(10);
|
||||||
|
storage.instances.insert(_create_sample_address(1), BasicInstance { id: 42, ip: IpAddr::V4(Ipv4Addr::LOCALHOST) });
|
||||||
|
storage.instances.insert(_create_sample_address(2), BasicInstance { id: 43, ip: IpAddr::V4(Ipv4Addr::LOCALHOST) });
|
||||||
|
let instances: Vec<_> = storage.into_iter().collect();
|
||||||
|
assert_eq!(instances.len(), 2);
|
||||||
|
let ids: Vec<u32> = instances.into_iter().map(|inst| inst.id).collect();
|
||||||
|
assert!(ids.contains(&42));
|
||||||
|
assert!(ids.contains(&43));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
/* Attribute System */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
// TODO: Optimize the heck out of this - This is effectively a placeholder
|
||||||
|
// It will likely be *very* space inefficient.
|
||||||
|
|
||||||
|
/* -------------------------------- Metadata -------------------------------- */
|
||||||
|
#[repr(u8)]
|
||||||
|
#[derive(PartialEq)]
|
||||||
|
enum AttributeType {
|
||||||
|
Float,
|
||||||
|
Integer,
|
||||||
|
Boolean,
|
||||||
|
String,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum AttributeValue {
|
||||||
|
Float(f32),
|
||||||
|
Integer(i32),
|
||||||
|
Boolean(bool),
|
||||||
|
String(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AttributeValue {
|
||||||
|
fn get_type(&self) -> AttributeType {
|
||||||
|
match self {
|
||||||
|
Float => AttributeType::Float,
|
||||||
|
Integer => AttributeType::Integer,
|
||||||
|
Boolean => AttributeType::Boolean,
|
||||||
|
String => AttributeType::String,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum AttributeError {
|
||||||
|
InvalidType(AttributeType),
|
||||||
|
InvalidKey(String)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --------------------------------- Schema --------------------------------- */
|
||||||
|
|
||||||
|
// TODO: Builder pattern to create this
|
||||||
|
pub struct AttributeSchema {
|
||||||
|
attribute_schema: Map<String, AttributeSchemaElement>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct AttributeSchemaElement {
|
||||||
|
default_type_and_value: AttributeValue,
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------------------------------- List ---------------------------------- */
|
||||||
|
|
||||||
|
struct AttributeListElement {
|
||||||
|
value: AttributeValue,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct AttributeList<'a> {
|
||||||
|
schema: &'a AttributeSchema,
|
||||||
|
data: RwLock<HashMap<String, AttributeListElement>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> AttributeList<'a> {
|
||||||
|
fn new(schema: &'a AttributeSchema) -> Self {
|
||||||
|
Self {
|
||||||
|
schema,
|
||||||
|
// TODO: Populate with initial values
|
||||||
|
data: RwLock::new(HashMap::new()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_attribute(&mut self, key: &str, value: AttributeValue) -> Result<(), AttributeError> {
|
||||||
|
// ! Naive unwrap is present
|
||||||
|
let mut write = self.data.write().unwrap();
|
||||||
|
{
|
||||||
|
let element_read = &write[key];
|
||||||
|
if value.get_type() != element_read.value.get_type() {
|
||||||
|
return Err(AttributeError::InvalidType(value.get_type()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
// ! Double query
|
||||||
|
let element = write.get_mut(key);
|
||||||
|
match element {
|
||||||
|
Some(v) => {
|
||||||
|
v.value = value;
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
// TODO: Verify that this is the best thing to do
|
||||||
|
return Err(AttributeError::InvalidKey(key.to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
33
src/server/util.rs
Normal file
33
src/server/util.rs
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
use std::net::IpAddr;
|
||||||
|
|
||||||
|
use macaddr::MacAddr;
|
||||||
|
|
||||||
|
#[derive(Debug, Hash, PartialOrd, Ord, Clone)]
|
||||||
|
pub struct Address {
|
||||||
|
mac: MacAddr,
|
||||||
|
ip: IpAddr,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Address {
|
||||||
|
pub fn new(ip: IpAddr, mac: MacAddr) -> Self {
|
||||||
|
return Self { ip, mac };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mac(&self) -> MacAddr {
|
||||||
|
self.mac
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ip(&self) -> IpAddr {
|
||||||
|
self.ip
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for Address {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.ip == other.ip
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Eq for Address {
|
||||||
|
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue