Skip to main content

tell_encoding/
event.rs

1use crate::helpers::*;
2use crate::{EventParams, UUID_LENGTH};
3
4/// Encode a single Event FlatBuffer.
5///
6/// Event table fields:
7/// - field 0: event_type `u8`
8/// - field 1: timestamp `u64`
9/// - field 2: device_id `[ubyte]`
10/// - field 3: session_id `[ubyte]`
11/// - field 4: event_name `string`
12/// - field 5: payload `[ubyte]`
13pub fn encode_event(params: &EventParams<'_>) -> Vec<u8> {
14    let has_device_id = params.device_id.is_some();
15    let has_session_id = params.session_id.is_some();
16    let has_event_name = params.event_name.is_some();
17    let has_payload = params.payload.is_some();
18
19    // VTable: size(u16) + table_size(u16) + 6 field slots = 16 bytes
20    let vtable_size: u16 = 4 + 6 * 2;
21
22    // Fixed table layout (after soffset):
23    // +4: device_id offset (u32)
24    // +8: session_id offset (u32)
25    // +12: event_name offset (u32)
26    // +16: payload offset (u32)
27    // +20: timestamp (u64)
28    // +28: event_type (u8)
29    // +29-31: padding (3 bytes)
30    let table_size: u16 = 4 + 28;
31
32    let device_id_size = if has_device_id { 4 + UUID_LENGTH } else { 0 };
33    let session_id_size = if has_session_id { 4 + UUID_LENGTH } else { 0 };
34    let event_name_size = params.event_name.map(|s| 4 + s.len() + 1).unwrap_or(0);
35    let payload_size = params.payload.map(|p| 4 + p.len()).unwrap_or(0);
36
37    let estimated = 4 + vtable_size as usize + table_size as usize
38        + device_id_size + session_id_size + event_name_size + payload_size + 16;
39    let mut buf = Vec::with_capacity(estimated);
40
41    // Root offset placeholder
42    buf.extend_from_slice(&[0u8; 4]);
43
44    // VTable
45    let vtable_start = buf.len();
46    write_u16(&mut buf, vtable_size);
47    write_u16(&mut buf, table_size);
48
49    // Field offsets
50    write_u16(&mut buf, 28);                                                // field 0: event_type at +28
51    write_u16(&mut buf, 20);                                                // field 1: timestamp at +20
52    write_u16(&mut buf, if has_device_id { 4 } else { 0 });                // field 2: device_id
53    write_u16(&mut buf, if has_session_id { 8 } else { 0 });               // field 3: session_id
54    write_u16(&mut buf, if has_event_name { 12 } else { 0 });              // field 4: event_name
55    write_u16(&mut buf, if has_payload { 16 } else { 0 });                 // field 5: payload
56
57    // Table
58    let table_start = buf.len();
59    let soffset = (table_start - vtable_start) as i32;
60    write_i32(&mut buf, soffset);
61
62    // Offset placeholders
63    let device_id_off_pos = buf.len();
64    write_u32(&mut buf, 0);
65
66    let session_id_off_pos = buf.len();
67    write_u32(&mut buf, 0);
68
69    let event_name_off_pos = buf.len();
70    write_u32(&mut buf, 0);
71
72    let payload_off_pos = buf.len();
73    write_u32(&mut buf, 0);
74
75    // timestamp (u64)
76    write_u64(&mut buf, params.timestamp);
77
78    // event_type (u8)
79    buf.push(params.event_type.as_u8());
80
81    // padding (3 bytes)
82    buf.extend_from_slice(&[0u8; 3]);
83
84    // Vectors and strings
85    align4(&mut buf);
86
87    // device_id
88    let device_id_start = params.device_id.map(|id| write_byte_vector(&mut buf, id));
89    align4(&mut buf);
90
91    // session_id
92    let session_id_start = params.session_id.map(|id| write_byte_vector(&mut buf, id));
93    align4(&mut buf);
94
95    // event_name
96    let event_name_start = params.event_name.map(|name| write_string(&mut buf, name));
97    align4(&mut buf);
98
99    // payload
100    let payload_start = params.payload.map(|data| write_byte_vector(&mut buf, data));
101
102    // Fill in offsets
103    buf[0..4].copy_from_slice(&(table_start as u32).to_le_bytes());
104
105    if let Some(start) = device_id_start {
106        patch_offset(&mut buf, device_id_off_pos, start);
107    }
108    if let Some(start) = session_id_start {
109        patch_offset(&mut buf, session_id_off_pos, start);
110    }
111    if let Some(start) = event_name_start {
112        patch_offset(&mut buf, event_name_off_pos, start);
113    }
114    if let Some(start) = payload_start {
115        patch_offset(&mut buf, payload_off_pos, start);
116    }
117
118    buf
119}
120
121/// Encode an EventData FlatBuffer containing a vector of pre-encoded events.
122///
123/// EventData table:
124/// - field 0: events `[Event]` (vector of tables)
125///
126/// Each entry in `encoded_events` is a standalone FlatBuffer with its own root offset.
127/// The vector offsets point to the actual table data within each event.
128pub fn encode_event_data(encoded_events: &[Vec<u8>]) -> Vec<u8> {
129    // VTable: size(u16) + table_size(u16) + 1 field = 6 bytes
130    let vtable_size: u16 = 4 + 2;
131    let table_size: u16 = 8; // soffset(4) + events_offset(4)
132
133    let events_total: usize = encoded_events.iter().map(|e| e.len() + 4).sum();
134    let estimated = 4 + vtable_size as usize + table_size as usize + 4 + events_total + 64;
135    let mut buf = Vec::with_capacity(estimated);
136
137    // Root offset placeholder
138    buf.extend_from_slice(&[0u8; 4]);
139
140    // VTable
141    let vtable_start = buf.len();
142    write_u16(&mut buf, vtable_size);
143    write_u16(&mut buf, table_size);
144    write_u16(&mut buf, 4); // field 0: events at table+4
145
146    // Align vtable to 4 bytes (6 bytes -> pad 2)
147    buf.extend_from_slice(&[0u8; 2]);
148
149    // Table
150    let table_start = buf.len();
151    let soffset = (table_start - vtable_start) as i32;
152    write_i32(&mut buf, soffset);
153
154    let events_off_pos = buf.len();
155    write_u32(&mut buf, 0);
156
157    align4(&mut buf);
158
159    // Events vector
160    let events_vec_start = buf.len();
161    let count = encoded_events.len();
162
163    // Vector length
164    write_u32(&mut buf, count as u32);
165
166    // Reserve offset slots
167    let offsets_start = buf.len();
168    for _ in 0..count {
169        write_u32(&mut buf, 0);
170    }
171
172    align4(&mut buf);
173
174    // Write event data, track table positions
175    let mut table_positions = Vec::with_capacity(count);
176    for event_bytes in encoded_events {
177        align4(&mut buf);
178
179        let event_start = buf.len();
180
181        // Read root offset from the event (first 4 bytes LE u32)
182        let root_offset = if event_bytes.len() >= 4 {
183            u32::from_le_bytes([event_bytes[0], event_bytes[1], event_bytes[2], event_bytes[3]])
184                as usize
185        } else {
186            0
187        };
188
189        table_positions.push(event_start + root_offset);
190        buf.extend_from_slice(event_bytes);
191    }
192
193    // Patch event offsets
194    for (i, &table_pos) in table_positions.iter().enumerate() {
195        let offset_pos = offsets_start + i * 4;
196        patch_offset(&mut buf, offset_pos, table_pos);
197    }
198
199    // Patch events vector offset
200    patch_offset(&mut buf, events_off_pos, events_vec_start);
201
202    // Patch root offset
203    buf[0..4].copy_from_slice(&(table_start as u32).to_le_bytes());
204
205    buf
206}
207
208/// Encode multiple events directly into a caller-owned buffer as an EventData FlatBuffer.
209///
210/// Zero-copy: writes the header first with reserved offset slots, then encodes
211/// events directly in their final position. No intermediate allocations or copies.
212/// The caller can reuse `buf` across flushes via `buf.clear()`.
213///
214/// Returns the range `start..buf.len()` of the EventData bytes within `buf`.
215pub fn encode_event_data_into(buf: &mut Vec<u8>, events: &[EventParams<'_>]) -> std::ops::Range<usize> {
216    let data_start = buf.len();
217    let count = events.len();
218
219    // Write EventData header (all sizes deterministic):
220    // [4] root offset placeholder
221    // [8] vtable (6 bytes + 2 pad)
222    // [8] table (soffset + events_offset)
223    // [4] vector length
224    // [4*N] offset slot placeholders
225
226    let root_pos = buf.len();
227    buf.extend_from_slice(&[0u8; 4]);
228
229    let vtable_start = buf.len();
230    write_u16(buf, 6); // vtable_size
231    write_u16(buf, 8); // table_size
232    write_u16(buf, 4); // field 0: events at table+4
233    buf.extend_from_slice(&[0u8; 2]); // align vtable
234
235    let table_start = buf.len();
236    write_i32(buf, (table_start - vtable_start) as i32);
237
238    let events_off_pos = buf.len();
239    write_u32(buf, 0);
240
241    align4(buf);
242
243    let events_vec_start = buf.len();
244    write_u32(buf, count as u32);
245
246    let offsets_start = buf.len();
247    for _ in 0..count {
248        write_u32(buf, 0);
249    }
250
251    align4(buf);
252
253    // Encode events directly after header — each written once, in final position
254    let mut table_positions = Vec::with_capacity(count);
255    for params in events {
256        align4(buf);
257        let event_start = buf.len();
258        encode_event_into(buf, params);
259        let root_offset = u32::from_le_bytes([
260            buf[event_start],
261            buf[event_start + 1],
262            buf[event_start + 2],
263            buf[event_start + 3],
264        ]) as usize;
265        table_positions.push(event_start + root_offset);
266    }
267
268    // Patch vector offset slots → each event's table position
269    for (i, &table_pos) in table_positions.iter().enumerate() {
270        patch_offset(buf, offsets_start + i * 4, table_pos);
271    }
272
273    patch_offset(buf, events_off_pos, events_vec_start);
274    buf[root_pos..root_pos + 4].copy_from_slice(&((table_start - data_start) as u32).to_le_bytes());
275
276    data_start..buf.len()
277}
278
279/// Encode a single event directly into an existing buffer.
280fn encode_event_into(buf: &mut Vec<u8>, params: &EventParams<'_>) {
281    let has_device_id = params.device_id.is_some();
282    let has_session_id = params.session_id.is_some();
283    let has_event_name = params.event_name.is_some();
284    let has_payload = params.payload.is_some();
285
286    let vtable_size: u16 = 4 + 6 * 2;
287    let table_size: u16 = 4 + 28;
288
289    // Root offset placeholder
290    let root_pos = buf.len();
291    buf.extend_from_slice(&[0u8; 4]);
292
293    // VTable
294    let vtable_start = buf.len();
295    write_u16(buf, vtable_size);
296    write_u16(buf, table_size);
297
298    write_u16(buf, 28);
299    write_u16(buf, 20);
300    write_u16(buf, if has_device_id { 4 } else { 0 });
301    write_u16(buf, if has_session_id { 8 } else { 0 });
302    write_u16(buf, if has_event_name { 12 } else { 0 });
303    write_u16(buf, if has_payload { 16 } else { 0 });
304
305    // Table
306    let table_start = buf.len();
307    let soffset = (table_start - vtable_start) as i32;
308    write_i32(buf, soffset);
309
310    let device_id_off_pos = buf.len();
311    write_u32(buf, 0);
312    let session_id_off_pos = buf.len();
313    write_u32(buf, 0);
314    let event_name_off_pos = buf.len();
315    write_u32(buf, 0);
316    let payload_off_pos = buf.len();
317    write_u32(buf, 0);
318
319    write_u64(buf, params.timestamp);
320    buf.push(params.event_type.as_u8());
321    buf.extend_from_slice(&[0u8; 3]);
322
323    align4(buf);
324
325    let device_id_start = params.device_id.map(|id| write_byte_vector(buf, id));
326    align4(buf);
327    let session_id_start = params.session_id.map(|id| write_byte_vector(buf, id));
328    align4(buf);
329    let event_name_start = params.event_name.map(|name| write_string(buf, name));
330    align4(buf);
331    let payload_start = params.payload.map(|data| write_byte_vector(buf, data));
332
333    // Root offset relative to event start (not absolute position)
334    buf[root_pos..root_pos + 4].copy_from_slice(&((table_start - root_pos) as u32).to_le_bytes());
335
336    if let Some(start) = device_id_start {
337        patch_offset(buf, device_id_off_pos, start);
338    }
339    if let Some(start) = session_id_start {
340        patch_offset(buf, session_id_off_pos, start);
341    }
342    if let Some(start) = event_name_start {
343        patch_offset(buf, event_name_off_pos, start);
344    }
345    if let Some(start) = payload_start {
346        patch_offset(buf, payload_off_pos, start);
347    }
348}