Browse Source

Initial commit - Rust relay selector library

master
buttercat1791 7 months ago
parent
commit
d854c19457
  1. 1
      lib/relay_selector/.gitignore
  2. 133
      lib/relay_selector/Cargo.lock
  3. 7
      lib/relay_selector/Cargo.toml
  4. 94
      lib/relay_selector/src/lib.rs
  5. 3
      lib/relay_selector/src/relay/mod.rs
  6. 10
      lib/relay_selector/src/relay/relay.rs
  7. 20
      lib/relay_selector/src/relay/relay_variant.rs
  8. 109
      lib/relay_selector/src/relay_selector.rs

1
lib/relay_selector/.gitignore vendored

@ -0,0 +1 @@
target/

133
lib/relay_selector/Cargo.lock generated

@ -0,0 +1,133 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "bumpalo"
version = "3.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
[[package]]
name = "cfg-if"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9"
[[package]]
name = "log"
version = "0.4.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
[[package]]
name = "once_cell"
version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "proc-macro2"
version = "1.0.101"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
dependencies = [
"proc-macro2",
]
[[package]]
name = "relay_selector"
version = "0.1.0"
dependencies = [
"wasm-bindgen",
]
[[package]]
name = "rustversion"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
[[package]]
name = "syn"
version = "2.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
[[package]]
name = "wasm-bindgen"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5"
dependencies = [
"cfg-if",
"once_cell",
"rustversion",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6"
dependencies = [
"bumpalo",
"log",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
dependencies = [
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d"
dependencies = [
"unicode-ident",
]

7
lib/relay_selector/Cargo.toml

@ -0,0 +1,7 @@
[package]
name = "relay_selector"
version = "0.1.0"
edition = "2024"
[dependencies]
wasm-bindgen = "0.2"

94
lib/relay_selector/src/lib.rs

@ -0,0 +1,94 @@
mod relay;
mod relay_selector;
use std::cell::RefCell;
use wasm_bindgen::prelude::*;
use relay::RelayVariant;
use relay_selector::RelaySelector;
thread_local! {
/// Static lifetime, thread-local `RelaySelector` instance.
///
/// The `RelaySelector` acts as an in-memory, mutable repository for relay ranking data. When
/// initialized, it loads data from persistent storage, and when it is no longer needed, it
/// saves the data back to persistent storage. The repository data may be mutated via the
/// functions provided in the `relay_selector` crate's API.
static RELAY_SELECTOR: RefCell<RelaySelector> = RefCell::new(RelaySelector::new());
}
#[wasm_bindgen]
pub fn init() {
// TODO: Initialize a reference-counted relay selector.
}
#[wasm_bindgen]
pub fn add_response_time(relay_url: &str, response_time: f32) {
// TODO: Implement.
// May delegate to other modules.
}
#[wasm_bindgen]
pub fn add_success(relay_url: &str, relay_type: &str) {
// TODO: Implement.
let relay_variant = RelayVariant::from_str(relay_type).unwrap_throw();
// May delegate to other modules.
}
#[wasm_bindgen]
pub fn add_relay(relay_url: &str, relay_type: &str) {
// TODO: Implement.
let relay_variant = RelayVariant::from_str(relay_type).unwrap_throw();
// May delegate to other modules.
}
/// Get a recommended relay URL based on current weights.
///
/// # Arguments
///
/// * `relay_type` - The type of relay. May be `"general"`, `"inbox"`, or `"outbox"`.
/// * `relay_rank` - The relay rank based on current weights. Defaults to `0` to select the
/// highest-ranked relay.
///
/// # Returns
///
/// A relay URL.
///
/// # Errors
///
/// Throws an error if the relay type is invalid, or if an error occurs while selecting the relay.
///
/// # Remarks
///
/// When the relay indicated by the returned URL is no longer in use, it should be returned with
/// [`return_relay`] to prevent memory leaks.
#[wasm_bindgen]
pub fn get_relay(relay_type: &str, relay_rank: Option<u8>) -> Result<String, String> {
let variant = RelayVariant::from_str(relay_type).unwrap_throw();
let rank = match relay_rank {
Some(rank) => rank,
None => 0,
} as usize;
RELAY_SELECTOR
.with_borrow_mut(|selector| selector.get_relay_by_weighted_round_robin(variant, rank))
}
/// Return a relay URL to indicate it that relay is no longer in use.
///
/// # Arguments
///
/// * `relay_url` - The URL of the relay to return.
///
/// # Errors
///
/// Throws an error if the caller attempts to return a relay URL that is not currently in use.
#[wasm_bindgen]
pub fn return_relay(relay_url: &str) -> Result<(), String> {
RELAY_SELECTOR.with_borrow_mut(|selector| selector.return_relay(relay_url))
}

3
lib/relay_selector/src/relay/mod.rs

@ -0,0 +1,3 @@
mod relay_variant;
pub use relay_variant::RelayVariant;

10
lib/relay_selector/src/relay/relay.rs

@ -0,0 +1,10 @@
pub struct Relay {
variant: RelayVariant,
url: String,
}
impl Relay {
pub fn new(variant: RelayVariant, url: String) -> Self {
Relay { variant, url }
}
}

20
lib/relay_selector/src/relay/relay_variant.rs

@ -0,0 +1,20 @@
#[derive(Debug)]
pub enum RelayVariant {
General,
Inbox,
Outbox,
Local,
}
// TODO: Define methods to convert from str to RelayType.
impl RelayVariant {
pub fn from_str(s: &str) -> Option<Self> {
match s {
"general" => Some(RelayVariant::General),
"inbox" => Some(RelayVariant::Inbox),
"outbox" => Some(RelayVariant::Outbox),
"local" => Some(RelayVariant::Local),
_ => None,
}
}
}

109
lib/relay_selector/src/relay_selector.rs

@ -0,0 +1,109 @@
use std::collections::HashMap;
use crate::relay::RelayVariant;
const CONNECTION_WEIGHT: f32 = 0.1;
pub struct RelaySelector {
initial_weights: HashMap<String, f32>,
current_weights: HashMap<String, f32>,
active_connections: HashMap<String, u8>,
// Sorted relay lists by variant
general: Vec<String>,
inbox: Vec<String>,
outbox: Vec<String>,
}
// Constructor and destructor
impl RelaySelector {
pub fn new() -> Self {
// TODO: Initialize with data from persistent storage.
Self {
initial_weights: HashMap::new(),
current_weights: HashMap::new(),
active_connections: HashMap::new(),
general: Vec::new(),
inbox: Vec::new(),
outbox: Vec::new(),
}
}
}
// Get and return methods
impl RelaySelector {
pub fn get_relay_by_weighted_round_robin(
&mut self,
variant: RelayVariant,
rank: usize,
) -> Result<String, String> {
let ranked = match variant {
RelayVariant::General => &self.general,
RelayVariant::Inbox => &self.inbox,
RelayVariant::Outbox => &self.outbox,
_ => {
return Err(format!(
"[RelaySelector] Unsupported variant: {:?}",
variant
));
}
};
// Grab the relay of the requested rank
// Assumes relays are sorted in descending order of rank
let selected = ranked.get(rank).clone().ok_or(format!(
"[RelaySelector] No {:?} relay found at rank {:?}",
variant, rank
))?;
// Increment the number of active connections
let count = self.active_connections.get_mut(selected).ok_or(format!(
"[RelaySelector] Relay {:?} not found in active connections",
selected
))?;
*count = count.checked_add(1).ok_or(format!(
"[RelaySelector] Relay {:?} has reached maximum active connections",
selected
))?;
// Update the current weight based on the new number of active connections
let initial_weight = self.initial_weights.get(selected).ok_or(format!(
"[RelaySelector] Relay {:?} not found in initial weights",
selected
))?;
let current_weight = self.current_weights.get_mut(selected).ok_or(format!(
"[RelaySelector] Relay {:?} not found in current weights",
selected
))?;
*current_weight = initial_weight + *count as f32 * CONNECTION_WEIGHT;
// TODO: Invoke sort on current weights.
Ok(selected.clone())
}
pub fn return_relay(&mut self, relay: &str) -> Result<(), String> {
// Decrement the number of active connections
let count = self.active_connections.get_mut(relay).ok_or(format!(
"[RelaySelector] Relay {:?} not found in active connections",
relay
))?;
*count = count.checked_sub(1).unwrap_or(0); // Quietly ignore lower bound violations
// Update the current weight based on the new number of active connections
let initial_weight = self.initial_weights.get(relay).ok_or(format!(
"[RelaySelector] Relay {:?} not found in initial weights",
relay
))?;
let current_weight = self.current_weights.get_mut(relay).ok_or(format!(
"[RelaySelector] Relay {:?} not found in current weights",
relay
))?;
*current_weight = initial_weight + *count as f32 * CONNECTION_WEIGHT;
// TODO: Invoke sort on current weights.
Ok(())
}
}
Loading…
Cancel
Save