1extern crate proc_macro;
5
6use proc_macro::TokenStream;
7use quote::quote;
8use syn::{parse_macro_input, Data, DeriveInput, Fields, GenericArgument, PathArguments, Type};
9
10#[derive(Clone)]
12enum FieldKind {
13 Primitive {
15 size: usize,
16 alignment: usize,
17 kind_tokens: proc_macro2::TokenStream,
18 },
19 String,
21 ByteVec,
23}
24
25#[proc_macro_derive(DDS, attributes(dds))]
50#[allow(clippy::too_many_lines)]
51pub fn derive_dds(input: TokenStream) -> TokenStream {
52 let input = parse_macro_input!(input as DeriveInput);
53
54 let name = &input.ident;
55 let type_name = name.to_string();
56 let type_id = compute_fnv1a_hash(&type_name);
57
58 let fields = match &input.data {
60 Data::Struct(data) => match &data.fields {
61 Fields::Named(f) => &f.named,
62 _ => {
63 return syn::Error::new_spanned(&input, "Only named fields are supported")
64 .to_compile_error()
65 .into()
66 }
67 },
68 _ => {
69 return syn::Error::new_spanned(&input, "Only structs are supported")
70 .to_compile_error()
71 .into()
72 }
73 };
74
75 struct FieldInfo {
77 name: syn::Ident,
78 ty: syn::Type,
79 kind: FieldKind,
80 offset: usize, }
82
83 let mut field_infos = Vec::new();
84 let mut current_offset = 0usize;
85 let mut max_alignment = 1usize;
86 let mut has_variable_size = false;
87
88 for field in fields {
89 let Some(field_name) = field.ident.as_ref() else {
90 return syn::Error::new_spanned(field, "Field must have a name")
91 .to_compile_error()
92 .into();
93 };
94 let field_type = &field.ty;
95
96 let Some(kind) = get_field_kind(field_type) else {
97 return syn::Error::new_spanned(
98 field_type,
99 format!("Unsupported type: {field_type:?}. Supported types: primitives, String, Vec<u8>."),
100 )
101 .to_compile_error()
102 .into();
103 };
104
105 let (size, alignment) = match &kind {
106 FieldKind::Primitive {
107 size, alignment, ..
108 } => (*size, *alignment),
109 FieldKind::String | FieldKind::ByteVec => {
110 has_variable_size = true;
111 (0, 4) }
113 };
114
115 current_offset = align_to(current_offset, alignment);
117 max_alignment = max_alignment.max(alignment);
118
119 field_infos.push(FieldInfo {
120 name: field_name.clone(),
121 ty: field_type.clone(),
122 kind,
123 offset: current_offset,
124 });
125
126 if !has_variable_size {
127 current_offset += size;
128 }
129 }
130
131 let total_size = if has_variable_size {
132 0xFFFF_FFFF_u32 } else {
134 align_to(current_offset, max_alignment) as u32
135 };
136
137 let field_layouts: Vec<_> = field_infos
139 .iter()
140 .map(|f| {
141 let name_str = f.name.to_string();
142 let offset = f.offset as u32;
143
144 match &f.kind {
145 FieldKind::Primitive {
146 size,
147 alignment,
148 kind_tokens,
149 } => {
150 let size = *size as u32;
151 let alignment = *alignment as u8;
152 quote! {
153 ::hdds::core::types::FieldLayout {
154 name: #name_str,
155 offset_bytes: #offset,
156 field_type: ::hdds::core::types::FieldType::Primitive(#kind_tokens),
157 alignment: #alignment,
158 size_bytes: #size,
159 element_type: None,
160 }
161 }
162 }
163 FieldKind::String => {
164 quote! {
165 ::hdds::core::types::FieldLayout {
166 name: #name_str,
167 offset_bytes: #offset,
168 field_type: ::hdds::core::types::FieldType::String,
169 alignment: 4,
170 size_bytes: 0xFFFF_FFFF, element_type: None,
172 }
173 }
174 }
175 FieldKind::ByteVec => {
176 quote! {
177 ::hdds::core::types::FieldLayout {
178 name: #name_str,
179 offset_bytes: #offset,
180 field_type: ::hdds::core::types::FieldType::Sequence,
181 alignment: 4,
182 size_bytes: 0xFFFF_FFFF, element_type: None,
184 }
185 }
186 }
187 }
188 })
189 .collect();
190
191 let encode_fields: Vec<_> = field_infos
193 .iter()
194 .map(|f| {
195 let field_name = &f.name;
196
197 match &f.kind {
198 FieldKind::Primitive { alignment, .. } => {
199 quote! {
200 while cursor.offset() % #alignment != 0 {
202 cursor.write_u8(0)?;
203 }
204 cursor.write_bytes(&self.#field_name.to_le_bytes())?;
206 }
207 }
208 FieldKind::String => {
209 quote! {
210 while cursor.offset() % 4 != 0 {
212 cursor.write_u8(0)?;
213 }
214 let str_bytes = self.#field_name.as_bytes();
216 let str_len = (str_bytes.len() + 1) as u32; cursor.write_bytes(&str_len.to_le_bytes())?;
218 cursor.write_bytes(str_bytes)?;
219 cursor.write_u8(0)?; }
221 }
222 FieldKind::ByteVec => {
223 quote! {
224 while cursor.offset() % 4 != 0 {
226 cursor.write_u8(0)?;
227 }
228 let vec_len = self.#field_name.len() as u32;
230 cursor.write_bytes(&vec_len.to_le_bytes())?;
231 cursor.write_bytes(&self.#field_name)?;
232 }
233 }
234 }
235 })
236 .collect();
237
238 let decode_fields: Vec<_> = field_infos
240 .iter()
241 .map(|f| {
242 let field_name = &f.name;
243 let field_type = &f.ty;
244
245 match &f.kind {
246 FieldKind::Primitive { size, alignment, .. } => {
247 quote! {
248 while cursor.offset() % #alignment != 0 {
250 let _ = cursor.read_u8()?;
251 }
252 let #field_name = {
254 let bytes_slice = cursor.read_bytes(#size)?;
255 let mut bytes = [0u8; #size];
256 bytes.copy_from_slice(bytes_slice);
257 <#field_type>::from_le_bytes(bytes)
258 };
259 }
260 }
261 FieldKind::String => {
262 quote! {
263 while cursor.offset() % 4 != 0 {
265 let _ = cursor.read_u8()?;
266 }
267 let #field_name = {
269 let len_bytes = cursor.read_bytes(4)?;
270 let str_len = u32::from_le_bytes([len_bytes[0], len_bytes[1], len_bytes[2], len_bytes[3]]) as usize;
271 if str_len == 0 {
272 String::new()
273 } else {
274 let str_bytes = cursor.read_bytes(str_len - 1)?; let _ = cursor.read_u8()?; String::from_utf8(str_bytes.to_vec())
277 .map_err(|_| ::hdds::dds::Error::SerializationError)?
278 }
279 };
280 }
281 }
282 FieldKind::ByteVec => {
283 quote! {
284 while cursor.offset() % 4 != 0 {
286 let _ = cursor.read_u8()?;
287 }
288 let #field_name = {
290 let len_bytes = cursor.read_bytes(4)?;
291 let vec_len = u32::from_le_bytes([len_bytes[0], len_bytes[1], len_bytes[2], len_bytes[3]]) as usize;
292 let data = cursor.read_bytes(vec_len)?;
293 data.to_vec()
294 };
295 }
296 }
297 }
298 })
299 .collect();
300
301 let field_names: Vec<_> = field_infos.iter().map(|f| &f.name).collect();
302
303 let type_object_members: Vec<_> = field_infos
305 .iter()
306 .enumerate()
307 .map(|(idx, f)| {
308 let Ok(member_id) = u32::try_from(idx) else {
309 return syn::Error::new_spanned(
310 &f.name,
311 format!("Struct has too many fields (index {idx} exceeds u32::MAX)"),
312 )
313 .to_compile_error();
314 };
315 let name_str = f.name.to_string();
316 let type_id_const = get_type_identifier_for_kind(&f.kind);
317
318 quote! {
319 ::hdds::xtypes::CompleteStructMember {
320 common: ::hdds::xtypes::CommonStructMember {
321 member_id: #member_id,
322 member_flags: ::hdds::xtypes::MemberFlag::empty(),
323 member_type_id: #type_id_const,
324 },
325 detail: ::hdds::xtypes::CompleteMemberDetail::new(#name_str),
326 }
327 }
328 })
329 .collect();
330
331 let max_alignment_u8 = max_alignment as u8;
332
333 let expanded = quote! {
334 impl ::hdds::api::DDS for #name {
335 fn type_descriptor() -> &'static ::hdds::core::types::TypeDescriptor {
336 static DESCRIPTOR: ::hdds::core::types::TypeDescriptor = ::hdds::core::types::TypeDescriptor {
337 type_id: #type_id,
338 type_name: #type_name,
339 size_bytes: #total_size,
340 alignment: #max_alignment_u8,
341 is_variable_size: #has_variable_size,
342 fields: &[#(#field_layouts),*],
343 };
344 &DESCRIPTOR
345 }
346
347 fn encode_cdr2(&self, buf: &mut [u8]) -> ::hdds::api::Result<usize> {
348 use ::hdds::core::ser::cursor::CursorMut;
349
350 let mut cursor = CursorMut::new(buf);
351
352 #(#encode_fields)*
354
355 Ok(cursor.offset())
356 }
357
358 fn decode_cdr2(buf: &[u8]) -> ::hdds::api::Result<Self> {
359 use ::hdds::core::ser::cursor::Cursor;
360
361 let mut cursor = Cursor::new(buf);
362
363 #(#decode_fields)*
365
366 Ok(Self {
367 #(#field_names),*
368 })
369 }
370
371 fn get_type_object() -> Option<::hdds::xtypes::CompleteTypeObject> {
390 Some(::hdds::xtypes::CompleteTypeObject::Struct(
391 ::hdds::xtypes::CompleteStructType {
392 struct_flags: ::hdds::xtypes::StructTypeFlag::IS_FINAL,
393 header: ::hdds::xtypes::CompleteStructHeader {
394 base_type: None, detail: ::hdds::xtypes::CompleteTypeDetail::new(#type_name),
396 },
397 member_seq: vec![
398 #(#type_object_members),*
399 ],
400 }
401 ))
402 }
403 }
404 };
405
406 TokenStream::from(expanded)
407}
408
409fn get_field_kind(ty: &syn::Type) -> Option<FieldKind> {
416 if let Type::Path(type_path) = ty {
417 let segment = type_path.path.segments.last()?;
418 let ident_str = segment.ident.to_string();
419
420 match ident_str.as_str() {
422 "i8" => {
423 return Some(FieldKind::Primitive {
424 size: 1,
425 alignment: 1,
426 kind_tokens: quote! { ::hdds::core::types::PrimitiveKind::I8 },
427 })
428 }
429 "i16" => {
430 return Some(FieldKind::Primitive {
431 size: 2,
432 alignment: 2,
433 kind_tokens: quote! { ::hdds::core::types::PrimitiveKind::I16 },
434 })
435 }
436 "i32" => {
437 return Some(FieldKind::Primitive {
438 size: 4,
439 alignment: 4,
440 kind_tokens: quote! { ::hdds::core::types::PrimitiveKind::I32 },
441 })
442 }
443 "i64" => {
444 return Some(FieldKind::Primitive {
445 size: 8,
446 alignment: 8,
447 kind_tokens: quote! { ::hdds::core::types::PrimitiveKind::I64 },
448 })
449 }
450 "u8" => {
451 return Some(FieldKind::Primitive {
452 size: 1,
453 alignment: 1,
454 kind_tokens: quote! { ::hdds::core::types::PrimitiveKind::U8 },
455 })
456 }
457 "u16" => {
458 return Some(FieldKind::Primitive {
459 size: 2,
460 alignment: 2,
461 kind_tokens: quote! { ::hdds::core::types::PrimitiveKind::U16 },
462 })
463 }
464 "u32" => {
465 return Some(FieldKind::Primitive {
466 size: 4,
467 alignment: 4,
468 kind_tokens: quote! { ::hdds::core::types::PrimitiveKind::U32 },
469 })
470 }
471 "u64" => {
472 return Some(FieldKind::Primitive {
473 size: 8,
474 alignment: 8,
475 kind_tokens: quote! { ::hdds::core::types::PrimitiveKind::U64 },
476 })
477 }
478 "f32" => {
479 return Some(FieldKind::Primitive {
480 size: 4,
481 alignment: 4,
482 kind_tokens: quote! { ::hdds::core::types::PrimitiveKind::F32 },
483 })
484 }
485 "f64" => {
486 return Some(FieldKind::Primitive {
487 size: 8,
488 alignment: 8,
489 kind_tokens: quote! { ::hdds::core::types::PrimitiveKind::F64 },
490 })
491 }
492 "bool" | "boolean" => {
493 return Some(FieldKind::Primitive {
494 size: 1,
495 alignment: 1,
496 kind_tokens: quote! { ::hdds::core::types::PrimitiveKind::Bool },
497 })
498 }
499 "String" => return Some(FieldKind::String),
500 "Vec" => {
501 if let PathArguments::AngleBracketed(args) = &segment.arguments {
503 if let Some(GenericArgument::Type(Type::Path(inner_path))) = args.args.first() {
504 if let Some(inner_segment) = inner_path.path.segments.last() {
505 if inner_segment.ident == "u8" {
506 return Some(FieldKind::ByteVec);
507 }
508 }
509 }
510 }
511 return None; }
513 _ => return None,
514 }
515 }
516 None
517}
518
519#[allow(clippy::integer_division_remainder_used, clippy::integer_division)]
521const fn align_to(offset: usize, alignment: usize) -> usize {
522 #[allow(clippy::arithmetic_side_effects)] #[allow(clippy::manual_div_ceil)] {
526 (offset + alignment - 1) / alignment * alignment
527 }
528}
529
530fn compute_fnv1a_hash(s: &str) -> u32 {
532 let mut hash = 2_166_136_261_u32;
533 for byte in s.bytes() {
534 hash ^= u32::from(byte);
535 hash = hash.wrapping_mul(16_777_619);
536 }
537 hash
538}
539
540fn get_type_identifier_for_kind(kind: &FieldKind) -> proc_macro2::TokenStream {
547 match kind {
548 FieldKind::Primitive { kind_tokens, .. } => {
549 let prim_str = kind_tokens.to_string();
550 let variant = prim_str.split("::").last().unwrap_or("").trim();
551
552 match variant {
553 "I8" => quote! { ::hdds::xtypes::TypeIdentifier::TK_INT8 },
554 "I16" => quote! { ::hdds::xtypes::TypeIdentifier::TK_INT16 },
555 "I32" => quote! { ::hdds::xtypes::TypeIdentifier::TK_INT32 },
556 "I64" => quote! { ::hdds::xtypes::TypeIdentifier::TK_INT64 },
557 "U8" => quote! { ::hdds::xtypes::TypeIdentifier::TK_UINT8 },
558 "U16" => quote! { ::hdds::xtypes::TypeIdentifier::TK_UINT16 },
559 "U32" => quote! { ::hdds::xtypes::TypeIdentifier::TK_UINT32 },
560 "U64" => quote! { ::hdds::xtypes::TypeIdentifier::TK_UINT64 },
561 "F32" => quote! { ::hdds::xtypes::TypeIdentifier::TK_FLOAT32 },
562 "F64" => quote! { ::hdds::xtypes::TypeIdentifier::TK_FLOAT64 },
563 "Bool" => quote! { ::hdds::xtypes::TypeIdentifier::TK_BOOLEAN },
564 _ => quote! {
565 compile_error!(concat!("Internal error: unsupported primitive kind: ", #variant))
566 },
567 }
568 }
569 FieldKind::String => {
570 quote! { ::hdds::xtypes::TypeIdentifier::TK_STRING8 }
571 }
572 FieldKind::ByteVec => {
573 quote! { ::hdds::xtypes::TypeIdentifier::Primitive(::hdds::xtypes::TypeKind::TK_SEQUENCE) }
576 }
577 }
578}