1use crate::schema::{
4 AssetKind, ColorScheme, DesignSystem, Key, MaterialRole, MessageDuration, Modifier, MotionRole,
5 NotificationImportance, NotificationPermission, NotificationPresentation, Paint, PropertyKind,
6 Selection, ShapeRole, SpaceRole, TYPE_ROLE_COUNT, Theme, WidgetKind,
7};
8use core::fmt;
9
10const TAG_ENVELOPE: u16 = 0;
11const TAG_CREATE: u16 = 1;
12const TAG_SET_PROP: u16 = 2;
13const TAG_SET_MODIFIER: u16 = 3;
14const TAG_INSERT: u16 = 4;
15const TAG_MOVE: u16 = 5;
16const TAG_REMOVE: u16 = 6;
17const TAG_SET_TEXT: u16 = 7;
18const TAG_APPEND_TEXT: u16 = 8;
19const TAG_SET_THEME: u16 = 9;
20const TAG_REGISTER_ASSET: u16 = 10;
21const TAG_RELEASE_ASSET: u16 = 11;
22const TAG_SHOW_MESSAGE: u16 = 12;
23const TAG_SET_WINDOW: u16 = 13;
24const TAG_POST_NOTIFICATION: u16 = 14;
25const TAG_WITHDRAW_NOTIFICATION: u16 = 15;
26const TAG_REQUEST_NOTIFICATION_PERMISSION: u16 = 16;
27const TAG_LAST: u16 = TAG_REQUEST_NOTIFICATION_PERMISSION;
30const ENVELOPE_LEN: usize = 12;
31pub const THEME_PAYLOAD_LEN: usize = 8 + 4 * TYPE_ROLE_COUNT + 8;
34const MIN_RECORD_LEN: usize = 4;
37
38const VALUE_NONE: u16 = 0;
39const VALUE_STRING: u16 = 1;
40const VALUE_BOOL: u16 = 2;
41const VALUE_I64: u16 = 3;
42const VALUE_F32: u16 = 4;
43const VALUE_BYTES: u16 = 5;
46
47#[derive(Clone, Debug, PartialEq)]
48pub enum PropertyValue<'a> {
49 None,
50 String(&'a str),
51 Bool(bool),
52 Integer(i64),
53 Float(f32),
54 Bytes(&'a [u8]),
55}
56
57#[derive(Clone, Debug, PartialEq)]
58pub enum Mutation<'a> {
59 Create {
60 node_id: u32,
61 widget: WidgetKind,
62 },
63 SetProp {
64 node_id: u32,
65 property: PropertyKind,
66 value: PropertyValue<'a>,
67 },
68 SetModifier {
69 node_id: u32,
70 index: u16,
71 modifier: Modifier,
72 },
73 Insert {
74 parent_id: u32,
75 node_id: u32,
76 index: u32,
77 },
78 Move {
79 parent_id: u32,
80 node_id: u32,
81 index: u32,
82 },
83 Remove {
84 node_id: u32,
85 },
86 SetText {
87 node_id: u32,
88 text: &'a str,
89 selection: Option<Selection>,
90 },
91 AppendText {
93 node_id: u32,
94 text: &'a str,
95 },
96 SetTheme(Theme),
98 SetWindow(crate::schema::Window),
106 RegisterAsset {
110 asset_id: u32,
111 kind: AssetKind,
112 bytes: &'a [u8],
113 },
114 ReleaseAsset {
116 asset_id: u32,
117 },
118 ShowMessage {
126 handler_id: u64,
127 text: &'a str,
128 action: &'a str,
129 duration: MessageDuration,
130 },
131 PostNotification {
139 key: &'a str,
140 title: &'a str,
141 body: &'a str,
142 channel: &'a str,
143 action_1: &'a str,
144 action_2: &'a str,
145 importance: NotificationImportance,
146 presentation: NotificationPresentation,
147 },
148 WithdrawNotification {
150 key: &'a str,
151 },
152 RequestNotificationPermission,
155}
156
157#[derive(Clone, Debug, Eq, PartialEq)]
158pub enum ProtocolError {
159 Truncated,
160 InvalidEnvelope,
161 InvalidRecordLength,
162 InvalidTag(u16),
163 InvalidWidget(u16),
164 InvalidProperty(u16),
165 InvalidValueKind(u16),
166 InvalidModifier(u16),
167 InvalidTheme(u16),
168 InvalidAssetKind(u16),
169 InvalidMessageDuration(u16),
170 InvalidNotification(u16),
172 InvalidStringRange,
173 InvalidUtf8,
174 LengthOverflow,
175}
176
177#[derive(Clone, Debug, PartialEq)]
178pub struct HostEvent<'a> {
179 pub node_id: u32,
180 pub handler_id: u64,
181 pub payload: crate::schema::EventPayload<'a>,
182}
183
184const EVENT_CLICK: u16 = 1;
185const EVENT_TEXT_CHANGED: u16 = 2;
186const EVENT_TEXT_SUBMITTED: u16 = 3;
187const EVENT_FOCUS_LOST: u16 = 4;
188const EVENT_PROTOCOL_ERROR: u16 = 5;
189const EVENT_KEY_DOWN: u16 = 6;
190const EVENT_RANGE_REQUESTED: u16 = 7;
191const EVENT_VALUE_CHANGED: u16 = 16;
194const EVENT_WINDOW_SIZE_CHANGED: u16 = 17;
195const EVENT_RESYNC: u16 = 18;
196const EVENT_LIFECYCLE_START: u16 = 19;
197const EVENT_LIFECYCLE_STOP: u16 = 20;
198const EVENT_DESIGN_SYSTEM_RESOLVED: u16 = 21;
199const EVENT_FILES_ENTERED: u16 = 22;
200const EVENT_FILES_DROPPED: u16 = 23;
201const EVENT_NOTIFICATION_ACTIVATED: u16 = 24;
202const EVENT_NOTIFICATION_PERMISSION_CHANGED: u16 = 25;
203
204const MODIFIER_SHIFT: u8 = 1 << 0;
205const MODIFIER_CTRL: u8 = 1 << 1;
206const MODIFIER_ALT: u8 = 1 << 2;
207const MODIFIER_META: u8 = 1 << 3;
208
209pub fn decode_event(bytes: &[u8]) -> Result<HostEvent<'_>, ProtocolError> {
211 if bytes.len() < 16 {
212 return Err(ProtocolError::Truncated);
213 }
214 let tag = read_u16(bytes, 0)?;
215 let record_len = usize::from(read_u16(bytes, 2)?);
216 if record_len < 16 || record_len % 4 != 0 || record_len > bytes.len() {
217 return Err(ProtocolError::InvalidRecordLength);
218 }
219 let node_id = read_u32(bytes, 4)?;
220 let handler_id = read_u64(bytes, 8)?;
221 let payload = match tag {
222 EVENT_CLICK if record_len == 16 => crate::schema::EventPayload::Clicked,
223 EVENT_TEXT_CHANGED if record_len == 24 => {
224 crate::schema::EventPayload::TextChanged(read_string(bytes, 16, record_len)?)
225 }
226 EVENT_TEXT_SUBMITTED if record_len == 24 => {
227 crate::schema::EventPayload::TextSubmitted(read_string(bytes, 16, record_len)?)
228 }
229 EVENT_FOCUS_LOST if record_len == 16 => crate::schema::EventPayload::FocusLost,
230 EVENT_PROTOCOL_ERROR if record_len == 28 => crate::schema::EventPayload::ProtocolError {
231 code: read_u32(bytes, 16)?,
232 message: read_string(bytes, 20, record_len)?,
233 },
234 EVENT_KEY_DOWN if record_len == 20 => {
235 let raw_key = read_u16(bytes, 16)?;
236 let modifiers = *bytes.get(18).ok_or(ProtocolError::Truncated)?;
237 crate::schema::EventPayload::KeyDown {
238 key: Key::try_from(raw_key)
239 .map_err(|()| ProtocolError::InvalidValueKind(raw_key))?,
240 shift_key: modifiers & MODIFIER_SHIFT != 0,
241 ctrl_key: modifiers & MODIFIER_CTRL != 0,
242 alt_key: modifiers & MODIFIER_ALT != 0,
243 meta_key: modifiers & MODIFIER_META != 0,
244 }
245 }
246 EVENT_RANGE_REQUESTED if record_len == 24 => crate::schema::EventPayload::RangeRequested {
247 start: read_u32(bytes, 16)?,
248 count: read_u32(bytes, 20)?,
249 },
250 EVENT_VALUE_CHANGED if record_len == 24 => {
251 crate::schema::EventPayload::ValueChanged(f64::from_bits(read_u64(bytes, 16)?))
252 }
253 EVENT_WINDOW_SIZE_CHANGED if record_len == 32 => {
254 let raw_class = read_u32(bytes, 24)?;
255 let class = u16::try_from(raw_class)
256 .ok()
257 .and_then(|value| crate::schema::WindowSizeClass::try_from(value).ok())
258 .ok_or(ProtocolError::InvalidValueKind(raw_class as u16))?;
259 let raw_height_class = read_u32(bytes, 28)?;
260 let height_class = u16::try_from(raw_height_class)
261 .ok()
262 .and_then(|value| crate::schema::WindowHeightClass::try_from(value).ok())
263 .ok_or(ProtocolError::InvalidValueKind(raw_height_class as u16))?;
264 crate::schema::EventPayload::WindowSizeChanged {
265 width_dp: f32::from_bits(read_u32(bytes, 16)?),
266 height_dp: f32::from_bits(read_u32(bytes, 20)?),
267 class,
268 height_class,
269 }
270 }
271 EVENT_DESIGN_SYSTEM_RESOLVED if record_len == 20 => {
272 let raw = read_u32(bytes, 16)?;
273 let system = u16::try_from(raw)
274 .ok()
275 .and_then(|tag| crate::schema::DesignSystem::try_from(tag).ok())
276 .ok_or(ProtocolError::InvalidValueKind(raw as u16))?;
277 crate::schema::EventPayload::DesignSystemResolved(system)
278 }
279 EVENT_FILES_ENTERED if record_len == 16 => crate::schema::EventPayload::FilesEntered,
280 EVENT_FILES_DROPPED if record_len == 24 => {
281 crate::schema::EventPayload::FilesDropped(read_string(bytes, 16, record_len)?)
282 }
283 EVENT_NOTIFICATION_ACTIVATED if record_len == 28 => {
284 let action = read_u32(bytes, 16)?;
285 if action > 2 {
288 return Err(ProtocolError::InvalidValueKind(
289 action.min(u32::from(u16::MAX)) as u16,
290 ));
291 }
292 crate::schema::EventPayload::NotificationActivated {
293 action,
294 key: read_string(bytes, 20, record_len)?,
295 }
296 }
297 EVENT_NOTIFICATION_PERMISSION_CHANGED if record_len == 20 => {
298 let raw = read_u32(bytes, 16)?;
299 let state = u16::try_from(raw)
300 .ok()
301 .and_then(|tag| NotificationPermission::try_from(tag).ok())
302 .ok_or(ProtocolError::InvalidValueKind(
303 raw.min(u32::from(u16::MAX)) as u16,
304 ))?;
305 crate::schema::EventPayload::NotificationPermissionChanged(state)
306 }
307 EVENT_RESYNC if record_len == 16 => crate::schema::EventPayload::Resync,
308 EVENT_LIFECYCLE_START if record_len == 16 => crate::schema::EventPayload::LifecycleStart,
309 EVENT_LIFECYCLE_STOP if record_len == 16 => crate::schema::EventPayload::LifecycleStop,
310 EVENT_CLICK..=EVENT_RANGE_REQUESTED
311 | EVENT_VALUE_CHANGED..=EVENT_NOTIFICATION_PERMISSION_CHANGED => {
312 return Err(ProtocolError::InvalidRecordLength);
313 }
314 other => return Err(ProtocolError::InvalidTag(other)),
315 };
316 Ok(HostEvent {
317 node_id,
318 handler_id,
319 payload,
320 })
321}
322
323pub fn encode_event(event: &HostEvent<'_>, output: &mut Vec<u8>) -> Result<(), ProtocolError> {
325 output.clear();
326 if let crate::schema::EventPayload::KeyDown {
327 key,
328 shift_key,
329 ctrl_key,
330 alt_key,
331 meta_key,
332 } = event.payload
333 {
334 output.extend_from_slice(&EVENT_KEY_DOWN.to_le_bytes());
335 output.extend_from_slice(&20_u16.to_le_bytes());
336 output.extend_from_slice(&event.node_id.to_le_bytes());
337 output.extend_from_slice(&event.handler_id.to_le_bytes());
338 output.extend_from_slice(&(key as u16).to_le_bytes());
339 let modifiers = (u8::from(shift_key) * MODIFIER_SHIFT)
340 | (u8::from(ctrl_key) * MODIFIER_CTRL)
341 | (u8::from(alt_key) * MODIFIER_ALT)
342 | (u8::from(meta_key) * MODIFIER_META);
343 output.push(modifiers);
344 output.push(0);
345 return Ok(());
346 }
347 if let crate::schema::EventPayload::ValueChanged(value) = event.payload {
348 output.extend_from_slice(&EVENT_VALUE_CHANGED.to_le_bytes());
349 output.extend_from_slice(&24_u16.to_le_bytes());
350 output.extend_from_slice(&event.node_id.to_le_bytes());
351 output.extend_from_slice(&event.handler_id.to_le_bytes());
352 output.extend_from_slice(&value.to_bits().to_le_bytes());
353 return Ok(());
354 }
355 if let crate::schema::EventPayload::WindowSizeChanged {
356 width_dp,
357 height_dp,
358 class,
359 height_class,
360 } = event.payload
361 {
362 output.extend_from_slice(&EVENT_WINDOW_SIZE_CHANGED.to_le_bytes());
363 output.extend_from_slice(&32_u16.to_le_bytes());
364 output.extend_from_slice(&event.node_id.to_le_bytes());
365 output.extend_from_slice(&event.handler_id.to_le_bytes());
366 output.extend_from_slice(&width_dp.to_bits().to_le_bytes());
367 output.extend_from_slice(&height_dp.to_bits().to_le_bytes());
368 output.extend_from_slice(&u32::from(u16::from(class)).to_le_bytes());
369 output.extend_from_slice(&u32::from(u16::from(height_class)).to_le_bytes());
370 return Ok(());
371 }
372 if let crate::schema::EventPayload::DesignSystemResolved(system) = event.payload {
373 output.extend_from_slice(&EVENT_DESIGN_SYSTEM_RESOLVED.to_le_bytes());
374 output.extend_from_slice(&20_u16.to_le_bytes());
375 output.extend_from_slice(&event.node_id.to_le_bytes());
376 output.extend_from_slice(&event.handler_id.to_le_bytes());
377 output.extend_from_slice(&u32::from(u16::from(system)).to_le_bytes());
380 return Ok(());
381 }
382 if let crate::schema::EventPayload::NotificationPermissionChanged(state) = event.payload {
383 output.extend_from_slice(&EVENT_NOTIFICATION_PERMISSION_CHANGED.to_le_bytes());
384 output.extend_from_slice(&20_u16.to_le_bytes());
385 output.extend_from_slice(&event.node_id.to_le_bytes());
386 output.extend_from_slice(&event.handler_id.to_le_bytes());
387 output.extend_from_slice(&u32::from(u16::from(state)).to_le_bytes());
388 return Ok(());
389 }
390 if let crate::schema::EventPayload::RangeRequested { start, count } = event.payload {
391 output.extend_from_slice(&EVENT_RANGE_REQUESTED.to_le_bytes());
392 output.extend_from_slice(&24_u16.to_le_bytes());
393 output.extend_from_slice(&event.node_id.to_le_bytes());
394 output.extend_from_slice(&event.handler_id.to_le_bytes());
395 output.extend_from_slice(&start.to_le_bytes());
396 output.extend_from_slice(&count.to_le_bytes());
397 return Ok(());
398 }
399 let (tag, record_len, text, error_code) = match &event.payload {
400 crate::schema::EventPayload::Clicked => (EVENT_CLICK, 16_u16, None, None),
401 crate::schema::EventPayload::TextChanged(value) => {
402 (EVENT_TEXT_CHANGED, 24, Some(*value), None)
403 }
404 crate::schema::EventPayload::TextSubmitted(value) => {
405 (EVENT_TEXT_SUBMITTED, 24, Some(*value), None)
406 }
407 crate::schema::EventPayload::FocusLost => (EVENT_FOCUS_LOST, 16, None, None),
408 crate::schema::EventPayload::FilesEntered => (EVENT_FILES_ENTERED, 16, None, None),
409 crate::schema::EventPayload::FilesDropped(paths) => {
410 (EVENT_FILES_DROPPED, 24, Some(*paths), None)
411 }
412 crate::schema::EventPayload::Resync => (EVENT_RESYNC, 16, None, None),
413 crate::schema::EventPayload::LifecycleStart => (EVENT_LIFECYCLE_START, 16, None, None),
414 crate::schema::EventPayload::LifecycleStop => (EVENT_LIFECYCLE_STOP, 16, None, None),
415 crate::schema::EventPayload::ProtocolError { code, message } => {
416 (EVENT_PROTOCOL_ERROR, 28, Some(*message), Some(*code))
417 }
418 crate::schema::EventPayload::NotificationActivated { action, key } => {
420 (EVENT_NOTIFICATION_ACTIVATED, 28, Some(*key), Some(*action))
421 }
422 crate::schema::EventPayload::KeyDown { .. }
423 | crate::schema::EventPayload::RangeRequested { .. }
424 | crate::schema::EventPayload::ValueChanged(_)
425 | crate::schema::EventPayload::WindowSizeChanged { .. }
426 | crate::schema::EventPayload::DesignSystemResolved(_)
427 | crate::schema::EventPayload::NotificationPermissionChanged(_) => unreachable!(),
428 };
429 output.extend_from_slice(&tag.to_le_bytes());
430 output.extend_from_slice(&record_len.to_le_bytes());
431 output.extend_from_slice(&event.node_id.to_le_bytes());
432 output.extend_from_slice(&event.handler_id.to_le_bytes());
433 if let Some(code) = error_code {
434 output.extend_from_slice(&code.to_le_bytes());
435 }
436 if let Some(text) = text {
437 let offset = u32::from(record_len);
438 let len = u32::try_from(text.len()).map_err(|_| ProtocolError::LengthOverflow)?;
439 output.extend_from_slice(&offset.to_le_bytes());
440 output.extend_from_slice(&len.to_le_bytes());
441 output.extend_from_slice(text.as_bytes());
442 }
443 Ok(())
444}
445
446impl fmt::Display for ProtocolError {
447 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
448 write!(formatter, "protocol error: {self:?}")
449 }
450}
451
452impl std::error::Error for ProtocolError {}
453
454pub struct BatchEncoder {
456 records: Vec<u8>,
457 strings: Vec<u8>,
458 string_fixups: Vec<usize>,
459 arena: Vec<u8>,
460 record_count: u32,
461}
462
463impl Default for BatchEncoder {
464 fn default() -> Self {
465 Self::with_capacity(4096, 1024, 64)
466 }
467}
468
469impl BatchEncoder {
470 pub fn with_capacity(record_bytes: usize, string_bytes: usize, string_count: usize) -> Self {
471 let mut encoder = Self {
472 records: Vec::with_capacity(record_bytes),
473 strings: Vec::with_capacity(string_bytes),
474 string_fixups: Vec::with_capacity(string_count),
475 arena: Vec::with_capacity(record_bytes + string_bytes),
476 record_count: 0,
477 };
478 encoder.clear();
479 encoder
480 }
481
482 pub fn clear(&mut self) {
483 self.records.clear();
484 self.strings.clear();
485 self.string_fixups.clear();
486 self.arena.clear();
487 self.record_count = 0;
488 self.begin_record(TAG_ENVELOPE, 8);
489 self.put_u32(0);
490 self.put_u32(0);
491 }
492
493 pub fn encode(&mut self, mutation: &Mutation<'_>) -> Result<(), ProtocolError> {
494 match mutation {
495 Mutation::Create { node_id, widget } => {
496 self.begin_record(TAG_CREATE, 8);
497 self.put_u32(*node_id);
498 self.put_u16(*widget as u16);
499 self.put_u16(0);
500 }
501 Mutation::SetProp {
502 node_id,
503 property,
504 value,
505 } => {
506 self.begin_record(TAG_SET_PROP, 20);
507 self.put_u32(*node_id);
508 self.put_u16(*property as u16);
509 match value {
510 PropertyValue::None => {
511 self.put_u16(VALUE_NONE);
512 self.put_u64(0);
513 self.put_u32(0);
514 }
515 PropertyValue::String(value) => {
516 self.put_u16(VALUE_STRING);
517 self.put_string_ref(value)?;
518 self.put_u32(0);
519 }
520 PropertyValue::Bool(value) => {
521 self.put_u16(VALUE_BOOL);
522 self.put_u64(u64::from(*value));
523 self.put_u32(0);
524 }
525 PropertyValue::Integer(value) => {
526 self.put_u16(VALUE_I64);
527 self.put_u64(*value as u64);
528 self.put_u32(0);
529 }
530 PropertyValue::Float(value) => {
531 self.put_u16(VALUE_F32);
532 self.put_u64(u64::from(value.to_bits()));
533 self.put_u32(0);
534 }
535 PropertyValue::Bytes(value) => {
536 self.put_u16(VALUE_BYTES);
537 self.put_bytes_ref(value)?;
538 self.put_u32(0);
539 }
540 }
541 }
542 Mutation::SetModifier {
543 node_id,
544 index,
545 modifier,
546 } => {
547 self.begin_record(TAG_SET_MODIFIER, 24);
548 self.put_u32(*node_id);
549 self.put_u16(*index);
550 let (tag, first, second) = modifier_fields(modifier);
551 self.put_u16(tag);
552 self.put_u64(first);
553 self.put_u64(second);
554 }
555 Mutation::Insert {
556 parent_id,
557 node_id,
558 index,
559 } => {
560 self.begin_record(TAG_INSERT, 12);
561 self.put_u32(*parent_id);
562 self.put_u32(*node_id);
563 self.put_u32(*index);
564 }
565 Mutation::Move {
566 parent_id,
567 node_id,
568 index,
569 } => {
570 self.begin_record(TAG_MOVE, 12);
571 self.put_u32(*parent_id);
572 self.put_u32(*node_id);
573 self.put_u32(*index);
574 }
575 Mutation::Remove { node_id } => {
576 self.begin_record(TAG_REMOVE, 4);
577 self.put_u32(*node_id);
578 }
579 Mutation::SetText {
580 node_id,
581 text,
582 selection,
583 } => {
584 self.begin_record(TAG_SET_TEXT, 20);
585 self.put_u32(*node_id);
586 self.put_string_ref(text)?;
587 let (start, end) =
588 selection.map_or((u32::MAX, u32::MAX), |value| (value.start, value.end));
589 self.put_u32(start);
590 self.put_u32(end);
591 }
592 Mutation::AppendText { node_id, text } => {
593 self.begin_record(TAG_APPEND_TEXT, 12);
594 self.put_u32(*node_id);
595 self.put_string_ref(text)?;
596 }
597 Mutation::RegisterAsset {
598 asset_id,
599 kind,
600 bytes,
601 } => {
602 self.begin_record(TAG_REGISTER_ASSET, 16);
603 self.put_u32(*asset_id);
604 self.put_u16(*kind as u16);
605 self.put_u16(0);
606 self.put_bytes_ref(bytes)?;
607 }
608 Mutation::ReleaseAsset { asset_id } => {
609 self.begin_record(TAG_RELEASE_ASSET, 4);
610 self.put_u32(*asset_id);
611 }
612 Mutation::ShowMessage {
613 handler_id,
614 text,
615 action,
616 duration,
617 } => {
618 self.begin_record(TAG_SHOW_MESSAGE, 28);
619 self.put_u64(*handler_id);
620 self.put_string_ref(text)?;
621 self.put_string_ref(action)?;
622 self.put_u16(*duration as u16);
623 self.put_u16(0);
624 }
625 Mutation::SetTheme(theme) => {
626 self.begin_record(TAG_SET_THEME, THEME_PAYLOAD_LEN as u16);
627 self.put_u16(theme.design_system as u16);
628 self.put_u16(theme.fallback as u16);
629 self.put_u16(theme.color_scheme as u16);
630 self.put_u16(u16::from(theme.adaptive));
631 for asset in theme.fonts {
635 self.put_u32(asset);
636 }
637 self.put_palette_ref(theme.palette)?;
642 }
643 Mutation::SetWindow(window) => {
644 self.begin_record(TAG_SET_WINDOW, 28);
645 self.put_u16(window.chrome as u16);
646 self.put_u16(window.title_bar as u16);
647 self.put_u16(window.width);
648 self.put_u16(window.height);
649 self.put_u16(window.min_width);
650 self.put_u16(window.min_height);
651 self.put_u16(u16::from(window.resizable));
652 self.put_u16(0);
655 self.put_string_ref(window.title)?;
656 self.put_u32(window.icon);
657 }
658 Mutation::PostNotification {
659 key,
660 title,
661 body,
662 channel,
663 action_1,
664 action_2,
665 importance,
666 presentation,
667 } => {
668 self.begin_record(TAG_POST_NOTIFICATION, 52);
669 self.put_string_ref(key)?;
670 self.put_string_ref(title)?;
671 self.put_string_ref(body)?;
672 self.put_string_ref(channel)?;
673 self.put_string_ref(action_1)?;
674 self.put_string_ref(action_2)?;
675 self.put_u16(*importance as u16);
676 self.put_u16(*presentation as u16);
677 }
678 Mutation::WithdrawNotification { key } => {
679 self.begin_record(TAG_WITHDRAW_NOTIFICATION, 8);
680 self.put_string_ref(key)?;
681 }
682 Mutation::RequestNotificationPermission => {
683 self.begin_record(TAG_REQUEST_NOTIFICATION_PERMISSION, 0);
684 }
685 }
686 self.record_count = self
687 .record_count
688 .checked_add(1)
689 .ok_or(ProtocolError::LengthOverflow)?;
690 Ok(())
691 }
692
693 pub fn finish(&mut self) -> Result<&[u8], ProtocolError> {
694 let records_len =
695 u32::try_from(self.records.len()).map_err(|_| ProtocolError::LengthOverflow)?;
696 self.records[4..8].copy_from_slice(&records_len.to_le_bytes());
697 self.records[8..12].copy_from_slice(&self.record_count.to_le_bytes());
698 let total = self
699 .records
700 .len()
701 .checked_add(self.strings.len())
702 .ok_or(ProtocolError::LengthOverflow)?;
703 self.arena.clear();
704 if self.arena.capacity() < total {
705 self.arena.reserve(total);
706 }
707 self.arena.extend_from_slice(&self.records);
708 self.arena.extend_from_slice(&self.strings);
709 for &position in &self.string_fixups {
710 let relative = read_u32(&self.arena, position)?;
711 let absolute = records_len
712 .checked_add(relative)
713 .ok_or(ProtocolError::LengthOverflow)?;
714 self.arena[position..position + 4].copy_from_slice(&absolute.to_le_bytes());
715 }
716 Ok(&self.arena)
717 }
718
719 pub fn arena(&self) -> (*const u8, usize) {
726 (self.arena.as_ptr(), self.arena.capacity())
727 }
728
729 pub fn capacity(&self) -> usize {
730 self.records.capacity() + self.strings.capacity() + self.arena.capacity()
731 }
732
733 fn begin_record(&mut self, tag: u16, payload_len: u16) {
734 self.put_u16(tag);
735 self.put_u16(payload_len + 4);
736 }
737
738 fn put_palette_ref(
741 &mut self,
742 palette: Option<&crate::palette::Palette>,
743 ) -> Result<(), ProtocolError> {
744 let start = self.strings.len();
745 let offset = u32::try_from(start).map_err(|_| ProtocolError::LengthOverflow)?;
746 if let Some(palette) = palette {
747 palette.write_into(&mut self.strings);
748 }
749 let len =
750 u32::try_from(self.strings.len() - start).map_err(|_| ProtocolError::LengthOverflow)?;
751 self.string_fixups.push(self.records.len());
752 self.put_u32(offset);
753 self.put_u32(len);
754 Ok(())
755 }
756
757 fn put_string_ref(&mut self, value: &str) -> Result<(), ProtocolError> {
758 self.put_bytes_ref(value.as_bytes())
759 }
760
761 fn put_bytes_ref(&mut self, value: &[u8]) -> Result<(), ProtocolError> {
764 let offset =
765 u32::try_from(self.strings.len()).map_err(|_| ProtocolError::LengthOverflow)?;
766 let len = u32::try_from(value.len()).map_err(|_| ProtocolError::LengthOverflow)?;
767 self.string_fixups.push(self.records.len());
768 self.put_u32(offset);
769 self.put_u32(len);
770 self.strings.extend_from_slice(value);
771 Ok(())
772 }
773
774 fn put_u16(&mut self, value: u16) {
775 self.records.extend_from_slice(&value.to_le_bytes());
776 }
777
778 fn put_u32(&mut self, value: u32) {
779 self.records.extend_from_slice(&value.to_le_bytes());
780 }
781
782 fn put_u64(&mut self, value: u64) {
783 self.records.extend_from_slice(&value.to_le_bytes());
784 }
785}
786
787pub fn decode_batch(bytes: &[u8]) -> Result<Vec<Mutation<'_>>, ProtocolError> {
788 if bytes.len() < ENVELOPE_LEN
789 || read_u16(bytes, 0)? != TAG_ENVELOPE
790 || read_u16(bytes, 2)? != 12
791 {
792 return Err(ProtocolError::InvalidEnvelope);
793 }
794 let records_len =
795 usize::try_from(read_u32(bytes, 4)?).map_err(|_| ProtocolError::LengthOverflow)?;
796 let count = usize::try_from(read_u32(bytes, 8)?).map_err(|_| ProtocolError::LengthOverflow)?;
797 if records_len < ENVELOPE_LEN || records_len > bytes.len() || records_len % 4 != 0 {
798 return Err(ProtocolError::InvalidEnvelope);
799 }
800 let capacity = count.min((records_len - ENVELOPE_LEN) / MIN_RECORD_LEN);
805 let mut output = Vec::with_capacity(capacity);
806 let mut position = ENVELOPE_LEN;
807 while position < records_len {
808 let tag = read_u16(bytes, position)?;
809 let len = usize::from(read_u16(bytes, position + 2)?);
810 if len < 4 || len % 4 != 0 || position + len > records_len {
811 return Err(ProtocolError::InvalidRecordLength);
812 }
813 let payload = position + 4;
814 let mutation = match tag {
815 TAG_CREATE if len == 12 => Mutation::Create {
816 node_id: read_u32(bytes, payload)?,
817 widget: WidgetKind::try_from(read_u16(bytes, payload + 4)?).map_err(|()| {
818 ProtocolError::InvalidWidget(read_u16(bytes, payload + 4).unwrap_or(0))
819 })?,
820 },
821 TAG_SET_PROP if len == 24 => {
822 let property_raw = read_u16(bytes, payload + 4)?;
823 let value_kind = read_u16(bytes, payload + 6)?;
824 let value = match value_kind {
825 VALUE_NONE => PropertyValue::None,
826 VALUE_STRING => {
827 PropertyValue::String(read_string(bytes, payload + 8, records_len)?)
828 }
829 VALUE_BOOL => PropertyValue::Bool(read_u64(bytes, payload + 8)? != 0),
830 VALUE_I64 => PropertyValue::Integer(read_u64(bytes, payload + 8)? as i64),
831 VALUE_F32 => {
832 PropertyValue::Float(f32::from_bits(read_u64(bytes, payload + 8)? as u32))
833 }
834 VALUE_BYTES => {
835 PropertyValue::Bytes(read_bytes(bytes, payload + 8, records_len)?)
836 }
837 other => return Err(ProtocolError::InvalidValueKind(other)),
838 };
839 Mutation::SetProp {
840 node_id: read_u32(bytes, payload)?,
841 property: PropertyKind::try_from(property_raw)
842 .map_err(|()| ProtocolError::InvalidProperty(property_raw))?,
843 value,
844 }
845 }
846 TAG_SET_MODIFIER if len == 28 => Mutation::SetModifier {
847 node_id: read_u32(bytes, payload)?,
848 index: read_u16(bytes, payload + 4)?,
849 modifier: decode_modifier(
850 read_u16(bytes, payload + 6)?,
851 read_u64(bytes, payload + 8)?,
852 read_u64(bytes, payload + 16)?,
853 )?,
854 },
855 TAG_INSERT if len == 16 => Mutation::Insert {
856 parent_id: read_u32(bytes, payload)?,
857 node_id: read_u32(bytes, payload + 4)?,
858 index: read_u32(bytes, payload + 8)?,
859 },
860 TAG_MOVE if len == 16 => Mutation::Move {
861 parent_id: read_u32(bytes, payload)?,
862 node_id: read_u32(bytes, payload + 4)?,
863 index: read_u32(bytes, payload + 8)?,
864 },
865 TAG_REMOVE if len == 8 => Mutation::Remove {
866 node_id: read_u32(bytes, payload)?,
867 },
868 TAG_SET_TEXT if len == 24 => {
869 let start = read_u32(bytes, payload + 12)?;
870 let end = read_u32(bytes, payload + 16)?;
871 Mutation::SetText {
872 node_id: read_u32(bytes, payload)?,
873 text: read_string(bytes, payload + 4, records_len)?,
874 selection: (start != u32::MAX || end != u32::MAX)
875 .then_some(Selection { start, end }),
876 }
877 }
878 TAG_APPEND_TEXT if len == 16 => Mutation::AppendText {
879 node_id: read_u32(bytes, payload)?,
880 text: read_string(bytes, payload + 4, records_len)?,
881 },
882 TAG_SET_THEME if len == 4 + THEME_PAYLOAD_LEN => {
883 let design_system = read_u16(bytes, payload)?;
884 let fallback = read_u16(bytes, payload + 2)?;
885 let color_scheme = read_u16(bytes, payload + 4)?;
886 let adaptive = read_u16(bytes, payload + 6)?;
887 if adaptive > 1 {
888 return Err(ProtocolError::InvalidTheme(adaptive));
889 }
890 let mut fonts = [0u32; TYPE_ROLE_COUNT];
891 for (index, slot) in fonts.iter_mut().enumerate() {
892 *slot = read_u32(bytes, payload + 8 + 4 * index)?;
893 }
894 let palette_bytes =
899 read_bytes(bytes, payload + 8 + 4 * TYPE_ROLE_COUNT, records_len)?;
900 let palette = if palette_bytes.is_empty() {
901 None
902 } else {
903 let (palette, _problems) = crate::palette::decode_palette(palette_bytes);
904 Some(&*Box::leak(Box::new(palette)))
905 };
906 Mutation::SetTheme(Theme {
907 design_system: DesignSystem::try_from(design_system)
908 .map_err(|()| ProtocolError::InvalidTheme(design_system))?,
909 fallback: DesignSystem::try_from(fallback)
910 .map_err(|()| ProtocolError::InvalidTheme(fallback))?,
911 color_scheme: ColorScheme::try_from(color_scheme)
912 .map_err(|()| ProtocolError::InvalidTheme(color_scheme))?,
913 adaptive: adaptive == 1,
914 fonts,
915 palette,
916 })
917 }
918 TAG_SET_WINDOW if len == 32 => {
919 let chrome = read_u16(bytes, payload)?;
920 let title_bar = read_u16(bytes, payload + 2)?;
921 let resizable = read_u16(bytes, payload + 12)?;
922 if resizable > 1 {
923 return Err(ProtocolError::InvalidValueKind(resizable));
924 }
925 Mutation::SetWindow(crate::schema::Window {
926 chrome: crate::schema::Chrome::try_from(chrome)
927 .map_err(|()| ProtocolError::InvalidValueKind(chrome))?,
928 title_bar: crate::schema::TitleBar::try_from(title_bar)
929 .map_err(|()| ProtocolError::InvalidValueKind(title_bar))?,
930 width: read_u16(bytes, payload + 4)?,
931 height: read_u16(bytes, payload + 6)?,
932 min_width: read_u16(bytes, payload + 8)?,
933 min_height: read_u16(bytes, payload + 10)?,
934 resizable: resizable == 1,
935 icon: read_u32(bytes, payload + 24)?,
939 title: Box::leak(
940 read_string(bytes, payload + 16, records_len)?
941 .to_owned()
942 .into_boxed_str(),
943 ),
944 })
945 }
946 TAG_REGISTER_ASSET if len == 20 => {
947 let raw_kind = read_u16(bytes, payload + 4)?;
948 Mutation::RegisterAsset {
949 asset_id: read_u32(bytes, payload)?,
950 kind: AssetKind::try_from(raw_kind)
951 .map_err(|()| ProtocolError::InvalidAssetKind(raw_kind))?,
952 bytes: read_bytes(bytes, payload + 8, records_len)?,
953 }
954 }
955 TAG_RELEASE_ASSET if len == 8 => Mutation::ReleaseAsset {
956 asset_id: read_u32(bytes, payload)?,
957 },
958 TAG_SHOW_MESSAGE if len == 32 => {
959 let raw_duration = read_u16(bytes, payload + 24)?;
960 Mutation::ShowMessage {
961 handler_id: read_u64(bytes, payload)?,
962 text: read_string(bytes, payload + 8, records_len)?,
963 action: read_string(bytes, payload + 16, records_len)?,
964 duration: MessageDuration::try_from(raw_duration)
965 .map_err(|()| ProtocolError::InvalidMessageDuration(raw_duration))?,
966 }
967 }
968 TAG_POST_NOTIFICATION if len == 56 => {
969 let raw_importance = read_u16(bytes, payload + 48)?;
970 let raw_presentation = read_u16(bytes, payload + 50)?;
971 Mutation::PostNotification {
972 key: read_string(bytes, payload, records_len)?,
973 title: read_string(bytes, payload + 8, records_len)?,
974 body: read_string(bytes, payload + 16, records_len)?,
975 channel: read_string(bytes, payload + 24, records_len)?,
976 action_1: read_string(bytes, payload + 32, records_len)?,
977 action_2: read_string(bytes, payload + 40, records_len)?,
978 importance: NotificationImportance::try_from(raw_importance)
979 .map_err(|()| ProtocolError::InvalidNotification(raw_importance))?,
980 presentation: NotificationPresentation::try_from(raw_presentation)
981 .map_err(|()| ProtocolError::InvalidNotification(raw_presentation))?,
982 }
983 }
984 TAG_WITHDRAW_NOTIFICATION if len == 12 => Mutation::WithdrawNotification {
985 key: read_string(bytes, payload, records_len)?,
986 },
987 TAG_REQUEST_NOTIFICATION_PERMISSION if len == 4 => {
988 Mutation::RequestNotificationPermission
989 }
990 TAG_CREATE..=TAG_LAST => {
991 return Err(ProtocolError::InvalidRecordLength);
992 }
993 other => return Err(ProtocolError::InvalidTag(other)),
994 };
995 output.push(mutation);
996 position += len;
997 }
998 if output.len() != count {
999 return Err(ProtocolError::InvalidEnvelope);
1000 }
1001 Ok(output)
1002}
1003
1004const fn pack_floats(low: f32, high: f32) -> u64 {
1006 (low.to_bits() as u64) | ((high.to_bits() as u64) << 32)
1007}
1008
1009const fn unpack_low(word: u64) -> f32 {
1010 f32::from_bits(word as u32)
1011}
1012
1013const fn unpack_high(word: u64) -> f32 {
1014 f32::from_bits((word >> 32) as u32)
1015}
1016
1017fn modifier_fields(modifier: &Modifier) -> (u16, u64, u64) {
1018 match modifier {
1019 Modifier::Empty => (0, 0, 0),
1020 Modifier::Padding(value) => (1, u64::from(value.to_bits()), 0),
1021 Modifier::FillMaxWidth => (2, 0, 0),
1022 Modifier::FillMaxHeight => (3, 0, 0),
1023 Modifier::Width(value) => (4, u64::from(value.to_bits()), 0),
1024 Modifier::Height(value) => (5, u64::from(value.to_bits()), 0),
1025 Modifier::Size { width, height } => {
1026 (6, u64::from(width.to_bits()), u64::from(height.to_bits()))
1027 }
1028 Modifier::Background(paint) => (7, paint.to_bits(), 0),
1029 Modifier::Clickable { handler_id } => (8, *handler_id, 0),
1030 Modifier::PaddingRole(role) => (9, *role as u64, 0),
1031 Modifier::PaddingEach {
1032 start,
1033 top,
1034 end,
1035 bottom,
1036 } => (10, pack_floats(*start, *top), pack_floats(*end, *bottom)),
1037 Modifier::Weight(value) => (11, u64::from(value.to_bits()), 0),
1038 Modifier::Shape {
1039 top_start,
1040 top_end,
1041 bottom_end,
1042 bottom_start,
1043 } => (
1044 12,
1045 pack_floats(*top_start, *top_end),
1046 pack_floats(*bottom_end, *bottom_start),
1047 ),
1048 Modifier::ShapeRole(role) => (13, *role as u64, 0),
1049 Modifier::Border { width, paint } => (14, u64::from(width.to_bits()), paint.to_bits()),
1050 Modifier::Elevation(dp) => (15, u64::from(dp.to_bits()), 0),
1051 Modifier::ObserveSize { token } => (16, u64::from(*token), 0),
1052 Modifier::Motion(role) => (17, *role as u64, 0),
1053 Modifier::Material(role) => (18, *role as u64, 0),
1054 }
1055}
1056
1057fn decode_role<T: TryFrom<u16, Error = ()>>(first: u64) -> Result<T, ProtocolError> {
1058 let tag = u16::try_from(first).map_err(|_| ProtocolError::InvalidModifier(0))?;
1059 T::try_from(tag).map_err(|()| ProtocolError::InvalidModifier(tag))
1060}
1061
1062fn decode_paint(bits: u64) -> Result<Paint, ProtocolError> {
1063 Paint::from_bits(bits).ok_or(ProtocolError::InvalidModifier(0))
1064}
1065
1066fn decode_modifier(tag: u16, first: u64, second: u64) -> Result<Modifier, ProtocolError> {
1067 match tag {
1068 0 => Ok(Modifier::Empty),
1069 1 => Ok(Modifier::Padding(f32::from_bits(first as u32))),
1070 2 => Ok(Modifier::FillMaxWidth),
1071 3 => Ok(Modifier::FillMaxHeight),
1072 4 => Ok(Modifier::Width(f32::from_bits(first as u32))),
1073 5 => Ok(Modifier::Height(f32::from_bits(first as u32))),
1074 6 => Ok(Modifier::Size {
1075 width: f32::from_bits(first as u32),
1076 height: f32::from_bits(second as u32),
1077 }),
1078 7 => Ok(Modifier::Background(decode_paint(first)?)),
1079 8 => Ok(Modifier::Clickable { handler_id: first }),
1080 9 => Ok(Modifier::PaddingRole(decode_role::<SpaceRole>(first)?)),
1081 10 => Ok(Modifier::PaddingEach {
1082 start: unpack_low(first),
1083 top: unpack_high(first),
1084 end: unpack_low(second),
1085 bottom: unpack_high(second),
1086 }),
1087 11 => Ok(Modifier::Weight(f32::from_bits(first as u32))),
1088 12 => Ok(Modifier::Shape {
1089 top_start: unpack_low(first),
1090 top_end: unpack_high(first),
1091 bottom_end: unpack_low(second),
1092 bottom_start: unpack_high(second),
1093 }),
1094 13 => Ok(Modifier::ShapeRole(decode_role::<ShapeRole>(first)?)),
1095 14 => Ok(Modifier::Border {
1096 width: f32::from_bits(first as u32),
1097 paint: decode_paint(second)?,
1098 }),
1099 15 => Ok(Modifier::Elevation(f32::from_bits(first as u32))),
1100 16 => Ok(Modifier::ObserveSize {
1101 token: first as u32,
1102 }),
1103 17 => Ok(Modifier::Motion(decode_role::<MotionRole>(first)?)),
1104 18 => Ok(Modifier::Material(decode_role::<MaterialRole>(first)?)),
1105 other => Err(ProtocolError::InvalidModifier(other)),
1106 }
1107}
1108
1109fn read_string(bytes: &[u8], position: usize, arena_start: usize) -> Result<&str, ProtocolError> {
1116 let value = read_bytes(bytes, position, arena_start)?;
1117 std::str::from_utf8(value).map_err(|_| ProtocolError::InvalidUtf8)
1118}
1119
1120fn read_bytes(bytes: &[u8], position: usize, arena_start: usize) -> Result<&[u8], ProtocolError> {
1122 let offset =
1123 usize::try_from(read_u32(bytes, position)?).map_err(|_| ProtocolError::LengthOverflow)?;
1124 let len = usize::try_from(read_u32(bytes, position + 4)?)
1125 .map_err(|_| ProtocolError::LengthOverflow)?;
1126 let end = offset
1127 .checked_add(len)
1128 .ok_or(ProtocolError::InvalidStringRange)?;
1129 if offset < arena_start {
1130 return Err(ProtocolError::InvalidStringRange);
1131 }
1132 bytes
1133 .get(offset..end)
1134 .ok_or(ProtocolError::InvalidStringRange)
1135}
1136
1137fn read_u16(bytes: &[u8], position: usize) -> Result<u16, ProtocolError> {
1138 let value = bytes
1139 .get(position..position + 2)
1140 .ok_or(ProtocolError::Truncated)?;
1141 Ok(u16::from_le_bytes([value[0], value[1]]))
1142}
1143
1144fn read_u32(bytes: &[u8], position: usize) -> Result<u32, ProtocolError> {
1145 let value = bytes
1146 .get(position..position + 4)
1147 .ok_or(ProtocolError::Truncated)?;
1148 Ok(u32::from_le_bytes([value[0], value[1], value[2], value[3]]))
1149}
1150
1151fn read_u64(bytes: &[u8], position: usize) -> Result<u64, ProtocolError> {
1152 let value = bytes
1153 .get(position..position + 8)
1154 .ok_or(ProtocolError::Truncated)?;
1155 Ok(u64::from_le_bytes(
1156 value.try_into().map_err(|_| ProtocolError::Truncated)?,
1157 ))
1158}
1159
1160#[cfg(test)]
1161mod tests {
1162 use super::*;
1163 use crate::schema::TypeRole;
1164
1165 #[test]
1166 fn round_trips_all_m0_mutations() {
1167 let mutations = [
1168 Mutation::Create {
1169 node_id: 1,
1170 widget: WidgetKind::Column,
1171 },
1172 Mutation::SetProp {
1173 node_id: 2,
1174 property: PropertyKind::Text,
1175 value: PropertyValue::String("안녕"),
1176 },
1177 Mutation::SetProp {
1178 node_id: 3,
1179 property: PropertyKind::Enabled,
1180 value: PropertyValue::Bool(true),
1181 },
1182 Mutation::SetModifier {
1183 node_id: 1,
1184 index: 0,
1185 modifier: Modifier::Padding(16.0),
1186 },
1187 Mutation::Insert {
1188 parent_id: 1,
1189 node_id: 2,
1190 index: 0,
1191 },
1192 Mutation::Move {
1193 parent_id: 1,
1194 node_id: 2,
1195 index: 1,
1196 },
1197 Mutation::Remove { node_id: 3 },
1198 Mutation::SetText {
1199 node_id: 4,
1200 text: "compose",
1201 selection: Some(Selection { start: 1, end: 4 }),
1202 },
1203 Mutation::AppendText {
1204 node_id: 4,
1205 text: " token",
1206 },
1207 ];
1208 let mut encoder = BatchEncoder::default();
1209 for mutation in &mutations {
1210 encoder.encode(mutation).unwrap();
1211 }
1212 let decoded = decode_batch(encoder.finish().unwrap()).unwrap();
1213 assert_eq!(decoded, mutations);
1214 }
1215
1216 #[test]
1219 fn fr13_paint_role_and_literal_share_one_record_length() {
1220 let encode_one = |modifier| {
1221 let mut encoder = BatchEncoder::default();
1222 encoder
1223 .encode(&Mutation::SetModifier {
1224 node_id: 1,
1225 index: 0,
1226 modifier,
1227 })
1228 .unwrap();
1229 encoder.finish().unwrap().to_vec()
1230 };
1231 let role = encode_one(Modifier::Background(Paint::Role(
1232 crate::schema::ColorRole::Surface,
1233 )));
1234 let literal = encode_one(Modifier::Background(Paint::Literal(
1235 crate::schema::Color::rgb(0x1b1b1f),
1236 )));
1237 assert_eq!(role.len(), literal.len());
1238 assert_eq!(
1239 decode_batch(&role).unwrap(),
1240 [Mutation::SetModifier {
1241 node_id: 1,
1242 index: 0,
1243 modifier: Modifier::Background(Paint::Role(crate::schema::ColorRole::Surface)),
1244 }]
1245 );
1246 assert_eq!(
1247 decode_batch(&literal).unwrap(),
1248 [Mutation::SetModifier {
1249 node_id: 1,
1250 index: 0,
1251 modifier: Modifier::Background(Paint::Literal(crate::schema::Color::rgb(0x1b1b1f))),
1252 }]
1253 );
1254 }
1255
1256 #[test]
1258 fn fr13_design_modifiers_round_trip_in_two_words() {
1259 let modifiers = [
1260 Modifier::PaddingRole(SpaceRole::Md),
1261 Modifier::PaddingEach {
1262 start: 1.0,
1263 top: 2.0,
1264 end: 3.0,
1265 bottom: 4.0,
1266 },
1267 Modifier::Weight(0.25),
1268 Modifier::Shape {
1269 top_start: 4.0,
1270 top_end: 8.0,
1271 bottom_end: 12.0,
1272 bottom_start: 16.0,
1273 },
1274 Modifier::ShapeRole(ShapeRole::Full),
1275 Modifier::Border {
1276 width: 2.0,
1277 paint: Paint::Role(crate::schema::ColorRole::Outline),
1278 },
1279 Modifier::Elevation(6.0),
1280 ];
1281 let mut encoder = BatchEncoder::default();
1282 let expected: Vec<_> = modifiers
1283 .iter()
1284 .enumerate()
1285 .map(|(index, modifier)| Mutation::SetModifier {
1286 node_id: 1,
1287 index: index as u16,
1288 modifier: modifier.clone(),
1289 })
1290 .collect();
1291 for mutation in &expected {
1292 encoder.encode(mutation).unwrap();
1293 }
1294 let bytes = encoder.finish().unwrap();
1295 assert_eq!(bytes.len(), ENVELOPE_LEN + expected.len() * 28);
1297 assert_eq!(decode_batch(bytes).unwrap(), expected);
1298 }
1299
1300 #[test]
1306 fn fr23_set_theme_round_trips_with_a_slot_for_every_type_role() {
1307 let theme = Theme::adaptive(DesignSystem::Cupertino)
1308 .with_color_scheme(ColorScheme::Dark)
1309 .with_font(TypeRole::Display, 7);
1310 let length = 20 + 4 * TYPE_ROLE_COUNT as u16;
1311 let mut encoder = BatchEncoder::default();
1312 encoder.encode(&Mutation::SetTheme(theme)).unwrap();
1313 let bytes = encoder.finish().unwrap();
1314 assert_eq!(bytes.len(), ENVELOPE_LEN + length as usize);
1315 assert_eq!(read_u16(bytes, ENVELOPE_LEN).unwrap(), TAG_SET_THEME);
1316 assert_eq!(read_u16(bytes, ENVELOPE_LEN + 2).unwrap(), length);
1317 assert_eq!(decode_batch(bytes).unwrap(), [Mutation::SetTheme(theme)]);
1318 }
1319
1320 #[test]
1322 fn fr23_a_font_is_named_for_one_role_and_no_others() {
1323 let theme = Theme::unified(DesignSystem::Material3).with_font(TypeRole::Display, 3);
1324 assert_eq!(theme.font(TypeRole::Display), Some(3));
1325 for role in [
1326 TypeRole::Body,
1327 TypeRole::Title,
1328 TypeRole::Mono,
1329 TypeRole::Caption,
1330 ] {
1331 assert_eq!(theme.font(role), None);
1332 }
1333 }
1334
1335 #[test]
1337 fn fr14_rejects_unknown_theme_tags() {
1338 let mut encoder = BatchEncoder::default();
1339 encoder
1340 .encode(&Mutation::SetTheme(Theme::default()))
1341 .unwrap();
1342 let mut bytes = encoder.finish().unwrap().to_vec();
1343 bytes[ENVELOPE_LEN + 4..ENVELOPE_LEN + 6].copy_from_slice(&99_u16.to_le_bytes());
1344 assert_eq!(decode_batch(&bytes), Err(ProtocolError::InvalidTheme(99)));
1345 }
1346
1347 #[test]
1348 fn rejects_unknown_tags_without_panicking() {
1349 let mut bytes = vec![0, 0, 12, 0, 20, 0, 0, 0, 1, 0, 0, 0];
1350 bytes.extend_from_slice(&999_u16.to_le_bytes());
1351 bytes.extend_from_slice(&8_u16.to_le_bytes());
1352 bytes.extend_from_slice(&0_u32.to_le_bytes());
1353 assert_eq!(decode_batch(&bytes), Err(ProtocolError::InvalidTag(999)));
1354 }
1355
1356 #[test]
1357 fn pr4_renderer_events_round_trip_fixed_layout() {
1358 let events = [
1359 HostEvent {
1360 node_id: 7,
1361 handler_id: 11,
1362 payload: crate::EventPayload::Clicked,
1363 },
1364 HostEvent {
1365 node_id: 8,
1366 handler_id: 12,
1367 payload: crate::EventPayload::TextChanged("한글"),
1368 },
1369 HostEvent {
1370 node_id: 8,
1371 handler_id: 13,
1372 payload: crate::EventPayload::TextSubmitted("done"),
1373 },
1374 HostEvent {
1375 node_id: 8,
1376 handler_id: 14,
1377 payload: crate::EventPayload::FocusLost,
1378 },
1379 HostEvent {
1380 node_id: 9,
1381 handler_id: 16,
1382 payload: crate::EventPayload::RangeRequested {
1383 start: 100,
1384 count: 20,
1385 },
1386 },
1387 HostEvent {
1388 node_id: 8,
1389 handler_id: 15,
1390 payload: crate::EventPayload::KeyDown {
1391 key: Key::Enter,
1392 shift_key: true,
1393 ctrl_key: false,
1394 alt_key: true,
1395 meta_key: false,
1396 },
1397 },
1398 ];
1399 let mut bytes = Vec::new();
1400 for event in events {
1401 encode_event(&event, &mut bytes).unwrap();
1402 assert_eq!(decode_event(&bytes).unwrap(), event);
1403 }
1404 }
1405
1406 #[test]
1407 fn fr20_window_size_changed_round_trips_in_thirty_two_bytes() {
1408 let event = HostEvent {
1409 node_id: 0,
1410 handler_id: 0,
1411 payload: crate::EventPayload::WindowSizeChanged {
1412 width_dp: 841.5,
1413 height_dp: 600.25,
1414 class: crate::WindowSizeClass::Expanded,
1415 height_class: crate::WindowHeightClass::Medium,
1416 },
1417 };
1418 let mut bytes = Vec::new();
1419 encode_event(&event, &mut bytes).unwrap();
1420 assert_eq!(bytes.len(), 32);
1421 assert_eq!(decode_event(&bytes).unwrap(), event);
1422 }
1423
1424 #[test]
1425 fn fr20_unknown_window_size_class_is_a_protocol_error() {
1426 let event = HostEvent {
1427 node_id: 0,
1428 handler_id: 0,
1429 payload: crate::EventPayload::WindowSizeChanged {
1430 width_dp: 320.0,
1431 height_dp: 640.0,
1432 class: crate::WindowSizeClass::Compact,
1433 height_class: crate::WindowHeightClass::Medium,
1434 },
1435 };
1436 let mut bytes = Vec::new();
1437 encode_event(&event, &mut bytes).unwrap();
1438 bytes[24..28].copy_from_slice(&99_u32.to_le_bytes());
1439 assert_eq!(
1440 decode_event(&bytes),
1441 Err(ProtocolError::InvalidValueKind(99))
1442 );
1443 }
1444
1445 #[test]
1446 fn fr20_unknown_window_height_class_is_a_protocol_error() {
1447 let event = HostEvent {
1448 node_id: 0,
1449 handler_id: 0,
1450 payload: crate::EventPayload::WindowSizeChanged {
1451 width_dp: 320.0,
1452 height_dp: 640.0,
1453 class: crate::WindowSizeClass::Compact,
1454 height_class: crate::WindowHeightClass::Medium,
1455 },
1456 };
1457 let mut bytes = Vec::new();
1458 encode_event(&event, &mut bytes).unwrap();
1459 bytes[28..32].copy_from_slice(&99_u32.to_le_bytes());
1460 assert_eq!(
1461 decode_event(&bytes),
1462 Err(ProtocolError::InvalidValueKind(99))
1463 );
1464 }
1465
1466 #[test]
1467 fn fr20_window_size_classes_follow_the_material_boundaries() {
1468 use crate::WindowSizeClass;
1469 assert_eq!(
1470 WindowSizeClass::from_width_dp(599.9),
1471 WindowSizeClass::Compact
1472 );
1473 assert_eq!(
1474 WindowSizeClass::from_width_dp(600.0),
1475 WindowSizeClass::Medium
1476 );
1477 assert_eq!(
1478 WindowSizeClass::from_width_dp(839.9),
1479 WindowSizeClass::Medium
1480 );
1481 assert_eq!(
1482 WindowSizeClass::from_width_dp(840.0),
1483 WindowSizeClass::Expanded
1484 );
1485 }
1486
1487 #[test]
1488 fn fr20_window_height_classes_follow_the_material_boundaries() {
1489 use crate::WindowHeightClass;
1490 assert_eq!(
1491 WindowHeightClass::from_height_dp(479.9),
1492 WindowHeightClass::Compact
1493 );
1494 assert_eq!(
1495 WindowHeightClass::from_height_dp(480.0),
1496 WindowHeightClass::Medium
1497 );
1498 assert_eq!(
1499 WindowHeightClass::from_height_dp(899.9),
1500 WindowHeightClass::Medium
1501 );
1502 assert_eq!(
1503 WindowHeightClass::from_height_dp(900.0),
1504 WindowHeightClass::Expanded
1505 );
1506 }
1507
1508 fn post_notification() -> Mutation<'static> {
1509 Mutation::PostNotification {
1510 key: "session/7",
1511 title: "세션이 끝났습니다",
1512 body: "refactor-parser: 테스트 214개 통과",
1513 channel: "세션",
1514 action_1: "열기",
1515 action_2: "",
1516 importance: NotificationImportance::Urgent,
1517 presentation: NotificationPresentation::WhenInactive,
1518 }
1519 }
1520
1521 #[test]
1524 fn fr36_notification_commands_round_trip_at_their_fixed_lengths() {
1525 let mutations = [
1526 post_notification(),
1527 Mutation::WithdrawNotification { key: "session/7" },
1528 Mutation::RequestNotificationPermission,
1529 ];
1530 let mut lengths = Vec::new();
1531 let mut encoder = BatchEncoder::default();
1532 for mutation in &mutations {
1533 encoder.encode(mutation).unwrap();
1534 }
1535 let bytes = encoder.finish().unwrap();
1536 let mut position = ENVELOPE_LEN;
1537 for _ in &mutations {
1538 let length = read_u16(bytes, position + 2).unwrap();
1539 lengths.push((read_u16(bytes, position).unwrap(), length));
1540 position += usize::from(length);
1541 }
1542 assert_eq!(
1543 lengths,
1544 [
1545 (TAG_POST_NOTIFICATION, 56),
1546 (TAG_WITHDRAW_NOTIFICATION, 12),
1547 (TAG_REQUEST_NOTIFICATION_PERMISSION, 4),
1548 ]
1549 );
1550 assert_eq!(decode_batch(bytes).unwrap(), mutations);
1551 }
1552
1553 #[test]
1556 fn fr36_unknown_importance_and_presentation_are_protocol_errors() {
1557 for (offset, expected) in [(52, 9_u16), (54, 7_u16)] {
1558 let mut encoder = BatchEncoder::default();
1559 encoder.encode(&post_notification()).unwrap();
1560 let mut bytes = encoder.finish().unwrap().to_vec();
1561 bytes[ENVELOPE_LEN + offset..ENVELOPE_LEN + offset + 2]
1562 .copy_from_slice(&expected.to_le_bytes());
1563 assert_eq!(
1564 decode_batch(&bytes),
1565 Err(ProtocolError::InvalidNotification(expected))
1566 );
1567 }
1568 }
1569
1570 #[test]
1573 fn fr36_a_notification_record_of_the_wrong_length_is_rejected() {
1574 let mut bytes = vec![0, 0, 12, 0, 20, 0, 0, 0, 1, 0, 0, 0];
1575 bytes.extend_from_slice(&TAG_REQUEST_NOTIFICATION_PERMISSION.to_le_bytes());
1576 bytes.extend_from_slice(&8_u16.to_le_bytes());
1577 bytes.extend_from_slice(&0_u32.to_le_bytes());
1578 assert_eq!(
1579 decode_batch(&bytes),
1580 Err(ProtocolError::InvalidRecordLength)
1581 );
1582 }
1583
1584 #[test]
1587 fn fr36_notification_events_round_trip_fixed_layout() {
1588 let activated = HostEvent {
1589 node_id: 0,
1590 handler_id: 0,
1591 payload: crate::EventPayload::NotificationActivated {
1592 action: 2,
1593 key: "session/7",
1594 },
1595 };
1596 let mut bytes = Vec::new();
1597 encode_event(&activated, &mut bytes).unwrap();
1598 assert_eq!(read_u16(&bytes, 0).unwrap(), EVENT_NOTIFICATION_ACTIVATED);
1599 assert_eq!(read_u16(&bytes, 2).unwrap(), 28);
1600 assert_eq!(bytes.len(), 28 + "session/7".len());
1601 assert_eq!(decode_event(&bytes).unwrap(), activated);
1602
1603 for state in [
1604 NotificationPermission::NotDetermined,
1605 NotificationPermission::Granted,
1606 NotificationPermission::Denied,
1607 NotificationPermission::Unsupported,
1608 ] {
1609 let changed = HostEvent {
1610 node_id: 0,
1611 handler_id: 0,
1612 payload: crate::EventPayload::NotificationPermissionChanged(state),
1613 };
1614 encode_event(&changed, &mut bytes).unwrap();
1615 assert_eq!(bytes.len(), 20);
1616 assert_eq!(decode_event(&bytes).unwrap(), changed);
1617 }
1618 }
1619
1620 #[test]
1622 fn fr36_unknown_permission_state_is_a_protocol_error() {
1623 let mut bytes = Vec::new();
1624 encode_event(
1625 &HostEvent {
1626 node_id: 0,
1627 handler_id: 0,
1628 payload: crate::EventPayload::NotificationPermissionChanged(
1629 NotificationPermission::Granted,
1630 ),
1631 },
1632 &mut bytes,
1633 )
1634 .unwrap();
1635 bytes[16..20].copy_from_slice(&5_u32.to_le_bytes());
1636 assert_eq!(
1637 decode_event(&bytes),
1638 Err(ProtocolError::InvalidValueKind(5))
1639 );
1640 }
1641
1642 #[test]
1645 fn fr36_an_activation_naming_a_third_button_is_a_protocol_error() {
1646 let mut bytes = Vec::new();
1647 encode_event(
1648 &HostEvent {
1649 node_id: 0,
1650 handler_id: 0,
1651 payload: crate::EventPayload::NotificationActivated {
1652 action: 1,
1653 key: "k",
1654 },
1655 },
1656 &mut bytes,
1657 )
1658 .unwrap();
1659 bytes[16..20].copy_from_slice(&3_u32.to_le_bytes());
1660 assert_eq!(
1661 decode_event(&bytes),
1662 Err(ProtocolError::InvalidValueKind(3))
1663 );
1664 }
1665}