///|
/// Routing action selected for an address-qualified application object.
pub enum RoutingAction {
RouteStore
RouteForward
RouteStoreAndForward
RouteDrop
RouteReject
} derive(Eq, Debug)
///|
pub struct RoutingRule {
id : Int
source : Int
destination : Int
common_address : CommonAddress?
type_ids : Array[ApplicationType]
action : RoutingAction
priority : Int
mut enabled : Bool
} derive(Eq, Debug)
///|
pub fn RoutingRule::new(
id : Int,
source : Int,
destination : Int,
action : RoutingAction,
common_address? : CommonAddress,
type_ids? : Array[ApplicationType] = [],
priority? : Int = 0,
) -> Result[RoutingRule, String] {
if id < 1 || source < 0 || destination < 0 || source == destination {
Err("routing rule endpoints are invalid")
} else if priority < 0 || priority > 255 {
Err("routing rule priority must fit one byte")
} else {
Ok({
id,
source,
destination,
common_address,
type_ids: type_ids.copy(),
action,
priority,
enabled: true,
})
}
}
///|
pub fn RoutingRule::id(self : RoutingRule) -> Int {
self.id
}
///|
pub fn RoutingRule::source(self : RoutingRule) -> Int {
self.source
}
///|
pub fn RoutingRule::destination(self : RoutingRule) -> Int {
self.destination
}
///|
pub fn RoutingRule::common_address(self : RoutingRule) -> CommonAddress? {
self.common_address
}
///|
pub fn RoutingRule::type_ids(self : RoutingRule) -> Array[ApplicationType] {
self.type_ids.copy()
}
///|
pub fn RoutingRule::action(self : RoutingRule) -> RoutingAction {
self.action
}
///|
pub fn RoutingRule::priority(self : RoutingRule) -> Int {
self.priority
}
///|
pub fn RoutingRule::enabled(self : RoutingRule) -> Bool {
self.enabled
}
///|
pub fn RoutingRule::disable(self : RoutingRule) -> Unit {
self.enabled = false
}
///|
pub struct RouteDecision {
accepted : Bool
rule_id : Int?
action : RoutingAction
source : Int
destination : Int
object : ApplicationObject
reason : String
} derive(Debug)
///|
pub fn RouteDecision::accepted(self : RouteDecision) -> Bool {
self.accepted
}
///|
pub fn RouteDecision::rule_id(self : RouteDecision) -> Int? {
self.rule_id
}
///|
pub fn RouteDecision::action(self : RouteDecision) -> RoutingAction {
self.action
}
///|
pub fn RouteDecision::source(self : RouteDecision) -> Int {
self.source
}
///|
pub fn RouteDecision::destination(self : RouteDecision) -> Int {
self.destination
}
///|
pub fn RouteDecision::object(self : RouteDecision) -> ApplicationObject {
self.object
}
///|
pub fn RouteDecision::reason(self : RouteDecision) -> String {
self.reason
}
///|
pub struct RoutedObject {
decision : RouteDecision
timestamp : Int
sequence : Int
} derive(Debug)
///|
pub fn RoutedObject::decision(self : RoutedObject) -> RouteDecision {
self.decision
}
///|
pub fn RoutedObject::timestamp(self : RoutedObject) -> Int {
self.timestamp
}
///|
pub fn RoutedObject::sequence(self : RoutedObject) -> Int {
self.sequence
}
///|
pub struct RouterMetrics {
mut evaluated : Int
mut forwarded : Int
mut stored : Int
mut rejected : Int
mut dropped : Int
mut queue_overflow : Int
} derive(Eq, Debug)
///|
pub fn RouterMetrics::new() -> RouterMetrics {
{
evaluated: 0,
forwarded: 0,
stored: 0,
rejected: 0,
dropped: 0,
queue_overflow: 0,
}
}
///|
pub fn RouterMetrics::evaluated(self : RouterMetrics) -> Int {
self.evaluated
}
///|
pub fn RouterMetrics::forwarded(self : RouterMetrics) -> Int {
self.forwarded
}
///|
pub fn RouterMetrics::stored(self : RouterMetrics) -> Int {
self.stored
}
///|
pub fn RouterMetrics::rejected(self : RouterMetrics) -> Int {
self.rejected
}
///|
pub fn RouterMetrics::dropped(self : RouterMetrics) -> Int {
self.dropped
}
///|
pub fn RouterMetrics::queue_overflow(self : RouterMetrics) -> Int {
self.queue_overflow
}
///|
/// Address/type-aware router with bounded egress queues.
pub struct ProtocolRouter {
rules : Array[RoutingRule]
queues : Map[Int, Array[RoutedObject]]
queue_capacity : Int
metrics : RouterMetrics
mut sequence : Int
} derive(Debug)
///|
pub fn ProtocolRouter::new(
queue_capacity? : Int = 1024,
) -> Result[ProtocolRouter, String] {
if queue_capacity < 1 {
Err("router queue capacity must be positive")
} else {
Ok({
rules: [],
queues: {},
queue_capacity,
metrics: RouterMetrics::new(),
sequence: 1,
})
}
}
///|
pub fn ProtocolRouter::metrics(self : ProtocolRouter) -> RouterMetrics {
self.metrics
}
///|
pub fn ProtocolRouter::rules(self : ProtocolRouter) -> Array[RoutingRule] {
self.rules.copy()
}
///|
pub fn ProtocolRouter::queue_count(self : ProtocolRouter) -> Int {
self.queues.length()
}
///|
pub fn ProtocolRouter::add_rule(
self : ProtocolRouter,
rule : RoutingRule,
) -> Result[Unit, String] {
for current in self.rules {
if current.id() == rule.id() {
return Err("routing rule id already exists")
}
}
self.rules.push(rule)
self.rules.sort_by((left, right) => {
if left.priority() != right.priority() {
right.priority() - left.priority()
} else {
left.id() - right.id()
}
})
Ok(())
}
///|
fn route_matches(
rule : RoutingRule,
source : Int,
destination : Int,
object : ApplicationObject,
) -> Bool {
let common_ok = match rule.common_address() {
Some(value) => value == address_common(object.address())
None => true
}
let type_ok = rule.type_ids().length() == 0 ||
rule.type_ids().contains(object.type_id())
rule.enabled() &&
rule.source() == source &&
rule.destination() == destination &&
common_ok &&
type_ok
}
///|
fn address_common(address : InformationAddress) -> CommonAddress {
ignore(address)
CommonAddress::new(1).unwrap()
}
///|
pub fn ProtocolRouter::evaluate(
self : ProtocolRouter,
source : Int,
destination : Int,
object : ApplicationObject,
) -> RouteDecision {
self.metrics.evaluated += 1
let mut selected : RoutingRule? = None
for rule in self.rules {
if route_matches(rule, source, destination, object) {
selected = Some(rule)
break
}
}
match selected {
None => {
self.metrics.rejected += 1
{
accepted: false,
rule_id: None,
action: RouteReject,
source,
destination,
object,
reason: "no routing rule matched",
}
}
Some(rule) => {
let action = rule.action()
if action == RouteForward || action == RouteStoreAndForward {
self.metrics.forwarded += 1
}
if action == RouteStore {
self.metrics.stored += 1
}
if action == RouteDrop {
self.metrics.dropped += 1
}
{
accepted: action != RouteReject && action != RouteDrop,
rule_id: Some(rule.id()),
action,
source,
destination,
object,
reason: "routing rule matched",
}
}
}
}
///|
pub fn ProtocolRouter::route(
self : ProtocolRouter,
source : Int,
destination : Int,
object : ApplicationObject,
timestamp : Int,
) -> Result[RouteDecision, Diagnostic] {
let decision = self.evaluate(source, destination, object)
if !decision.accepted() {
Err(Diagnostic::new(UnsupportedFeature, decision.reason()))
} else if decision.action() == RouteStore ||
decision.action() == RouteStoreAndForward ||
decision.action() == RouteForward {
let queue = self.queues.get(destination).unwrap_or([])
if queue.length() >= self.queue_capacity {
self.metrics.queue_overflow += 1
Err(Diagnostic::new(WindowExhausted, "router egress queue is full"))
} else {
queue.push({ decision, timestamp, sequence: self.sequence })
self.sequence += 1
self.queues[destination] = queue
if decision.action() == RouteStore ||
decision.action() == RouteStoreAndForward {
self.metrics.stored += 1
}
Ok(decision)
}
} else {
Ok(decision)
}
}
///|
pub fn ProtocolRouter::drain(
self : ProtocolRouter,
destination : Int,
limit : Int,
) -> Array[RoutedObject] {
let result : Array[RoutedObject] = []
if limit < 1 {
result
} else {
match self.queues.get(destination) {
None => result
Some(queue) => {
let count = if limit > queue.length() { queue.length() } else { limit }
for _ in 0.. Array[RoutingAction] {
[RouteStore, RouteForward, RouteStoreAndForward, RouteDrop, RouteReject]
}