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