diff --git a/tools/s2n-events/src/generate_config.rs b/tools/s2n-events/src/generate_config.rs index 3a6df68289..e302a78793 100644 --- a/tools/s2n-events/src/generate_config.rs +++ b/tools/s2n-events/src/generate_config.rs @@ -333,6 +333,37 @@ impl GenerateConfig { OutputMode::Mut => quote!(), } } + + pub fn c_ffi( + &self, + inner: &TokenStream, + connection_meta_c_type: &TokenStream, + connection_info_c_type: &TokenStream, + ) -> TokenStream { + if !self.c_api { + return quote!(); + } + + assert!( + !connection_meta_c_type.is_empty(), + "An associated C type must be specified for ConnectionMeta with the #[c_type()] \ + attribute." + ); + assert!( + !connection_info_c_type.is_empty(), + "An associated C type must be specified for ConnectionInfo with the #[c_type()] \ + attribute." + ); + + quote!( + pub mod c_ffi { + #[allow(unused_imports)] + use std::ffi::*; + + #inner + } + ) + } } impl ToTokens for OutputMode { diff --git a/tools/s2n-events/src/output.rs b/tools/s2n-events/src/output.rs index 7ccd56ef50..5052f4fc5e 100644 --- a/tools/s2n-events/src/output.rs +++ b/tools/s2n-events/src/output.rs @@ -36,6 +36,9 @@ pub struct Output { pub s2n_quic_core_path: TokenStream, pub top_level: TokenStream, pub feature_alloc: TokenStream, + pub c_ffi: TokenStream, + pub connection_meta_c_type: TokenStream, + pub connection_info_c_type: TokenStream, pub root: PathBuf, } @@ -115,6 +118,9 @@ impl ToTokens for Output { top_level, feature_alloc: _, crate_name, + c_ffi, + connection_meta_c_type, + connection_info_c_type, root: _, } = self; @@ -129,6 +135,9 @@ impl ToTokens for Output { let query_mut = self.config.query_mut(); let query_mut_tuple = self.config.query_mut_tuple(); let trait_constraints = self.config.trait_constraints(); + let c_ffi = self + .config + .c_ffi(c_ffi, connection_meta_c_type, connection_info_c_type); let ref_subscriber = self.config.ref_subscriber(quote!( type ConnectionContext = T::ConnectionContext; @@ -462,6 +471,8 @@ impl ToTokens for Output { } } + #c_ffi + #[cfg(any(test, feature = "testing"))] pub mod testing { use super::*; diff --git a/tools/s2n-events/src/parser.rs b/tools/s2n-events/src/parser.rs index 4e91f0836b..301067264c 100644 --- a/tools/s2n-events/src/parser.rs +++ b/tools/s2n-events/src/parser.rs @@ -97,6 +97,34 @@ impl Struct { } fn to_tokens(&self, output: &mut Output) { + match self.attrs.c_definition { + true => self.to_tokens_c_definition(output), + false => self.to_tokens_rust_definition(output), + } + } + + fn to_tokens_c_definition(&self, output: &mut Output) { + assert!( + self.attrs.event_name.is_none(), + "C struct definitions cannot be directly used as events." + ); + + let ident = &self.ident; + let extra_attrs = &self.attrs.extra; + let c_definition_attrs = &self.attrs.c_definition_attrs; + let builder_fields = self.fields.iter().map(Field::builder); + + output.c_ffi.extend(quote!( + #c_definition_attrs + #[derive(Clone, Debug)] + #extra_attrs + pub struct #ident { + #(#builder_fields)* + } + )); + } + + fn to_tokens_rust_definition(&self, output: &mut Output) { let Self { attrs, ident, @@ -173,6 +201,12 @@ impl Struct { } )); + if ident_str == "ConnectionMeta" { + output.connection_meta_c_type = attrs.associated_c_type.clone(); + } else if ident_str == "ConnectionInfo" { + output.connection_info_c_type = attrs.associated_c_type.clone(); + } + if let Some(event_name) = attrs.event_name.as_ref() { output.api.extend(quote!( #allow_deprecated @@ -296,6 +330,12 @@ impl Struct { self.output #lock.push(out); } )); + + assert!( + attrs.associated_c_type.is_empty(), + "C types cannot be associated with endpoint events. Publishing endpoint \ + events from the C API is not yet supported." + ); } Subject::Connection => { output.subscriber.extend(quote!( @@ -405,6 +445,15 @@ impl Struct { } } )); + + if output.config.c_api { + let c_type = &attrs.associated_c_type; + assert!( + !c_type.is_empty(), + "Events must specify an associated C type with the #[c_type()] \ + attribute.", + ); + } } } } @@ -432,6 +481,34 @@ impl Enum { } fn to_tokens(&self, output: &mut Output) { + assert!( + self.attrs.event_name.is_none(), + "enum events are not currently supported" + ); + + match self.attrs.c_definition { + true => self.to_tokens_c_definition(output), + false => self.to_tokens_rust_definition(output), + } + } + + fn to_tokens_c_definition(&self, output: &mut Output) { + let ident = &self.ident; + let extra_attrs = &self.attrs.extra; + let c_definition_attrs = &self.attrs.c_definition_attrs; + let builder_fields = self.variants.iter().map(Variant::builder); + + output.c_ffi.extend(quote!( + #c_definition_attrs + #[derive(Clone, Debug)] + #extra_attrs + pub enum #ident { + #(#builder_fields)* + } + )); + } + + fn to_tokens_rust_definition(&self, output: &mut Output) { let Self { attrs, ident, @@ -439,11 +516,6 @@ impl Enum { variants, } = self; - assert!( - attrs.event_name.is_none(), - "enum events are not currently supported" - ); - let derive_attrs = &attrs.derive_attrs; let builder_derive_attrs = &attrs.builder_derive_attrs; let extra_attrs = &attrs.extra; @@ -543,6 +615,9 @@ pub struct ContainerAttrs { pub checkpoint: Vec, pub measure_counter: Vec, pub extra: TokenStream, + pub associated_c_type: TokenStream, + pub c_definition: bool, + pub c_definition_attrs: TokenStream, } impl ContainerAttrs { @@ -563,6 +638,9 @@ impl ContainerAttrs { checkpoint: vec![], measure_counter: vec![], extra: quote!(), + associated_c_type: quote!(), + c_definition: false, + c_definition_attrs: TokenStream::default(), }; for attr in attrs { @@ -591,6 +669,13 @@ impl ContainerAttrs { v.checkpoint.push(attr.parse_args().unwrap()); } else if path.is_ident("measure_counter") { v.measure_counter.push(attr.parse_args().unwrap()); + } else if path.is_ident("c_type") { + v.associated_c_type = attr.parse_args().unwrap(); + } else if path.is_ident("repr") { + // Structs/enums with the #[repr(...)] attribute are assumed to be defined for the + // C API. + v.c_definition = true; + attr.to_tokens(&mut v.c_definition_attrs); } else { attr.to_tokens(&mut v.extra) } diff --git a/tools/s2n-events/tests/c_ffi_events/event/generated.rs b/tools/s2n-events/tests/c_ffi_events/event/generated.rs index cbaa647eb5..53eb30837f 100644 --- a/tools/s2n-events/tests/c_ffi_events/event/generated.rs +++ b/tools/s2n-events/tests/c_ffi_events/event/generated.rs @@ -155,6 +155,42 @@ pub mod api { impl Event for CountEvent { const NAME: &'static str = "count_event"; } + impl IntoEvent for &c_ffi::s2n_event_connection_meta { + fn into_event(self) -> builder::ConnectionMeta { + let duration = Duration::from_nanos(self.timestamp); + let timestamp = + unsafe { s2n_quic_core::time::Timestamp::from_duration(duration).into_event() }; + builder::ConnectionMeta { id: 0, timestamp } + } + } + impl IntoEvent for &c_ffi::s2n_event_connection_info { + fn into_event(self) -> builder::ConnectionInfo { + builder::ConnectionInfo {} + } + } + impl<'a> IntoEvent> for &c_ffi::s2n_byte_array_event { + fn into_event(self) -> builder::ByteArrayEvent<'a> { + let data = + unsafe { std::slice::from_raw_parts(self.data, self.len.try_into().unwrap()) }; + builder::ByteArrayEvent { data } + } + } + impl IntoEvent for c_ffi::s2n_test_enum { + fn into_event(self) -> builder::TestEnum { + match self { + Self::S2N_TEST_VALUE_1 => builder::TestEnum::TestValue1, + Self::S2N_TEST_VALUE_2 => builder::TestEnum::TestValue2, + } + } + } + impl IntoEvent for &c_ffi::s2n_enum_event { + fn into_event(self) -> builder::EnumEvent { + let value = self.value.clone(); + builder::EnumEvent { + value: value.into_event(), + } + } + } } pub mod tracing { #![doc = r" This module contains event integration with [`tracing`](https://docs.rs/tracing)"] @@ -293,7 +329,7 @@ pub mod builder { } } } - #[derive(Clone, Debug)] + #[derive(PartialEq, Clone, Debug)] pub enum TestEnum { TestValue1, TestValue2, @@ -638,6 +674,40 @@ mod traits { } } } +pub mod c_ffi { + #[allow(unused_imports)] + use std::ffi::*; + #[repr(C)] + #[derive(Clone, Debug)] + #[allow(non_camel_case_types)] + pub struct s2n_event_connection_meta { + pub timestamp: u64, + } + #[repr(C)] + #[derive(Clone, Debug)] + #[allow(non_camel_case_types)] + pub struct s2n_event_connection_info {} + #[repr(C)] + #[derive(Clone, Debug)] + #[allow(non_camel_case_types)] + pub struct s2n_byte_array_event { + pub data: *const u8, + pub len: u32, + } + #[repr(C)] + #[derive(Clone, Debug)] + #[allow(non_camel_case_types)] + pub struct s2n_enum_event { + pub value: s2n_test_enum, + } + #[repr(C)] + #[derive(Clone, Debug)] + #[allow(non_camel_case_types)] + pub enum s2n_test_enum { + S2N_TEST_VALUE_1, + S2N_TEST_VALUE_2, + } +} #[cfg(any(test, feature = "testing"))] pub mod testing { use super::*; diff --git a/tools/s2n-events/tests/c_ffi_events/events/common.rs b/tools/s2n-events/tests/c_ffi_events/events/common.rs index 6173946868..08136a0c19 100644 --- a/tools/s2n-events/tests/c_ffi_events/events/common.rs +++ b/tools/s2n-events/tests/c_ffi_events/events/common.rs @@ -8,11 +8,42 @@ enum Subject { }, } +#[c_type(s2n_event_connection_meta)] struct ConnectionMeta { id: u64, timestamp: Timestamp, } +#[repr(C)] +#[allow(non_camel_case_types)] +struct s2n_event_connection_meta { + timestamp: u64, +} + +impl IntoEvent for &c_ffi::s2n_event_connection_meta { + fn into_event(self) -> builder::ConnectionMeta { + let duration = Duration::from_nanos(self.timestamp); + let timestamp = unsafe { + s2n_quic_core::time::Timestamp::from_duration(duration).into_event() + }; + builder::ConnectionMeta { + id: 0, + timestamp, + } + } +} + struct EndpointMeta {} +#[c_type(s2n_event_connection_info)] struct ConnectionInfo {} + +#[repr(C)] +#[allow(non_camel_case_types)] +struct s2n_event_connection_info {} + +impl IntoEvent for &c_ffi::s2n_event_connection_info { + fn into_event(self) -> builder::ConnectionInfo { + builder::ConnectionInfo {} + } +} diff --git a/tools/s2n-events/tests/c_ffi_events/events/connection.rs b/tools/s2n-events/tests/c_ffi_events/events/connection.rs index 9156c1d6aa..7727c0e2ad 100644 --- a/tools/s2n-events/tests/c_ffi_events/events/connection.rs +++ b/tools/s2n-events/tests/c_ffi_events/events/connection.rs @@ -2,16 +2,66 @@ // SPDX-License-Identifier: Apache-2.0 #[event("byte_array_event")] +#[c_type(s2n_byte_array_event)] struct ByteArrayEvent<'a> { data: &'a [u8], } +#[repr(C)] +#[allow(non_camel_case_types)] +struct s2n_byte_array_event { + data: *const u8, + len: u32, +} + +impl<'a> IntoEvent> for &c_ffi::s2n_byte_array_event { + fn into_event(self) -> builder::ByteArrayEvent<'a> { + let data = unsafe { + std::slice::from_raw_parts(self.data, self.len.try_into().unwrap()) + }; + builder::ByteArrayEvent { data } + } +} + +#[builder_derive(derive(PartialEq))] enum TestEnum { TestValue1, TestValue2, } #[event("enum_event")] +#[c_type(s2n_enum_event)] struct EnumEvent { value: TestEnum, } + +#[repr(C)] +#[allow(non_camel_case_types)] +enum s2n_test_enum { + S2N_TEST_VALUE_1, + S2N_TEST_VALUE_2, +} + +impl IntoEvent for c_ffi::s2n_test_enum { + fn into_event(self) -> builder::TestEnum { + match self { + Self::S2N_TEST_VALUE_1 => builder::TestEnum::TestValue1, + Self::S2N_TEST_VALUE_2 => builder::TestEnum::TestValue2, + } + } +} + +#[repr(C)] +#[allow(non_camel_case_types)] +struct s2n_enum_event { + value: s2n_test_enum, +} + +impl IntoEvent for &c_ffi::s2n_enum_event { + fn into_event(self) -> builder::EnumEvent { + let value = self.value.clone(); + builder::EnumEvent { + value: value.into_event(), + } + } +} diff --git a/tools/s2n-events/tests/test_c_ffi_events.rs b/tools/s2n-events/tests/test_c_ffi_events.rs index e09ca5a4aa..bb126ffe4e 100644 --- a/tools/s2n-events/tests/test_c_ffi_events.rs +++ b/tools/s2n-events/tests/test_c_ffi_events.rs @@ -9,6 +9,7 @@ use s2n_quic_core::{ event::IntoEvent, time::{testing::Clock as MockClock, Clock}, }; +use std::time::Duration; #[test] fn publish_byte_array_event() { @@ -53,3 +54,58 @@ fn publish_byte_array_event() { assert_eq!(subscriber.received_data, vec![1, 2, 3]); } + +#[test] +fn convert_byte_array_event() { + let data: Vec = vec![4, 5, 6]; + let len = data.len(); + let event = &event::c_ffi::s2n_byte_array_event { + data: data.as_ptr(), + len: len as u32, + }; + + let converted = event.into_event(); + + assert_eq!(converted.data, data.as_slice()); +} + +#[test] +fn convert_enum_event() { + struct TestCase { + value: event::c_ffi::s2n_test_enum, + expected_value: event::builder::TestEnum, + } + + let test_cases = [ + TestCase { + value: event::c_ffi::s2n_test_enum::S2N_TEST_VALUE_1, + expected_value: event::builder::TestEnum::TestValue1, + }, + TestCase { + value: event::c_ffi::s2n_test_enum::S2N_TEST_VALUE_2, + expected_value: event::builder::TestEnum::TestValue2, + }, + ]; + + for test in test_cases { + let event = &event::c_ffi::s2n_enum_event { value: test.value }; + + let converted = event.into_event(); + + assert_eq!(converted.value, test.expected_value); + } +} + +#[test] +fn convert_connection_meta() { + let meta = &event::c_ffi::s2n_event_connection_meta { + timestamp: Duration::new(1, 0).as_nanos() as u64, + }; + + let converted = meta.into_event(); + + assert_eq!( + converted.timestamp.duration_since_start(), + Duration::new(1, 0) + ); +}