Skip to content

Commit

Permalink
Feature: Implement defender metrics (#70)
Browse files Browse the repository at this point in the history
* Initial structure of defender metrics

* metric structure and tests

* generic custom metric

* Change Custom metric to use references

* aws types with references

* impl tuple for Version

* remove timestamp as argument for function in custom metric

* include aws metrics and use bon crate for building metric struct

* error handling

* error handling

* error handling

* feature flag for cbor and temp fix for Header serialize

* smal changes

* String list example

* Metric integration test

* Update src/defender_metrics/data_types.rs

Co-authored-by: Mathias Koch <[email protected]>

* Cargo clippy and unit test

* cargo clippy fix

* fixed unit test and version serialization

* fix test

---------

Co-authored-by: Mathias Koch <[email protected]>
  • Loading branch information
KennethKnudsen97 and MathiasKoch authored Feb 7, 2025
1 parent 3e2cad0 commit 9103492
Show file tree
Hide file tree
Showing 23 changed files with 898 additions and 60 deletions.
5 changes: 3 additions & 2 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"rust-analyzer.checkOnSave.allTargets": false,
"rust-analyzer.checkOnSave.allTargets": true,
"rust-analyzer.cargo.features": ["log"],
"rust-analyzer.cargo.target": "x86_64-unknown-linux-gnu"
"rust-analyzer.cargo.target": "x86_64-unknown-linux-gnu",

}
13 changes: 9 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ embassy-futures = "0.1"

log = { version = "0.4", default-features = false, optional = true }
defmt = { version = "0.3", optional = true }
bon = { version = "3.3.2", default-features = false }

[dev-dependencies]
native-tls = { version = "0.2" }
Expand All @@ -62,16 +63,19 @@ tokio = { version = "1.33", default-features = false, features = [
] }
tokio-native-tls = { version = "0.3.1" }
embassy-futures = { version = "0.1.0" }
embassy-time = { version = "0.4", features = ["log", "std", "generic-queue"] }
embassy-time = { version = "0.4", features = ["log", "std", "generic-queue-8"] }
embedded-io-adapters = { version = "0.6.0", features = ["tokio-1"] }

ecdsa = { version = "0.16", features = ["pkcs8", "pem"] }
p256 = "0.13"
pkcs8 = { version = "0.10", features = ["encryption", "pem"] }
hex = { version = "0.4.3", features = ["alloc"] }


[features]
default = ["ota_mqtt_data", "provision_cbor"]
default = ["ota_mqtt_data", "metric_cbor", "provision_cbor"]

metric_cbor = ["dep:minicbor", "dep:minicbor-serde"]

provision_cbor = ["dep:minicbor", "dep:minicbor-serde"]

Expand All @@ -89,5 +93,6 @@ defmt = [
]
log = ["dep:log", "embedded-mqtt/log"]

# [patch."ssh://[email protected]/FactbirdHQ/embedded-mqtt"]
# embedded-mqtt = { path = "../embedded-mqtt" }

[patch."ssh://git@github.com/FactbirdHQ/embedded-mqtt"]
embedded-mqtt = { path = "../embedded-mqtt" }
2 changes: 1 addition & 1 deletion rust-toolchain.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[toolchain]
channel = "nightly-2024-07-17"
channel = "nightly-2024-09-06"
components = ["rust-src", "rustfmt", "llvm-tools"]
targets = [
"x86_64-unknown-linux-gnu",
Expand Down
24 changes: 11 additions & 13 deletions shadow_derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,7 @@ fn create_assigners(fields: &Vec<Field>) -> Vec<proc_macro2::TokenStream> {
if field
.attrs
.iter()
.find(|a| a.path.is_ident("static_shadow_field"))
.is_some()
.any(|a| a.path.is_ident("static_shadow_field"))
{
None
} else {
Expand All @@ -133,7 +132,7 @@ fn create_assigners(fields: &Vec<Field>) -> Vec<proc_macro2::TokenStream> {
.collect::<Vec<_>>()
}

fn create_optional_fields(fields: &Vec<Field>) -> Vec<proc_macro2::TokenStream> {
fn create_optional_fields(fields: &[Field]) -> Vec<proc_macro2::TokenStream> {
fields
.iter()
.filter_map(|field| {
Expand All @@ -153,8 +152,7 @@ fn create_optional_fields(fields: &Vec<Field>) -> Vec<proc_macro2::TokenStream>
if field
.attrs
.iter()
.find(|a| a.path.is_ident("static_shadow_field"))
.is_some()
.any(|a| a.path.is_ident("static_shadow_field"))
{
None
} else {
Expand Down Expand Up @@ -183,13 +181,13 @@ fn generate_shadow_state(input: &StructParseInput) -> proc_macro2::TokenStream {
None => quote! { None },
};

return quote! {
quote! {
#[automatically_derived]
impl #impl_generics rustot::shadows::ShadowState for #ident #ty_generics #where_clause {
const NAME: Option<&'static str> = #name;
// const MAX_PAYLOAD_SIZE: usize = 512;
}
};
}
}

fn generate_shadow_patch_struct(input: &StructParseInput) -> proc_macro2::TokenStream {
Expand All @@ -205,10 +203,10 @@ fn generate_shadow_patch_struct(input: &StructParseInput) -> proc_macro2::TokenS

let optional_ident = format_ident!("Patch{}", ident);

let assigners = create_assigners(&shadow_fields);
let optional_fields = create_optional_fields(&shadow_fields);
let assigners = create_assigners(shadow_fields);
let optional_fields = create_optional_fields(shadow_fields);

return quote! {
quote! {
#[automatically_derived]
#[derive(Default, Clone, ::serde::Deserialize, ::serde::Serialize)]
#(#copy_attrs)*
Expand All @@ -228,7 +226,7 @@ fn generate_shadow_patch_struct(input: &StructParseInput) -> proc_macro2::TokenS
)*
}
}
};
}
}

fn generate_shadow_patch_enum(input: &EnumParseInput) -> proc_macro2::TokenStream {
Expand All @@ -238,7 +236,7 @@ fn generate_shadow_patch_enum(input: &EnumParseInput) -> proc_macro2::TokenStrea

let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();

return quote! {
quote! {
#[automatically_derived]
impl #impl_generics rustot::shadows::ShadowPatch for #ident #ty_generics #where_clause {
type PatchState = #ident #ty_generics;
Expand All @@ -247,5 +245,5 @@ fn generate_shadow_patch_enum(input: &EnumParseInput) -> proc_macro2::TokenStrea
*self = opt;
}
}
};
}
}
81 changes: 81 additions & 0 deletions src/defender_metrics/aws_types.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
use serde::Serialize;

#[derive(Debug, Serialize)]
pub struct TcpConnections<'a> {
#[serde(rename = "ec")]
pub established_connections: Option<&'a EstablishedConnections<'a>>,
}

#[derive(Debug, Serialize)]
pub struct EstablishedConnections<'a> {
#[serde(rename = "cs")]
pub connections: Option<&'a [&'a Connection<'a>]>,

#[serde(rename = "t")]
pub total: Option<u64>,
}

#[derive(Debug, Serialize)]
pub struct Connection<'a> {
#[serde(rename = "rad")]
pub remote_addr: &'a str,

/// Port number, must be >= 0
#[serde(rename = "lp")]
pub local_port: Option<u16>,

/// Interface name
#[serde(rename = "li")]
pub local_interface: Option<&'a str>,
}

#[derive(Debug, Serialize)]
pub struct ListeningTcpPorts<'a> {
#[serde(rename = "pts")]
pub ports: Option<&'a [&'a TcpPort<'a>]>,

#[serde(rename = "t")]
pub total: Option<u64>,
}

#[derive(Debug, Serialize)]
pub struct TcpPort<'a> {
#[serde(rename = "pt")]
pub port: u16,

#[serde(rename = "if")]
pub interface: Option<&'a str>,
}

#[derive(Debug, Serialize)]
pub struct ListeningUdpPorts<'a> {
#[serde(rename = "pts")]
pub ports: Option<&'a [&'a UdpPort<'a>]>,

#[serde(rename = "t")]
pub total: Option<u64>,
}

#[derive(Debug, Serialize)]
pub struct UdpPort<'a> {
#[serde(rename = "pt")]
pub port: u16,

#[serde(rename = "if")]
pub interface: Option<&'a str>,
}

#[derive(Debug, Serialize)]
pub struct NetworkStats {
#[serde(rename = "bi")]
pub bytes_in: Option<u64>,

#[serde(rename = "bo")]
pub bytes_out: Option<u64>,

#[serde(rename = "pi")]
pub packets_in: Option<u64>,

#[serde(rename = "po")]
pub packets_out: Option<u64>,
}
84 changes: 84 additions & 0 deletions src/defender_metrics/data_types.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
use core::fmt::{Display, Write};

use bon::Builder;
use embassy_time::Instant;
use serde::{ser::SerializeStruct, Deserialize, Serialize};

use super::aws_types::{ListeningTcpPorts, ListeningUdpPorts, NetworkStats, TcpConnections};

#[derive(Debug, Serialize, Builder)]
pub struct Metric<'a, C: Serialize> {
#[serde(rename = "hed")]
pub header: Header,

#[serde(rename = "met")]
pub metrics: Option<Metrics<'a>>,

#[serde(rename = "cmet")]
pub custom_metrics: Option<C>,
}

#[derive(Debug, Serialize)]
pub struct Metrics<'a> {
listening_tcp_ports: Option<ListeningTcpPorts<'a>>,
listening_udp_ports: Option<ListeningUdpPorts<'a>>,
network_stats: Option<NetworkStats>,
tcp_connections: Option<TcpConnections<'a>>,
}

#[derive(Debug, Serialize)]
pub struct Header {
/// Monotonically increasing value. Epoch timestamp recommended.
#[serde(rename = "rid")]
pub report_id: i64,

/// Version in Major.Minor format.
#[serde(rename = "v")]
pub version: Version,
}

impl Default for Header {
fn default() -> Self {
Self {
report_id: Instant::now().as_millis() as i64,
version: Default::default(),
}
}
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum CustomMetric<'a> {
Number(i64),
NumberList(&'a [u64]),
StringList(&'a [&'a str]),
IpList(&'a [&'a str]),
}

/// Format is `Version(Major, Minor)`
#[derive(Debug, PartialEq, Deserialize)]
pub struct Version(pub u8, pub u8);

impl Serialize for Version {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let mut st: heapless::String<7> = heapless::String::new();
st.write_fmt(format_args!("{}.{}", self.0, self.1)).ok();

serializer.serialize_str(&st)
}
}

impl Display for Version {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{}.{}", self.0, self.1,)
}
}

impl Default for Version {
fn default() -> Self {
Self(1, 0)
}
}
29 changes: 29 additions & 0 deletions src/defender_metrics/errors.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
use serde::Deserialize;

#[derive(Debug, Deserialize)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct ErrorResponse<'a> {
#[serde(rename = "thingName")]
pub thing_name: &'a str,
pub status: &'a str,
#[serde(rename = "statusDetails")]
pub status_details: StatusDetails<'a>,
pub timestamp: i64,
}
#[derive(Debug, Deserialize)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct StatusDetails<'a> {
#[serde(rename = "ErrorCode")]
pub error_code: MetricError,
#[serde(rename = "ErrorMessage")]
pub error_message: Option<&'a str>,
}
#[derive(Debug, Deserialize)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum MetricError {
Malformed,
InvalidPayload,
Throttled,
MissingHeader,
Other,
}
Loading

0 comments on commit 9103492

Please sign in to comment.