@ignex/nova 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +313 -0
  3. package/docs/architecture.md +146 -0
  4. package/docs/publishing.md +119 -0
  5. package/docs/wire-format.md +170 -0
  6. package/index.ts +61 -0
  7. package/package.json +89 -0
  8. package/prebuilds/linux-x64/libignex_ffi.so +0 -0
  9. package/public/client.ts +23 -0
  10. package/public/nats.ts +19 -0
  11. package/public/server.ts +35 -0
  12. package/rust/Cargo.toml +19 -0
  13. package/rust/src/ffi.rs +135 -0
  14. package/rust/src/generated/backend.rs +2817 -0
  15. package/rust/src/generated/mod.rs +2 -0
  16. package/rust/src/lib.rs +9 -0
  17. package/rust/src/transcode/generated.rs +1352 -0
  18. package/rust/src/transcode/mod.rs +2 -0
  19. package/src/bridge/nats.ts +269 -0
  20. package/src/bridge/subjects.ts +30 -0
  21. package/src/core/auth.ts +56 -0
  22. package/src/core/backpressure.ts +39 -0
  23. package/src/core/client-heartbeat.ts +27 -0
  24. package/src/core/client-reconnect.ts +35 -0
  25. package/src/core/client-state.ts +76 -0
  26. package/src/core/client-wire.ts +72 -0
  27. package/src/core/client.ts +176 -0
  28. package/src/core/groups.ts +52 -0
  29. package/src/core/int64-guard.ts +44 -0
  30. package/src/core/metrics.ts +105 -0
  31. package/src/core/outbound.ts +76 -0
  32. package/src/core/replay.ts +31 -0
  33. package/src/core/ring.ts +85 -0
  34. package/src/core/rooms.ts +44 -0
  35. package/src/core/routing.ts +94 -0
  36. package/src/core/server.ts +294 -0
  37. package/src/core/state.ts +179 -0
  38. package/src/generated/direct-ser.ts +495 -0
  39. package/src/generated/fbs/backend.fbs +139 -0
  40. package/src/generated/registry.ts +341 -0
  41. package/src/generated/rust/backend_generated.rs +2817 -0
  42. package/src/generated/ts/backend.ts +25 -0
  43. package/src/generated/ts/big-val.ts +106 -0
  44. package/src/generated/ts/complex.ts +303 -0
  45. package/src/generated/ts/customer.ts +137 -0
  46. package/src/generated/ts/hello.ts +123 -0
  47. package/src/generated/ts/join-group.ts +78 -0
  48. package/src/generated/ts/leave-group.ts +78 -0
  49. package/src/generated/ts/order-billing.ts +137 -0
  50. package/src/generated/ts/order-line.ts +144 -0
  51. package/src/generated/ts/order.ts +236 -0
  52. package/src/generated/ts/ping.ts +74 -0
  53. package/src/generated/ts/pong.ts +74 -0
  54. package/src/generated/ts/portfolio-position.ts +120 -0
  55. package/src/generated/ts/portfolio-snapshot.ts +170 -0
  56. package/src/generated/ts/quote.ts +148 -0
  57. package/src/generated/ts/side.ts +8 -0
  58. package/src/generated/ts/snapshot-request.ts +78 -0
  59. package/src/generated/ts/subscribe.ts +78 -0
  60. package/src/generated/ts/tags.ts +9 -0
  61. package/src/generated/ts/trade.ts +135 -0
  62. package/src/generated/ts/unsubscribe.ts +78 -0
  63. package/src/generated/ts/welcome.ts +112 -0
  64. package/src/generated/ts-ser.ts +465 -0
  65. package/src/generated/wire-registry.json +20 -0
  66. package/src/native/codec.ts +35 -0
  67. package/src/native/ffi.ts +214 -0
  68. package/src/native/loader.ts +55 -0
  69. package/src/schema/index.ts +217 -0
  70. package/src/server.ts +87 -0
  71. package/src/transport/byte-buffer-pool.ts +63 -0
  72. package/src/transport/scratch.ts +48 -0
  73. package/src/transport/stats.ts +44 -0
  74. package/src/transport/transport.ts +106 -0
@@ -0,0 +1,1352 @@
1
+ // @generated by scripts/rust-glue-gen.ts — DO NOT EDIT
2
+ #![allow(clippy::all)]
3
+ #![allow(dead_code)]
4
+ #![allow(unused_imports)]
5
+
6
+ use std::cell::RefCell;
7
+ use std::ffi::CStr;
8
+ use std::os::raw::c_char;
9
+ use serde::Deserialize;
10
+ use crate::generated::backend;
11
+
12
+ pub const WIRE_VERSION: u8 = 1;
13
+ pub const WIRE_HEADER_LEN: usize = 5; // [version:1][event_id:u32 LE]
14
+
15
+ thread_local! {
16
+ static FBB: RefCell<flatbuffers::FlatBufferBuilder<'static>> =
17
+ RefCell::new(flatbuffers::FlatBufferBuilder::new());
18
+ }
19
+
20
+ fn copy_finished<T>(
21
+ out: &mut [u8],
22
+ build: impl FnOnce(&mut flatbuffers::FlatBufferBuilder<'static>) -> flatbuffers::WIPOffset<T>,
23
+ ) -> Result<usize, TranscodeError> {
24
+ FBB.with(|cell| {
25
+ let mut fbb = cell.borrow_mut();
26
+ fbb.reset();
27
+ let root = build(&mut fbb);
28
+ fbb.finish_size_prefixed(root, Some("IGNX"));
29
+ let data = fbb.finished_data();
30
+ if data.len() > out.len() {
31
+ return Ok(data.len());
32
+ }
33
+ out[..data.len()].copy_from_slice(data);
34
+ Ok(data.len())
35
+ })
36
+ }
37
+
38
+ #[derive(Debug)]
39
+ pub enum TranscodeError {
40
+ Parse,
41
+ UnknownEvent,
42
+ }
43
+
44
+ fn de_i64<'de, D>(d: D) -> Result<i64, D::Error>
45
+ where
46
+ D: serde::Deserializer<'de>,
47
+ {
48
+ let v = serde_json::Value::deserialize(d)?;
49
+ Ok(v.as_i64().or_else(|| v.as_f64().map(|f| f as i64)).unwrap_or(0))
50
+ }
51
+
52
+ fn side_from_str(s: &str) -> backend::Side {
53
+ match s {
54
+ "buy" => backend::Side::BUY,
55
+ "sell" => backend::Side::SELL,
56
+ _ => backend::Side::default(),
57
+ }
58
+ }
59
+
60
+ fn tags_from_str(s: &str) -> backend::Tags {
61
+ match s {
62
+ "hot" => backend::Tags::HOT,
63
+ "new" => backend::Tags::NEW,
64
+ "sale" => backend::Tags::SALE,
65
+ _ => backend::Tags::default(),
66
+ }
67
+ }
68
+
69
+ #[derive(Deserialize, Default)]
70
+ #[serde(rename_all = "camelCase")]
71
+ pub struct QuoteJson {
72
+ #[serde(default)]
73
+ pub symbol: String,
74
+ #[serde(default)]
75
+ pub bid: f64,
76
+ #[serde(default)]
77
+ pub ask: f64,
78
+ #[serde(default, deserialize_with = "de_i64")]
79
+ pub bid_size: i64,
80
+ #[serde(default, deserialize_with = "de_i64")]
81
+ pub ask_size: i64,
82
+ #[serde(default, deserialize_with = "de_i64")]
83
+ pub ts: i64,
84
+ }
85
+
86
+ pub fn build_quote<'a>(fbb: &mut flatbuffers::FlatBufferBuilder<'a>, j: &QuoteJson) -> flatbuffers::WIPOffset<backend::Quote<'a>> {
87
+ let symbol = fbb.create_string(&j.symbol);
88
+ backend::Quote::create(fbb, &backend::QuoteArgs {
89
+ symbol: Some(symbol),
90
+ bid: j.bid,
91
+ ask: j.ask,
92
+ bid_size: j.bid_size,
93
+ ask_size: j.ask_size,
94
+ ts: j.ts,
95
+ ..Default::default()
96
+ })
97
+ }
98
+
99
+ #[derive(Deserialize, Default)]
100
+ #[serde(rename_all = "camelCase")]
101
+ pub struct TradeJson {
102
+ #[serde(default)]
103
+ pub symbol: String,
104
+ #[serde(default)]
105
+ pub price: f64,
106
+ #[serde(default, deserialize_with = "de_i64")]
107
+ pub volume: i64,
108
+ #[serde(default)]
109
+ pub side: Option<String>,
110
+ #[serde(default, deserialize_with = "de_i64")]
111
+ pub ts: i64,
112
+ }
113
+
114
+ pub fn build_trade<'a>(fbb: &mut flatbuffers::FlatBufferBuilder<'a>, j: &TradeJson) -> flatbuffers::WIPOffset<backend::Trade<'a>> {
115
+ let symbol = fbb.create_string(&j.symbol);
116
+ let side = j.side.as_deref().map(side_from_str).unwrap_or_default();
117
+ backend::Trade::create(fbb, &backend::TradeArgs {
118
+ symbol: Some(symbol),
119
+ price: j.price,
120
+ volume: j.volume,
121
+ side,
122
+ ts: j.ts,
123
+ ..Default::default()
124
+ })
125
+ }
126
+
127
+ #[derive(Deserialize, Default)]
128
+ #[serde(rename_all = "camelCase")]
129
+ pub struct PortfolioSnapshotJson {
130
+ #[serde(default)]
131
+ pub account_id: String,
132
+ #[serde(default)]
133
+ pub positions: Vec<PortfolioPositionJson>,
134
+ #[serde(default)]
135
+ pub total_value: f64,
136
+ #[serde(default)]
137
+ pub cash: f64,
138
+ #[serde(default, deserialize_with = "de_i64")]
139
+ pub ts: i64,
140
+ #[serde(default)]
141
+ pub updated_by: Option<String>,
142
+ }
143
+
144
+ pub fn build_portfolio_snapshot<'a>(fbb: &mut flatbuffers::FlatBufferBuilder<'a>, j: &PortfolioSnapshotJson) -> flatbuffers::WIPOffset<backend::PortfolioSnapshot<'a>> {
145
+ let account_id = fbb.create_string(&j.account_id);
146
+ let positions = if j.positions.is_empty() { None } else { let offs: Vec<flatbuffers::WIPOffset<backend::PortfolioPosition<'a>>> = j.positions.iter().map(|c| build_portfolio_position(fbb, c)).collect(); Some(fbb.create_vector(&offs)) };
147
+ let updated_by = j.updated_by.as_deref().map(|s| fbb.create_string(s));
148
+ backend::PortfolioSnapshot::create(fbb, &backend::PortfolioSnapshotArgs {
149
+ account_id: Some(account_id),
150
+ positions,
151
+ total_value: j.total_value,
152
+ cash: j.cash,
153
+ ts: j.ts,
154
+ updated_by,
155
+ ..Default::default()
156
+ })
157
+ }
158
+
159
+ #[derive(Deserialize, Default)]
160
+ #[serde(rename_all = "camelCase")]
161
+ pub struct PortfolioPositionJson {
162
+ #[serde(default)]
163
+ pub symbol: String,
164
+ #[serde(default, deserialize_with = "de_i64")]
165
+ pub quantity: i64,
166
+ #[serde(default)]
167
+ pub avg_price: f64,
168
+ #[serde(default)]
169
+ pub pnl: f64,
170
+ }
171
+
172
+ pub fn build_portfolio_position<'a>(fbb: &mut flatbuffers::FlatBufferBuilder<'a>, j: &PortfolioPositionJson) -> flatbuffers::WIPOffset<backend::PortfolioPosition<'a>> {
173
+ let symbol = fbb.create_string(&j.symbol);
174
+ backend::PortfolioPosition::create(fbb, &backend::PortfolioPositionArgs {
175
+ symbol: Some(symbol),
176
+ quantity: j.quantity,
177
+ avg_price: j.avg_price,
178
+ pnl: j.pnl,
179
+ ..Default::default()
180
+ })
181
+ }
182
+
183
+ #[derive(Deserialize, Default)]
184
+ #[serde(rename_all = "camelCase")]
185
+ pub struct ComplexJson {
186
+ #[serde(default)]
187
+ pub id: String,
188
+ #[serde(default)]
189
+ pub names: Vec<String>,
190
+ #[serde(default)]
191
+ pub prices: Vec<f64>,
192
+ #[serde(default)]
193
+ pub counts: Vec<i64>,
194
+ #[serde(default)]
195
+ pub flags: Vec<bool>,
196
+ #[serde(default)]
197
+ pub tags: Vec<String>,
198
+ #[serde(default)]
199
+ pub active: bool,
200
+ #[serde(default)]
201
+ pub total: f64,
202
+ #[serde(default, deserialize_with = "de_i64")]
203
+ pub ts: i64,
204
+ }
205
+
206
+ pub fn build_complex<'a>(fbb: &mut flatbuffers::FlatBufferBuilder<'a>, j: &ComplexJson) -> flatbuffers::WIPOffset<backend::Complex<'a>> {
207
+ let id = fbb.create_string(&j.id);
208
+ let names = if j.names.is_empty() { None } else { let offs: Vec<flatbuffers::WIPOffset<&str>> = j.names.iter().map(|s| fbb.create_string(s)).collect(); Some(fbb.create_vector(&offs)) };
209
+ let prices = if j.prices.is_empty() { None } else { Some(fbb.create_vector(&j.prices)) };
210
+ let counts = if j.counts.is_empty() { None } else { Some(fbb.create_vector(&j.counts)) };
211
+ let flags = if j.flags.is_empty() { None } else { Some(fbb.create_vector(&j.flags)) };
212
+ let tags = if j.tags.is_empty() { None } else { let vals: Vec<backend::Tags> = j.tags.iter().map(|s| tags_from_str(s)).collect(); Some(fbb.create_vector(&vals)) };
213
+ backend::Complex::create(fbb, &backend::ComplexArgs {
214
+ id: Some(id),
215
+ names,
216
+ prices,
217
+ counts,
218
+ flags,
219
+ tags,
220
+ active: j.active,
221
+ total: j.total,
222
+ ts: j.ts,
223
+ ..Default::default()
224
+ })
225
+ }
226
+
227
+ #[derive(Deserialize, Default)]
228
+ #[serde(rename_all = "camelCase")]
229
+ pub struct OrderJson {
230
+ #[serde(default)]
231
+ pub order_id: String,
232
+ #[serde(default)]
233
+ pub customer: Option<CustomerJson>,
234
+ #[serde(default)]
235
+ pub lines: Vec<OrderLineJson>,
236
+ #[serde(default)]
237
+ pub notes: Vec<String>,
238
+ #[serde(default)]
239
+ pub discounts: Vec<f64>,
240
+ #[serde(default)]
241
+ pub active: bool,
242
+ #[serde(default, deserialize_with = "de_i64")]
243
+ pub created_at: i64,
244
+ #[serde(default)]
245
+ pub billing: Option<OrderBillingJson>,
246
+ }
247
+
248
+ pub fn build_order<'a>(fbb: &mut flatbuffers::FlatBufferBuilder<'a>, j: &OrderJson) -> flatbuffers::WIPOffset<backend::Order<'a>> {
249
+ let order_id = fbb.create_string(&j.order_id);
250
+ let customer = j.customer.as_ref().map(|c| build_customer(fbb, c));
251
+ let lines = if j.lines.is_empty() { None } else { let offs: Vec<flatbuffers::WIPOffset<backend::OrderLine<'a>>> = j.lines.iter().map(|c| build_order_line(fbb, c)).collect(); Some(fbb.create_vector(&offs)) };
252
+ let notes = if j.notes.is_empty() { None } else { let offs: Vec<flatbuffers::WIPOffset<&str>> = j.notes.iter().map(|s| fbb.create_string(s)).collect(); Some(fbb.create_vector(&offs)) };
253
+ let discounts = if j.discounts.is_empty() { None } else { Some(fbb.create_vector(&j.discounts)) };
254
+ let billing = j.billing.as_ref().map(|c| build_order_billing(fbb, c));
255
+ backend::Order::create(fbb, &backend::OrderArgs {
256
+ order_id: Some(order_id),
257
+ customer,
258
+ lines,
259
+ notes,
260
+ discounts,
261
+ active: j.active,
262
+ created_at: j.created_at,
263
+ billing,
264
+ ..Default::default()
265
+ })
266
+ }
267
+
268
+ #[derive(Deserialize, Default)]
269
+ #[serde(rename_all = "camelCase")]
270
+ pub struct CustomerJson {
271
+ #[serde(default)]
272
+ pub id: String,
273
+ #[serde(default)]
274
+ pub name: String,
275
+ #[serde(default)]
276
+ pub vip: bool,
277
+ #[serde(default, deserialize_with = "de_i64")]
278
+ pub loyalty_points: i64,
279
+ #[serde(default)]
280
+ pub rating: f64,
281
+ }
282
+
283
+ pub fn build_customer<'a>(fbb: &mut flatbuffers::FlatBufferBuilder<'a>, j: &CustomerJson) -> flatbuffers::WIPOffset<backend::Customer<'a>> {
284
+ let id = fbb.create_string(&j.id);
285
+ let name = fbb.create_string(&j.name);
286
+ backend::Customer::create(fbb, &backend::CustomerArgs {
287
+ id: Some(id),
288
+ name: Some(name),
289
+ vip: j.vip,
290
+ loyalty_points: j.loyalty_points,
291
+ rating: j.rating,
292
+ ..Default::default()
293
+ })
294
+ }
295
+
296
+ #[derive(Deserialize, Default)]
297
+ #[serde(rename_all = "camelCase")]
298
+ pub struct OrderLineJson {
299
+ #[serde(default)]
300
+ pub sku: String,
301
+ #[serde(default, deserialize_with = "de_i64")]
302
+ pub qty: i64,
303
+ #[serde(default)]
304
+ pub unit_price: f64,
305
+ #[serde(default)]
306
+ pub tags: Vec<String>,
307
+ }
308
+
309
+ pub fn build_order_line<'a>(fbb: &mut flatbuffers::FlatBufferBuilder<'a>, j: &OrderLineJson) -> flatbuffers::WIPOffset<backend::OrderLine<'a>> {
310
+ let sku = fbb.create_string(&j.sku);
311
+ let tags = if j.tags.is_empty() { None } else { let vals: Vec<backend::Tags> = j.tags.iter().map(|s| tags_from_str(s)).collect(); Some(fbb.create_vector(&vals)) };
312
+ backend::OrderLine::create(fbb, &backend::OrderLineArgs {
313
+ sku: Some(sku),
314
+ qty: j.qty,
315
+ unit_price: j.unit_price,
316
+ tags,
317
+ ..Default::default()
318
+ })
319
+ }
320
+
321
+ #[derive(Deserialize, Default)]
322
+ #[serde(rename_all = "camelCase")]
323
+ pub struct OrderBillingJson {
324
+ #[serde(default)]
325
+ pub id: String,
326
+ #[serde(default)]
327
+ pub name: String,
328
+ #[serde(default)]
329
+ pub vip: bool,
330
+ #[serde(default, deserialize_with = "de_i64")]
331
+ pub loyalty_points: i64,
332
+ #[serde(default)]
333
+ pub rating: f64,
334
+ }
335
+
336
+ pub fn build_order_billing<'a>(fbb: &mut flatbuffers::FlatBufferBuilder<'a>, j: &OrderBillingJson) -> flatbuffers::WIPOffset<backend::OrderBilling<'a>> {
337
+ let id = fbb.create_string(&j.id);
338
+ let name = fbb.create_string(&j.name);
339
+ backend::OrderBilling::create(fbb, &backend::OrderBillingArgs {
340
+ id: Some(id),
341
+ name: Some(name),
342
+ vip: j.vip,
343
+ loyalty_points: j.loyalty_points,
344
+ rating: j.rating,
345
+ ..Default::default()
346
+ })
347
+ }
348
+
349
+ #[derive(Deserialize, Default)]
350
+ #[serde(rename_all = "camelCase")]
351
+ pub struct BigValJson {
352
+ #[serde(default)]
353
+ pub id: String,
354
+ #[serde(default, deserialize_with = "de_i64")]
355
+ pub seq: i64,
356
+ #[serde(default, deserialize_with = "de_i64")]
357
+ pub when: i64,
358
+ }
359
+
360
+ pub fn build_big_val<'a>(fbb: &mut flatbuffers::FlatBufferBuilder<'a>, j: &BigValJson) -> flatbuffers::WIPOffset<backend::BigVal<'a>> {
361
+ let id = fbb.create_string(&j.id);
362
+ backend::BigVal::create(fbb, &backend::BigValArgs {
363
+ id: Some(id),
364
+ seq: j.seq,
365
+ when: j.when,
366
+ ..Default::default()
367
+ })
368
+ }
369
+
370
+ #[derive(Deserialize, Default)]
371
+ #[serde(rename_all = "camelCase")]
372
+ pub struct HelloJson {
373
+ #[serde(default, deserialize_with = "de_i64")]
374
+ pub version: i64,
375
+ #[serde(default)]
376
+ pub caps: Vec<String>,
377
+ #[serde(default, deserialize_with = "de_i64")]
378
+ pub last_seq: i64,
379
+ }
380
+
381
+ pub fn build_hello<'a>(fbb: &mut flatbuffers::FlatBufferBuilder<'a>, j: &HelloJson) -> flatbuffers::WIPOffset<backend::Hello<'a>> {
382
+ let caps = if j.caps.is_empty() { None } else { let offs: Vec<flatbuffers::WIPOffset<&str>> = j.caps.iter().map(|s| fbb.create_string(s)).collect(); Some(fbb.create_vector(&offs)) };
383
+ backend::Hello::create(fbb, &backend::HelloArgs {
384
+ version: j.version,
385
+ caps,
386
+ last_seq: j.last_seq,
387
+ ..Default::default()
388
+ })
389
+ }
390
+
391
+ #[derive(Deserialize, Default)]
392
+ #[serde(rename_all = "camelCase")]
393
+ pub struct WelcomeJson {
394
+ #[serde(default)]
395
+ pub client_id: String,
396
+ #[serde(default)]
397
+ pub groups: Vec<String>,
398
+ }
399
+
400
+ pub fn build_welcome<'a>(fbb: &mut flatbuffers::FlatBufferBuilder<'a>, j: &WelcomeJson) -> flatbuffers::WIPOffset<backend::Welcome<'a>> {
401
+ let client_id = fbb.create_string(&j.client_id);
402
+ let groups = if j.groups.is_empty() { None } else { let offs: Vec<flatbuffers::WIPOffset<&str>> = j.groups.iter().map(|s| fbb.create_string(s)).collect(); Some(fbb.create_vector(&offs)) };
403
+ backend::Welcome::create(fbb, &backend::WelcomeArgs {
404
+ client_id: Some(client_id),
405
+ groups,
406
+ ..Default::default()
407
+ })
408
+ }
409
+
410
+ #[derive(Deserialize, Default)]
411
+ #[serde(rename_all = "camelCase")]
412
+ pub struct SubscribeJson {
413
+ #[serde(default)]
414
+ pub topic: String,
415
+ }
416
+
417
+ pub fn build_subscribe<'a>(fbb: &mut flatbuffers::FlatBufferBuilder<'a>, j: &SubscribeJson) -> flatbuffers::WIPOffset<backend::Subscribe<'a>> {
418
+ let topic = fbb.create_string(&j.topic);
419
+ backend::Subscribe::create(fbb, &backend::SubscribeArgs {
420
+ topic: Some(topic),
421
+ ..Default::default()
422
+ })
423
+ }
424
+
425
+ #[derive(Deserialize, Default)]
426
+ #[serde(rename_all = "camelCase")]
427
+ pub struct UnsubscribeJson {
428
+ #[serde(default)]
429
+ pub topic: String,
430
+ }
431
+
432
+ pub fn build_unsubscribe<'a>(fbb: &mut flatbuffers::FlatBufferBuilder<'a>, j: &UnsubscribeJson) -> flatbuffers::WIPOffset<backend::Unsubscribe<'a>> {
433
+ let topic = fbb.create_string(&j.topic);
434
+ backend::Unsubscribe::create(fbb, &backend::UnsubscribeArgs {
435
+ topic: Some(topic),
436
+ ..Default::default()
437
+ })
438
+ }
439
+
440
+ #[derive(Deserialize, Default)]
441
+ #[serde(rename_all = "camelCase")]
442
+ pub struct JoinGroupJson {
443
+ #[serde(default)]
444
+ pub group: String,
445
+ }
446
+
447
+ pub fn build_join_group<'a>(fbb: &mut flatbuffers::FlatBufferBuilder<'a>, j: &JoinGroupJson) -> flatbuffers::WIPOffset<backend::JoinGroup<'a>> {
448
+ let group = fbb.create_string(&j.group);
449
+ backend::JoinGroup::create(fbb, &backend::JoinGroupArgs {
450
+ group: Some(group),
451
+ ..Default::default()
452
+ })
453
+ }
454
+
455
+ #[derive(Deserialize, Default)]
456
+ #[serde(rename_all = "camelCase")]
457
+ pub struct LeaveGroupJson {
458
+ #[serde(default)]
459
+ pub group: String,
460
+ }
461
+
462
+ pub fn build_leave_group<'a>(fbb: &mut flatbuffers::FlatBufferBuilder<'a>, j: &LeaveGroupJson) -> flatbuffers::WIPOffset<backend::LeaveGroup<'a>> {
463
+ let group = fbb.create_string(&j.group);
464
+ backend::LeaveGroup::create(fbb, &backend::LeaveGroupArgs {
465
+ group: Some(group),
466
+ ..Default::default()
467
+ })
468
+ }
469
+
470
+ #[derive(Deserialize, Default)]
471
+ #[serde(rename_all = "camelCase")]
472
+ pub struct SnapshotRequestJson {
473
+ #[serde(default)]
474
+ pub topic: String,
475
+ }
476
+
477
+ pub fn build_snapshot_request<'a>(fbb: &mut flatbuffers::FlatBufferBuilder<'a>, j: &SnapshotRequestJson) -> flatbuffers::WIPOffset<backend::SnapshotRequest<'a>> {
478
+ let topic = fbb.create_string(&j.topic);
479
+ backend::SnapshotRequest::create(fbb, &backend::SnapshotRequestArgs {
480
+ topic: Some(topic),
481
+ ..Default::default()
482
+ })
483
+ }
484
+
485
+ #[derive(Deserialize, Default)]
486
+ #[serde(rename_all = "camelCase")]
487
+ pub struct PingJson {
488
+ #[serde(default, deserialize_with = "de_i64")]
489
+ pub ts: i64,
490
+ }
491
+
492
+ pub fn build_ping<'a>(fbb: &mut flatbuffers::FlatBufferBuilder<'a>, j: &PingJson) -> flatbuffers::WIPOffset<backend::Ping<'a>> {
493
+
494
+ backend::Ping::create(fbb, &backend::PingArgs {
495
+ ts: j.ts,
496
+ ..Default::default()
497
+ })
498
+ }
499
+
500
+ #[derive(Deserialize, Default)]
501
+ #[serde(rename_all = "camelCase")]
502
+ pub struct PongJson {
503
+ #[serde(default, deserialize_with = "de_i64")]
504
+ pub ts: i64,
505
+ }
506
+
507
+ pub fn build_pong<'a>(fbb: &mut flatbuffers::FlatBufferBuilder<'a>, j: &PongJson) -> flatbuffers::WIPOffset<backend::Pong<'a>> {
508
+
509
+ backend::Pong::create(fbb, &backend::PongArgs {
510
+ ts: j.ts,
511
+ ..Default::default()
512
+ })
513
+ }
514
+
515
+ pub fn serialize_quote(json: &[u8], out: &mut [u8]) -> Result<usize, TranscodeError> {
516
+ let j: QuoteJson = serde_json::from_slice(json).map_err(|_| TranscodeError::Parse)?;
517
+ copy_finished(out, |fbb| build_quote(fbb, &j))
518
+ }
519
+
520
+ pub fn serialize_trade(json: &[u8], out: &mut [u8]) -> Result<usize, TranscodeError> {
521
+ let j: TradeJson = serde_json::from_slice(json).map_err(|_| TranscodeError::Parse)?;
522
+ copy_finished(out, |fbb| build_trade(fbb, &j))
523
+ }
524
+
525
+ pub fn serialize_portfolio(json: &[u8], out: &mut [u8]) -> Result<usize, TranscodeError> {
526
+ let j: PortfolioSnapshotJson = serde_json::from_slice(json).map_err(|_| TranscodeError::Parse)?;
527
+ copy_finished(out, |fbb| build_portfolio_snapshot(fbb, &j))
528
+ }
529
+
530
+ pub fn serialize_complex(json: &[u8], out: &mut [u8]) -> Result<usize, TranscodeError> {
531
+ let j: ComplexJson = serde_json::from_slice(json).map_err(|_| TranscodeError::Parse)?;
532
+ copy_finished(out, |fbb| build_complex(fbb, &j))
533
+ }
534
+
535
+ pub fn serialize_order(json: &[u8], out: &mut [u8]) -> Result<usize, TranscodeError> {
536
+ let j: OrderJson = serde_json::from_slice(json).map_err(|_| TranscodeError::Parse)?;
537
+ copy_finished(out, |fbb| build_order(fbb, &j))
538
+ }
539
+
540
+ pub fn serialize_big_val(json: &[u8], out: &mut [u8]) -> Result<usize, TranscodeError> {
541
+ let j: BigValJson = serde_json::from_slice(json).map_err(|_| TranscodeError::Parse)?;
542
+ copy_finished(out, |fbb| build_big_val(fbb, &j))
543
+ }
544
+
545
+ pub fn serialize_hello(json: &[u8], out: &mut [u8]) -> Result<usize, TranscodeError> {
546
+ let j: HelloJson = serde_json::from_slice(json).map_err(|_| TranscodeError::Parse)?;
547
+ copy_finished(out, |fbb| build_hello(fbb, &j))
548
+ }
549
+
550
+ pub fn serialize_welcome(json: &[u8], out: &mut [u8]) -> Result<usize, TranscodeError> {
551
+ let j: WelcomeJson = serde_json::from_slice(json).map_err(|_| TranscodeError::Parse)?;
552
+ copy_finished(out, |fbb| build_welcome(fbb, &j))
553
+ }
554
+
555
+ pub fn serialize_subscribe(json: &[u8], out: &mut [u8]) -> Result<usize, TranscodeError> {
556
+ let j: SubscribeJson = serde_json::from_slice(json).map_err(|_| TranscodeError::Parse)?;
557
+ copy_finished(out, |fbb| build_subscribe(fbb, &j))
558
+ }
559
+
560
+ pub fn serialize_unsubscribe(json: &[u8], out: &mut [u8]) -> Result<usize, TranscodeError> {
561
+ let j: UnsubscribeJson = serde_json::from_slice(json).map_err(|_| TranscodeError::Parse)?;
562
+ copy_finished(out, |fbb| build_unsubscribe(fbb, &j))
563
+ }
564
+
565
+ pub fn serialize_join_group(json: &[u8], out: &mut [u8]) -> Result<usize, TranscodeError> {
566
+ let j: JoinGroupJson = serde_json::from_slice(json).map_err(|_| TranscodeError::Parse)?;
567
+ copy_finished(out, |fbb| build_join_group(fbb, &j))
568
+ }
569
+
570
+ pub fn serialize_leave_group(json: &[u8], out: &mut [u8]) -> Result<usize, TranscodeError> {
571
+ let j: LeaveGroupJson = serde_json::from_slice(json).map_err(|_| TranscodeError::Parse)?;
572
+ copy_finished(out, |fbb| build_leave_group(fbb, &j))
573
+ }
574
+
575
+ pub fn serialize_snapshot_request(json: &[u8], out: &mut [u8]) -> Result<usize, TranscodeError> {
576
+ let j: SnapshotRequestJson = serde_json::from_slice(json).map_err(|_| TranscodeError::Parse)?;
577
+ copy_finished(out, |fbb| build_snapshot_request(fbb, &j))
578
+ }
579
+
580
+ pub fn serialize_ping(json: &[u8], out: &mut [u8]) -> Result<usize, TranscodeError> {
581
+ let j: PingJson = serde_json::from_slice(json).map_err(|_| TranscodeError::Parse)?;
582
+ copy_finished(out, |fbb| build_ping(fbb, &j))
583
+ }
584
+
585
+ pub fn serialize_pong(json: &[u8], out: &mut [u8]) -> Result<usize, TranscodeError> {
586
+ let j: PongJson = serde_json::from_slice(json).map_err(|_| TranscodeError::Parse)?;
587
+ copy_finished(out, |fbb| build_pong(fbb, &j))
588
+ }
589
+
590
+ fn read_u32(b: &[u8], off: &mut usize) -> u32 {
591
+ let v = u32::from_le_bytes([b[*off], b[*off + 1], b[*off + 2], b[*off + 3]]);
592
+ *off += 4;
593
+ v
594
+ }
595
+
596
+ fn read_i64(b: &[u8], off: &mut usize) -> i64 {
597
+ let mut buf = [0u8; 8];
598
+ buf.copy_from_slice(&b[*off..*off + 8]);
599
+ *off += 8;
600
+ i64::from_le_bytes(buf)
601
+ }
602
+
603
+ fn read_f64(b: &[u8], off: &mut usize) -> f64 {
604
+ f64::from_bits(read_i64(b, off) as u64)
605
+ }
606
+
607
+ fn read_packed_portfolio_snapshot_positions<'a>(fbb: &mut flatbuffers::FlatBufferBuilder<'a>, ptr: *const u8, len: usize) -> Option<flatbuffers::WIPOffset<flatbuffers::Vector<'a, flatbuffers::ForwardsUOffset<backend::PortfolioPosition<'a>>>>> {
608
+ if ptr.is_null() || len == 0 { return None; }
609
+ let b = unsafe { std::slice::from_raw_parts(ptr, len) };
610
+ let mut off = 0usize;
611
+ let count = read_u32(b, &mut off) as usize;
612
+ let mut offsets: Vec<flatbuffers::WIPOffset<backend::PortfolioPosition<'a>>> = Vec::with_capacity(count);
613
+ for _ in 0..count {
614
+ let symbol_len = read_u32(b, &mut off) as usize; let symbol = std::str::from_utf8(&b[off..off + symbol_len]).unwrap_or(""); off += symbol_len; let symbol_off = fbb.create_string(symbol);
615
+ let quantity = read_i64(b, &mut off);
616
+ let avg_price = read_f64(b, &mut off);
617
+ let pnl = read_f64(b, &mut off);
618
+ let item = backend::PortfolioPosition::create(fbb, &backend::PortfolioPositionArgs {
619
+ symbol: Some(symbol_off),
620
+ quantity,
621
+ avg_price,
622
+ pnl,
623
+ ..Default::default()
624
+ });
625
+ offsets.push(item);
626
+ }
627
+ Some(fbb.create_vector(&offsets))
628
+ }
629
+
630
+ fn read_packed_complex_names<'a>(fbb: &mut flatbuffers::FlatBufferBuilder<'a>, ptr: *const u8, len: usize) -> Option<flatbuffers::WIPOffset<flatbuffers::Vector<'a, flatbuffers::ForwardsUOffset<&'a str>>>> {
631
+ if ptr.is_null() || len == 0 { return None; }
632
+ let b = unsafe { std::slice::from_raw_parts(ptr, len) };
633
+ let mut off = 0usize;
634
+ let count = read_u32(b, &mut off) as usize;
635
+ let mut vals: Vec<flatbuffers::WIPOffset<&'a str>> = Vec::with_capacity(count);
636
+ for _ in 0..count {
637
+ let l = read_u32(b, &mut off) as usize; let s = std::str::from_utf8(&b[off..off + l]).unwrap_or(""); off += l; vals.push(fbb.create_string(s));
638
+ }
639
+ Some(fbb.create_vector(&vals))
640
+ }
641
+
642
+ fn read_packed_complex_prices<'a>(fbb: &mut flatbuffers::FlatBufferBuilder<'a>, ptr: *const u8, len: usize) -> Option<flatbuffers::WIPOffset<flatbuffers::Vector<'a, f64>>> {
643
+ if ptr.is_null() || len == 0 { return None; }
644
+ let b = unsafe { std::slice::from_raw_parts(ptr, len) };
645
+ let mut off = 0usize;
646
+ let count = read_u32(b, &mut off) as usize;
647
+ let mut vals: Vec<f64> = Vec::with_capacity(count);
648
+ for _ in 0..count {
649
+ vals.push(read_f64(b, &mut off));
650
+ }
651
+ Some(fbb.create_vector(&vals))
652
+ }
653
+
654
+ fn read_packed_complex_counts<'a>(fbb: &mut flatbuffers::FlatBufferBuilder<'a>, ptr: *const u8, len: usize) -> Option<flatbuffers::WIPOffset<flatbuffers::Vector<'a, i64>>> {
655
+ if ptr.is_null() || len == 0 { return None; }
656
+ let b = unsafe { std::slice::from_raw_parts(ptr, len) };
657
+ let mut off = 0usize;
658
+ let count = read_u32(b, &mut off) as usize;
659
+ let mut vals: Vec<i64> = Vec::with_capacity(count);
660
+ for _ in 0..count {
661
+ vals.push(read_i64(b, &mut off));
662
+ }
663
+ Some(fbb.create_vector(&vals))
664
+ }
665
+
666
+ fn read_packed_complex_flags<'a>(fbb: &mut flatbuffers::FlatBufferBuilder<'a>, ptr: *const u8, len: usize) -> Option<flatbuffers::WIPOffset<flatbuffers::Vector<'a, bool>>> {
667
+ if ptr.is_null() || len == 0 { return None; }
668
+ let b = unsafe { std::slice::from_raw_parts(ptr, len) };
669
+ let mut off = 0usize;
670
+ let count = read_u32(b, &mut off) as usize;
671
+ let mut vals: Vec<bool> = Vec::with_capacity(count);
672
+ for _ in 0..count {
673
+ vals.push(b[off] != 0); off += 1;
674
+ }
675
+ Some(fbb.create_vector(&vals))
676
+ }
677
+
678
+ fn read_packed_complex_tags<'a>(fbb: &mut flatbuffers::FlatBufferBuilder<'a>, ptr: *const u8, len: usize) -> Option<flatbuffers::WIPOffset<flatbuffers::Vector<'a, backend::Tags>>> {
679
+ if ptr.is_null() || len == 0 { return None; }
680
+ let b = unsafe { std::slice::from_raw_parts(ptr, len) };
681
+ let mut off = 0usize;
682
+ let count = read_u32(b, &mut off) as usize;
683
+ let mut vals: Vec<backend::Tags> = Vec::with_capacity(count);
684
+ for _ in 0..count {
685
+ vals.push(backend::Tags(read_u32(b, &mut off) as i32));
686
+ }
687
+ Some(fbb.create_vector(&vals))
688
+ }
689
+
690
+ fn read_packed_hello_caps<'a>(fbb: &mut flatbuffers::FlatBufferBuilder<'a>, ptr: *const u8, len: usize) -> Option<flatbuffers::WIPOffset<flatbuffers::Vector<'a, flatbuffers::ForwardsUOffset<&'a str>>>> {
691
+ if ptr.is_null() || len == 0 { return None; }
692
+ let b = unsafe { std::slice::from_raw_parts(ptr, len) };
693
+ let mut off = 0usize;
694
+ let count = read_u32(b, &mut off) as usize;
695
+ let mut vals: Vec<flatbuffers::WIPOffset<&'a str>> = Vec::with_capacity(count);
696
+ for _ in 0..count {
697
+ let l = read_u32(b, &mut off) as usize; let s = std::str::from_utf8(&b[off..off + l]).unwrap_or(""); off += l; vals.push(fbb.create_string(s));
698
+ }
699
+ Some(fbb.create_vector(&vals))
700
+ }
701
+
702
+ fn read_packed_welcome_groups<'a>(fbb: &mut flatbuffers::FlatBufferBuilder<'a>, ptr: *const u8, len: usize) -> Option<flatbuffers::WIPOffset<flatbuffers::Vector<'a, flatbuffers::ForwardsUOffset<&'a str>>>> {
703
+ if ptr.is_null() || len == 0 { return None; }
704
+ let b = unsafe { std::slice::from_raw_parts(ptr, len) };
705
+ let mut off = 0usize;
706
+ let count = read_u32(b, &mut off) as usize;
707
+ let mut vals: Vec<flatbuffers::WIPOffset<&'a str>> = Vec::with_capacity(count);
708
+ for _ in 0..count {
709
+ let l = read_u32(b, &mut off) as usize; let s = std::str::from_utf8(&b[off..off + l]).unwrap_or(""); off += l; vals.push(fbb.create_string(s));
710
+ }
711
+ Some(fbb.create_vector(&vals))
712
+ }
713
+
714
+
715
+ #[no_mangle]
716
+ pub unsafe extern "C" fn fb_quote_serialize(
717
+ symbol: *const c_char,
718
+ bid: f64,
719
+ ask: f64,
720
+ bid_size: i64,
721
+ ask_size: i64,
722
+ ts: i64,
723
+ out: *mut u8,
724
+ out_cap: usize,
725
+ ) -> usize {
726
+ std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
727
+ if out.is_null() && out_cap != 0 {
728
+ return 0;
729
+ }
730
+ let symbol = if symbol.is_null() { "" } else { std::str::from_utf8_unchecked(CStr::from_ptr(symbol).to_bytes()) };
731
+ // envelope = [WIRE_VERSION:1][event_id:u32 LE]; flatbuffer payload follows
732
+ let payload: &mut [u8] = if out_cap < WIRE_HEADER_LEN {
733
+ &mut []
734
+ } else {
735
+ std::slice::from_raw_parts_mut(out.add(WIRE_HEADER_LEN), out_cap - WIRE_HEADER_LEN)
736
+ };
737
+ match copy_finished(payload, |fbb| {
738
+ let symbol = fbb.create_string(symbol);
739
+ backend::Quote::create(fbb, &backend::QuoteArgs {
740
+ symbol: Some(symbol),
741
+ bid: bid,
742
+ ask: ask,
743
+ bid_size: bid_size,
744
+ ask_size: ask_size,
745
+ ts: ts,
746
+ ..Default::default()
747
+ })
748
+ }) {
749
+ Ok(written) => {
750
+ let needed = written + WIRE_HEADER_LEN;
751
+ if needed <= out_cap {
752
+ *out.add(0) = WIRE_VERSION;
753
+ let id_bytes = 2995289047u32.to_le_bytes();
754
+ std::ptr::copy_nonoverlapping(id_bytes.as_ptr(), out.add(1), 4);
755
+ }
756
+ needed
757
+ }
758
+ Err(_) => 0,
759
+ }
760
+ }))
761
+ .unwrap_or(0)
762
+ }
763
+
764
+ #[no_mangle]
765
+ pub unsafe extern "C" fn fb_trade_serialize(
766
+ symbol: *const c_char,
767
+ price: f64,
768
+ volume: i64,
769
+ side: u32,
770
+ ts: i64,
771
+ out: *mut u8,
772
+ out_cap: usize,
773
+ ) -> usize {
774
+ std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
775
+ if out.is_null() && out_cap != 0 {
776
+ return 0;
777
+ }
778
+ let symbol = if symbol.is_null() { "" } else { std::str::from_utf8_unchecked(CStr::from_ptr(symbol).to_bytes()) };
779
+ let side = backend::Side(side as i32);
780
+ // envelope = [WIRE_VERSION:1][event_id:u32 LE]; flatbuffer payload follows
781
+ let payload: &mut [u8] = if out_cap < WIRE_HEADER_LEN {
782
+ &mut []
783
+ } else {
784
+ std::slice::from_raw_parts_mut(out.add(WIRE_HEADER_LEN), out_cap - WIRE_HEADER_LEN)
785
+ };
786
+ match copy_finished(payload, |fbb| {
787
+ let symbol = fbb.create_string(symbol);
788
+ backend::Trade::create(fbb, &backend::TradeArgs {
789
+ symbol: Some(symbol),
790
+ price: price,
791
+ volume: volume,
792
+ side,
793
+ ts: ts,
794
+ ..Default::default()
795
+ })
796
+ }) {
797
+ Ok(written) => {
798
+ let needed = written + WIRE_HEADER_LEN;
799
+ if needed <= out_cap {
800
+ *out.add(0) = WIRE_VERSION;
801
+ let id_bytes = 1362521689u32.to_le_bytes();
802
+ std::ptr::copy_nonoverlapping(id_bytes.as_ptr(), out.add(1), 4);
803
+ }
804
+ needed
805
+ }
806
+ Err(_) => 0,
807
+ }
808
+ }))
809
+ .unwrap_or(0)
810
+ }
811
+
812
+ #[no_mangle]
813
+ pub unsafe extern "C" fn fb_portfolio_serialize(
814
+ account_id: *const c_char,
815
+ positions_ptr: *const u8,
816
+ positions_len: usize,
817
+ total_value: f64,
818
+ cash: f64,
819
+ ts: i64,
820
+ updated_by: *const c_char,
821
+ out: *mut u8,
822
+ out_cap: usize,
823
+ ) -> usize {
824
+ std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
825
+ if out.is_null() && out_cap != 0 {
826
+ return 0;
827
+ }
828
+ let account_id = if account_id.is_null() { "" } else { std::str::from_utf8_unchecked(CStr::from_ptr(account_id).to_bytes()) };
829
+ let updated_by = if updated_by.is_null() { None } else { Some(std::str::from_utf8_unchecked(CStr::from_ptr(updated_by).to_bytes())) };
830
+ // envelope = [WIRE_VERSION:1][event_id:u32 LE]; flatbuffer payload follows
831
+ let payload: &mut [u8] = if out_cap < WIRE_HEADER_LEN {
832
+ &mut []
833
+ } else {
834
+ std::slice::from_raw_parts_mut(out.add(WIRE_HEADER_LEN), out_cap - WIRE_HEADER_LEN)
835
+ };
836
+ match copy_finished(payload, |fbb| {
837
+ let account_id = fbb.create_string(account_id);
838
+ let positions = read_packed_portfolio_snapshot_positions(fbb, positions_ptr, positions_len);
839
+ let updated_by = updated_by.map(|s| fbb.create_string(s));
840
+ backend::PortfolioSnapshot::create(fbb, &backend::PortfolioSnapshotArgs {
841
+ account_id: Some(account_id),
842
+ positions,
843
+ total_value: total_value,
844
+ cash: cash,
845
+ ts: ts,
846
+ updated_by,
847
+ ..Default::default()
848
+ })
849
+ }) {
850
+ Ok(written) => {
851
+ let needed = written + WIRE_HEADER_LEN;
852
+ if needed <= out_cap {
853
+ *out.add(0) = WIRE_VERSION;
854
+ let id_bytes = 464274147u32.to_le_bytes();
855
+ std::ptr::copy_nonoverlapping(id_bytes.as_ptr(), out.add(1), 4);
856
+ }
857
+ needed
858
+ }
859
+ Err(_) => 0,
860
+ }
861
+ }))
862
+ .unwrap_or(0)
863
+ }
864
+
865
+ #[no_mangle]
866
+ pub unsafe extern "C" fn fb_complex_serialize(
867
+ id: *const c_char,
868
+ names_ptr: *const u8,
869
+ names_len: usize,
870
+ prices_ptr: *const u8,
871
+ prices_len: usize,
872
+ counts_ptr: *const u8,
873
+ counts_len: usize,
874
+ flags_ptr: *const u8,
875
+ flags_len: usize,
876
+ tags_ptr: *const u8,
877
+ tags_len: usize,
878
+ active: u8,
879
+ total: f64,
880
+ ts: i64,
881
+ out: *mut u8,
882
+ out_cap: usize,
883
+ ) -> usize {
884
+ std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
885
+ if out.is_null() && out_cap != 0 {
886
+ return 0;
887
+ }
888
+ let id = if id.is_null() { "" } else { std::str::from_utf8_unchecked(CStr::from_ptr(id).to_bytes()) };
889
+ // envelope = [WIRE_VERSION:1][event_id:u32 LE]; flatbuffer payload follows
890
+ let payload: &mut [u8] = if out_cap < WIRE_HEADER_LEN {
891
+ &mut []
892
+ } else {
893
+ std::slice::from_raw_parts_mut(out.add(WIRE_HEADER_LEN), out_cap - WIRE_HEADER_LEN)
894
+ };
895
+ match copy_finished(payload, |fbb| {
896
+ let id = fbb.create_string(id);
897
+ let names = read_packed_complex_names(fbb, names_ptr, names_len);
898
+ let prices = read_packed_complex_prices(fbb, prices_ptr, prices_len);
899
+ let counts = read_packed_complex_counts(fbb, counts_ptr, counts_len);
900
+ let flags = read_packed_complex_flags(fbb, flags_ptr, flags_len);
901
+ let tags = read_packed_complex_tags(fbb, tags_ptr, tags_len);
902
+ backend::Complex::create(fbb, &backend::ComplexArgs {
903
+ id: Some(id),
904
+ names,
905
+ prices,
906
+ counts,
907
+ flags,
908
+ tags,
909
+ active: active != 0,
910
+ total: total,
911
+ ts: ts,
912
+ ..Default::default()
913
+ })
914
+ }) {
915
+ Ok(written) => {
916
+ let needed = written + WIRE_HEADER_LEN;
917
+ if needed <= out_cap {
918
+ *out.add(0) = WIRE_VERSION;
919
+ let id_bytes = 2318166199u32.to_le_bytes();
920
+ std::ptr::copy_nonoverlapping(id_bytes.as_ptr(), out.add(1), 4);
921
+ }
922
+ needed
923
+ }
924
+ Err(_) => 0,
925
+ }
926
+ }))
927
+ .unwrap_or(0)
928
+ }
929
+
930
+ #[no_mangle]
931
+ pub unsafe extern "C" fn fb_big_val_serialize(
932
+ id: *const c_char,
933
+ seq: i64,
934
+ when: i64,
935
+ out: *mut u8,
936
+ out_cap: usize,
937
+ ) -> usize {
938
+ std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
939
+ if out.is_null() && out_cap != 0 {
940
+ return 0;
941
+ }
942
+ let id = if id.is_null() { "" } else { std::str::from_utf8_unchecked(CStr::from_ptr(id).to_bytes()) };
943
+ // envelope = [WIRE_VERSION:1][event_id:u32 LE]; flatbuffer payload follows
944
+ let payload: &mut [u8] = if out_cap < WIRE_HEADER_LEN {
945
+ &mut []
946
+ } else {
947
+ std::slice::from_raw_parts_mut(out.add(WIRE_HEADER_LEN), out_cap - WIRE_HEADER_LEN)
948
+ };
949
+ match copy_finished(payload, |fbb| {
950
+ let id = fbb.create_string(id);
951
+ backend::BigVal::create(fbb, &backend::BigValArgs {
952
+ id: Some(id),
953
+ seq: seq,
954
+ when: when,
955
+ ..Default::default()
956
+ })
957
+ }) {
958
+ Ok(written) => {
959
+ let needed = written + WIRE_HEADER_LEN;
960
+ if needed <= out_cap {
961
+ *out.add(0) = WIRE_VERSION;
962
+ let id_bytes = 3211729064u32.to_le_bytes();
963
+ std::ptr::copy_nonoverlapping(id_bytes.as_ptr(), out.add(1), 4);
964
+ }
965
+ needed
966
+ }
967
+ Err(_) => 0,
968
+ }
969
+ }))
970
+ .unwrap_or(0)
971
+ }
972
+
973
+ #[no_mangle]
974
+ pub unsafe extern "C" fn fb_hello_serialize(
975
+ version: i64,
976
+ caps_ptr: *const u8,
977
+ caps_len: usize,
978
+ last_seq: i64,
979
+ out: *mut u8,
980
+ out_cap: usize,
981
+ ) -> usize {
982
+ std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
983
+ if out.is_null() && out_cap != 0 {
984
+ return 0;
985
+ }
986
+
987
+ // envelope = [WIRE_VERSION:1][event_id:u32 LE]; flatbuffer payload follows
988
+ let payload: &mut [u8] = if out_cap < WIRE_HEADER_LEN {
989
+ &mut []
990
+ } else {
991
+ std::slice::from_raw_parts_mut(out.add(WIRE_HEADER_LEN), out_cap - WIRE_HEADER_LEN)
992
+ };
993
+ match copy_finished(payload, |fbb| {
994
+ let caps = read_packed_hello_caps(fbb, caps_ptr, caps_len);
995
+ backend::Hello::create(fbb, &backend::HelloArgs {
996
+ version: version,
997
+ caps,
998
+ last_seq: last_seq,
999
+ ..Default::default()
1000
+ })
1001
+ }) {
1002
+ Ok(written) => {
1003
+ let needed = written + WIRE_HEADER_LEN;
1004
+ if needed <= out_cap {
1005
+ *out.add(0) = WIRE_VERSION;
1006
+ let id_bytes = 1335831723u32.to_le_bytes();
1007
+ std::ptr::copy_nonoverlapping(id_bytes.as_ptr(), out.add(1), 4);
1008
+ }
1009
+ needed
1010
+ }
1011
+ Err(_) => 0,
1012
+ }
1013
+ }))
1014
+ .unwrap_or(0)
1015
+ }
1016
+
1017
+ #[no_mangle]
1018
+ pub unsafe extern "C" fn fb_welcome_serialize(
1019
+ client_id: *const c_char,
1020
+ groups_ptr: *const u8,
1021
+ groups_len: usize,
1022
+ out: *mut u8,
1023
+ out_cap: usize,
1024
+ ) -> usize {
1025
+ std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
1026
+ if out.is_null() && out_cap != 0 {
1027
+ return 0;
1028
+ }
1029
+ let client_id = if client_id.is_null() { "" } else { std::str::from_utf8_unchecked(CStr::from_ptr(client_id).to_bytes()) };
1030
+ // envelope = [WIRE_VERSION:1][event_id:u32 LE]; flatbuffer payload follows
1031
+ let payload: &mut [u8] = if out_cap < WIRE_HEADER_LEN {
1032
+ &mut []
1033
+ } else {
1034
+ std::slice::from_raw_parts_mut(out.add(WIRE_HEADER_LEN), out_cap - WIRE_HEADER_LEN)
1035
+ };
1036
+ match copy_finished(payload, |fbb| {
1037
+ let client_id = fbb.create_string(client_id);
1038
+ let groups = read_packed_welcome_groups(fbb, groups_ptr, groups_len);
1039
+ backend::Welcome::create(fbb, &backend::WelcomeArgs {
1040
+ client_id: Some(client_id),
1041
+ groups,
1042
+ ..Default::default()
1043
+ })
1044
+ }) {
1045
+ Ok(written) => {
1046
+ let needed = written + WIRE_HEADER_LEN;
1047
+ if needed <= out_cap {
1048
+ *out.add(0) = WIRE_VERSION;
1049
+ let id_bytes = 669089267u32.to_le_bytes();
1050
+ std::ptr::copy_nonoverlapping(id_bytes.as_ptr(), out.add(1), 4);
1051
+ }
1052
+ needed
1053
+ }
1054
+ Err(_) => 0,
1055
+ }
1056
+ }))
1057
+ .unwrap_or(0)
1058
+ }
1059
+
1060
+ #[no_mangle]
1061
+ pub unsafe extern "C" fn fb_subscribe_serialize(
1062
+ topic: *const c_char,
1063
+ out: *mut u8,
1064
+ out_cap: usize,
1065
+ ) -> usize {
1066
+ std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
1067
+ if out.is_null() && out_cap != 0 {
1068
+ return 0;
1069
+ }
1070
+ let topic = if topic.is_null() { "" } else { std::str::from_utf8_unchecked(CStr::from_ptr(topic).to_bytes()) };
1071
+ // envelope = [WIRE_VERSION:1][event_id:u32 LE]; flatbuffer payload follows
1072
+ let payload: &mut [u8] = if out_cap < WIRE_HEADER_LEN {
1073
+ &mut []
1074
+ } else {
1075
+ std::slice::from_raw_parts_mut(out.add(WIRE_HEADER_LEN), out_cap - WIRE_HEADER_LEN)
1076
+ };
1077
+ match copy_finished(payload, |fbb| {
1078
+ let topic = fbb.create_string(topic);
1079
+ backend::Subscribe::create(fbb, &backend::SubscribeArgs {
1080
+ topic: Some(topic),
1081
+ ..Default::default()
1082
+ })
1083
+ }) {
1084
+ Ok(written) => {
1085
+ let needed = written + WIRE_HEADER_LEN;
1086
+ if needed <= out_cap {
1087
+ *out.add(0) = WIRE_VERSION;
1088
+ let id_bytes = 2946386435u32.to_le_bytes();
1089
+ std::ptr::copy_nonoverlapping(id_bytes.as_ptr(), out.add(1), 4);
1090
+ }
1091
+ needed
1092
+ }
1093
+ Err(_) => 0,
1094
+ }
1095
+ }))
1096
+ .unwrap_or(0)
1097
+ }
1098
+
1099
+ #[no_mangle]
1100
+ pub unsafe extern "C" fn fb_unsubscribe_serialize(
1101
+ topic: *const c_char,
1102
+ out: *mut u8,
1103
+ out_cap: usize,
1104
+ ) -> usize {
1105
+ std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
1106
+ if out.is_null() && out_cap != 0 {
1107
+ return 0;
1108
+ }
1109
+ let topic = if topic.is_null() { "" } else { std::str::from_utf8_unchecked(CStr::from_ptr(topic).to_bytes()) };
1110
+ // envelope = [WIRE_VERSION:1][event_id:u32 LE]; flatbuffer payload follows
1111
+ let payload: &mut [u8] = if out_cap < WIRE_HEADER_LEN {
1112
+ &mut []
1113
+ } else {
1114
+ std::slice::from_raw_parts_mut(out.add(WIRE_HEADER_LEN), out_cap - WIRE_HEADER_LEN)
1115
+ };
1116
+ match copy_finished(payload, |fbb| {
1117
+ let topic = fbb.create_string(topic);
1118
+ backend::Unsubscribe::create(fbb, &backend::UnsubscribeArgs {
1119
+ topic: Some(topic),
1120
+ ..Default::default()
1121
+ })
1122
+ }) {
1123
+ Ok(written) => {
1124
+ let needed = written + WIRE_HEADER_LEN;
1125
+ if needed <= out_cap {
1126
+ *out.add(0) = WIRE_VERSION;
1127
+ let id_bytes = 4190043798u32.to_le_bytes();
1128
+ std::ptr::copy_nonoverlapping(id_bytes.as_ptr(), out.add(1), 4);
1129
+ }
1130
+ needed
1131
+ }
1132
+ Err(_) => 0,
1133
+ }
1134
+ }))
1135
+ .unwrap_or(0)
1136
+ }
1137
+
1138
+ #[no_mangle]
1139
+ pub unsafe extern "C" fn fb_join_group_serialize(
1140
+ group: *const c_char,
1141
+ out: *mut u8,
1142
+ out_cap: usize,
1143
+ ) -> usize {
1144
+ std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
1145
+ if out.is_null() && out_cap != 0 {
1146
+ return 0;
1147
+ }
1148
+ let group = if group.is_null() { "" } else { std::str::from_utf8_unchecked(CStr::from_ptr(group).to_bytes()) };
1149
+ // envelope = [WIRE_VERSION:1][event_id:u32 LE]; flatbuffer payload follows
1150
+ let payload: &mut [u8] = if out_cap < WIRE_HEADER_LEN {
1151
+ &mut []
1152
+ } else {
1153
+ std::slice::from_raw_parts_mut(out.add(WIRE_HEADER_LEN), out_cap - WIRE_HEADER_LEN)
1154
+ };
1155
+ match copy_finished(payload, |fbb| {
1156
+ let group = fbb.create_string(group);
1157
+ backend::JoinGroup::create(fbb, &backend::JoinGroupArgs {
1158
+ group: Some(group),
1159
+ ..Default::default()
1160
+ })
1161
+ }) {
1162
+ Ok(written) => {
1163
+ let needed = written + WIRE_HEADER_LEN;
1164
+ if needed <= out_cap {
1165
+ *out.add(0) = WIRE_VERSION;
1166
+ let id_bytes = 3026600144u32.to_le_bytes();
1167
+ std::ptr::copy_nonoverlapping(id_bytes.as_ptr(), out.add(1), 4);
1168
+ }
1169
+ needed
1170
+ }
1171
+ Err(_) => 0,
1172
+ }
1173
+ }))
1174
+ .unwrap_or(0)
1175
+ }
1176
+
1177
+ #[no_mangle]
1178
+ pub unsafe extern "C" fn fb_leave_group_serialize(
1179
+ group: *const c_char,
1180
+ out: *mut u8,
1181
+ out_cap: usize,
1182
+ ) -> usize {
1183
+ std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
1184
+ if out.is_null() && out_cap != 0 {
1185
+ return 0;
1186
+ }
1187
+ let group = if group.is_null() { "" } else { std::str::from_utf8_unchecked(CStr::from_ptr(group).to_bytes()) };
1188
+ // envelope = [WIRE_VERSION:1][event_id:u32 LE]; flatbuffer payload follows
1189
+ let payload: &mut [u8] = if out_cap < WIRE_HEADER_LEN {
1190
+ &mut []
1191
+ } else {
1192
+ std::slice::from_raw_parts_mut(out.add(WIRE_HEADER_LEN), out_cap - WIRE_HEADER_LEN)
1193
+ };
1194
+ match copy_finished(payload, |fbb| {
1195
+ let group = fbb.create_string(group);
1196
+ backend::LeaveGroup::create(fbb, &backend::LeaveGroupArgs {
1197
+ group: Some(group),
1198
+ ..Default::default()
1199
+ })
1200
+ }) {
1201
+ Ok(written) => {
1202
+ let needed = written + WIRE_HEADER_LEN;
1203
+ if needed <= out_cap {
1204
+ *out.add(0) = WIRE_VERSION;
1205
+ let id_bytes = 3755061443u32.to_le_bytes();
1206
+ std::ptr::copy_nonoverlapping(id_bytes.as_ptr(), out.add(1), 4);
1207
+ }
1208
+ needed
1209
+ }
1210
+ Err(_) => 0,
1211
+ }
1212
+ }))
1213
+ .unwrap_or(0)
1214
+ }
1215
+
1216
+ #[no_mangle]
1217
+ pub unsafe extern "C" fn fb_snapshot_request_serialize(
1218
+ topic: *const c_char,
1219
+ out: *mut u8,
1220
+ out_cap: usize,
1221
+ ) -> usize {
1222
+ std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
1223
+ if out.is_null() && out_cap != 0 {
1224
+ return 0;
1225
+ }
1226
+ let topic = if topic.is_null() { "" } else { std::str::from_utf8_unchecked(CStr::from_ptr(topic).to_bytes()) };
1227
+ // envelope = [WIRE_VERSION:1][event_id:u32 LE]; flatbuffer payload follows
1228
+ let payload: &mut [u8] = if out_cap < WIRE_HEADER_LEN {
1229
+ &mut []
1230
+ } else {
1231
+ std::slice::from_raw_parts_mut(out.add(WIRE_HEADER_LEN), out_cap - WIRE_HEADER_LEN)
1232
+ };
1233
+ match copy_finished(payload, |fbb| {
1234
+ let topic = fbb.create_string(topic);
1235
+ backend::SnapshotRequest::create(fbb, &backend::SnapshotRequestArgs {
1236
+ topic: Some(topic),
1237
+ ..Default::default()
1238
+ })
1239
+ }) {
1240
+ Ok(written) => {
1241
+ let needed = written + WIRE_HEADER_LEN;
1242
+ if needed <= out_cap {
1243
+ *out.add(0) = WIRE_VERSION;
1244
+ let id_bytes = 1249175018u32.to_le_bytes();
1245
+ std::ptr::copy_nonoverlapping(id_bytes.as_ptr(), out.add(1), 4);
1246
+ }
1247
+ needed
1248
+ }
1249
+ Err(_) => 0,
1250
+ }
1251
+ }))
1252
+ .unwrap_or(0)
1253
+ }
1254
+
1255
+ #[no_mangle]
1256
+ pub unsafe extern "C" fn fb_ping_serialize(
1257
+ ts: i64,
1258
+ out: *mut u8,
1259
+ out_cap: usize,
1260
+ ) -> usize {
1261
+ std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
1262
+ if out.is_null() && out_cap != 0 {
1263
+ return 0;
1264
+ }
1265
+
1266
+ // envelope = [WIRE_VERSION:1][event_id:u32 LE]; flatbuffer payload follows
1267
+ let payload: &mut [u8] = if out_cap < WIRE_HEADER_LEN {
1268
+ &mut []
1269
+ } else {
1270
+ std::slice::from_raw_parts_mut(out.add(WIRE_HEADER_LEN), out_cap - WIRE_HEADER_LEN)
1271
+ };
1272
+ match copy_finished(payload, |fbb| {
1273
+
1274
+ backend::Ping::create(fbb, &backend::PingArgs {
1275
+ ts: ts,
1276
+ ..Default::default()
1277
+ })
1278
+ }) {
1279
+ Ok(written) => {
1280
+ let needed = written + WIRE_HEADER_LEN;
1281
+ if needed <= out_cap {
1282
+ *out.add(0) = WIRE_VERSION;
1283
+ let id_bytes = 375255177u32.to_le_bytes();
1284
+ std::ptr::copy_nonoverlapping(id_bytes.as_ptr(), out.add(1), 4);
1285
+ }
1286
+ needed
1287
+ }
1288
+ Err(_) => 0,
1289
+ }
1290
+ }))
1291
+ .unwrap_or(0)
1292
+ }
1293
+
1294
+ #[no_mangle]
1295
+ pub unsafe extern "C" fn fb_pong_serialize(
1296
+ ts: i64,
1297
+ out: *mut u8,
1298
+ out_cap: usize,
1299
+ ) -> usize {
1300
+ std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
1301
+ if out.is_null() && out_cap != 0 {
1302
+ return 0;
1303
+ }
1304
+
1305
+ // envelope = [WIRE_VERSION:1][event_id:u32 LE]; flatbuffer payload follows
1306
+ let payload: &mut [u8] = if out_cap < WIRE_HEADER_LEN {
1307
+ &mut []
1308
+ } else {
1309
+ std::slice::from_raw_parts_mut(out.add(WIRE_HEADER_LEN), out_cap - WIRE_HEADER_LEN)
1310
+ };
1311
+ match copy_finished(payload, |fbb| {
1312
+
1313
+ backend::Pong::create(fbb, &backend::PongArgs {
1314
+ ts: ts,
1315
+ ..Default::default()
1316
+ })
1317
+ }) {
1318
+ Ok(written) => {
1319
+ let needed = written + WIRE_HEADER_LEN;
1320
+ if needed <= out_cap {
1321
+ *out.add(0) = WIRE_VERSION;
1322
+ let id_bytes = 2061178551u32.to_le_bytes();
1323
+ std::ptr::copy_nonoverlapping(id_bytes.as_ptr(), out.add(1), 4);
1324
+ }
1325
+ needed
1326
+ }
1327
+ Err(_) => 0,
1328
+ }
1329
+ }))
1330
+ .unwrap_or(0)
1331
+ }
1332
+
1333
+ pub fn serialize_event(event_id: u32, json: &[u8], out: &mut [u8]) -> Result<usize, TranscodeError> {
1334
+ match event_id {
1335
+ 2995289047 => serialize_quote(json, out),
1336
+ 1362521689 => serialize_trade(json, out),
1337
+ 464274147 => serialize_portfolio(json, out),
1338
+ 2318166199 => serialize_complex(json, out),
1339
+ 1932267671 => serialize_order(json, out),
1340
+ 3211729064 => serialize_big_val(json, out),
1341
+ 1335831723 => serialize_hello(json, out),
1342
+ 669089267 => serialize_welcome(json, out),
1343
+ 2946386435 => serialize_subscribe(json, out),
1344
+ 4190043798 => serialize_unsubscribe(json, out),
1345
+ 3026600144 => serialize_join_group(json, out),
1346
+ 3755061443 => serialize_leave_group(json, out),
1347
+ 1249175018 => serialize_snapshot_request(json, out),
1348
+ 375255177 => serialize_ping(json, out),
1349
+ 2061178551 => serialize_pong(json, out),
1350
+ _ => Err(TranscodeError::UnknownEvent),
1351
+ }
1352
+ }