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