Page MenuHomePhabricator

No OneTemporary

diff --git a/crates/store/src/network.rs b/crates/store/src/network.rs
index 7de0da3..2421c27 100644
--- a/crates/store/src/network.rs
+++ b/crates/store/src/network.rs
@@ -1,1338 +1,1535 @@
//! Network management module for storing and managing network configurations.
//!
//! This module provides functionality to create, read, update, and delete network
//! configurations. Each network is defined by a name, network range (IPv4 or IPv6),
//! a provider implementation, and an optional assigner implementation.
//!
//! # Example
//!
//! ```
//! use store::network::*;
//! # use tempfile::TempDir;
//! # fn example() -> store::Result<()> {
//! # let temp_dir = tempfile::tempdir().unwrap();
//! # let db = sled::open(temp_dir.path())?;
//! let store = NetworkStore::open(&db)?;
//!
//! // Create a network with IPv4 range
//! let netrange = NetRange::from_cidr("192.168.1.0/24")?;
//! let provider = BasicProvider::new()
//! .with_config("type", "aws")
//! .with_config("region", "us-west-2");
//! let assigner = Some(BasicAssigner::new("dhcp"));
//!
//! store.create("production", netrange, provider, assigner)?;
//!
//! // Retrieve the network
//! let network = store.get("production")?.unwrap();
//! assert_eq!(network.name, "production");
//!
//! // List all networks
//! let networks = store.list()?;
//! assert!(networks.contains(&"production".to_string()));
//! # Ok(())
//! # }
//! ```
use crate::{Result, Error, RangeStore};
use sled::Transactional;
use std::collections::HashMap;
use std::net::{Ipv4Addr, Ipv6Addr, IpAddr};
/// Represents an IPv4 or IPv6 network range
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub enum NetRange {
/// IPv4 network with address and prefix length
V4 { addr: Ipv4Addr, prefix: u8 },
/// IPv6 network with address and prefix length
V6 { addr: Ipv6Addr, prefix: u8 },
}
impl NetRange {
/// Create a new IPv4 network range
pub fn ipv4(addr: Ipv4Addr, prefix: u8) -> Result<Self> {
if prefix > 32 {
return Err(Error::StoreError(sled::Error::Unsupported(
format!("Invalid IPv4 prefix length: {}", prefix)
)));
}
Ok(NetRange::V4 { addr, prefix })
}
/// Create a new IPv6 network range
pub fn ipv6(addr: Ipv6Addr, prefix: u8) -> Result<Self> {
if prefix > 128 {
return Err(Error::StoreError(sled::Error::Unsupported(
format!("Invalid IPv6 prefix length: {}", prefix)
)));
}
Ok(NetRange::V6 { addr, prefix })
}
/// Parse a network range from CIDR notation
pub fn from_cidr(cidr: &str) -> Result<Self> {
let parts: Vec<&str> = cidr.split('/').collect();
if parts.len() != 2 {
return Err(Error::StoreError(sled::Error::Unsupported(
"Invalid CIDR format".to_string()
)));
}
let prefix: u8 = parts[1].parse().map_err(|_| {
Error::StoreError(sled::Error::Unsupported("Invalid prefix length".to_string()))
})?;
if let Ok(ipv4) = parts[0].parse::<Ipv4Addr>() {
Self::ipv4(ipv4, prefix)
} else if let Ok(ipv6) = parts[0].parse::<Ipv6Addr>() {
Self::ipv6(ipv6, prefix)
} else {
Err(Error::StoreError(sled::Error::Unsupported(
"Invalid IP address format".to_string()
)))
}
}
/// Convert to CIDR notation string
pub fn to_cidr(&self) -> String {
match self {
NetRange::V4 { addr, prefix } => format!("{}/{}", addr, prefix),
NetRange::V6 { addr, prefix } => format!("{}/{}", addr, prefix),
}
}
}
/// Trait for network providers
pub trait NetworkProvider: std::fmt::Debug {
/// Get the provider type identifier
fn provider_type(&self) -> &'static str;
/// Serialize provider configuration to JSON
fn to_json(&self) -> Result<String>;
/// Create provider from JSON
fn from_json(json: &str) -> Result<Self> where Self: Sized;
}
/// Trait for network assigners
pub trait NetworkAssigner: std::fmt::Debug {
/// Get the assigner type identifier
fn assigner_type(&self) -> &'static str;
/// Serialize assigner configuration to JSON
fn to_json(&self) -> Result<String>;
/// Create assigner from JSON
fn from_json(json: &str) -> Result<Self> where Self: Sized;
/// Initialize the assigner for a network during creation
fn initialize_for_network(&self, _network_name: &str, _netrange: &NetRange, _range_store: Option<&RangeStore>) -> Result<()> {
// Default implementation does nothing
Ok(())
}
/// Initialize the assigner for a network within a transaction
fn initialize_for_network_in_transaction(
&self,
_network_name: &str,
_netrange: &NetRange,
_range_trees: Option<(&sled::transaction::TransactionalTree, &sled::transaction::TransactionalTree)>
) -> Result<(), sled::transaction::ConflictableTransactionError<()>> {
// Default implementation does nothing
Ok(())
}
+ /// Cleanup the assigner for a network during deletion
+ fn cleanup_for_network(&self, _network_name: &str, _range_store: Option<&RangeStore>) -> Result<()> {
+ // Default implementation does nothing
+ Ok(())
+ }
+
+ /// Cleanup the assigner for a network within a transaction
+ fn cleanup_for_network_in_transaction(
+ &self,
+ _network_name: &str,
+ _range_trees: Option<(&sled::transaction::TransactionalTree, &sled::transaction::TransactionalTree)>
+ ) -> Result<(), sled::transaction::ConflictableTransactionError<()>> {
+ // Default implementation does nothing
+ Ok(())
+ }
+
/// Get an IP assignment for the given identifier
fn get_assignment(&self, _network: &Network, _identifier: &str, _range_store: Option<&RangeStore>) -> Result<Option<IpAddr>> {
// Default implementation returns None (no assignment tracking)
Ok(None)
}
}
/// Basic network provider implementation
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct BasicProvider {
pub config: HashMap<String, String>,
}
impl BasicProvider {
pub fn new() -> Self {
Self {
config: HashMap::new(),
}
}
pub fn with_config(mut self, key: &str, value: &str) -> Self {
self.config.insert(key.to_string(), value.to_string());
self
}
}
impl NetworkProvider for BasicProvider {
fn provider_type(&self) -> &'static str {
"basic"
}
fn to_json(&self) -> Result<String> {
serde_json::to_string(self).map_err(|e| {
Error::StoreError(sled::Error::Unsupported(format!("JSON serialization error: {}", e)))
})
}
fn from_json(json: &str) -> Result<Self> {
serde_json::from_str(json).map_err(|e| {
Error::StoreError(sled::Error::Unsupported(format!("JSON deserialization error: {}", e)))
})
}
}
/// Basic network assigner implementation
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct BasicAssigner {
pub strategy: String,
pub config: HashMap<String, String>,
}
impl BasicAssigner {
pub fn new(strategy: &str) -> Self {
Self {
strategy: strategy.to_string(),
config: HashMap::new(),
}
}
pub fn with_config(mut self, key: &str, value: &str) -> Self {
self.config.insert(key.to_string(), value.to_string());
self
}
}
impl NetworkAssigner for BasicAssigner {
fn assigner_type(&self) -> &'static str {
"basic"
}
fn to_json(&self) -> Result<String> {
serde_json::to_string(self).map_err(|e| {
Error::StoreError(sled::Error::Unsupported(format!("JSON serialization error: {}", e)))
})
}
fn from_json(json: &str) -> Result<Self> {
serde_json::from_str(json).map_err(|e| {
Error::StoreError(sled::Error::Unsupported(format!("JSON deserialization error: {}", e)))
})
}
}
/// Range-based network assigner that uses RangeStore for IP assignment
///
/// The `RangeAssigner` provides IP address assignment functionality by integrating
/// with the `RangeStore`. It automatically manages IP address allocation within
/// a network range using a bitmap-based approach for efficient tracking.
///
/// # Features
///
/// - Automatic range initialization when creating networks
/// - Sequential IP assignment within the network range
/// - Assignment tracking with custom identifiers
/// - IP unassignment and reuse
/// - Integration with the NetworkStore lifecycle
///
/// # Example
///
/// ```
/// use store::network::{RangeAssigner, NetRange, BasicProvider, NetworkStore};
/// # use tempfile::TempDir;
/// # fn example() -> store::Result<()> {
/// # let temp_dir = tempfile::tempdir().unwrap();
/// # let db = sled::open(temp_dir.path())?;
/// # let ranges = store::range::RangeStore::open(&db)?;
/// # let store = NetworkStore::open_with_ranges(&db, ranges.clone())?;
///
/// // Create a RangeAssigner for a network (uses network name as range name)
/// let assigner = RangeAssigner::new()
/// .with_config("strategy", "sequential");
///
/// // Create a network with the range assigner
/// let netrange = NetRange::from_cidr("192.168.1.0/24")?;
/// let provider = BasicProvider::new();
/// store.create("my-network", netrange, provider, Some(assigner))?;
///
/// // Use the assigner to allocate IPs
/// let range_assigner = RangeAssigner::new();
/// let ip_bit = range_assigner.assign_ip("my-network", &ranges, "device1")?;
/// # Ok(())
/// # }
/// ```
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct RangeAssigner {
pub range_name: Option<String>,
pub config: HashMap<String, String>,
}
impl RangeAssigner {
/// Create a new RangeAssigner with default settings
///
/// The range name will default to the network name when the network is created.
/// Use `with_range_name()` to specify a custom range name if needed.
pub fn new() -> Self {
Self {
range_name: None,
config: HashMap::new(),
}
}
/// Create a new RangeAssigner with a specific range name
///
/// # Arguments
///
/// * `range_name` - Custom identifier for the IP range
pub fn with_range_name(range_name: &str) -> Self {
Self {
range_name: Some(range_name.to_string()),
config: HashMap::new(),
}
}
/// Add configuration key-value pairs to the assigner
///
/// Configuration options can be used to customize the behavior of the
/// assigner. Common configuration keys might include:
/// - `strategy`: Assignment strategy (e.g., "sequential", "random")
/// - `reserve_gateway`: Whether to reserve the first IP for gateway
/// - `pool_size`: Maximum number of IPs to manage
///
/// # Arguments
///
/// * `key` - Configuration key
/// * `value` - Configuration value
pub fn with_config(mut self, key: &str, value: &str) -> Self {
self.config.insert(key.to_string(), value.to_string());
self
}
/// Initialize the range for this assigner with the given network size
///
/// This method calculates the number of available IP addresses in the network
/// range and creates a corresponding range in the RangeStore. It's automatically
/// called when creating a network with a RangeAssigner.
///
/// # Arguments
///
/// * `range_name` - Name to use for the range
/// * `range_store` - Reference to the RangeStore
/// * `network` - Network range (IPv4 or IPv6) to initialize
///
/// # Returns
///
/// Returns `Ok(())` if the range was successfully initialized, or an error
/// if the network parameters are invalid or the range already exists.
pub fn initialize_range(&self, range_name: &str, range_store: &RangeStore, network: &NetRange) -> Result<()> {
let size = match network {
NetRange::V4 { prefix, .. } => {
if *prefix >= 32 {
return Err(Error::StoreError(sled::Error::Unsupported(
"IPv4 prefix must be less than 32".to_string()
)));
}
1u64 << (32 - prefix)
}
NetRange::V6 { prefix, .. } => {
if *prefix >= 128 {
return Err(Error::StoreError(sled::Error::Unsupported(
"IPv6 prefix must be less than 128".to_string()
)));
}
// For IPv6, we'll limit to a reasonable size to avoid huge ranges
let host_bits = 128 - prefix;
if host_bits > 32 {
1u64 << 32 // Cap at 2^32 addresses
} else {
1u64 << host_bits
}
}
};
range_store.define(range_name, size)
}
/// Assign an IP address from the range
///
/// Assigns the next available IP address in the range to the specified identifier.
/// The assignment uses a sequential allocation strategy, finding the first
/// available bit position in the range bitmap.
///
/// # Arguments
///
/// * `range_name` - Name of the range to assign from
/// * `range_store` - Reference to the RangeStore
/// * `identifier` - Unique identifier for the device/entity receiving the IP
///
/// # Returns
///
/// Returns the bit position of the assigned IP address, which can be used
/// to calculate the actual IP address within the network range.
///
/// # Errors
///
/// Returns an error if the range is full or doesn't exist.
pub fn assign_ip(&self, range_name: &str, range_store: &RangeStore, identifier: &str) -> Result<u64> {
range_store.assign(range_name, identifier)
}
/// Get assigned IP information
///
/// Retrieves the identifier associated with a specific bit position in the range.
/// This is useful for determining which device or entity is assigned to a
/// particular IP address.
///
/// # Arguments
///
/// * `range_name` - Name of the range to query
/// * `range_store` - Reference to the RangeStore
/// * `bit_position` - The bit position to query
///
/// # Returns
///
/// Returns `Some(identifier)` if the bit position is assigned, or `None`
/// if it's free or out of range.
pub fn get_assignment_by_bit(&self, range_name: &str, range_store: &RangeStore, bit_position: u64) -> Result<Option<String>> {
range_store.get(range_name, bit_position)
}
/// Unassign an IP address
///
/// Releases an IP address assignment, making it available for future assignments.
/// This is typically called when a device is removed or no longer needs its
/// assigned IP address.
///
/// # Arguments
///
/// * `range_name` - Name of the range to unassign from
/// * `range_store` - Reference to the RangeStore
/// * `bit_position` - The bit position to unassign
///
/// # Returns
///
/// Returns `true` if the IP was successfully unassigned, or `false` if it
/// was already free or the bit position is out of range.
pub fn unassign_ip(&self, range_name: &str, range_store: &RangeStore, bit_position: u64) -> Result<bool> {
range_store.unassign_bit(range_name, bit_position)
}
/// Get the range name for this assigner, using the network name as default
///
/// # Arguments
///
/// * `network_name` - Network name to use as default if no custom range name is set
///
/// # Returns
///
/// Returns the range name to use for operations
pub fn get_range_name(&self, network_name: &str) -> String {
self.range_name.clone().unwrap_or_else(|| network_name.to_string())
}
/// Convert a bit position to an IP address within the network range
///
/// # Arguments
///
/// * `bit_position` - The bit position within the range
/// * `netrange` - The network range to calculate the IP within
///
/// # Returns
///
/// Returns the IP address corresponding to the bit position
pub fn bit_position_to_ip(&self, bit_position: u64, netrange: &NetRange) -> Result<IpAddr> {
match netrange {
NetRange::V4 { addr, prefix: _ } => {
let network_addr = u32::from(*addr);
let host_addr = network_addr + bit_position as u32;
Ok(IpAddr::V4(Ipv4Addr::from(host_addr)))
}
NetRange::V6 { addr, prefix: _ } => {
let network_addr = u128::from(*addr);
let host_addr = network_addr + bit_position as u128;
Ok(IpAddr::V6(Ipv6Addr::from(host_addr)))
}
}
}
/// Find the bit position for a given identifier in the range
///
/// # Arguments
///
/// * `range_name` - Name of the range to search
/// * `range_store` - Reference to the RangeStore
/// * `identifier` - The identifier to search for
///
/// # Returns
///
/// Returns the bit position if found, None otherwise
pub fn find_identifier_bit_position(&self, range_name: &str, range_store: &RangeStore, identifier: &str) -> Result<Option<u64>> {
// Get range ID first
let range_id = match range_store.names.get(range_name)? {
Some(data) => {
if data.len() != 16 {
return Ok(None);
}
let id_bytes: [u8; 8] = data[0..8].try_into()
.map_err(|_| Error::StoreError(sled::Error::Unsupported("Invalid range data".to_string())))?;
u64::from_be_bytes(id_bytes)
}
None => return Ok(None),
};
// Search for the assignment with this identifier
let prefix = range_id.to_be_bytes();
for result in range_store.assign.scan_prefix(&prefix) {
let (key, stored_value) = result?;
if key.len() == 16 {
let stored_value_str = String::from_utf8(stored_value.to_vec())?;
if stored_value_str == identifier {
let bit_position_bytes: [u8; 8] = key[8..16].try_into()
.map_err(|_| Error::StoreError(sled::Error::Unsupported("Invalid key format".to_string())))?;
return Ok(Some(u64::from_be_bytes(bit_position_bytes)));
}
}
}
Ok(None)
}
}
impl NetworkAssigner for RangeAssigner {
fn assigner_type(&self) -> &'static str {
"range"
}
fn to_json(&self) -> Result<String> {
serde_json::to_string(self).map_err(|e| {
Error::StoreError(sled::Error::Unsupported(format!("JSON serialization error: {}", e)))
})
}
fn from_json(json: &str) -> Result<Self> {
serde_json::from_str(json).map_err(|e| {
Error::StoreError(sled::Error::Unsupported(format!("JSON deserialization error: {}", e)))
})
}
fn initialize_for_network(&self, network_name: &str, netrange: &NetRange, range_store: Option<&RangeStore>) -> Result<()> {
if let Some(store) = range_store {
let range_name = self.get_range_name(network_name);
self.initialize_range(&range_name, store, netrange)?;
}
Ok(())
}
fn initialize_for_network_in_transaction(
&self,
network_name: &str,
netrange: &NetRange,
range_trees: Option<(&sled::transaction::TransactionalTree, &sled::transaction::TransactionalTree)>
) -> Result<(), sled::transaction::ConflictableTransactionError<()>> {
if let Some((range_names, range_map)) = range_trees {
let range_name = self.get_range_name(network_name);
let size = match netrange {
NetRange::V4 { prefix, .. } => {
if *prefix >= 32 {
return Err(sled::transaction::ConflictableTransactionError::Abort(()));
}
1u64 << (32 - prefix)
}
NetRange::V6 { prefix, .. } => {
if *prefix >= 128 {
return Err(sled::transaction::ConflictableTransactionError::Abort(()));
}
// For IPv6, we'll limit to a reasonable size to avoid huge ranges
let host_bits = 128 - prefix;
if host_bits > 32 {
1u64 << 32 // Cap at 2^32 addresses
} else {
1u64 << host_bits
}
}
};
// Define the range within the transaction
match range_names.get(range_name.as_bytes())? {
Some(_) => Ok(()), // Range already exists
None => {
let id = range_names.generate_id()?;
// Store name -> (id, size)
let mut value = Vec::new();
value.extend_from_slice(&id.to_be_bytes());
value.extend_from_slice(&size.to_be_bytes());
range_names.insert(range_name.as_bytes(), value)?;
// Initialize bitmap for the range (all zeros)
let bitmap_size = (size + 7) / 8; // Round up to nearest byte
let bitmap = vec![0u8; bitmap_size as usize];
range_map.insert(&id.to_be_bytes(), bitmap)?;
Ok(())
}
}
} else {
Ok(())
}
}
fn get_assignment(&self, network: &Network, identifier: &str, range_store: Option<&RangeStore>) -> Result<Option<IpAddr>> {
if let Some(store) = range_store {
let range_name = self.get_range_name(&network.name);
// Find the bit position for this identifier
if let Some(bit_position) = self.find_identifier_bit_position(&range_name, store, identifier)? {
// Convert bit position to IP address
let ip = self.bit_position_to_ip(bit_position, &network.netrange)?;
Ok(Some(ip))
} else {
Ok(None)
}
} else {
Ok(None)
}
}
+
+ fn cleanup_for_network(&self, network_name: &str, range_store: Option<&RangeStore>) -> Result<()> {
+ if let Some(store) = range_store {
+ let range_name = self.get_range_name(network_name);
+ store.delete_range(&range_name)?;
+ }
+ Ok(())
+ }
+
+ fn cleanup_for_network_in_transaction(
+ &self,
+ network_name: &str,
+ range_trees: Option<(&sled::transaction::TransactionalTree, &sled::transaction::TransactionalTree)>
+ ) -> Result<(), sled::transaction::ConflictableTransactionError<()>> {
+ if let Some((range_names, range_map)) = range_trees {
+ let range_name = self.get_range_name(network_name);
+
+ // Get the range ID from the name
+ if let Some(value) = range_names.get(range_name.as_bytes())? {
+ let id = u64::from_be_bytes([
+ value[0], value[1], value[2], value[3],
+ value[4], value[5], value[6], value[7],
+ ]);
+
+ // Remove the range data
+ range_map.remove(&id.to_be_bytes())?;
+ }
+
+ // Remove the range name
+ range_names.remove(range_name.as_bytes())?;
+ }
+ Ok(())
+ }
}
/// Network configuration
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct Network {
pub name: String,
pub netrange: NetRange,
pub provider_type: String,
pub provider_config: String,
pub assigner_type: Option<String>,
pub assigner_config: Option<String>,
}
impl Network {
/// Get an IP assignment for the given identifier
///
/// This method calls the appropriate assigner implementation to retrieve
/// the assigned IP address for a given identifier. Different assigners
/// may return the IP in different formats (bit position or full IP).
///
/// # Arguments
///
/// * `identifier` - The identifier to look up (e.g., device name, container ID)
/// * `range_store` - Optional reference to RangeStore (required for RangeAssigner)
///
/// # Returns
///
/// Returns the assigned IP address if found, None if no assignment exists
///
/// # Example
///
/// ```
/// # use store::network::{Network, NetRange, RangeAssigner};
/// # use tempfile::TempDir;
/// # fn example() -> store::Result<()> {
/// # let temp_dir = tempfile::tempdir().unwrap();
/// # let db = sled::open(temp_dir.path())?;
/// # let ranges = store::range::RangeStore::open(&db)?;
/// # let store = store::network::NetworkStore::open_with_ranges(&db, ranges.clone())?;
/// # let netrange = NetRange::from_cidr("192.168.1.0/24")?;
/// # let provider = store::network::BasicProvider::new();
/// # let assigner = RangeAssigner::new();
/// # store.create("test", netrange, provider, Some(assigner))?;
/// # let network = store.get("test")?.unwrap();
///
/// let ip = network.get_assignment("device1", Some(&ranges))?;
/// if let Some(addr) = ip {
/// println!("Device1 is assigned IP: {}", addr);
/// }
/// # Ok(())
/// # }
/// ```
+ /// Create an assigner instance from the network configuration
+ pub fn create_assigner(&self) -> Result<Box<dyn NetworkAssigner>> {
+ if let (Some(assigner_type), Some(assigner_config)) = (&self.assigner_type, &self.assigner_config) {
+ match assigner_type.as_str() {
+ "range" => {
+ let range_assigner: RangeAssigner = serde_json::from_str(assigner_config)
+ .map_err(|e| Error::StoreError(sled::Error::Unsupported(
+ format!("Failed to deserialize RangeAssigner: {}", e)
+ )))?;
+ Ok(Box::new(range_assigner))
+ }
+ "basic" => {
+ let basic_assigner: BasicAssigner = serde_json::from_str(assigner_config)
+ .map_err(|e| Error::StoreError(sled::Error::Unsupported(
+ format!("Failed to deserialize BasicAssigner: {}", e)
+ )))?;
+ Ok(Box::new(basic_assigner))
+ }
+ _ => {
+ Err(Error::StoreError(sled::Error::Unsupported(
+ format!("Unknown assigner type: {}", assigner_type)
+ )))
+ }
+ }
+ } else {
+ Err(Error::StoreError(sled::Error::Unsupported(
+ "No assigner configured".to_string()
+ )))
+ }
+ }
+
pub fn get_assignment(&self, identifier: &str, range_store: Option<&RangeStore>) -> Result<Option<IpAddr>> {
if let (Some(assigner_type), Some(assigner_config)) = (&self.assigner_type, &self.assigner_config) {
match assigner_type.as_str() {
"range" => {
let range_assigner: RangeAssigner = serde_json::from_str(assigner_config)
.map_err(|e| Error::StoreError(sled::Error::Unsupported(
format!("Failed to deserialize RangeAssigner: {}", e)
)))?;
range_assigner.get_assignment(self, identifier, range_store)
}
"basic" => {
let basic_assigner: BasicAssigner = serde_json::from_str(assigner_config)
.map_err(|e| Error::StoreError(sled::Error::Unsupported(
format!("Failed to deserialize BasicAssigner: {}", e)
)))?;
basic_assigner.get_assignment(self, identifier, range_store)
}
_ => {
// Unknown assigner type
Ok(None)
}
}
} else {
// No assigner configured
Ok(None)
}
}
/// Get an IP address from a bit position in the network range
///
/// This method converts a bit position to the corresponding IP address
/// within this network's range. Useful when you already know the bit
/// position from range operations.
///
/// # Arguments
///
/// * `bit_position` - The bit position within the network range
///
/// # Returns
///
/// Returns the IP address corresponding to the bit position
///
/// # Example
///
/// ```
/// # use store::network::{Network, NetRange};
/// # fn example() -> store::Result<()> {
/// # let netrange = NetRange::from_cidr("192.168.1.0/24")?;
/// let network = Network {
/// name: "test".to_string(),
/// netrange: netrange,
/// provider_type: "basic".to_string(),
/// provider_config: "{}".to_string(),
/// assigner_type: None,
/// assigner_config: None,
/// };
///
/// let ip = network.get_assignment_by_bit_position(5)?;
/// println!("Bit position 5 corresponds to IP: {}", ip);
/// # Ok(())
/// # }
/// ```
pub fn get_assignment_by_bit_position(&self, bit_position: u64) -> Result<IpAddr> {
match &self.netrange {
NetRange::V4 { addr, prefix: _ } => {
let network_addr = u32::from(*addr);
let host_addr = network_addr + bit_position as u32;
Ok(IpAddr::V4(Ipv4Addr::from(host_addr)))
}
NetRange::V6 { addr, prefix: _ } => {
let network_addr = u128::from(*addr);
let host_addr = network_addr + bit_position as u128;
Ok(IpAddr::V6(Ipv6Addr::from(host_addr)))
}
}
}
}
/// Network store for managing network configurations
#[derive(Debug, Clone)]
pub struct NetworkStore {
pub(crate) namespaces: crate::namespace::NamespaceStore,
pub(crate) networks: sled::Tree,
pub(crate) ranges: Option<crate::range::RangeStore>,
}
impl NetworkStore {
/// Open a new network store
pub fn open(db: &sled::Db) -> Result<Self> {
Ok(NetworkStore {
namespaces: crate::namespace::NamespaceStore::open(db)?,
networks: db.open_tree("networks/1/data")?,
ranges: None,
})
}
/// Open a new network store with range store
pub fn open_with_ranges(db: &sled::Db, ranges: crate::range::RangeStore) -> Result<Self> {
Ok(NetworkStore {
namespaces: crate::namespace::NamespaceStore::open(db)?,
networks: db.open_tree("networks/1/data")?,
ranges: Some(ranges),
})
}
/// Create a new network with provider and optional assigner
pub fn create<P, A>(&self, name: &str, netrange: NetRange, provider: P, assigner: Option<A>) -> Result<()>
where
P: NetworkProvider,
A: NetworkAssigner,
{
// Ensure "networks" namespace exists
if !self.namespaces.namespace_exists("networks")? {
self.namespaces.define("networks")?;
}
let network = Network {
name: name.to_string(),
netrange: netrange.clone(),
provider_type: provider.provider_type().to_string(),
provider_config: provider.to_json()?,
assigner_type: assigner.as_ref().map(|a| a.assigner_type().to_string()),
assigner_config: assigner.as_ref().map(|a| a.to_json()).transpose()?,
};
let network_json = serde_json::to_string(&network).map_err(|e| {
Error::StoreError(sled::Error::Unsupported(format!("JSON serialization error: {}", e)))
})?;
// Include range store trees in transaction if needed
if let (Some(assigner_ref), Some(range_store)) = (&assigner, &self.ranges) {
if assigner_ref.assigner_type() == "range" {
// Transaction with both network and range stores
(&self.namespaces.names, &self.namespaces.spaces, &self.networks, &range_store.names, &range_store.map).transaction(
|(names, spaces, networks, range_names, range_map)| {
// Reserve the network name in the "networks" namespace
if !self.namespaces.reserve_in_transaction(names, spaces, "networks", name, name)? {
return Err(sled::transaction::ConflictableTransactionError::Abort(()));
}
// Initialize the assigner within the transaction
assigner_ref.initialize_for_network_in_transaction(name, &netrange, Some((range_names, range_map)))?;
// Store the network configuration
networks.insert(name.as_bytes(), network_json.as_bytes())?;
Ok(())
}
).map_err(|e| match e {
sled::transaction::TransactionError::Abort(()) => {
Error::NamespaceKeyReserved("networks".to_string(), name.to_string())
}
sled::transaction::TransactionError::Storage(storage_err) => Error::StoreError(storage_err),
})?;
} else {
// Transaction without range store
(&self.namespaces.names, &self.namespaces.spaces, &self.networks).transaction(
|(names, spaces, networks)| {
// Reserve the network name in the "networks" namespace
if !self.namespaces.reserve_in_transaction(names, spaces, "networks", name, name)? {
return Err(sled::transaction::ConflictableTransactionError::Abort(()));
}
// Store the network configuration
networks.insert(name.as_bytes(), network_json.as_bytes())?;
Ok(())
}
).map_err(|e| match e {
sled::transaction::TransactionError::Abort(()) => {
Error::NamespaceKeyReserved("networks".to_string(), name.to_string())
}
sled::transaction::TransactionError::Storage(storage_err) => Error::StoreError(storage_err),
})?;
}
} else {
// Transaction without assigner or range store
(&self.namespaces.names, &self.namespaces.spaces, &self.networks).transaction(
|(names, spaces, networks)| {
// Reserve the network name in the "networks" namespace
if !self.namespaces.reserve_in_transaction(names, spaces, "networks", name, name)? {
return Err(sled::transaction::ConflictableTransactionError::Abort(()));
}
// Store the network configuration
networks.insert(name.as_bytes(), network_json.as_bytes())?;
Ok(())
}
).map_err(|e| match e {
sled::transaction::TransactionError::Abort(()) => {
Error::NamespaceKeyReserved("networks".to_string(), name.to_string())
}
sled::transaction::TransactionError::Storage(storage_err) => Error::StoreError(storage_err),
})?;
}
Ok(())
}
/// Get a network by name
pub fn get(&self, name: &str) -> Result<Option<Network>> {
if let Some(data) = self.networks.get(name.as_bytes())? {
let network_json = String::from_utf8(data.to_vec())?;
let network: Network = serde_json::from_str(&network_json).map_err(|e| {
Error::StoreError(sled::Error::Unsupported(format!("JSON deserialization error: {}", e)))
})?;
Ok(Some(network))
} else {
Ok(None)
}
}
/// Update an existing network
pub fn update<P, A>(&self, name: &str, netrange: NetRange, provider: P, assigner: Option<A>) -> Result<()>
where
P: NetworkProvider,
A: NetworkAssigner,
{
if !self.namespaces.key_exists("networks", name)? {
return Err(Error::StoreError(sled::Error::Unsupported(
format!("Network '{}' does not exist", name)
)));
}
let network = Network {
name: name.to_string(),
netrange,
provider_type: provider.provider_type().to_string(),
provider_config: provider.to_json()?,
assigner_type: assigner.as_ref().map(|a| a.assigner_type().to_string()),
assigner_config: assigner.as_ref().map(|a| a.to_json()).transpose()?,
};
let network_json = serde_json::to_string(&network).map_err(|e| {
Error::StoreError(sled::Error::Unsupported(format!("JSON serialization error: {}", e)))
})?;
self.networks.insert(name.as_bytes(), network_json.as_bytes())?;
Ok(())
}
/// Delete a network by name
pub fn delete(&self, name: &str) -> Result<bool> {
- let result = (&self.namespaces.names, &self.namespaces.spaces, &self.networks).transaction(
- |(names, spaces, networks)| {
- // Remove from namespace
- let removed = self.namespaces.remove_in_transaction(names, spaces, "networks", name)?;
- if removed {
- // Remove network data
- networks.remove(name.as_bytes())?;
+ let result = if let Some(ranges) = &self.ranges {
+ // Transaction with range cleanup
+ (&self.namespaces.names, &self.namespaces.spaces, &self.networks, &ranges.names, &ranges.map).transaction(
+ |(names, spaces, networks, range_names, range_map)| {
+ // Get network data before deletion to access assigner
+ if let Some(network_data) = networks.get(name.as_bytes())? {
+ let network: Network = serde_json::from_slice(network_data.as_ref())
+ .map_err(|_| sled::transaction::ConflictableTransactionError::Abort(()))?;
+
+ // Clean up assigner if it exists
+ if network.assigner_type.is_some() {
+ let assigner = network.create_assigner()
+ .map_err(|_| sled::transaction::ConflictableTransactionError::Abort(()))?;
+ assigner.cleanup_for_network_in_transaction(name, Some((range_names, range_map)))?;
+ }
+ }
+
+ // Remove from namespace
+ let removed = self.namespaces.remove_in_transaction(names, spaces, "networks", name)?;
+ if removed {
+ // Remove network data
+ networks.remove(name.as_bytes())?;
+ }
+ Ok(removed)
}
- Ok(removed)
- }
- ).map_err(|e| match e {
+ )
+ } else {
+ // Transaction without range cleanup
+ (&self.namespaces.names, &self.namespaces.spaces, &self.networks).transaction(
+ |(names, spaces, networks)| {
+ // Remove from namespace
+ let removed = self.namespaces.remove_in_transaction(names, spaces, "networks", name)?;
+ if removed {
+ // Remove network data
+ networks.remove(name.as_bytes())?;
+ }
+ Ok(removed)
+ }
+ )
+ };
+
+ let result = result.map_err(|e| match e {
sled::transaction::TransactionError::Abort(()) => {
Error::StoreError(sled::Error::Unsupported("Transaction aborted".to_string()))
}
sled::transaction::TransactionError::Storage(storage_err) => Error::StoreError(storage_err),
})?;
Ok(result)
}
/// List all network names
pub fn list(&self) -> Result<Vec<String>> {
self.namespaces.list_keys("networks")
}
/// Check if a network exists
pub fn exists(&self, name: &str) -> Result<bool> {
self.namespaces.key_exists("networks", name)
}
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::TempDir;
fn create_test_store() -> Result<(NetworkStore, TempDir)> {
let temp_dir = tempfile::tempdir().unwrap();
let db = sled::open(temp_dir.path())?;
let ranges = crate::range::RangeStore::open(&db)?;
let store = NetworkStore::open_with_ranges(&db, ranges)?;
Ok((store, temp_dir))
}
#[test]
fn test_netrange_ipv4() -> Result<()> {
let netrange = NetRange::ipv4("192.168.1.0".parse().unwrap(), 24)?;
assert_eq!(netrange.to_cidr(), "192.168.1.0/24");
Ok(())
}
#[test]
fn test_netrange_ipv6() -> Result<()> {
let netrange = NetRange::ipv6("2001:db8::".parse().unwrap(), 64)?;
assert_eq!(netrange.to_cidr(), "2001:db8::/64");
Ok(())
}
#[test]
fn test_netrange_from_cidr() -> Result<()> {
let ipv4_range = NetRange::from_cidr("10.0.0.0/8")?;
assert_eq!(ipv4_range.to_cidr(), "10.0.0.0/8");
let ipv6_range = NetRange::from_cidr("fe80::/10")?;
assert_eq!(ipv6_range.to_cidr(), "fe80::/10");
Ok(())
}
#[test]
fn test_basic_provider() -> Result<()> {
let provider = BasicProvider::new()
.with_config("endpoint", "https://api.example.com")
.with_config("timeout", "30");
let json = provider.to_json()?;
let restored = BasicProvider::from_json(&json)?;
assert_eq!(provider.config, restored.config);
assert_eq!(provider.provider_type(), "basic");
Ok(())
}
#[test]
fn test_basic_assigner() -> Result<()> {
let assigner = BasicAssigner::new("round_robin")
.with_config("pool_size", "100");
let json = assigner.to_json()?;
let restored = BasicAssigner::from_json(&json)?;
assert_eq!(assigner.strategy, restored.strategy);
assert_eq!(assigner.config, restored.config);
assert_eq!(assigner.assigner_type(), "basic");
Ok(())
}
#[test]
fn test_create_and_get_network() -> Result<()> {
let (store, _temp_dir) = create_test_store()?;
let netrange = NetRange::ipv4("192.168.1.0".parse().unwrap(), 24)?;
let provider = BasicProvider::new().with_config("type", "test");
let assigner = Some(BasicAssigner::new("sequential"));
store.create("test_network", netrange.clone(), provider, assigner)?;
let network = store.get("test_network")?.unwrap();
assert_eq!(network.name, "test_network");
assert_eq!(network.netrange, netrange);
assert_eq!(network.provider_type, "basic");
assert!(network.assigner_type.is_some());
Ok(())
}
#[test]
fn test_create_network_without_assigner() -> Result<()> {
let (store, _temp_dir) = create_test_store()?;
let netrange = NetRange::ipv6("2001:db8::".parse().unwrap(), 64)?;
let provider = BasicProvider::new();
let assigner: Option<BasicAssigner> = None;
store.create("ipv6_network", netrange.clone(), provider, assigner)?;
let network = store.get("ipv6_network")?.unwrap();
assert_eq!(network.name, "ipv6_network");
assert_eq!(network.netrange, netrange);
assert!(network.assigner_type.is_none());
assert!(network.assigner_config.is_none());
Ok(())
}
#[test]
fn test_create_duplicate_network() -> Result<()> {
let (store, _temp_dir) = create_test_store()?;
let netrange = NetRange::ipv4("10.0.0.0".parse().unwrap(), 8)?;
let provider = BasicProvider::new();
let assigner: Option<BasicAssigner> = None;
// First creation should succeed
store.create("duplicate_test", netrange.clone(), provider.clone(), assigner.clone())?;
// Second creation should fail
let result = store.create("duplicate_test", netrange, provider, assigner);
assert!(result.is_err());
Ok(())
}
#[test]
fn test_update_network() -> Result<()> {
let (store, _temp_dir) = create_test_store()?;
// Create initial network
let netrange = NetRange::ipv4("172.16.0.0".parse().unwrap(), 12)?;
let provider = BasicProvider::new().with_config("version", "1");
let assigner: Option<BasicAssigner> = None;
store.create("update_test", netrange, provider, assigner)?;
// Update the network
let new_netrange = NetRange::ipv4("172.16.0.0".parse().unwrap(), 16)?;
let new_provider = BasicProvider::new().with_config("version", "2");
let new_assigner = Some(BasicAssigner::new("random"));
store.update("update_test", new_netrange.clone(), new_provider, new_assigner)?;
// Verify the update
let network = store.get("update_test")?.unwrap();
assert_eq!(network.netrange, new_netrange);
assert!(network.assigner_type.is_some());
Ok(())
}
#[test]
fn test_delete_network() -> Result<()> {
let (store, _temp_dir) = create_test_store()?;
let netrange = NetRange::ipv4("203.0.113.0".parse().unwrap(), 24)?;
let provider = BasicProvider::new();
let assigner: Option<BasicAssigner> = None;
store.create("delete_test", netrange, provider, assigner)?;
assert!(store.exists("delete_test")?);
let deleted = store.delete("delete_test")?;
assert!(deleted);
assert!(!store.exists("delete_test")?);
// Try to delete non-existent network
let deleted_again = store.delete("delete_test")?;
assert!(!deleted_again);
Ok(())
}
#[test]
fn test_list_networks() -> Result<()> {
let (store, _temp_dir) = create_test_store()?;
// Create multiple networks
let networks = vec![
("net1", "10.1.0.0/16"),
("net2", "10.2.0.0/16"),
("net3", "2001:db8:1::/48"),
];
for (name, cidr) in &networks {
let netrange = NetRange::from_cidr(cidr)?;
let provider = BasicProvider::new();
let assigner: Option<BasicAssigner> = None;
store.create(name, netrange, provider, assigner)?;
}
let mut network_names = store.list()?;
network_names.sort();
let mut expected: Vec<String> = networks.iter().map(|(name, _)| name.to_string()).collect();
expected.sort();
assert_eq!(network_names, expected);
Ok(())
}
#[test]
fn test_update_nonexistent_network() -> Result<()> {
let (store, _temp_dir) = create_test_store()?;
let netrange = NetRange::ipv4("198.51.100.0".parse().unwrap(), 24)?;
let provider = BasicProvider::new();
let assigner: Option<BasicAssigner> = None;
let result = store.update("nonexistent", netrange, provider, assigner);
assert!(result.is_err());
Ok(())
}
#[test]
fn test_invalid_cidr() {
let result = NetRange::from_cidr("invalid");
assert!(result.is_err());
let result = NetRange::from_cidr("192.168.1.0");
assert!(result.is_err());
let result = NetRange::from_cidr("192.168.1.0/33");
assert!(result.is_err());
}
#[test]
fn test_range_assigner() -> Result<()> {
let (store, _temp_dir) = create_test_store()?;
// Create a network with RangeAssigner
let netrange = NetRange::from_cidr("192.168.1.0/24")?;
let provider = BasicProvider::new()
.with_config("type", "test");
let assigner = RangeAssigner::new()
.with_config("strategy", "sequential");
store.create("test-network", netrange, provider, Some(assigner))?;
// Verify the network was created
let network = store.get("test-network")?.unwrap();
assert_eq!(network.name, "test-network");
assert_eq!(network.assigner_type, Some("range".to_string()));
// Test range assignment functionality
if let Some(ref range_store) = store.ranges {
let range_assigner = RangeAssigner::new();
let range_name = "test-network"; // Uses network name as range name
// Assign some IPs
let bit1 = range_assigner.assign_ip(range_name, range_store, "device1")?;
let bit2 = range_assigner.assign_ip(range_name, range_store, "device2")?;
// Verify assignments
assert_eq!(bit1, 0);
assert_eq!(bit2, 1);
// Get assignment info
let assignment1 = range_assigner.get_assignment_by_bit(range_name, range_store, bit1)?.unwrap();
let assignment2 = range_assigner.get_assignment_by_bit(range_name, range_store, bit2)?.unwrap();
assert_eq!(assignment1, "device1");
assert_eq!(assignment2, "device2");
// Test unassignment
let unassigned = range_assigner.unassign_ip(range_name, range_store, bit1)?;
assert!(unassigned);
let assignment1_after = range_assigner.get_assignment_by_bit(range_name, range_store, bit1)?;
assert!(assignment1_after.is_none());
}
Ok(())
}
#[test]
fn test_range_assigner_serialization() -> Result<()> {
let assigner = RangeAssigner::with_range_name("test-range")
.with_config("strategy", "random")
.with_config("pool_size", "100");
// Test serialization
let json = assigner.to_json()?;
assert!(json.contains("test-range"));
assert!(json.contains("random"));
assert!(json.contains("100"));
// Test deserialization
let deserialized: RangeAssigner = RangeAssigner::from_json(&json)?;
assert_eq!(deserialized.range_name, Some("test-range".to_string()));
assert_eq!(deserialized.config.get("strategy"), Some(&"random".to_string()));
assert_eq!(deserialized.config.get("pool_size"), Some(&"100".to_string()));
Ok(())
}
#[test]
fn test_network_get_assignment() -> Result<()> {
let (store, _temp_dir) = create_test_store()?;
// Create a network with RangeAssigner
let netrange = NetRange::from_cidr("192.168.1.0/24")?;
let provider = BasicProvider::new()
.with_config("type", "test");
let assigner = RangeAssigner::new()
.with_config("strategy", "sequential");
store.create("test-network", netrange, provider, Some(assigner))?;
// Get the network
let network = store.get("test-network")?.unwrap();
// Test that no assignment exists initially
let ip = network.get_assignment("device1", store.ranges.as_ref())?;
assert!(ip.is_none());
// Assign an IP using the range assigner directly
if let Some(ref range_store) = store.ranges {
let range_assigner = RangeAssigner::new();
let range_name = "test-network"; // Uses network name as range name
// Assign an IP to device1
let bit_position = range_assigner.assign_ip(range_name, range_store, "device1")?;
assert_eq!(bit_position, 0);
// Now test get_assignment on the network
let ip = network.get_assignment("device1", store.ranges.as_ref())?;
assert!(ip.is_some());
let assigned_ip = ip.unwrap();
match assigned_ip {
std::net::IpAddr::V4(ipv4) => {
// Should be 192.168.1.0 + bit_position
let expected = std::net::Ipv4Addr::new(192, 168, 1, bit_position as u8);
assert_eq!(ipv4, expected);
}
_ => panic!("Expected IPv4 address"),
}
// Test assignment for non-existent device
let no_ip = network.get_assignment("nonexistent", store.ranges.as_ref())?;
assert!(no_ip.is_none());
// Assign another device
let bit_position2 = range_assigner.assign_ip(range_name, range_store, "device2")?;
assert_eq!(bit_position2, 1);
let ip2 = network.get_assignment("device2", store.ranges.as_ref())?;
assert!(ip2.is_some());
let assigned_ip2 = ip2.unwrap();
match assigned_ip2 {
std::net::IpAddr::V4(ipv4) => {
let expected = std::net::Ipv4Addr::new(192, 168, 1, bit_position2 as u8);
assert_eq!(ipv4, expected);
}
_ => panic!("Expected IPv4 address"),
}
}
Ok(())
}
#[test]
fn test_network_get_assignment_no_assigner() -> Result<()> {
let (store, _temp_dir) = create_test_store()?;
// Create a network without an assigner
let netrange = NetRange::from_cidr("192.168.1.0/24")?;
let provider = BasicProvider::new()
.with_config("type", "test");
store.create("no-assigner-network", netrange, provider, None::<RangeAssigner>)?;
// Get the network
let network = store.get("no-assigner-network")?.unwrap();
// Test that get_assignment returns None when no assigner is configured
let ip = network.get_assignment("device1", store.ranges.as_ref())?;
assert!(ip.is_none());
Ok(())
}
+ #[test]
+ fn test_delete_network_with_range_assigner() -> Result<()> {
+ let (store, _temp_dir) = create_test_store()?;
+
+ // Create a network with a range assigner
+ let netrange = NetRange::from_cidr("192.168.100.0/24")?;
+ let provider = BasicProvider::new()
+ .with_config("type", "test");
+ let assigner = RangeAssigner::new()
+ .with_config("strategy", "sequential");
+
+ store.create("test-range-network", netrange, provider, Some(assigner))?;
+
+ // Verify the network was created
+ assert!(store.exists("test-range-network")?);
+ let network = store.get("test-range-network")?.unwrap();
+
+ // Verify the range was initialized by checking if the range exists
+ if let Some(ranges) = &store.ranges {
+ let range_name = "test-range-network";
+ // Check if range exists by trying to get range info
+ let range_exists = ranges.names.get(range_name.as_bytes())?.is_some();
+ assert!(range_exists, "Range should be initialized after network creation");
+
+ // Make an assignment using the range assigner directly
+ let range_assigner = RangeAssigner::new();
+ let _bit_position = range_assigner.assign_ip(range_name, ranges, "device1")?;
+ // Assignment should succeed (bit_position is always >= 0 since it's u64)
+
+ // Now verify the assignment exists
+ let ip = network.get_assignment("device1", store.ranges.as_ref())?;
+ assert!(ip.is_some(), "Assignment should be retrievable");
+
+ // Verify assignment data exists
+ let range_info = ranges.list_range(range_name)?;
+ assert!(!range_info.is_empty(), "Range should have assignment data");
+ }
+
+ // Delete the network
+ let deleted = store.delete("test-range-network")?;
+ assert!(deleted);
+
+ // Verify the network no longer exists
+ assert!(!store.exists("test-range-network")?);
+ assert!(store.get("test-range-network")?.is_none());
+
+ // Verify range data was cleaned up
+ if let Some(ranges) = &store.ranges {
+ let range_name = "test-range-network";
+ let range_exists = ranges.names.get(range_name.as_bytes())?.is_some();
+ assert!(!range_exists, "Range should be deleted");
+
+ let range_info = ranges.list_range(range_name)?;
+ assert!(range_info.is_empty(), "Range assignments should be cleaned up");
+ }
+
+ Ok(())
+ }
+
+ #[test]
+ fn test_delete_network_without_assigner() -> Result<()> {
+ let (store, _temp_dir) = create_test_store()?;
+
+ // Create a network without an assigner
+ let netrange = NetRange::from_cidr("10.10.0.0/16")?;
+ let provider = BasicProvider::new()
+ .with_config("type", "test");
+
+ store.create("test-no-assigner", netrange, provider, None::<RangeAssigner>)?;
+
+ // Verify the network was created
+ assert!(store.exists("test-no-assigner")?);
+ let network = store.get("test-no-assigner")?.unwrap();
+ assert!(network.assigner_type.is_none());
+
+ // Delete the network
+ let deleted = store.delete("test-no-assigner")?;
+ assert!(deleted);
+
+ // Verify the network no longer exists
+ assert!(!store.exists("test-no-assigner")?);
+ assert!(store.get("test-no-assigner")?.is_none());
+
+ Ok(())
+ }
+
#[test]
fn test_network_get_assignment_ipv6() -> Result<()> {
let (store, _temp_dir) = create_test_store()?;
// Create an IPv6 network with RangeAssigner
let netrange = NetRange::from_cidr("2001:db8::/64")?;
let provider = BasicProvider::new()
.with_config("type", "test");
let assigner = RangeAssigner::new()
.with_config("strategy", "sequential");
store.create("ipv6-network", netrange, provider, Some(assigner))?;
// Get the network
let network = store.get("ipv6-network")?.unwrap();
// Assign an IP using the range assigner directly
if let Some(ref range_store) = store.ranges {
let range_assigner = RangeAssigner::new();
let range_name = "ipv6-network";
// Assign an IP to device1
let bit_position = range_assigner.assign_ip(range_name, range_store, "ipv6-device1")?;
assert_eq!(bit_position, 0);
// Test get_assignment on the network
let ip = network.get_assignment("ipv6-device1", store.ranges.as_ref())?;
assert!(ip.is_some());
let assigned_ip = ip.unwrap();
match assigned_ip {
std::net::IpAddr::V6(ipv6) => {
// Should be 2001:db8:: + bit_position
let expected = std::net::Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, bit_position as u16);
assert_eq!(ipv6, expected);
}
_ => panic!("Expected IPv6 address"),
}
}
Ok(())
}
}
\ No newline at end of file
diff --git a/crates/store/src/range.rs b/crates/store/src/range.rs
index c599615..686915d 100644
--- a/crates/store/src/range.rs
+++ b/crates/store/src/range.rs
@@ -1,929 +1,1126 @@
//! # Range Store Module
//!
//! This module implements "ranges" which are buckets of unique range assignments
//! that map values to specific bit positions within fixed-size ranges. This is useful
//! for allocating unique identifiers, session tokens, or any scenario where you need
//! to assign sequential positions to values.
//!
//! ## Storage Design
//!
//! The RangeStore uses three sled trees:
//!
//! - **`names` tree**: Maps range names to their metadata
//! - Key: `name` (string)
//! - Value: `id || size` where `id` is a u64 from `generate_id()` and `size` is a u64
//!
//! - **`map` tree**: Stores bitmaps for each range to track allocated positions
//! - Key: `id` (u64 from the `names` tree)
//! - Value: `bitmap` (binary data representing which bits are allocated)
//!
//! - **`assign` tree**: Maps range positions to their assigned values
//! - Key: `id || bit_position` where `id` is the range ID and `bit_position` is a u64
//! - Value: `value` (the string value assigned to this position)
//!
//! ## API Operations
//!
//! - `define(name, size)`: Create a new range with the given name and maximum size
//! - `assign(name, value)`: Assign a value to the next available position in the range
//! - `get(name, position)`: Retrieve the value at a specific position
//! - `list_range(name)`: List all assignments in a range
//! - `list_ranges()`: List all defined ranges
//! - `unassign(name, value)`: Remove a value from the range and free its position
//!
//! ## Example Usage
//!
//! ```rust
//! use store::{open, Result};
//!
//! fn example() -> Result<()> {
//! let store = open()?;
//! let ranges = store.ranges();
//!
//! // Define a range for user IDs
//! ranges.define("user_ids", 1000)?;
//!
//! // Assign some users
//! let alice_id = ranges.assign("user_ids", "alice@example.com")?; // Returns 0
//! let bob_id = ranges.assign("user_ids", "bob@example.com")?; // Returns 1
//!
//! // Get a user by position
//! let email = ranges.get("user_ids", alice_id)?; // Some("alice@example.com")
//!
//! // Remove a user (frees the position for reuse)
//! ranges.unassign("user_ids", "bob@example.com")?;
//!
//! // Next assignment will reuse Bob's position
//! let charlie_id = ranges.assign("user_ids", "charlie@example.com")?; // Returns 1
//!
//! Ok(())
//! }
//! ```
use crate::Result;
use crate::Error;
use sled::Transactional;
#[derive(Debug, Clone)]
pub struct RangeStore {
pub(crate) names: sled::Tree,
pub(crate) map: sled::Tree,
pub(crate) assign: sled::Tree,
}
impl RangeStore {
pub fn open(db: &sled::Db) -> Result<Self> {
Ok(RangeStore {
names: db.open_tree("ranges/1/names")?,
map: db.open_tree("ranges/1/map")?,
assign: db.open_tree("ranges/1/assign")?,
})
}
/// Assign a value to a range within an existing transaction context
pub fn assign_in_transaction(
&self,
names: &sled::transaction::TransactionalTree,
map: &sled::transaction::TransactionalTree,
assign: &sled::transaction::TransactionalTree,
range_name: &str,
value: &str,
) -> sled::transaction::ConflictableTransactionResult<u64, ()> {
// Get range info
let (range_id, range_size) = match names.get(range_name)? {
Some(data) => {
if data.len() != 16 {
return Err(sled::transaction::ConflictableTransactionError::Abort(()));
}
let id_bytes: [u8; 8] = data[0..8].try_into()
.map_err(|_| sled::transaction::ConflictableTransactionError::Abort(()))?;
let size_bytes: [u8; 8] = data[8..16].try_into()
.map_err(|_| sled::transaction::ConflictableTransactionError::Abort(()))?;
(u64::from_be_bytes(id_bytes), u64::from_be_bytes(size_bytes))
}
None => return Err(sled::transaction::ConflictableTransactionError::Abort(())),
};
// Get current bitmap
let mut bitmap = match map.get(&range_id.to_be_bytes())? {
Some(data) => data.to_vec(),
None => return Err(sled::transaction::ConflictableTransactionError::Abort(())),
};
// Find first free bit
let mut bit_position = None;
for bit in 0..range_size {
let byte_index = (bit / 8) as usize;
let bit_index = bit % 8;
if byte_index < bitmap.len() {
let mask = 1u8 << bit_index;
if (bitmap[byte_index] & mask) == 0 {
// This bit is free
bitmap[byte_index] |= mask;
bit_position = Some(bit);
break;
}
}
}
let bit_pos = match bit_position {
Some(pos) => pos,
None => return Err(sled::transaction::ConflictableTransactionError::Abort(())),
};
// Update bitmap
map.insert(&range_id.to_be_bytes(), bitmap)?;
// Store assignment
let mut assign_key = Vec::new();
assign_key.extend_from_slice(&range_id.to_be_bytes());
assign_key.extend_from_slice(&bit_pos.to_be_bytes());
assign.insert(assign_key, value.as_bytes())?;
Ok(bit_pos)
}
/// Get a value from a specific bit position within an existing transaction context
/// Get range assignment details within an existing transaction context
pub fn get_in_transaction(
&self,
names: &sled::transaction::TransactionalTree,
assign: &sled::transaction::TransactionalTree,
range_name: &str,
bit_position: u64,
) -> sled::transaction::ConflictableTransactionResult<Option<String>, ()> {
// Get range info
let (range_id, range_size) = match names.get(range_name)? {
Some(data) => {
if data.len() != 16 {
return Ok(None);
}
let id_bytes: [u8; 8] = data[0..8].try_into()
.map_err(|_| sled::transaction::ConflictableTransactionError::Abort(()))?;
let size_bytes: [u8; 8] = data[8..16].try_into()
.map_err(|_| sled::transaction::ConflictableTransactionError::Abort(()))?;
(u64::from_be_bytes(id_bytes), u64::from_be_bytes(size_bytes))
}
None => return Ok(None),
};
if bit_position >= range_size {
return Ok(None);
}
// Get assignment
let mut assign_key = Vec::new();
assign_key.extend_from_slice(&range_id.to_be_bytes());
assign_key.extend_from_slice(&bit_position.to_be_bytes());
match assign.get(&assign_key)? {
Some(value_bytes) => {
let value = String::from_utf8(value_bytes.to_vec())
.map_err(|_| sled::transaction::ConflictableTransactionError::Abort(()))?;
Ok(Some(value))
}
None => Ok(None),
}
}
/// Unassign a specific bit position within an existing transaction context
pub fn unassign_bit_in_transaction(
&self,
names: &sled::transaction::TransactionalTree,
map: &sled::transaction::TransactionalTree,
assign: &sled::transaction::TransactionalTree,
range_name: &str,
bit_position: u64,
) -> sled::transaction::ConflictableTransactionResult<bool, ()> {
// Get range info
let (range_id, range_size) = match names.get(range_name)? {
Some(data) => {
if data.len() != 16 {
return Ok(false);
}
let id_bytes: [u8; 8] = data[0..8].try_into()
.map_err(|_| sled::transaction::ConflictableTransactionError::Abort(()))?;
let size_bytes: [u8; 8] = data[8..16].try_into()
.map_err(|_| sled::transaction::ConflictableTransactionError::Abort(()))?;
(u64::from_be_bytes(id_bytes), u64::from_be_bytes(size_bytes))
}
None => return Ok(false),
};
if bit_position >= range_size {
return Ok(false);
}
// Remove assignment
let mut assign_key = Vec::new();
assign_key.extend_from_slice(&range_id.to_be_bytes());
assign_key.extend_from_slice(&bit_position.to_be_bytes());
let removed = assign.remove(assign_key)?.is_some();
if removed {
// Update bitmap
let mut bitmap = match map.get(&range_id.to_be_bytes())? {
Some(data) => data.to_vec(),
None => return Err(sled::transaction::ConflictableTransactionError::Abort(())),
};
let byte_index = (bit_position / 8) as usize;
let bit_index = bit_position % 8;
if byte_index < bitmap.len() {
let mask = 1u8 << bit_index;
bitmap[byte_index] &= !mask; // Clear the bit
map.insert(&range_id.to_be_bytes(), bitmap)?;
}
}
Ok(removed)
}
/// Check if a range exists within an existing transaction context
pub fn exists_in_transaction(
&self,
names: &sled::transaction::TransactionalTree,
range_name: &str,
) -> sled::transaction::ConflictableTransactionResult<bool, ()> {
Ok(names.get(range_name)?.is_some())
}
/// Get range info (id and size) within an existing transaction context
pub fn info_in_transaction(
&self,
names: &sled::transaction::TransactionalTree,
range_name: &str,
) -> sled::transaction::ConflictableTransactionResult<Option<(u64, u64)>, ()> {
match names.get(range_name)? {
Some(data) => {
if data.len() != 16 {
return Ok(None);
}
let id_bytes: [u8; 8] = data[0..8].try_into()
.map_err(|_| sled::transaction::ConflictableTransactionError::Abort(()))?;
let size_bytes: [u8; 8] = data[8..16].try_into()
.map_err(|_| sled::transaction::ConflictableTransactionError::Abort(()))?;
Ok(Some((u64::from_be_bytes(id_bytes), u64::from_be_bytes(size_bytes))))
}
None => Ok(None),
}
}
// Define a new range with a given name and size
pub fn define(&self, name: &str, size: u64) -> Result<()> {
(&self.names, &self.map).transaction(|(names, map)| {
self.define_in_transaction(names, map, name, size)
}).map_err(|e| match e {
sled::transaction::TransactionError::Abort(()) => Error::StoreError(sled::Error::Unsupported("Range definition failed".to_string())),
sled::transaction::TransactionError::Storage(storage_err) => Error::StoreError(storage_err),
})?;
Ok(())
}
/// Define a new range within an existing transaction
pub fn define_in_transaction(
&self,
names: &sled::transaction::TransactionalTree,
map: &sled::transaction::TransactionalTree,
name: &str,
size: u64,
) -> Result<(), sled::transaction::ConflictableTransactionError<()>> {
match names.get(name)? {
Some(_) => Ok(()), // Range already exists
None => {
let id = names.generate_id()?;
// Store name -> (id, size)
let mut value = Vec::new();
value.extend_from_slice(&id.to_be_bytes());
value.extend_from_slice(&size.to_be_bytes());
names.insert(name, value)?;
// Initialize bitmap for the range (all zeros)
let bitmap_size = (size + 7) / 8; // Round up to nearest byte
let bitmap = vec![0u8; bitmap_size as usize];
map.insert(&id.to_be_bytes(), bitmap)?;
Ok(())
}
}
}
// Assign the next free bit in the range to a value
pub fn assign(&self, range_name: &str, value: &str) -> Result<u64> {
let result = (&self.names, &self.map, &self.assign).transaction(|(names, map, assign)| {
// Get range info
let (range_id, range_size) = match names.get(range_name)? {
Some(data) => {
if data.len() != 16 {
return Err(sled::transaction::ConflictableTransactionError::Abort(()));
}
let id_bytes: [u8; 8] = data[0..8].try_into()
.map_err(|_| sled::transaction::ConflictableTransactionError::Abort(()))?;
let size_bytes: [u8; 8] = data[8..16].try_into()
.map_err(|_| sled::transaction::ConflictableTransactionError::Abort(()))?;
(u64::from_be_bytes(id_bytes), u64::from_be_bytes(size_bytes))
}
None => return Err(sled::transaction::ConflictableTransactionError::Abort(())),
};
// Get current bitmap
let mut bitmap = match map.get(&range_id.to_be_bytes())? {
Some(data) => data.to_vec(),
None => return Err(sled::transaction::ConflictableTransactionError::Abort(())),
};
// Find first free bit
let mut bit_position = None;
for bit in 0..range_size {
let byte_index = (bit / 8) as usize;
let bit_index = bit % 8;
if byte_index < bitmap.len() {
let mask = 1u8 << bit_index;
if (bitmap[byte_index] & mask) == 0 {
// This bit is free
bitmap[byte_index] |= mask;
bit_position = Some(bit);
break;
}
}
}
let bit_pos = match bit_position {
Some(pos) => pos,
None => return Err(sled::transaction::ConflictableTransactionError::Abort(())),
};
// Update bitmap
map.insert(&range_id.to_be_bytes(), bitmap)?;
// Store assignment
let mut assign_key = Vec::new();
assign_key.extend_from_slice(&range_id.to_be_bytes());
assign_key.extend_from_slice(&bit_pos.to_be_bytes());
assign.insert(assign_key, value.as_bytes())?;
Ok(bit_pos)
}).map_err(|e| match e {
sled::transaction::TransactionError::Abort(()) => Error::RangeFull(range_name.to_string()),
sled::transaction::TransactionError::Storage(storage_err) => Error::StoreError(storage_err),
})?;
Ok(result)
}
// Get a value from a specific bit position in the range
pub fn get(&self, range_name: &str, bit_position: u64) -> Result<Option<String>> {
let result = (&self.names, &self.assign).transaction(|(names, assign)| {
// Get range info
let (range_id, range_size) = match names.get(range_name)? {
Some(data) => {
if data.len() != 16 {
return Ok(None);
}
let id_bytes: [u8; 8] = data[0..8].try_into()
.map_err(|_| sled::transaction::ConflictableTransactionError::Abort(()))?;
let size_bytes: [u8; 8] = data[8..16].try_into()
.map_err(|_| sled::transaction::ConflictableTransactionError::Abort(()))?;
(u64::from_be_bytes(id_bytes), u64::from_be_bytes(size_bytes))
}
None => return Ok(None),
};
if bit_position >= range_size {
return Err(sled::transaction::ConflictableTransactionError::Abort(()));
}
// Get assignment
let mut assign_key = Vec::new();
assign_key.extend_from_slice(&range_id.to_be_bytes());
assign_key.extend_from_slice(&bit_position.to_be_bytes());
match assign.get(assign_key)? {
Some(value_bytes) => {
let value = String::from_utf8(value_bytes.to_vec())
.map_err(|_| sled::transaction::ConflictableTransactionError::Abort(()))?;
Ok(Some(value))
}
None => Ok(None),
}
}).map_err(|e| match e {
sled::transaction::TransactionError::Abort(()) => Error::BitOutOfRange(range_name.to_string(), bit_position),
sled::transaction::TransactionError::Storage(storage_err) => Error::StoreError(storage_err),
})?;
Ok(result)
}
// List all assignments in a range
pub fn list_range(&self, range_name: &str) -> Result<Vec<(u64, String)>> {
// Get range info first
let range_id = match self.names.get(range_name)? {
Some(data) => {
if data.len() != 16 {
return Ok(Vec::new());
}
let id_bytes: [u8; 8] = data[0..8].try_into()
.map_err(|_| Error::StoreError(sled::Error::Unsupported("Invalid range data".to_string())))?;
u64::from_be_bytes(id_bytes)
}
None => return Ok(Vec::new()),
};
let mut assignments = Vec::new();
let prefix = range_id.to_be_bytes();
for result in self.assign.scan_prefix(&prefix) {
let (key, value) = result?;
if key.len() == 16 { // 8 bytes for range_id + 8 bytes for bit_position
let bit_position_bytes: [u8; 8] = key[8..16].try_into()
.map_err(|_| Error::StoreError(sled::Error::Unsupported("Invalid key format".to_string())))?;
let bit_position = u64::from_be_bytes(bit_position_bytes);
let value_str = String::from_utf8(value.to_vec())?;
assignments.push((bit_position, value_str));
}
}
assignments.sort_by_key(|&(bit_pos, _)| bit_pos);
Ok(assignments)
}
// List all ranges
pub fn list_ranges(&self) -> Result<Vec<(String, u64)>> {
let mut ranges = Vec::new();
for result in self.names.iter() {
let (key, value) = result?;
if value.len() == 16 {
let name = String::from_utf8(key.to_vec())?;
let size_bytes: [u8; 8] = value[8..16].try_into()
.map_err(|_| Error::StoreError(sled::Error::Unsupported("Invalid size data".to_string())))?;
let size = u64::from_be_bytes(size_bytes);
ranges.push((name, size));
}
}
ranges.sort_by(|a, b| a.0.cmp(&b.0));
Ok(ranges)
}
// Unassign a specific bit position from the range
pub fn unassign_bit(&self, range_name: &str, bit_position: u64) -> Result<bool> {
let result = (&self.names, &self.map, &self.assign).transaction(|(names, map, assign)| {
self.unassign_bit_in_transaction(names, map, assign, range_name, bit_position)
}).map_err(|e| match e {
sled::transaction::TransactionError::Abort(()) => Error::BitOutOfRange(range_name.to_string(), bit_position),
sled::transaction::TransactionError::Storage(storage_err) => Error::StoreError(storage_err),
})?;
Ok(result)
}
// Unassign a value from the range (find by value and remove)
pub fn unassign(&self, range_name: &str, value: &str) -> Result<bool> {
// First find the assignment outside of transaction
let range_id = match self.names.get(range_name)? {
Some(data) => {
if data.len() != 16 {
return Ok(false);
}
let id_bytes: [u8; 8] = data[0..8].try_into()
.map_err(|_| Error::StoreError(sled::Error::Unsupported("Invalid range data".to_string())))?;
u64::from_be_bytes(id_bytes)
}
None => return Ok(false),
};
// Find the assignment with this value
let prefix = range_id.to_be_bytes();
let mut found_bit = None;
for result in self.assign.scan_prefix(&prefix) {
let (key, stored_value) = result?;
if key.len() == 16 {
let stored_value_str = String::from_utf8(stored_value.to_vec())?;
if stored_value_str == value {
let bit_position_bytes: [u8; 8] = key[8..16].try_into()
.map_err(|_| Error::StoreError(sled::Error::Unsupported("Invalid key format".to_string())))?;
found_bit = Some(u64::from_be_bytes(bit_position_bytes));
break;
}
}
}
let bit_position = match found_bit {
Some(pos) => pos,
None => return Ok(false),
};
// Now perform the removal in a transaction
let result = (&self.map, &self.assign).transaction(|(map, assign)| {
// Remove assignment
let mut assign_key = Vec::new();
assign_key.extend_from_slice(&range_id.to_be_bytes());
assign_key.extend_from_slice(&bit_position.to_be_bytes());
assign.remove(assign_key)?;
// Update bitmap
let mut bitmap = match map.get(&range_id.to_be_bytes())? {
Some(data) => data.to_vec(),
None => return Err(sled::transaction::ConflictableTransactionError::Abort(())),
};
let byte_index = (bit_position / 8) as usize;
let bit_index = bit_position % 8;
if byte_index < bitmap.len() {
let mask = 1u8 << bit_index;
bitmap[byte_index] &= !mask; // Clear the bit
map.insert(&range_id.to_be_bytes(), bitmap)?;
}
Ok(true)
}).map_err(|e| match e {
sled::transaction::TransactionError::Abort(()) => Error::ValueNotInRange(range_name.to_string(), value.to_string()),
sled::transaction::TransactionError::Storage(storage_err) => Error::StoreError(storage_err),
})?;
Ok(result)
}
+ /// Delete a range and all its associated data
+ pub fn delete_range(&self, range_name: &str) -> Result<bool> {
+ // First collect keys to remove outside of transaction
+ let prefix = format!("{}:", range_name);
+ let mut keys_to_remove = Vec::new();
+ for result in self.assign.scan_prefix(prefix.as_bytes()) {
+ let (key, _) = result?;
+ keys_to_remove.push(key.to_vec());
+ }
+
+ let result = (&self.names, &self.map, &self.assign).transaction(
+ |(names, map, assign)| {
+ // Get the range ID from the name
+ if let Some(value) = names.get(range_name.as_bytes())? {
+ let id = u64::from_be_bytes([
+ value[0], value[1], value[2], value[3],
+ value[4], value[5], value[6], value[7],
+ ]);
+
+ // Remove the range data
+ map.remove(&id.to_be_bytes())?;
+
+ // Remove all assignments for this range
+ for key in &keys_to_remove {
+ assign.remove(key.as_slice())?;
+ }
+
+ // Remove the range name
+ names.remove(range_name.as_bytes())?;
+
+ Ok(true)
+ } else {
+ Ok(false)
+ }
+ }
+ ).map_err(|e| match e {
+ sled::transaction::TransactionError::Abort(()) => {
+ Error::StoreError(sled::Error::Unsupported("Transaction aborted".to_string()))
+ }
+ sled::transaction::TransactionError::Storage(storage_err) => Error::StoreError(storage_err),
+ })?;
+
+ Ok(result)
+ }
+
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::tempdir;
fn create_test_store() -> Result<RangeStore> {
let temp_dir = tempdir().unwrap();
let db = sled::open(temp_dir.path())?;
RangeStore::open(&db)
}
#[test]
fn test_transaction_methods() -> Result<()> {
let store = create_test_store()?;
// Define a range
store.define("test_range", 100)?;
// Test transaction methods
let result = (&store.names, &store.map, &store.assign).transaction(|(names, map, assign)| {
// Assign in transaction
let bit_pos = store.assign_in_transaction(names, map, assign, "test_range", "tx_value")?;
assert!(bit_pos < 100);
// Get in transaction
let value = store.get_in_transaction(names, assign, "test_range", bit_pos)?;
assert_eq!(value, Some("tx_value".to_string()));
// Check if range exists in transaction
let exists = store.exists_in_transaction(names, "test_range")?;
assert_eq!(exists, true);
// Get range info in transaction
let info = store.info_in_transaction(names, "test_range")?;
assert!(info.is_some());
let (_, size) = info.unwrap();
assert_eq!(size, 100);
// Verify assignment exists
let value_check = store.get_in_transaction(names, assign, "test_range", bit_pos)?;
assert_eq!(value_check, Some("tx_value".to_string()));
// Unassign bit in transaction
let unassigned = store.unassign_bit_in_transaction(names, map, assign, "test_range", bit_pos)?;
assert_eq!(unassigned, true);
// Verify it's gone
let value_after = store.get_in_transaction(names, assign, "test_range", bit_pos)?;
assert_eq!(value_after, None);
Ok(())
}).map_err(|e| match e {
sled::transaction::TransactionError::Abort(()) => Error::StoreError(sled::Error::Unsupported("Transaction aborted".to_string())),
sled::transaction::TransactionError::Storage(storage_err) => Error::StoreError(storage_err),
})?;
Ok(result)
}
#[test]
fn test_transaction_rollback() -> Result<()> {
let store = create_test_store()?;
// Define a range and assign initial value
store.define("test_range", 5)?; // Small range to easily fill
let initial_bit = store.assign("test_range", "initial_value")?;
// Fill up the range
for i in 1..5 {
store.assign("test_range", &format!("value_{}", i))?;
}
// Attempt a transaction that should fail (range full)
let result = (&store.names, &store.map, &store.assign).transaction(|(names, map, assign)| {
// This should succeed
let _unassigned = store.unassign_bit_in_transaction(names, map, assign, "test_range", initial_bit)?;
// This should fail (range is still full after we just freed one bit)
// Actually, this should succeed since we freed a bit, so let's try to assign to a full range differently
// Let's assign multiple values to ensure failure
store.assign_in_transaction(names, map, assign, "test_range", "temp1")?;
store.assign_in_transaction(names, map, assign, "test_range", "temp2")?; // This should fail
Ok(())
}).map_err(|e| match e {
sled::transaction::TransactionError::Abort(()) => Error::RangeFull("test_range".to_string()),
sled::transaction::TransactionError::Storage(storage_err) => Error::StoreError(storage_err),
});
// Transaction should have failed
assert!(result.is_err());
// Verify rollback - initial value should still be there
let value = store.get("test_range", initial_bit)?;
assert_eq!(value, Some("initial_value".to_string()));
Ok(())
}
#[test]
fn test_bit_out_of_range() -> Result<()> {
let store = create_test_store()?;
// Define a small range
store.define("small_range", 10)?;
// Try to get bit beyond range
let result = store.get("small_range", 15);
assert!(result.is_err());
// Try to unassign bit beyond range
let result = store.unassign_bit("small_range", 15);
assert!(result.is_ok());
assert_eq!(result?, false);
Ok(())
}
#[test]
fn test_multiple_ranges() -> Result<()> {
let store = create_test_store()?;
// Define multiple ranges
store.define("range_a", 50)?;
store.define("range_b", 100)?;
store.define("range_c", 25)?;
// Assign values to each range
let bit_a = store.assign("range_a", "value_a")?;
let bit_b = store.assign("range_b", "value_b")?;
let bit_c = store.assign("range_c", "value_c")?;
// Verify isolation between ranges
assert_eq!(store.get("range_a", bit_a)?, Some("value_a".to_string()));
assert_eq!(store.get("range_b", bit_b)?, Some("value_b".to_string()));
assert_eq!(store.get("range_c", bit_c)?, Some("value_c".to_string()));
// Cross-range access: accessing bit positions that exist in one range but not another
// Since all ranges start at bit 0, we need to test with positions beyond smaller ranges
// Test accessing valid bit positions across ranges
// All ranges have their first assignment at bit 0, so this tests basic isolation
assert_eq!(store.get("range_a", bit_a)?, Some("value_a".to_string()));
// Test accessing bit positions that are out of range
// range_c has only 25 bits (0-24), so bit 25 and above should give an error
let result = store.get("range_c", 25);
assert!(result.is_err());
// range_a has 50 bits (0-49), so bit 50 and above should give an error
let result = store.get("range_a", 50);
assert!(result.is_err());
// List all ranges
let ranges = store.list_ranges()?;
assert_eq!(ranges.len(), 3);
assert!(ranges.iter().any(|(name, size)| name == "range_a" && *size == 50));
assert!(ranges.iter().any(|(name, size)| name == "range_b" && *size == 100));
assert!(ranges.iter().any(|(name, size)| name == "range_c" && *size == 25));
// List assignments in each range
let assignments_a = store.list_range("range_a")?;
assert_eq!(assignments_a.len(), 1);
assert_eq!(assignments_a[0], (bit_a, "value_a".to_string()));
let assignments_b = store.list_range("range_b")?;
assert_eq!(assignments_b.len(), 1);
assert_eq!(assignments_b[0], (bit_b, "value_b".to_string()));
let assignments_c = store.list_range("range_c")?;
assert_eq!(assignments_c.len(), 1);
assert_eq!(assignments_c[0], (bit_c, "value_c".to_string()));
Ok(())
}
#[test]
fn test_sequential_bit_assignment() -> Result<()> {
let store = create_test_store()?;
// Define a range
store.define("sequential", 10)?;
// Assign values and verify they get sequential bits
let mut bits = Vec::new();
for i in 0..5 {
let bit = store.assign("sequential", &format!("seq_{}", i))?;
bits.push(bit);
}
// Should get bits 0, 1, 2, 3, 4 in that order
for (i, &bit) in bits.iter().enumerate() {
assert_eq!(bit, i as u64);
}
// Unassign bit 2
store.unassign("sequential", "seq_2")?;
// Next assignment should reuse bit 2
let next_bit = store.assign("sequential", "reused")?;
assert_eq!(next_bit, 2);
Ok(())
}
#[test]
fn test_define_range() -> Result<()> {
let store = create_test_store()?;
// Define a range
store.define("test_range", 100)?;
// Defining the same range again should not error
store.define("test_range", 100)?;
// Check that ranges are listed
let ranges = store.list_ranges()?;
assert!(ranges.iter().any(|(name, size)| name == "test_range" && *size == 100));
Ok(())
}
#[test]
fn test_assign_and_get() -> Result<()> {
let store = create_test_store()?;
// Define a range first
store.define("test_range", 100)?;
// Assign a value
let bit_pos = store.assign("test_range", "test_value")?;
assert!(bit_pos < 100);
// Get the value
let value = store.get("test_range", bit_pos)?;
assert_eq!(value, Some("test_value".to_string()));
// Test getting non-existent bit
let empty = store.get("test_range", 99)?;
assert_eq!(empty, None);
Ok(())
}
#[test]
fn test_multiple_assignments() -> Result<()> {
let store = create_test_store()?;
// Define range
store.define("test_range", 64)?;
// Assign multiple values
let pos1 = store.assign("test_range", "value1")?;
let pos2 = store.assign("test_range", "value2")?;
let pos3 = store.assign("test_range", "value3")?;
assert_eq!(pos1, 0);
assert_eq!(pos2, 1);
assert_eq!(pos3, 2);
// Verify all values
assert_eq!(store.get("test_range", 0)?, Some("value1".to_string()));
assert_eq!(store.get("test_range", 1)?, Some("value2".to_string()));
assert_eq!(store.get("test_range", 2)?, Some("value3".to_string()));
Ok(())
}
#[test]
fn test_list_range() -> Result<()> {
let store = create_test_store()?;
// Define range
store.define("test_range", 64)?;
// Assign some values
store.assign("test_range", "alpha")?;
store.assign("test_range", "beta")?;
store.assign("test_range", "gamma")?;
// List assignments
let assignments = store.list_range("test_range")?;
assert_eq!(assignments.len(), 3);
assert_eq!(assignments[0], (0, "alpha".to_string()));
assert_eq!(assignments[1], (1, "beta".to_string()));
assert_eq!(assignments[2], (2, "gamma".to_string()));
Ok(())
}
#[test]
fn test_list_ranges() -> Result<()> {
let store = create_test_store()?;
// Define multiple ranges
store.define("range_a", 32)?;
store.define("range_b", 64)?;
store.define("range_c", 128)?;
// List all ranges
let ranges = store.list_ranges()?;
assert_eq!(ranges.len(), 3);
assert_eq!(ranges[0], ("range_a".to_string(), 32));
assert_eq!(ranges[1], ("range_b".to_string(), 64));
assert_eq!(ranges[2], ("range_c".to_string(), 128));
Ok(())
}
#[test]
fn test_unassign() -> Result<()> {
let store = create_test_store()?;
// Define range
store.define("test_range", 64)?;
// Assign values
store.assign("test_range", "value1")?;
store.assign("test_range", "value2")?;
store.assign("test_range", "value3")?;
// Unassign middle value
let removed = store.unassign("test_range", "value2")?;
assert!(removed);
// Verify it's gone
assert_eq!(store.get("test_range", 1)?, None);
// Other values should still be there
assert_eq!(store.get("test_range", 0)?, Some("value1".to_string()));
assert_eq!(store.get("test_range", 2)?, Some("value3".to_string()));
// Assign a new value - should reuse the freed slot
let new_pos = store.assign("test_range", "new_value")?;
assert_eq!(new_pos, 1); // Should reuse position 1
Ok(())
}
#[test]
fn test_range_full() -> Result<()> {
let store = create_test_store()?;
// Define small range
store.define("small_range", 2)?;
// Fill it up
store.assign("small_range", "value1")?;
store.assign("small_range", "value2")?;
// Try to assign one more - should fail
let result = store.assign("small_range", "value3");
assert!(result.is_err());
Ok(())
}
#[test]
fn test_undefined_range() -> Result<()> {
let store = create_test_store()?;
// Try operations on undefined range
let result = store.assign("undefined_range", "value");
assert!(result.is_err());
let value = store.get("undefined_range", 0)?;
assert_eq!(value, None);
let assignments = store.list_range("undefined_range")?;
assert_eq!(assignments.len(), 0);
let removed = store.unassign("undefined_range", "value")?;
assert!(!removed);
Ok(())
}
+
+ #[test]
+ fn test_delete_range() -> Result<()> {
+ let store = create_test_store()?;
+
+ // Define a range and make some assignments
+ store.define("delete_test", 100)?;
+ store.assign("delete_test", "value1")?;
+ store.assign("delete_test", "value2")?;
+ store.assign("delete_test", "value3")?;
+
+ // Verify assignments exist
+ let assignments = store.list_range("delete_test")?;
+ assert_eq!(assignments.len(), 3);
+
+ // Verify range exists
+ let range_exists = store.names.get("delete_test".as_bytes())?.is_some();
+ assert!(range_exists);
+
+ // Delete the range
+ let deleted = store.delete_range("delete_test")?;
+ assert!(deleted);
+
+ // Verify range no longer exists
+ let range_exists = store.names.get("delete_test".as_bytes())?.is_some();
+ assert!(!range_exists);
+
+ // Verify assignments are gone
+ let assignments = store.list_range("delete_test")?;
+ assert_eq!(assignments.len(), 0);
+
+ // Test deleting non-existent range
+ let deleted = store.delete_range("nonexistent")?;
+ assert!(!deleted);
+
+ Ok(())
+ }
+
+ #[test]
+ fn test_delete_range_comprehensive() -> Result<()> {
+ let store = create_test_store()?;
+
+ // Test deleting empty range
+ store.define("empty_range", 50)?;
+ let deleted = store.delete_range("empty_range")?;
+ assert!(deleted);
+
+ // Verify it's gone
+ let range_exists = store.names.get("empty_range".as_bytes())?.is_some();
+ assert!(!range_exists);
+
+ // Test deleting range with single assignment
+ store.define("single_range", 10)?;
+ store.assign("single_range", "single_value")?;
+
+ let assignments = store.list_range("single_range")?;
+ assert_eq!(assignments.len(), 1);
+
+ let deleted = store.delete_range("single_range")?;
+ assert!(deleted);
+
+ let assignments = store.list_range("single_range")?;
+ assert_eq!(assignments.len(), 0);
+
+ // Test deleting range with maximum assignments
+ store.define("full_range", 3)?;
+ store.assign("full_range", "val1")?;
+ store.assign("full_range", "val2")?;
+ store.assign("full_range", "val3")?;
+
+ let assignments = store.list_range("full_range")?;
+ assert_eq!(assignments.len(), 3);
+
+ let deleted = store.delete_range("full_range")?;
+ assert!(deleted);
+
+ let assignments = store.list_range("full_range")?;
+ assert_eq!(assignments.len(), 0);
+
+ // Test that we can recreate a range with the same name after deletion
+ store.define("recreate_test", 20)?;
+ store.assign("recreate_test", "original")?;
+
+ let deleted = store.delete_range("recreate_test")?;
+ assert!(deleted);
+
+ // Recreate with different size
+ store.define("recreate_test", 30)?;
+ store.assign("recreate_test", "new_value")?;
+
+ let assignments = store.list_range("recreate_test")?;
+ assert_eq!(assignments.len(), 1);
+ assert_eq!(assignments[0].1, "new_value");
+
+ // Test multiple deletions don't interfere
+ store.define("multi1", 5)?;
+ store.define("multi2", 5)?;
+ store.assign("multi1", "m1_val1")?;
+ store.assign("multi2", "m2_val1")?;
+
+ let deleted1 = store.delete_range("multi1")?;
+ assert!(deleted1);
+
+ // multi2 should still exist
+ let assignments = store.list_range("multi2")?;
+ assert_eq!(assignments.len(), 1);
+
+ let deleted2 = store.delete_range("multi2")?;
+ assert!(deleted2);
+
+ let assignments = store.list_range("multi2")?;
+ assert_eq!(assignments.len(), 0);
+
+ Ok(())
+ }
+
+ #[test]
+ fn test_delete_range_atomicity() -> Result<()> {
+ let store = create_test_store()?;
+
+ // Create multiple ranges to verify isolation
+ store.define("atomic_test1", 100)?;
+ store.define("atomic_test2", 50)?;
+ store.assign("atomic_test1", "value1")?;
+ store.assign("atomic_test1", "value2")?;
+ store.assign("atomic_test2", "other_value")?;
+
+ // Verify initial state
+ let assignments1 = store.list_range("atomic_test1")?;
+ assert_eq!(assignments1.len(), 2);
+ let assignments2 = store.list_range("atomic_test2")?;
+ assert_eq!(assignments2.len(), 1);
+
+ // Delete one range - this should be atomic
+ let deleted = store.delete_range("atomic_test1")?;
+ assert!(deleted);
+
+ // Verify first range is completely gone
+ let range1_exists = store.names.get("atomic_test1".as_bytes())?.is_some();
+ assert!(!range1_exists);
+ let assignments1 = store.list_range("atomic_test1")?;
+ assert_eq!(assignments1.len(), 0);
+
+ // Verify second range is unaffected
+ let range2_exists = store.names.get("atomic_test2".as_bytes())?.is_some();
+ assert!(range2_exists);
+ let assignments2 = store.list_range("atomic_test2")?;
+ assert_eq!(assignments2.len(), 1);
+ assert_eq!(assignments2[0].1, "other_value");
+
+ Ok(())
+ }
}

File Metadata

Mime Type
text/x-diff
Expires
Sun, Jun 8, 11:31 AM (1 d, 7 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
47601
Default Alt Text
(102 KB)

Event Timeline