///|
pub struct Query {
priv prefix : Prefix?
priv prefix_mode : String
priv asn : UInt?
priv path_id : UInt?
priv from_time : UInt?
priv to_time : UInt?
priv from_micros : Int
priv to_micros : Int
}
///|
pub fn Query::new(
prefix? : String,
prefix_mode? : String = "exact",
asn? : UInt,
path_id? : UInt,
from_time? : UInt,
to_time? : UInt,
from_micros? : Int = 0,
to_micros? : Int = 999999,
) -> Query raise ParseError {
match (from_time, to_time) {
(Some(a), Some(b)) if a > b =>
raise Invalid(0, "开始时间晚于结束时间")
_ => ()
}
if prefix_mode != "exact" &&
prefix_mode != "more-specific" &&
prefix_mode != "covering" {
raise Invalid(0, "前缀模式无效")
}
if from_micros < 0 ||
from_micros > 999999 ||
to_micros < 0 ||
to_micros > 999999 {
raise Invalid(0, "微秒端点无效")
}
match (from_time, to_time) {
(Some(a), Some(b)) if event_time(a, Some(from_micros)) >
event_time(b, Some(to_micros)) =>
raise Invalid(0, "开始时间晚于结束时间")
_ => ()
}
let normalized = match prefix {
Some(text) => Some(parse_prefix_text(text))
None => None
}
{
prefix: normalized,
prefix_mode,
asn,
path_id,
from_time,
to_time,
from_micros,
to_micros,
}
}
///|
/// 前缀精确匹配;ASN 匹配 peer 或有效 AS 路径,时间包含端点。
pub fn Query::matches(self : Query, event : RouteEvent) -> Bool {
if self.path_id is Some(id) && event.path_id != Some(id) {
return false
}
if self.prefix is Some(prefix) {
let target = parse_prefix_text(event.prefix) catch { _ => return false }
let accepted = match self.prefix_mode {
"more-specific" =>
prefix.length < target.length && prefix.contains(target)
"covering" => target.contains(prefix)
_ => prefix == target
}
if !accepted {
return false
}
}
if self.from_time is Some(t) &&
event_time(event.timestamp, event.microseconds) <
event_time(t, Some(self.from_micros)) {
return false
}
if self.to_time is Some(t) &&
event_time(event.timestamp, event.microseconds) >
event_time(t, Some(self.to_micros)) {
return false
}
if self.asn is Some(asn) {
if event.peer_asn == asn {
return true
}
return event.attributes.path.any(fn(segment) { segment.asns.contains(asn) })
}
true
}
///|
pub fn path_text(path : Array[AsSegment]) -> String {
let out = StringBuilder()
for i = 0; i < path.length(); i = i + 1 {
if i > 0 {
out.write_string(" ")
}
let segment = path[i]
let (open, close, separator) = match segment.kind {
1 => ("{", "}", ",")
3 => ("(", ")", " ")
4 => ("[", "]", ",")
_ => ("", "", " ")
}
out.write_string(open)
for j = 0; j < segment.asns.length(); j = j + 1 {
if j > 0 {
out.write_string(separator)
}
out.write_string(segment.asns[j].to_string())
}
out.write_string(close)
}
out.to_string()
}
///|
pub fn event_json(event : RouteEvent) -> String {
event.to_json().stringify()
}
///|
fn csv_quote(value : String) -> String {
let out = StringBuilder()
out.write_string("\"")
for ch in value {
if ch == '"' {
out.write_string("\"\"")
} else {
out.write_char(ch)
}
}
out.write_string("\"")
out.to_string()
}
///|
pub fn csv_header() -> String {
"schema_version,timestamp,microseconds,action,prefix,peer_asn,peer_ip,outbound,origin_as,path,next_hop,originated,source_file,source_record,source_offset,event_index,large_communities,path_id,atomic_aggregate,aggregator_asn,aggregator_address,wire_aggregator_asn,wire_aggregator_address,as4_aggregator_asn,as4_aggregator_address"
}
///|
pub fn event_csv(event : RouteEvent) -> String {
let micros = match event.microseconds {
Some(n) => n.to_string()
None => ""
}
let origin = match origin_as(event.attributes.path) {
Some(n) => n.to_string()
None => ""
}
let hop = match event.attributes.next_hop {
Some(s) => s
None => ""
}
let originated = match event.originated {
Some(n) => n.to_string()
None => ""
}
let source = match event.source {
Some(s) => "\{csv_quote(s.file)},\{s.record},\{s.offset},\{s.event_index}"
None => ",,,"
}
let large = StringBuilder()
for i, (global, local1, local2) in event.attributes.large_communities {
if i > 0 {
large.write_string(" ")
}
large.write_string("\{global}:\{local1}:\{local2}")
}
let id = event.path_id.map(fn(n) { n.to_string() }).unwrap_or("")
let aggregator = csv_aggregator(event.attributes.aggregator)
let wire = csv_aggregator(event.attributes.wire_aggregator)
let as4 = csv_aggregator(event.attributes.as4_aggregator)
"\{event.schema_version},\{event.timestamp},\{micros},\{csv_quote(event.action)},\{csv_quote(event.prefix)},\{event.peer_asn},\{csv_quote(event.peer_ip)},\{event.outbound},\{origin},\{csv_quote(path_text(event.attributes.path))},\{csv_quote(hop)},\{originated},\{source},\{csv_quote(large.to_string())},\{id},\{event.attributes.atomic_aggregate},\{aggregator},\{wire},\{as4}"
}
///|
fn csv_aggregator(value : Aggregator?) -> String {
match value {
Some(a) => "\{a.asn},\{csv_quote(a.address)}"
None => ","
}
}