1use std::ffi::{CStr, c_char, c_int};
13
14use libc::size_t;
15
16pub const TRACE_ERROR: c_int = 1;
18pub const TRACE_INFO: c_int = 2;
20pub const TRACE_DEBUG: c_int = 3;
22pub const TRACE_FLOW: c_int = 4;
24
25fn get_thread_id() -> i32 {
29 unsafe { libc::syscall(libc::SYS_gettid) as i32 }
30}
31
32#[allow(dead_code)]
34fn is_printable_ascii(byte: u8) -> bool {
35 (32..=126).contains(&byte)
36}
37
38fn get_level_string(level: c_int) -> &'static str {
46 match level {
47 TRACE_ERROR => "ERR",
48 TRACE_INFO => "INF",
49 TRACE_DEBUG => "DBG",
50 TRACE_FLOW => "FLW",
51 _ => "",
52 }
53}
54
55#[expect(clippy::not_unsafe_ptr_arg_deref)]
71#[unsafe(no_mangle)]
72pub extern "C" fn rust_dprintf(
73 function: *const c_char,
74 line: c_int,
75 level: c_int,
76 prefix: *const c_char,
77 message: *const c_char,
78) -> c_int {
79 if prefix.is_null() || message.is_null() {
80 return -1;
81 }
82
83 let msg = {
84 let c_str = unsafe { CStr::from_ptr(message) };
86 match c_str.to_str() {
87 Ok(s) => s,
88 Err(_) => return -1,
89 }
90 };
91
92 let output = if !function.is_null() && line > 0 {
93 let function_str = unsafe { CStr::from_ptr(function) }
96 .to_str()
97 .unwrap_or("<invalid_utf8>");
98
99 let prefix_str = unsafe { CStr::from_ptr(prefix) }
101 .to_str()
102 .unwrap_or("<invalid_utf8>");
103 let thread_id = get_thread_id();
104 let level_str = get_level_string(level);
105
106 format!("{level_str} [{thread_id}] {prefix_str}:{function_str}:{line}: {msg}\n")
107 } else {
108 format!("{msg}\n")
109 };
110
111 print!("{output}");
112
113 output.len() as c_int
114}
115
116#[cfg(any(feature = "debug_level_3", feature = "debug_level_4"))]
126#[expect(clippy::not_unsafe_ptr_arg_deref)]
127#[unsafe(no_mangle)]
128pub extern "C" fn dump_buffer(bname: *const c_char, buffer: *const u8, blen: size_t) {
129 if bname.is_null() || buffer.is_null() || blen == 0 {
130 return;
131 }
132
133 let bname_str = unsafe { CStr::from_ptr(bname) }
136 .to_str()
137 .unwrap_or("<invalid_utf8>");
138
139 eprintln!("#### {bname_str}");
140
141 let buffer_slice = unsafe { std::slice::from_raw_parts(buffer, blen) };
143 let mut offset = 0;
144
145 while offset < buffer_slice.len() {
146 for i in 0..16 {
148 if offset + i < buffer_slice.len() {
149 eprint!("{:02x} ", buffer_slice[offset + i]);
150 } else {
151 eprint!(" "); }
153
154 if i == 7 {
155 eprint!(" "); }
157 }
158
159 eprint!(" |");
160
161 for i in 0..16 {
163 if offset + i < buffer_slice.len() {
164 let byte = buffer_slice[offset + i];
165 if is_printable_ascii(byte) {
166 eprint!("{}", byte as char);
167 } else {
168 eprint!("."); }
170 }
171 }
172
173 eprintln!("|");
174
175 offset += 16;
176 }
177}
178
179#[cfg(not(any(feature = "debug_level_3", feature = "debug_level_4")))]
183#[unsafe(no_mangle)]
184pub extern "C" fn dump_buffer(_bname: *const c_char, _buffer: *const u8, _blen: size_t) {
185 }
187
188#[cfg(test)]
189mod teec_trace_tests {
190 use super::*;
191 use std::ffi::CString;
192
193 fn make_c_string_with_ptr(s: &str) -> (CString, *const c_char) {
194 let cstr = CString::new(s).expect("Failed to create CString");
195 let ptr = cstr.as_ptr();
196 (cstr, ptr)
197 }
198
199 #[test]
200 fn test_is_printable_ascii() {
201 assert!(is_printable_ascii(b'A'));
203 assert!(is_printable_ascii(b'z'));
204 assert!(is_printable_ascii(b' '));
205 assert!(is_printable_ascii(b'~'));
206
207 assert!(!is_printable_ascii(0));
208 assert!(!is_printable_ascii(31));
209 assert!(!is_printable_ascii(127));
210 assert!(!is_printable_ascii(128));
211 assert!(!is_printable_ascii(255));
212 }
213
214 #[test]
215 fn test_get_level_string() {
216 assert_eq!(get_level_string(TRACE_ERROR), "ERR");
218 assert_eq!(get_level_string(TRACE_INFO), "INF");
219 assert_eq!(get_level_string(TRACE_DEBUG), "DBG");
220 assert_eq!(get_level_string(TRACE_FLOW), "FLW");
221 assert_eq!(get_level_string(0), ""); assert_eq!(get_level_string(999), ""); }
224
225 #[test]
226 fn test_rust_dprintf_valid_input() {
227 let function = Some("test_function");
228 let line = 42;
229 let level = TRACE_INFO;
230 let prefix = "mylib";
231 let message = "This is a test message";
232
233 let (_function_cstr, function_ptr) = if let Some(func) = function {
234 make_c_string_with_ptr(func)
235 } else {
236 let cstr = CString::new("").expect("Empty string should always create CString");
238 (cstr, std::ptr::null())
239 };
240
241 let (_prefix_cstr, prefix_ptr) = make_c_string_with_ptr(prefix);
242 let (_message_cstr, message_ptr) = make_c_string_with_ptr(message);
243
244 let result = rust_dprintf(function_ptr, line, level, prefix_ptr, message_ptr);
245
246 assert!(result > 0);
247 }
248
249 #[test]
250 fn test_rust_dprintf_different_levels() {
251 for (level, _expected_prefix) in [
252 (TRACE_ERROR, "ERR"),
253 (TRACE_INFO, "INF"),
254 (TRACE_DEBUG, "DBG"),
255 (TRACE_FLOW, "FLW"),
256 ] {
257 let function = Some("func");
258 let line = 1;
259
260 let (_function_cstr, function_ptr) = if let Some(func) = function {
261 make_c_string_with_ptr(func)
262 } else {
263 let cstr = CString::new("").expect("Empty string should always create CString");
265 (cstr, std::ptr::null())
266 };
267
268 let (_prefix_cstr, prefix_ptr) = make_c_string_with_ptr("prefix");
269 let (_message_cstr, message_ptr) = make_c_string_with_ptr("message");
270
271 let result = rust_dprintf(function_ptr, line, level, prefix_ptr, message_ptr);
272
273 assert!(result > 0);
274 }
275 }
276
277 #[test]
278 fn test_rust_dprintf_no_function_info() {
279 let level = TRACE_INFO;
280 let prefix = "prefix";
281 let message = "simple message";
282
283 let (_prefix_cstr, prefix_ptr) = make_c_string_with_ptr(prefix);
284 let (_message_cstr, message_ptr) = make_c_string_with_ptr(message);
285
286 let result = rust_dprintf(std::ptr::null(), 0, level, prefix_ptr, message_ptr);
287
288 assert_eq!(result, (message.len() + 1) as i32); }
292
293 #[test]
294 fn test_rust_dprintf_null_pointers() {
295 let (_prefix_cstr, prefix_ptr) = make_c_string_with_ptr("prefix");
297 let result = rust_dprintf(
298 std::ptr::null(),
299 0,
300 TRACE_INFO,
301 prefix_ptr,
302 std::ptr::null(),
303 );
304 assert_eq!(result, -1);
305
306 let (_message_cstr, message_ptr) = make_c_string_with_ptr("message");
307 let result = rust_dprintf(
308 std::ptr::null(),
309 0,
310 TRACE_INFO,
311 std::ptr::null(),
312 message_ptr,
313 );
314 assert_eq!(result, -1);
315
316 let result = rust_dprintf(
317 std::ptr::null(),
318 0,
319 TRACE_INFO,
320 std::ptr::null(),
321 std::ptr::null(),
322 );
323 assert_eq!(result, -1);
324 }
325
326 #[test]
327 fn test_rust_dprintf_invalid_utf8() {
328 let invalid_utf8 = b"valid\xFFpart\0";
330 let cstr = unsafe { CStr::from_bytes_with_nul_unchecked(invalid_utf8) };
331 let ptr = cstr.as_ptr();
332
333 let (_prefix_cstr, prefix_ptr) = make_c_string_with_ptr("prefix");
334 let result = rust_dprintf(std::ptr::null(), 0, TRACE_INFO, prefix_ptr, ptr);
335
336 assert_eq!(result, -1);
338 }
339
340 #[test]
341 fn test_thread_id_positive() {
342 let thread_id = get_thread_id();
344 assert!(thread_id > 0);
345 }
346
347 #[test]
348 fn test_integration_style() {
349 let test_cases = vec![
351 (
352 Some("connect"),
353 101,
354 TRACE_ERROR,
355 "network",
356 "Connection failed",
357 ),
358 (
359 Some("process"),
360 205,
361 TRACE_INFO,
362 "worker",
363 "Processing item",
364 ),
365 (
366 Some("validate"),
367 42,
368 TRACE_DEBUG,
369 "parser",
370 "Validating input",
371 ),
372 (Some("loop"), 15, TRACE_FLOW, "main", "Iteration complete"),
373 ];
374
375 for (func, line, level, prefix, msg) in test_cases {
376 let (_function_cstr, function_ptr) = if let Some(f) = func {
377 make_c_string_with_ptr(f)
378 } else {
379 let cstr = CString::new("").expect("Empty string should always create CString");
381 (cstr, std::ptr::null())
382 };
383
384 let (_prefix_cstr, prefix_ptr) = make_c_string_with_ptr(prefix);
385 let (_message_cstr, message_ptr) = make_c_string_with_ptr(msg);
386
387 let result = rust_dprintf(function_ptr, line, level, prefix_ptr, message_ptr);
388
389 assert!(
391 result > 0,
392 "Failed for case: {:?}",
393 (func, line, level, prefix, msg)
394 );
395 }
396 }
397
398 #[test]
399 fn test_performance_no_panics() {
400 let (_prefix_cstr, prefix_ptr) = make_c_string_with_ptr("perf");
402 let (_message_cstr, message_ptr) = make_c_string_with_ptr("test");
403
404 for _ in 0..1000 {
405 let result = rust_dprintf(std::ptr::null(), 0, TRACE_INFO, prefix_ptr, message_ptr);
406 assert!(result >= 0);
407 }
408 }
409
410 #[test]
412 fn test_dump_buffer_exists() {
413 let buffer = b"Hello, World!";
415 let (_name_cstr, name_ptr) = make_c_string_with_ptr("test_buffer");
416
417 dump_buffer(name_ptr, buffer.as_ptr(), buffer.len());
419
420 dump_buffer(std::ptr::null(), std::ptr::null(), 0);
422 dump_buffer(name_ptr, std::ptr::null(), 10);
423 dump_buffer(std::ptr::null(), buffer.as_ptr(), buffer.len());
424 }
425
426 #[test]
427 fn test_dump_buffer_edge_cases() {
428 let buffer: [u8; 0] = [];
430 let (_name_cstr, name_ptr) = make_c_string_with_ptr("empty");
431
432 dump_buffer(name_ptr, buffer.as_ptr(), buffer.len());
433
434 let small_buffer = b"short";
435 let (_name2_cstr, name2_ptr) = make_c_string_with_ptr("small");
436
437 dump_buffer(name2_ptr, small_buffer.as_ptr(), small_buffer.len());
438
439 let non_printable = [0u8, 1, 2, 3, 128, 255, 10, 13];
440 let (_name3_cstr, name3_ptr) = make_c_string_with_ptr("non_printable");
441
442 dump_buffer(name3_ptr, non_printable.as_ptr(), non_printable.len());
443 }
444}
445
446#[cfg(all(test, any(feature = "debug_level_3", feature = "debug_level_4")))]
447mod dump_buffer_tests {
448 use super::*;
449 use std::ffi::CString;
450
451 fn make_c_string_with_ptr(s: &str) -> (CString, *const c_char) {
453 let cstr = CString::new(s).expect("Failed to create CString");
454 let ptr = cstr.as_ptr();
455 (cstr, ptr)
456 }
457
458 #[test]
459 fn test_dump_buffer_output() {
460 let buffer = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%";
462 let (_name_cstr, name_ptr) = make_c_string_with_ptr("hex_dump_test");
463
464 dump_buffer(name_ptr, buffer.as_ptr(), buffer.len());
466 }
467
468 #[test]
469 fn test_dump_buffer_exact_format() {
470 let buffer = vec![
472 b'0', b'1', b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', b'A', b'B', b'C', b'D',
474 b'E', b'F', 0x00, 0x7F, 0x80, 0xFF, 32, 126, 10, 13, 255, 1, 2, 3, b'X', b'Y', b'Z', 0,
476 ];
477
478 let (_name_cstr, name_ptr) = make_c_string_with_ptr("exact_format_test");
479
480 dump_buffer(name_ptr, buffer.as_ptr(), buffer.len());
482 }
483}