Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
475 changes: 475 additions & 0 deletions Cargo.lock

Large diffs are not rendered by default.

24 changes: 21 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ resolver = "2"

[package]
name = "stackforge"
version = "0.1.1"
edition = "2024"
license = "GPL-3.0-only"
version.workspace = true
edition.workspace = true
authors.workspace = true
license.workspace = true
description = "A high-performance, modular networking stack and automata framework."
repository = "https://github.com/LaBackDoor/stackforge"
homepage = "https://github.com/LaBackDoor/stackforge"
Expand All @@ -22,14 +23,31 @@ tag-name = "v{{version}}"
version = "0.1.1"
edition = "2024"
license = "GPL-3.0-only"
authors = ["Stackforge Contributors"]
repository = "https://github.com/LaBackDoor/stackforge"

[workspace.dependencies]
bytes = "1.11.0"
smallvec = "1.15.1"
thiserror = "2.0.17"
stackforge-core = { path = "crates/stackforge-core" }
stackforge-automata = { path = "crates/stackforge-automata" }

[lib]
name = "stackforge"
crate-type = ["cdylib"]

[dependencies]
pyo3 = { version = "0.27.2", features = ["extension-module"] }
stackforge-core.workspace = true
stackforge-automata.workspace = true


[package.metadata.bin]
tre-command = { version = "0.4.0", bins = ["tre"] }

[workspace.lints.rust]
missing_docs = "allow"

[lints]
workspace = true
5 changes: 5 additions & 0 deletions crates/stackforge-automata/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,8 @@ license.workspace = true
description = "Automata networking logic for Stackforge."

[dependencies]
stackforge-core.workspace = true
bytes.workspace = true

[lints]
workspace = true
33 changes: 33 additions & 0 deletions crates/stackforge-automata/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1 +1,34 @@
//! # Stackforge Automata
//!
//! Async state machine framework for network automation tasks.
//!
//! This crate provides the infrastructure for implementing "Answering Machines"
//! and other stateful network automation patterns using Rust's async/await.
//!
//! ## Planned Features
//!
//! - Automaton trait for defining state machines
//! - ARP spoofer implementation
//! - DHCP server framework
//! - Generic request/response matchers
//!
//! This module will be implemented in Phase 3 (Months 7-9) of the roadmap.

#![warn(missing_docs)]

// Re-export core types for convenience
pub use stackforge_core::{LayerKind, Packet};

/// Placeholder for the Automaton trait.
///
/// This will be fully implemented in Phase 3.
pub trait Automaton {
/// The type of event this automaton processes.
type Event;

/// The type of action this automaton can take.
type Action;

/// Process an event and return an action.
fn process(&mut self, event: Self::Event) -> Option<Self::Action>;
}
12 changes: 12 additions & 0 deletions crates/stackforge-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,15 @@ license.workspace = true
description = "Core networking logic for Stackforge."

[dependencies]
thiserror.workspace = true
bytes.workspace = true
smallvec.workspace = true
pnet_datalink = "0.35.0"
default-net = "0.22.0"
rand = { version = "0.9.2", optional = true }

[features]
default = ["rand"]

[lints]
workspace = true
141 changes: 141 additions & 0 deletions crates/stackforge-core/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
//! Error types for the stackforge-core crate.

use crate::layer::LayerKind;
use thiserror::Error;

/// Errors that can occur during packet operations.
#[derive(Debug, Error)]
pub enum PacketError {
/// The packet buffer is too short to contain the expected data.
#[error("buffer too short: expected at least {expected} bytes, got {actual}")]
BufferTooShort { expected: usize, actual: usize },

/// Invalid field value encountered during parsing.
#[error("invalid field value: {0}")]
InvalidField(String),

/// The layer type is not supported or recognized.
#[error("unsupported layer type: {0}")]
UnsupportedLayer(String),

/// Attempted to access a layer that doesn't exist in the packet.
#[error("layer not found: {0:?}")]
LayerNotFound(LayerKind),

/// Checksum verification failed.
#[error("checksum mismatch: expected {expected:#06x}, got {actual:#06x}")]
ChecksumMismatch { expected: u16, actual: u16 },

/// Invalid protocol number.
#[error("invalid protocol number: {0}")]
InvalidProtocol(u8),

/// Failed to parse layer at the given offset.
#[error("parse error at offset {offset}: {message}")]
ParseError { offset: usize, message: String },

/// Field access error.
#[error("field error: {0}")]
FieldError(#[from] crate::layer::field::FieldError),

/// Layer binding not found.
#[error("no binding found for {lower:?} -> {upper:?}")]
BindingNotFound { lower: LayerKind, upper: LayerKind },

/// Neighbor resolution failed.
#[error("failed to resolve neighbor for {0}")]
NeighborResolutionFailed(String),

/// Invalid MAC address.
#[error("invalid MAC address: {0}")]
InvalidMac(String),

/// Invalid IP address.
#[error("invalid IP address: {0}")]
InvalidIp(String),

/// Operation not supported.
#[error("operation not supported: {0}")]
NotSupported(String),

/// Timeout error.
#[error("operation timed out after {0} ms")]
Timeout(u64),

/// I/O error wrapper.
#[error("I/O error: {0}")]
Io(String),
}

impl PacketError {
/// Create a buffer too short error.
pub fn buffer_too_short(expected: usize, actual: usize) -> Self {
Self::BufferTooShort { expected, actual }
}

/// Create a parse error.
pub fn parse_error(offset: usize, message: impl Into<String>) -> Self {
Self::ParseError {
offset,
message: message.into(),
}
}

/// Create a binding not found error.
pub fn binding_not_found(lower: LayerKind, upper: LayerKind) -> Self {
Self::BindingNotFound { lower, upper }
}

/// Check if this is a recoverable error.
pub fn is_recoverable(&self) -> bool {
matches!(
self,
Self::BufferTooShort { .. } | Self::Timeout(_) | Self::NeighborResolutionFailed(_)
)
}
}

/// Result type alias for packet operations.
pub type Result<T> = std::result::Result<T, PacketError>;

/// Extension trait for Result types.
pub trait ResultExt<T> {
/// Convert to PacketError with context.
fn with_context(self, context: impl FnOnce() -> String) -> Result<T>;
}

impl<T, E: std::fmt::Display> ResultExt<T> for std::result::Result<T, E> {
fn with_context(self, context: impl FnOnce() -> String) -> Result<T> {
self.map_err(|e| PacketError::InvalidField(format!("{}: {}", context(), e)))
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_error_display() {
let err = PacketError::BufferTooShort {
expected: 20,
actual: 10,
};
assert!(err.to_string().contains("20"));
assert!(err.to_string().contains("10"));
}

#[test]
fn test_error_helpers() {
let err = PacketError::buffer_too_short(100, 50);
assert!(matches!(err, PacketError::BufferTooShort { .. }));

let err = PacketError::parse_error(10, "invalid header");
assert!(matches!(err, PacketError::ParseError { .. }));
}

#[test]
fn test_is_recoverable() {
assert!(PacketError::Timeout(1000).is_recoverable());
assert!(!PacketError::InvalidProtocol(255).is_recoverable());
}
}
Loading