mirror of https://github.com/OISF/suricata
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
164 lines
5.3 KiB
Rust
164 lines
5.3 KiB
Rust
/* Copyright (C) 2021-2023 Open Information Security Foundation
|
|
*
|
|
* You can copy, redistribute or modify this Program under the terms of
|
|
* the GNU General Public License version 2 as published by the Free
|
|
* Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* version 2 along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
|
* 02110-1301, USA.
|
|
*/
|
|
|
|
extern crate proc_macro;
|
|
use proc_macro::TokenStream;
|
|
use quote::quote;
|
|
use syn::{self, parse_macro_input, DeriveInput};
|
|
|
|
pub fn derive_app_layer_event(input: TokenStream) -> TokenStream {
|
|
let input = parse_macro_input!(input as DeriveInput);
|
|
let name = input.ident;
|
|
|
|
let mut fields = Vec::new();
|
|
let mut event_ids = Vec::new();
|
|
let mut event_cstrings = Vec::new();
|
|
let mut event_names = Vec::new();
|
|
|
|
match input.data {
|
|
syn::Data::Enum(ref data) => {
|
|
for (i, v) in (&data.variants).into_iter().enumerate() {
|
|
fields.push(v.ident.clone());
|
|
let event_name = if let Some(xname) = parse_name(&v.attrs) {
|
|
xname.value()
|
|
} else {
|
|
transform_name(&v.ident.to_string())
|
|
};
|
|
let cname = format!("{}\0", event_name);
|
|
event_names.push(event_name);
|
|
event_cstrings.push(cname);
|
|
event_ids.push(i as i32);
|
|
}
|
|
}
|
|
_ => panic!("AppLayerEvent can only be derived for enums"),
|
|
}
|
|
|
|
// If we're being used from within Suricata we have to reference the internal name space with
|
|
// "crate", but if we're being used by a library or plugin user we need to reference the
|
|
// Suricata name space as "suricata". Check the CARGO_PKG_NAME environment variable to
|
|
// determine what identifier to setup.
|
|
let is_suricata = std::env::var("CARGO_PKG_NAME").map(|var| var == "suricata").unwrap_or(false);
|
|
let crate_id = if is_suricata {
|
|
syn::Ident::new("crate", proc_macro2::Span::call_site())
|
|
} else {
|
|
syn::Ident::new("suricata", proc_macro2::Span::call_site())
|
|
};
|
|
|
|
let expanded = quote! {
|
|
impl #crate_id::applayer::AppLayerEvent for #name {
|
|
fn from_id(id: i32) -> Option<#name> {
|
|
match id {
|
|
#( #event_ids => Some(#name::#fields) ,)*
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
fn as_i32(&self) -> i32 {
|
|
match *self {
|
|
#( #name::#fields => #event_ids ,)*
|
|
}
|
|
}
|
|
|
|
fn to_cstring(&self) -> &str {
|
|
match *self {
|
|
#( #name::#fields => #event_cstrings ,)*
|
|
}
|
|
}
|
|
|
|
fn from_string(s: &str) -> Option<#name> {
|
|
match s {
|
|
#( #event_names => Some(#name::#fields) ,)*
|
|
_ => None
|
|
}
|
|
}
|
|
|
|
unsafe extern "C" fn get_event_info(
|
|
event_name: *const std::os::raw::c_char,
|
|
event_id: *mut std::os::raw::c_int,
|
|
event_type: *mut #crate_id::core::AppLayerEventType,
|
|
) -> std::os::raw::c_int {
|
|
#crate_id::applayer::get_event_info::<#name>(event_name, event_id, event_type)
|
|
}
|
|
|
|
unsafe extern "C" fn get_event_info_by_id(
|
|
event_id: std::os::raw::c_int,
|
|
event_name: *mut *const std::os::raw::c_char,
|
|
event_type: *mut #crate_id::core::AppLayerEventType,
|
|
) -> i8 {
|
|
#crate_id::applayer::get_event_info_by_id::<#name>(event_id, event_name, event_type)
|
|
}
|
|
|
|
}
|
|
};
|
|
|
|
proc_macro::TokenStream::from(expanded)
|
|
}
|
|
|
|
/// Transform names such as "OneTwoThree" to "one_two_three".
|
|
pub fn transform_name(in_name: &str) -> String {
|
|
let mut out = String::new();
|
|
for (i, c) in in_name.chars().enumerate() {
|
|
if i == 0 {
|
|
out.push_str(&c.to_lowercase().to_string());
|
|
} else if c.is_uppercase() {
|
|
out.push('_');
|
|
out.push_str(&c.to_lowercase().to_string());
|
|
} else {
|
|
out.push(c);
|
|
}
|
|
}
|
|
out
|
|
}
|
|
|
|
/// Parse the event name from the "name" attribute.
|
|
///
|
|
/// For example:
|
|
/// ```ignore
|
|
/// #[derive(AppLayerEvent)]
|
|
/// pub enum FtpEvent {
|
|
/// #[name("request_command_too_long")]
|
|
/// FtpEventRequestCommandTooLong,
|
|
/// #[name("response_command_too_long")]
|
|
/// FtpEventResponseCommandTooLong,
|
|
/// }
|
|
/// ```
|
|
fn parse_name(attrs: &[syn::Attribute]) -> Option<syn::LitStr> {
|
|
for attr in attrs {
|
|
if attr.path.is_ident("name") {
|
|
if let Ok(val) = attr.parse_args::<syn::LitStr>() {
|
|
return Some(val);
|
|
}
|
|
}
|
|
}
|
|
None
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod test {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_transform_name() {
|
|
assert_eq!(transform_name("One"), "one".to_string());
|
|
assert_eq!(transform_name("SomeEvent"), "some_event".to_string());
|
|
assert_eq!(
|
|
transform_name("UnassignedMsgType"),
|
|
"unassigned_msg_type".to_string()
|
|
);
|
|
}
|
|
}
|