From 173ca7ee23db4bf570045fde1f17366fb2b70c0b Mon Sep 17 00:00:00 2001
From: Alaric Senat <dev.asenat@posteo.net>
Date: Tue, 11 Mar 2025 00:10:08 +0100
Subject: [PATCH 1/9] vlcrs-core: tracer: redefine license headers as comments

These should not be considered as documentation, in some cases, it
messes with the formatter and possibly rust-doc.
---
 src/rust/vlcrs-core/src/tracer/mod.rs | 33 ++++++++++++++-------------
 src/rust/vlcrs-core/src/tracer/sys.rs | 33 ++++++++++++++-------------
 2 files changed, 34 insertions(+), 32 deletions(-)

diff --git a/src/rust/vlcrs-core/src/tracer/mod.rs b/src/rust/vlcrs-core/src/tracer/mod.rs
index 48b3c18b34af..1efa689995fa 100644
--- a/src/rust/vlcrs-core/src/tracer/mod.rs
+++ b/src/rust/vlcrs-core/src/tracer/mod.rs
@@ -1,19 +1,20 @@
-/// SPDX-License-Identifier: LGPL-2.1-or-later
-/// Copyright (C) 2024-2025 Alexandre Janniaux <ajanni@videolabs.io>
-///
-/// This program is free software; you can redistribute it and/or modify it
-/// under the terms of the GNU Lesser General Public License as published by
-/// the Free Software Foundation; either version 2.1 of the License, or
-/// (at your option) any later version.
-///
-/// 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 Lesser General Public License for more details.
-///
-/// You should have received a copy of the GNU Lesser General Public License
-/// along with this program; if not, write to the Free Software Foundation,
-/// Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+// SPDX-License-Identifier: LGPL-2.1-or-later
+// Copyright (C) 2024-2025 Alexandre Janniaux <ajanni@videolabs.io>
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation; either version 2.1 of the License, or
+// (at your option) any later version.
+//
+// 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 Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program; if not, write to the Free Software Foundation,
+// Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+
 use std::{
     cell::UnsafeCell,
     ffi::{c_char, c_void, CStr},
diff --git a/src/rust/vlcrs-core/src/tracer/sys.rs b/src/rust/vlcrs-core/src/tracer/sys.rs
index 7ddabf0dcafd..1ff964d272ce 100644
--- a/src/rust/vlcrs-core/src/tracer/sys.rs
+++ b/src/rust/vlcrs-core/src/tracer/sys.rs
@@ -1,25 +1,26 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+// Copyright (C) 2024-2025 Alexandre Janniaux <ajanni@videolabs.io>
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation; either version 2.1 of the License, or
+// (at your option) any later version.
+//
+// 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 Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program; if not, write to the Free Software Foundation,
+// Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+
 #![allow(rustdoc::bare_urls)]
 #![allow(rustdoc::broken_intra_doc_links)]
 #![allow(non_upper_case_globals)]
 #![allow(non_camel_case_types)]
 #![allow(non_snake_case)]
 
-/// SPDX-License-Identifier: LGPL-2.1-or-later
-/// Copyright (C) 2024-2025 Alexandre Janniaux <ajanni@videolabs.io>
-///
-/// This program is free software; you can redistribute it and/or modify it
-/// under the terms of the GNU Lesser General Public License as published by
-/// the Free Software Foundation; either version 2.1 of the License, or
-/// (at your option) any later version.
-///
-/// 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 Lesser General Public License for more details.
-///
-/// You should have received a copy of the GNU Lesser General Public License
-/// along with this program; if not, write to the Free Software Foundation,
-/// Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
 use std::{
     ffi::{c_char, c_double, c_void, CStr},
     ptr::NonNull,
-- 
GitLab


From 6d30dc06243cb33e836788b4ffed6a389857e950 Mon Sep 17 00:00:00 2001
From: Alaric Senat <dev.asenat@posteo.net>
Date: Thu, 6 Mar 2025 14:42:58 +0100
Subject: [PATCH 2/9] vlcrs-core: tracer: move higher level bindings out of
 `sys`

Reserve the sys module for raw VLC bindings. The moved abstraction are
meant to be transformed into safe encapsulations of sys content.
---
 src/rust/vlcrs-core/src/tracer/mod.rs | 126 +++++++++++++++++++++++++-
 src/rust/vlcrs-core/src/tracer/sys.rs | 125 ++-----------------------
 2 files changed, 130 insertions(+), 121 deletions(-)

diff --git a/src/rust/vlcrs-core/src/tracer/mod.rs b/src/rust/vlcrs-core/src/tracer/mod.rs
index 1efa689995fa..3829b47d667d 100644
--- a/src/rust/vlcrs-core/src/tracer/mod.rs
+++ b/src/rust/vlcrs-core/src/tracer/mod.rs
@@ -25,7 +25,6 @@ use std::{
 use crate::{object::Object, plugin::ModuleProtocol};
 
 pub mod sys;
-pub use sys::{Trace, TraceField};
 
 pub struct Tick(pub i64);
 
@@ -94,11 +93,16 @@ pub type TracerCapabilityActivate =
         opaque: &mut MaybeUninit<*mut c_void>,
     ) -> Option<&'static sys::vlc_tracer_operations>;
 
-extern "C" fn tracer_trace(opaque: *const c_void, tick: sys::vlc_tick, entries: sys::Trace) {
+extern "C" fn tracer_trace(
+    opaque: *const c_void,
+    tick: sys::vlc_tick,
+    entries: NonNull<sys::vlc_tracer_trace>,
+) {
     {
         let tracer: &dyn TracerCapability =
             unsafe { &**(opaque as *const Box<dyn TracerCapability>) };
-        tracer.trace(Tick(tick), &entries);
+        let trace = Trace { entries };
+        tracer.trace(Tick(tick), &trace);
     }
 }
 
@@ -205,7 +209,7 @@ impl Tracer {
 
             // SAFETY: the pointer `tracer` is guaranteed to be non-null and
             //         nobody else has reference to it.
-            sys::vlc_tracer_TraceWithTs(tracer, tick.0, entries);
+            sys::vlc_tracer_TraceWithTs(tracer, tick.0, entries.entries);
         }
     }
 }
@@ -214,3 +218,117 @@ impl Tracer {
 macro_rules! trace {
     ($tracer: ident, $ts: expr, $($key:ident = $value:expr)*) => {};
 }
+
+#[derive(PartialEq, Copy, Clone)]
+#[repr(transparent)]
+pub struct Trace {
+    pub entries: NonNull<sys::vlc_tracer_trace>,
+}
+
+#[derive(PartialEq, Copy, Clone, Debug)]
+#[repr(transparent)]
+pub struct TraceField {
+    pub entry: NonNull<sys::vlc_tracer_entry>,
+}
+
+impl TraceField {
+    pub fn key(&self) -> &str {
+        unsafe {
+            let key = CStr::from_ptr(self.entry.read().key);
+            key.to_str().unwrap()
+        }
+    }
+
+    pub fn kind(&self) -> sys::vlc_tracer_value_type {
+        unsafe { self.entry.read().kind }
+    }
+
+    pub fn value(&self) -> sys::vlc_tracer_value {
+        unsafe { self.entry.read().value }
+    }
+}
+
+pub struct TraceIterator {
+    current_field: NonNull<sys::vlc_tracer_entry>,
+}
+
+impl IntoIterator for Trace {
+    type Item = TraceField;
+    type IntoIter = TraceIterator;
+    fn into_iter(self) -> Self::IntoIter {
+        TraceIterator {
+            current_field: unsafe { self.entries.read().entries },
+        }
+    }
+}
+
+impl Iterator for TraceIterator {
+    type Item = TraceField;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        unsafe {
+            if self.current_field.read().key.is_null() {
+                return None;
+            }
+            let output = Some(TraceField {
+                entry: self.current_field,
+            });
+            self.current_field = self.current_field.add(1);
+            output
+        }
+    }
+}
+
+#[cfg(test)]
+mod test {
+    #[test]
+    fn test_trace_interop() {
+        use super::*;
+        use sys::*;
+        let entries = [
+            vlc_tracer_entry {
+                kind: vlc_tracer_value_type::String,
+                value: vlc_tracer_value {
+                    string: c"value1".as_ptr(),
+                },
+                key: c"test1".as_ptr(),
+            },
+            vlc_tracer_entry {
+                kind: vlc_tracer_value_type::Integer,
+                value: vlc_tracer_value { integer: 0 },
+                key: std::ptr::null(),
+            },
+        ];
+
+        let trace_field = TraceField {
+            entry: NonNull::from(&entries[0]),
+        };
+        assert_eq!(trace_field.kind(), vlc_tracer_value_type::String);
+        assert_eq!(trace_field.key(), "test1");
+
+        let trace_field = TraceField {
+            entry: NonNull::from(&entries[1]),
+        };
+        assert_eq!(trace_field.kind(), vlc_tracer_value_type::Integer);
+
+        let trace = vlc_tracer_trace {
+            entries: NonNull::from(&entries[0]),
+        };
+
+        let trace = Trace {
+            entries: NonNull::from(&trace),
+        };
+
+        let mut iterator = trace.into_iter();
+
+        let first = iterator.next().expect("First field must be valid");
+        assert_eq!(first.kind(), vlc_tracer_value_type::String);
+        assert_eq!(first.key(), "test1");
+        unsafe {
+            assert_eq!(CStr::from_ptr(first.value().string), c"value1");
+        }
+
+        let second = iterator.next();
+        assert_eq!(second, None);
+    }
+}
diff --git a/src/rust/vlcrs-core/src/tracer/sys.rs b/src/rust/vlcrs-core/src/tracer/sys.rs
index 1ff964d272ce..afbb14039671 100644
--- a/src/rust/vlcrs-core/src/tracer/sys.rs
+++ b/src/rust/vlcrs-core/src/tracer/sys.rs
@@ -22,7 +22,7 @@
 #![allow(non_snake_case)]
 
 use std::{
-    ffi::{c_char, c_double, c_void, CStr},
+    ffi::{c_char, c_double, c_void},
     ptr::NonNull,
 };
 
@@ -69,128 +69,19 @@ pub struct vlc_tracer {
     _unused: [u8; 0],
 }
 
-#[derive(PartialEq, Copy, Clone)]
-#[repr(transparent)]
-pub struct Trace {
-    pub entries: NonNull<vlc_tracer_trace>,
-}
-
-#[derive(PartialEq, Copy, Clone, Debug)]
-#[repr(transparent)]
-pub struct TraceField {
-    pub entry: NonNull<vlc_tracer_entry>,
-}
-
-impl TraceField {
-    pub fn key(&self) -> &str {
-        unsafe {
-            let key = CStr::from_ptr(self.entry.read().key);
-            key.to_str().unwrap()
-        }
-    }
-
-    pub fn kind(&self) -> vlc_tracer_value_type {
-        unsafe { self.entry.read().kind }
-    }
-
-    pub fn value(&self) -> vlc_tracer_value {
-        unsafe { self.entry.read().value }
-    }
-}
-
-pub struct TraceIterator {
-    current_field: NonNull<vlc_tracer_entry>,
-}
-
-impl IntoIterator for Trace {
-    type Item = TraceField;
-    type IntoIter = TraceIterator;
-    fn into_iter(self) -> Self::IntoIter {
-        TraceIterator {
-            current_field: unsafe { self.entries.read().entries },
-        }
-    }
-}
-
-impl Iterator for TraceIterator {
-    type Item = TraceField;
-
-    fn next(&mut self) -> Option<Self::Item> {
-        unsafe {
-            if self.current_field.read().key.is_null() {
-                return None;
-            }
-            let output = Some(TraceField {
-                entry: self.current_field,
-            });
-            self.current_field = self.current_field.add(1);
-            output
-        }
-    }
-}
-
 #[repr(C)]
 #[derive(Debug, Copy, Clone)]
 pub struct vlc_tracer_operations {
-    pub trace: extern "C" fn(opaque: *const c_void, ts: vlc_tick, entries: Trace),
+    pub trace:
+        extern "C" fn(opaque: *const c_void, ts: vlc_tick, entries: NonNull<vlc_tracer_trace>),
     pub destroy: extern "C" fn(*mut c_void),
 }
 
 extern "C" {
     pub fn vlc_tracer_Create(parent: &Object, name: *const c_char) -> Option<NonNull<vlc_tracer>>;
-
-    pub fn vlc_tracer_TraceWithTs(tracer: NonNull<vlc_tracer>, tick: vlc_tick, entries: Trace);
-}
-
-#[cfg(test)]
-mod test {
-    #[test]
-    fn test_trace_interop() {
-        use super::*;
-        let entries = [
-            vlc_tracer_entry {
-                kind: vlc_tracer_value_type::String,
-                value: vlc_tracer_value {
-                    string: c"value1".as_ptr(),
-                },
-                key: c"test1".as_ptr(),
-            },
-            vlc_tracer_entry {
-                kind: vlc_tracer_value_type::Integer,
-                value: vlc_tracer_value { integer: 0 },
-                key: std::ptr::null(),
-            },
-        ];
-
-        let trace_field = TraceField {
-            entry: NonNull::from(&entries[0]),
-        };
-        assert_eq!(trace_field.kind(), vlc_tracer_value_type::String);
-        assert_eq!(trace_field.key(), "test1");
-
-        let trace_field = TraceField {
-            entry: NonNull::from(&entries[1]),
-        };
-        assert_eq!(trace_field.kind(), vlc_tracer_value_type::Integer);
-
-        let trace = vlc_tracer_trace {
-            entries: NonNull::from(&entries[0]),
-        };
-
-        let trace = Trace {
-            entries: NonNull::from(&trace),
-        };
-
-        let mut iterator = trace.into_iter();
-
-        let first = iterator.next().expect("First field must be valid");
-        assert_eq!(first.kind(), vlc_tracer_value_type::String);
-        assert_eq!(first.key(), "test1");
-        unsafe {
-            assert_eq!(CStr::from_ptr(first.value().string), c"value1");
-        }
-
-        let second = iterator.next();
-        assert_eq!(second, None);
-    }
+    pub fn vlc_tracer_TraceWithTs(
+        tracer: NonNull<vlc_tracer>,
+        tick: vlc_tick,
+        entries: NonNull<vlc_tracer_trace>,
+    );
 }
-- 
GitLab


From 629731341e1e24acd2f122916cdcb94fca189633 Mon Sep 17 00:00:00 2001
From: Alaric Senat <dev.asenat@posteo.net>
Date: Sat, 8 Mar 2025 01:13:09 +0100
Subject: [PATCH 3/9] vlcrs-core: tracer: define `Trace` as tuple struct

Since `Trace` is a wrapper around the VLC tracer object. `entries` as a
name was not actually giving access to trace entries but to the
internals of the object.
---
 src/rust/vlcrs-core/src/tracer/mod.rs | 14 +++++---------
 1 file changed, 5 insertions(+), 9 deletions(-)

diff --git a/src/rust/vlcrs-core/src/tracer/mod.rs b/src/rust/vlcrs-core/src/tracer/mod.rs
index 3829b47d667d..07ac25840108 100644
--- a/src/rust/vlcrs-core/src/tracer/mod.rs
+++ b/src/rust/vlcrs-core/src/tracer/mod.rs
@@ -101,7 +101,7 @@ extern "C" fn tracer_trace(
     {
         let tracer: &dyn TracerCapability =
             unsafe { &**(opaque as *const Box<dyn TracerCapability>) };
-        let trace = Trace { entries };
+        let trace = Trace(entries);
         tracer.trace(Tick(tick), &trace);
     }
 }
@@ -209,7 +209,7 @@ impl Tracer {
 
             // SAFETY: the pointer `tracer` is guaranteed to be non-null and
             //         nobody else has reference to it.
-            sys::vlc_tracer_TraceWithTs(tracer, tick.0, entries.entries);
+            sys::vlc_tracer_TraceWithTs(tracer, tick.0, entries.0);
         }
     }
 }
@@ -221,9 +221,7 @@ macro_rules! trace {
 
 #[derive(PartialEq, Copy, Clone)]
 #[repr(transparent)]
-pub struct Trace {
-    pub entries: NonNull<sys::vlc_tracer_trace>,
-}
+pub struct Trace(NonNull<sys::vlc_tracer_trace>);
 
 #[derive(PartialEq, Copy, Clone, Debug)]
 #[repr(transparent)]
@@ -257,7 +255,7 @@ impl IntoIterator for Trace {
     type IntoIter = TraceIterator;
     fn into_iter(self) -> Self::IntoIter {
         TraceIterator {
-            current_field: unsafe { self.entries.read().entries },
+            current_field: unsafe { self.0.read().entries },
         }
     }
 }
@@ -315,9 +313,7 @@ mod test {
             entries: NonNull::from(&entries[0]),
         };
 
-        let trace = Trace {
-            entries: NonNull::from(&trace),
-        };
+        let trace = Trace(NonNull::from(&trace));
 
         let mut iterator = trace.into_iter();
 
-- 
GitLab


From b4235480a5d97ffe8519bc4707100d69fef9d1d3 Mon Sep 17 00:00:00 2001
From: Alaric Senat <dev.asenat@posteo.net>
Date: Sat, 8 Mar 2025 01:13:09 +0100
Subject: [PATCH 4/9] vlcrs-core: tracer: unify trace parameter names

---
 modules/logger/telegraf-rs/src/lib.rs |  8 ++++----
 src/rust/vlcrs-core/src/tracer/mod.rs | 14 +++++++-------
 src/rust/vlcrs-core/src/tracer/sys.rs |  5 ++---
 3 files changed, 13 insertions(+), 14 deletions(-)

diff --git a/modules/logger/telegraf-rs/src/lib.rs b/modules/logger/telegraf-rs/src/lib.rs
index eceede51e318..32d404b492a6 100644
--- a/modules/logger/telegraf-rs/src/lib.rs
+++ b/modules/logger/telegraf-rs/src/lib.rs
@@ -61,15 +61,15 @@ impl TracerCapability for TelegrafTracer {
         Some(Self { endpoint })
     }
 
-    fn trace(&self, _tick: vlcrs_core::tracer::Tick, entries: &'_ vlcrs_core::tracer::Trace) {
-        if !entries
+    fn trace(&self, _tick: vlcrs_core::tracer::Tick, trace: &'_ vlcrs_core::tracer::Trace) {
+        if !trace
             .into_iter()
             .any(|e| e.kind() != vlcrs_core::tracer::sys::vlc_tracer_value_type::String)
         {
             /* We cannot support events for now. */
             return;
         }
-        let tags: Vec<(String, String)> = entries
+        let tags: Vec<(String, String)> = trace
             .into_iter()
             .filter(|e| e.kind() == vlcrs_core::tracer::sys::vlc_tracer_value_type::String)
             .map(|entry| unsafe {
@@ -81,7 +81,7 @@ impl TracerCapability for TelegrafTracer {
             })
             .collect();
 
-        let record: Vec<(String, Box<dyn IntoFieldData + 'static>)> = entries
+        let record: Vec<(String, Box<dyn IntoFieldData + 'static>)> = trace
             .into_iter()
             .filter(|e| e.kind() != vlcrs_core::tracer::sys::vlc_tracer_value_type::String)
             .map(|entry| {
diff --git a/src/rust/vlcrs-core/src/tracer/mod.rs b/src/rust/vlcrs-core/src/tracer/mod.rs
index 07ac25840108..98b7c6ae916d 100644
--- a/src/rust/vlcrs-core/src/tracer/mod.rs
+++ b/src/rust/vlcrs-core/src/tracer/mod.rs
@@ -53,7 +53,7 @@ pub struct Tick(pub i64);
 ///         Some(Self{ last_trace_tick: Cell::from(Tick(0)) })
 ///     }
 ///
-///     fn trace(&self, tick: Tick, entries: &Trace) {
+///     fn trace(&self, tick: Tick, trace: &Trace) {
 ///         let mut state = self.last_trace_tick.get_mut();
 ///         *state = tick;
 ///     }
@@ -83,7 +83,7 @@ pub trait TracerCapability: Sync {
     where
         Self: Sized;
 
-    fn trace(&self, tick: Tick, entries: &Trace);
+    fn trace(&self, tick: Tick, trace: &Trace);
 }
 
 #[allow(non_camel_case_types)]
@@ -96,12 +96,12 @@ pub type TracerCapabilityActivate =
 extern "C" fn tracer_trace(
     opaque: *const c_void,
     tick: sys::vlc_tick,
-    entries: NonNull<sys::vlc_tracer_trace>,
+    trace: NonNull<sys::vlc_tracer_trace>,
 ) {
     {
         let tracer: &dyn TracerCapability =
             unsafe { &**(opaque as *const Box<dyn TracerCapability>) };
-        let trace = Trace(entries);
+        let trace = Trace(trace);
         tracer.trace(Tick(tick), &trace);
     }
 }
@@ -149,7 +149,7 @@ extern "C" fn activate_tracer<T: TracerCapability>(
 ///     fn open(obj: &mut Object) -> Option<impl TracerCapability> {
 ///         Some(Self{})
 ///     }
-///     fn trace(&self, _tick: Tick, _entries: &Trace) {}
+///     fn trace(&self, _tick: Tick, _trace: &Trace) {}
 /// }
 ///
 /// module!{
@@ -202,14 +202,14 @@ impl Tracer {
     ///
     /// Register the new point at time [tick] with the metadata [entries].
     ///
-    pub fn trace(&self, tick: Tick, entries: Trace) {
+    pub fn trace(&self, tick: Tick, trace: Trace) {
         unsafe {
             // SAFETY: TODO
             let tracer = *self.tracer.get();
 
             // SAFETY: the pointer `tracer` is guaranteed to be non-null and
             //         nobody else has reference to it.
-            sys::vlc_tracer_TraceWithTs(tracer, tick.0, entries.0);
+            sys::vlc_tracer_TraceWithTs(tracer, tick.0, trace.0);
         }
     }
 }
diff --git a/src/rust/vlcrs-core/src/tracer/sys.rs b/src/rust/vlcrs-core/src/tracer/sys.rs
index afbb14039671..4501bac5f238 100644
--- a/src/rust/vlcrs-core/src/tracer/sys.rs
+++ b/src/rust/vlcrs-core/src/tracer/sys.rs
@@ -72,8 +72,7 @@ pub struct vlc_tracer {
 #[repr(C)]
 #[derive(Debug, Copy, Clone)]
 pub struct vlc_tracer_operations {
-    pub trace:
-        extern "C" fn(opaque: *const c_void, ts: vlc_tick, entries: NonNull<vlc_tracer_trace>),
+    pub trace: extern "C" fn(opaque: *const c_void, ts: vlc_tick, trace: NonNull<vlc_tracer_trace>),
     pub destroy: extern "C" fn(*mut c_void),
 }
 
@@ -82,6 +81,6 @@ extern "C" {
     pub fn vlc_tracer_TraceWithTs(
         tracer: NonNull<vlc_tracer>,
         tick: vlc_tick,
-        entries: NonNull<vlc_tracer_trace>,
+        trace: NonNull<vlc_tracer_trace>,
     );
 }
-- 
GitLab


From 1986a3d2dcb31bdeda7513b2dc26f252b5f77104 Mon Sep 17 00:00:00 2001
From: Alaric Senat <alaric@videolabs.io>
Date: Thu, 13 Mar 2025 12:02:49 +0100
Subject: [PATCH 5/9] vlcrs-core: tracer: add a ref-only iteration function

Introduce a small accessor to the entries that communicates the intent
better than `into_iter()`.
---
 modules/logger/telegraf-rs/src/lib.rs |  6 ++--
 src/rust/vlcrs-core/src/tracer/mod.rs | 44 ++++++++++++++++++++-------
 2 files changed, 36 insertions(+), 14 deletions(-)

diff --git a/modules/logger/telegraf-rs/src/lib.rs b/modules/logger/telegraf-rs/src/lib.rs
index 32d404b492a6..e0b6a4c88d77 100644
--- a/modules/logger/telegraf-rs/src/lib.rs
+++ b/modules/logger/telegraf-rs/src/lib.rs
@@ -63,14 +63,14 @@ impl TracerCapability for TelegrafTracer {
 
     fn trace(&self, _tick: vlcrs_core::tracer::Tick, trace: &'_ vlcrs_core::tracer::Trace) {
         if !trace
-            .into_iter()
+            .entries()
             .any(|e| e.kind() != vlcrs_core::tracer::sys::vlc_tracer_value_type::String)
         {
             /* We cannot support events for now. */
             return;
         }
         let tags: Vec<(String, String)> = trace
-            .into_iter()
+            .entries()
             .filter(|e| e.kind() == vlcrs_core::tracer::sys::vlc_tracer_value_type::String)
             .map(|entry| unsafe {
                 let value = CStr::from_ptr(entry.value().string);
@@ -82,7 +82,7 @@ impl TracerCapability for TelegrafTracer {
             .collect();
 
         let record: Vec<(String, Box<dyn IntoFieldData + 'static>)> = trace
-            .into_iter()
+            .entries()
             .filter(|e| e.kind() != vlcrs_core::tracer::sys::vlc_tracer_value_type::String)
             .map(|entry| {
                 (
diff --git a/src/rust/vlcrs-core/src/tracer/mod.rs b/src/rust/vlcrs-core/src/tracer/mod.rs
index 98b7c6ae916d..bd39f5ea6f2b 100644
--- a/src/rust/vlcrs-core/src/tracer/mod.rs
+++ b/src/rust/vlcrs-core/src/tracer/mod.rs
@@ -219,10 +219,42 @@ macro_rules! trace {
     ($tracer: ident, $ts: expr, $($key:ident = $value:expr)*) => {};
 }
 
+/// A trace record obtained from VLC.
+///
+/// Trace records are a set of `entries`, a list of key-values describing the traced event.
+///
+/// # Examples
+///
+/// ```
+/// impl TracerCapability for T {
+///     fn trace(&self, trace: &Trace) {
+///         for entry in trace.entries() {
+///             println!("{}", entry.key);
+///         }
+///     }
+/// }
+/// ```
 #[derive(PartialEq, Copy, Clone)]
 #[repr(transparent)]
 pub struct Trace(NonNull<sys::vlc_tracer_trace>);
 
+impl Trace {
+    /// Get an iterator over the trace entries.
+    pub fn entries(&self) -> TraceIterator {
+        self.into_iter()
+    }
+}
+
+impl IntoIterator for Trace {
+    type Item = TraceField;
+    type IntoIter = TraceIterator;
+    fn into_iter(self) -> Self::IntoIter {
+        TraceIterator {
+            current_field: unsafe { self.0.read().entries },
+        }
+    }
+}
+
 #[derive(PartialEq, Copy, Clone, Debug)]
 #[repr(transparent)]
 pub struct TraceField {
@@ -250,16 +282,6 @@ pub struct TraceIterator {
     current_field: NonNull<sys::vlc_tracer_entry>,
 }
 
-impl IntoIterator for Trace {
-    type Item = TraceField;
-    type IntoIter = TraceIterator;
-    fn into_iter(self) -> Self::IntoIter {
-        TraceIterator {
-            current_field: unsafe { self.0.read().entries },
-        }
-    }
-}
-
 impl Iterator for TraceIterator {
     type Item = TraceField;
 
@@ -315,7 +337,7 @@ mod test {
 
         let trace = Trace(NonNull::from(&trace));
 
-        let mut iterator = trace.into_iter();
+        let mut iterator = trace.entries();
 
         let first = iterator.next().expect("First field must be valid");
         assert_eq!(first.kind(), vlc_tracer_value_type::String);
-- 
GitLab


From 667447d5bdc5afc814b1577d83b080e925aab228 Mon Sep 17 00:00:00 2001
From: Alaric Senat <dev.asenat@posteo.net>
Date: Sat, 8 Mar 2025 18:41:49 +0100
Subject: [PATCH 6/9] logger: telegraf: reduce iterations over trace entries

Use `fold` to avoid two iterations over the entries while keeping the
functional code style.
---
 modules/logger/telegraf-rs/src/lib.rs | 49 ++++++++++++---------------
 1 file changed, 22 insertions(+), 27 deletions(-)

diff --git a/modules/logger/telegraf-rs/src/lib.rs b/modules/logger/telegraf-rs/src/lib.rs
index e0b6a4c88d77..f9b4ea48328f 100644
--- a/modules/logger/telegraf-rs/src/lib.rs
+++ b/modules/logger/telegraf-rs/src/lib.rs
@@ -62,40 +62,35 @@ impl TracerCapability for TelegrafTracer {
     }
 
     fn trace(&self, _tick: vlcrs_core::tracer::Tick, trace: &'_ vlcrs_core::tracer::Trace) {
-        if !trace
-            .entries()
-            .any(|e| e.kind() != vlcrs_core::tracer::sys::vlc_tracer_value_type::String)
-        {
+        let (tags, records) = trace.entries().fold(
+            (Vec::new(), Vec::new()),
+            |(mut tags, mut records), entry| {
+                if entry.kind() == vlcrs_core::tracer::sys::vlc_tracer_value_type::String {
+                    let value = unsafe { CStr::from_ptr(entry.value().string) };
+                    tags.push((
+                        String::from(entry.key()),
+                        String::from(value.to_str().unwrap()),
+                    ));
+                } else {
+                    records.push((
+                        String::from(entry.key()),
+                        Box::new(TraceField(entry)) as Box<dyn IntoFieldData>,
+                    ));
+                }
+
+                (tags, records)
+            },
+        );
+
+        if records.is_empty() {
             /* We cannot support events for now. */
             return;
         }
-        let tags: Vec<(String, String)> = trace
-            .entries()
-            .filter(|e| e.kind() == vlcrs_core::tracer::sys::vlc_tracer_value_type::String)
-            .map(|entry| unsafe {
-                let value = CStr::from_ptr(entry.value().string);
-                (
-                    String::from(entry.key()),
-                    String::from(value.to_str().unwrap()),
-                )
-            })
-            .collect();
-
-        let record: Vec<(String, Box<dyn IntoFieldData + 'static>)> = trace
-            .entries()
-            .filter(|e| e.kind() != vlcrs_core::tracer::sys::vlc_tracer_value_type::String)
-            .map(|entry| {
-                (
-                    String::from(entry.key()),
-                    Box::new(TraceField(entry)) as Box<dyn IntoFieldData>,
-                )
-            })
-            .collect();
 
         let p = Point::new(
             String::from("measurement"),
             tags,
-            record,
+            records,
             None, //Some(tick.0 as u64),
         );
 
-- 
GitLab


From e0aabc1d8bce7c155b27188fe5ae227f712fd94b Mon Sep 17 00:00:00 2001
From: Alaric Senat <dev.asenat@posteo.net>
Date: Mon, 10 Mar 2025 23:45:48 +0100
Subject: [PATCH 7/9] vlcrs-core: add helper trait for unchecked conversions

See trait documentation for extended context.
---
 src/rust/vlcrs-core/src/convert.rs | 67 ++++++++++++++++++++++++++++++
 src/rust/vlcrs-core/src/lib.rs     |  2 +
 2 files changed, 69 insertions(+)
 create mode 100644 src/rust/vlcrs-core/src/convert.rs

diff --git a/src/rust/vlcrs-core/src/convert.rs b/src/rust/vlcrs-core/src/convert.rs
new file mode 100644
index 000000000000..864f33dae9e4
--- /dev/null
+++ b/src/rust/vlcrs-core/src/convert.rs
@@ -0,0 +1,67 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+// Copyright (C) 2025 Alaric Senat <alaric@videolabs.io>
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation; either version 2.1 of the License, or
+// (at your option) any later version.
+//
+// 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 Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program; if not, write to the Free Software Foundation,
+// Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+
+/// Unchecked conversions on internal VLC values.
+///
+/// VLC core APIs often have implicit constraints or internal check on exposed data that allow
+/// conversions without the usual safety checks.
+///
+/// Implementers of this trait must ensure that the conversion is safe within the context of
+/// VLC, and new implementations must be thoroughly reviewed.
+///
+/// # Safety
+///
+/// Calling `assume_valid` is **always unsafe**, as it bypasses Rust's usual validity checks.
+/// The caller must ensure that the input is indeed valid and checked in some way beforehand by VLC.
+///
+/// # Examples
+///
+/// [`AssumeValid`]`<&`[`str`](std::str)`>` is implemented for [`CStr`](std::ffi::CStr) as the internals of
+/// VLC manipulate already checked UTF8 strings. This lift the bindings from the obligation to
+/// perform UTF8 validity verifications:
+///
+/// ```
+/// extern "C" fn tracer_trace(trace: *const std::ffi::c_char) {
+///     // `trace` is from VLC and is assumed to be valid UTF8.
+///     let trace: &str = unsafe { std::ffi::CStr::from_ptr(trace).assume_valid() };
+/// }
+/// ```
+pub trait AssumeValid<T> {
+    /// Converts `self` into `T`, without safety checks.
+    unsafe fn assume_valid(self) -> T;
+}
+
+impl<'a> AssumeValid<&'a str> for &'a std::ffi::CStr {
+    /// Convert a [`CStr`](std::ffi::CStr) into an `&`[`str`](std::str).
+    ///
+    /// # Safety
+    ///
+    /// Should only be used with VLC strings which are already UTF8 validated. Otherwise, the
+    /// string will be left unchecked and will lead to undefined behaviors.
+    ///
+    /// # Panics
+    ///
+    /// In **debug builds only**, this function will perform the UTF8 checks and panic on failure to
+    /// highlight and help resolve VLC core missing checks.
+    unsafe fn assume_valid(self) -> &'a str {
+        if cfg!(debug_assertions) {
+            str::from_utf8(self.to_bytes()).expect("Unexpected invalid UTF8 coming from VLC")
+        } else {
+            unsafe { str::from_utf8_unchecked(self.to_bytes()) }
+        }
+    }
+}
diff --git a/src/rust/vlcrs-core/src/lib.rs b/src/rust/vlcrs-core/src/lib.rs
index 90950409bca3..5f9a99a2c4b0 100644
--- a/src/rust/vlcrs-core/src/lib.rs
+++ b/src/rust/vlcrs-core/src/lib.rs
@@ -34,3 +34,5 @@ pub mod plugin;
 pub mod object;
 
 pub mod tracer;
+
+pub(crate) mod convert;
-- 
GitLab


From 6bfea2228a99288fdf5a5ec4eea5cf31928d3a89 Mon Sep 17 00:00:00 2001
From: Alaric Senat <dev.asenat@posteo.net>
Date: Sat, 8 Mar 2025 19:04:00 +0100
Subject: [PATCH 8/9] vlcrs-core: tracer: add safe bindings for trace iteration

Introduce a lightweight safe `TraceValue` wrapper of the VLC tracer
tagged union.
---
 modules/logger/telegraf-rs/src/lib.rs |  58 ++++++---------
 src/rust/vlcrs-core/src/tracer/mod.rs | 103 +++++++++++++-------------
 2 files changed, 75 insertions(+), 86 deletions(-)

diff --git a/modules/logger/telegraf-rs/src/lib.rs b/modules/logger/telegraf-rs/src/lib.rs
index f9b4ea48328f..fce30d9964f6 100644
--- a/modules/logger/telegraf-rs/src/lib.rs
+++ b/modules/logger/telegraf-rs/src/lib.rs
@@ -15,30 +15,20 @@
 // along with this program; if not, write to the Free Software Foundation,
 // Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
 
-use std::{cell::UnsafeCell, ffi::CStr, sync::Mutex};
-use telegraf::{Client, IntoFieldData, Point};
-use vlcrs_core::tracer::{sys::vlc_tracer_value_type, TracerCapability, TracerModuleLoader};
+use std::{cell::UnsafeCell, sync::Mutex};
+use telegraf::{Client, IntoFieldData};
+use vlcrs_core::tracer::{TraceValue, TracerCapability, TracerModuleLoader};
 use vlcrs_macros::module;
 
-struct TraceField(vlcrs_core::tracer::TraceField);
+struct TraceValueWrapper<'a>(TraceValue<'a>);
 
-impl IntoFieldData for TraceField {
+impl<'a> IntoFieldData for TraceValueWrapper<'a> {
     fn field_data(&self) -> telegraf::FieldData {
-        match self.0.kind() {
-            vlc_tracer_value_type::String => unsafe {
-                let value = CStr::from_ptr(self.0.value().string);
-                telegraf::FieldData::Str(value.to_str().unwrap().to_string())
-            },
-            vlc_tracer_value_type::Integer => unsafe {
-                telegraf::FieldData::Number(self.0.value().integer)
-            },
-            vlc_tracer_value_type::Unsigned => unsafe {
-                telegraf::FieldData::UNumber(self.0.value().unsigned)
-            },
-            vlc_tracer_value_type::Double => unsafe {
-                telegraf::FieldData::Float(self.0.value().double)
-            },
-            _ => unreachable!(),
+        match self.0 {
+            TraceValue::String(value) => telegraf::FieldData::Str(String::from(value)),
+            TraceValue::Integer(value) => telegraf::FieldData::Number(value),
+            TraceValue::Unsigned(value) => telegraf::FieldData::UNumber(value),
+            TraceValue::Double(value) => telegraf::FieldData::Float(value),
         }
     }
 }
@@ -61,21 +51,17 @@ impl TracerCapability for TelegrafTracer {
         Some(Self { endpoint })
     }
 
-    fn trace(&self, _tick: vlcrs_core::tracer::Tick, trace: &'_ vlcrs_core::tracer::Trace) {
+    fn trace(&self, _tick: vlcrs_core::tracer::Tick, trace: &vlcrs_core::tracer::Trace) {
         let (tags, records) = trace.entries().fold(
             (Vec::new(), Vec::new()),
             |(mut tags, mut records), entry| {
-                if entry.kind() == vlcrs_core::tracer::sys::vlc_tracer_value_type::String {
-                    let value = unsafe { CStr::from_ptr(entry.value().string) };
-                    tags.push((
-                        String::from(entry.key()),
-                        String::from(value.to_str().unwrap()),
-                    ));
+                let name = String::from(entry.key);
+                if let TraceValue::String(value) = entry.value {
+                    let value = String::from(value);
+                    tags.push(telegraf::protocol::Tag { name, value });
                 } else {
-                    records.push((
-                        String::from(entry.key()),
-                        Box::new(TraceField(entry)) as Box<dyn IntoFieldData>,
-                    ));
+                    let value = TraceValueWrapper(entry.value).field_data();
+                    records.push(telegraf::protocol::Field { name, value });
                 }
 
                 (tags, records)
@@ -87,12 +73,12 @@ impl TracerCapability for TelegrafTracer {
             return;
         }
 
-        let p = Point::new(
-            String::from("measurement"),
+        let p = telegraf::Point {
+            measurement: String::from("measurement"),
             tags,
-            records,
-            None, //Some(tick.0 as u64),
-        );
+            fields: records,
+            timestamp: None, //Some(tick.0 as u64),
+        };
 
         let mut endpoint = self.endpoint.lock().unwrap();
         if let Err(err) = endpoint.get_mut().write_point(&p) {
diff --git a/src/rust/vlcrs-core/src/tracer/mod.rs b/src/rust/vlcrs-core/src/tracer/mod.rs
index bd39f5ea6f2b..5320c6948b69 100644
--- a/src/rust/vlcrs-core/src/tracer/mod.rs
+++ b/src/rust/vlcrs-core/src/tracer/mod.rs
@@ -22,7 +22,7 @@ use std::{
     ptr::NonNull,
 };
 
-use crate::{object::Object, plugin::ModuleProtocol};
+use crate::{convert::AssumeValid, object::Object, plugin::ModuleProtocol};
 
 pub mod sys;
 
@@ -245,60 +245,74 @@ impl Trace {
     }
 }
 
-impl IntoIterator for Trace {
-    type Item = TraceField;
-    type IntoIter = TraceIterator;
+impl<'a> IntoIterator for &'a Trace {
+    type Item = TraceEntry<'a>;
+    type IntoIter = TraceIterator<'a>;
     fn into_iter(self) -> Self::IntoIter {
         TraceIterator {
             current_field: unsafe { self.0.read().entries },
+            _plt: std::marker::PhantomData,
         }
     }
 }
 
-#[derive(PartialEq, Copy, Clone, Debug)]
-#[repr(transparent)]
-pub struct TraceField {
-    pub entry: NonNull<sys::vlc_tracer_entry>,
-}
-
-impl TraceField {
-    pub fn key(&self) -> &str {
-        unsafe {
-            let key = CStr::from_ptr(self.entry.read().key);
-            key.to_str().unwrap()
-        }
-    }
-
-    pub fn kind(&self) -> sys::vlc_tracer_value_type {
-        unsafe { self.entry.read().kind }
-    }
-
-    pub fn value(&self) -> sys::vlc_tracer_value {
-        unsafe { self.entry.read().value }
-    }
-}
-
-pub struct TraceIterator {
+/// Iterate over trace record entries.
+pub struct TraceIterator<'a> {
     current_field: NonNull<sys::vlc_tracer_entry>,
+    _plt: std::marker::PhantomData<&'a sys::vlc_tracer_entry>,
 }
 
-impl Iterator for TraceIterator {
-    type Item = TraceField;
+impl<'a> Iterator for TraceIterator<'a> {
+    type Item = TraceEntry<'a>;
 
     fn next(&mut self) -> Option<Self::Item> {
         unsafe {
             if self.current_field.read().key.is_null() {
                 return None;
             }
-            let output = Some(TraceField {
-                entry: self.current_field,
-            });
+            let output = Some(TraceEntry::from(self.current_field.read()));
             self.current_field = self.current_field.add(1);
             output
         }
     }
 }
 
+/// A key-value pair recorded in a trace event.
+#[derive(Debug, Clone, Copy, PartialEq)]
+pub struct TraceEntry<'a> {
+    pub key: &'a str,
+    pub value: TraceValue<'a>,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq)]
+pub enum TraceValue<'a> {
+    Integer(i64),
+    Double(f64),
+    String(&'a str),
+    Unsigned(u64),
+}
+
+impl<'a> From<sys::vlc_tracer_entry> for TraceEntry<'a> {
+    fn from(entry: sys::vlc_tracer_entry) -> Self {
+        // SAFETY: Key is guaranteed to be non-null by the iterator.
+        let key = unsafe { CStr::from_ptr(entry.key).assume_valid() };
+
+        // SAFETY: Union accesses are only made with the associated entry tag.
+        let value = unsafe {
+            match entry.kind {
+                sys::vlc_tracer_value_type::Integer => TraceValue::Integer(entry.value.integer),
+                sys::vlc_tracer_value_type::Double => TraceValue::Double(entry.value.double),
+                sys::vlc_tracer_value_type::String => {
+                    TraceValue::String(CStr::from_ptr(entry.value.string).assume_valid())
+                }
+                sys::vlc_tracer_value_type::Unsigned => TraceValue::Unsigned(entry.value.unsigned),
+            }
+        };
+
+        Self { key, value }
+    }
+}
+
 #[cfg(test)]
 mod test {
     #[test]
@@ -320,16 +334,9 @@ mod test {
             },
         ];
 
-        let trace_field = TraceField {
-            entry: NonNull::from(&entries[0]),
-        };
-        assert_eq!(trace_field.kind(), vlc_tracer_value_type::String);
-        assert_eq!(trace_field.key(), "test1");
-
-        let trace_field = TraceField {
-            entry: NonNull::from(&entries[1]),
-        };
-        assert_eq!(trace_field.kind(), vlc_tracer_value_type::Integer);
+        let trace_field = TraceEntry::from(entries[0]);
+        assert_eq!(trace_field.key, "test1");
+        assert_eq!(trace_field.value, TraceValue::String("value1"));
 
         let trace = vlc_tracer_trace {
             entries: NonNull::from(&entries[0]),
@@ -340,13 +347,9 @@ mod test {
         let mut iterator = trace.entries();
 
         let first = iterator.next().expect("First field must be valid");
-        assert_eq!(first.kind(), vlc_tracer_value_type::String);
-        assert_eq!(first.key(), "test1");
-        unsafe {
-            assert_eq!(CStr::from_ptr(first.value().string), c"value1");
-        }
+        assert_eq!(first.value, TraceValue::String("value1"));
+        assert_eq!(first.key, "test1");
 
-        let second = iterator.next();
-        assert_eq!(second, None);
+        assert_eq!(iterator.next(), None);
     }
 }
-- 
GitLab


From 562bc831b2668901fb1df202990d250600441ef2 Mon Sep 17 00:00:00 2001
From: Alaric Senat <dev.asenat@posteo.net>
Date: Sun, 9 Mar 2025 01:30:28 +0100
Subject: [PATCH 9/9] vlcrs-core: tracer: privatize unsafe sys bindings

The core unsafe bindings aren't needed anymore outside the scope of the
bindings.
---
 src/rust/vlcrs-core/src/tracer/mod.rs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/rust/vlcrs-core/src/tracer/mod.rs b/src/rust/vlcrs-core/src/tracer/mod.rs
index 5320c6948b69..d0656ae07e4e 100644
--- a/src/rust/vlcrs-core/src/tracer/mod.rs
+++ b/src/rust/vlcrs-core/src/tracer/mod.rs
@@ -24,7 +24,7 @@ use std::{
 
 use crate::{convert::AssumeValid, object::Object, plugin::ModuleProtocol};
 
-pub mod sys;
+mod sys;
 
 pub struct Tick(pub i64);
 
-- 
GitLab