1use crate::schema::{
4 AssetKind, ColorScheme, DesignSystem, Key, MaterialRole, MessageDuration, Modifier, MotionRole,
5 Paint, PropertyKind, Selection, ShapeRole, SpaceRole, TYPE_ROLE_COUNT, Theme, WidgetKind,
6};
7use core::fmt;
8
9const TAG_ENVELOPE: u16 = 0;
10const TAG_CREATE: u16 = 1;
11const TAG_SET_PROP: u16 = 2;
12const TAG_SET_MODIFIER: u16 = 3;
13const TAG_INSERT: u16 = 4;
14const TAG_MOVE: u16 = 5;
15const TAG_REMOVE: u16 = 6;
16const TAG_SET_TEXT: u16 = 7;
17const TAG_APPEND_TEXT: u16 = 8;
18const TAG_SET_THEME: u16 = 9;
19const TAG_REGISTER_ASSET: u16 = 10;
20const TAG_RELEASE_ASSET: u16 = 11;
21const TAG_SHOW_MESSAGE: u16 = 12;
22const TAG_SET_WINDOW: u16 = 13;
23const ENVELOPE_LEN: usize = 12;
24const MIN_RECORD_LEN: usize = 8;
26
27const VALUE_NONE: u16 = 0;
28const VALUE_STRING: u16 = 1;
29const VALUE_BOOL: u16 = 2;
30const VALUE_I64: u16 = 3;
31const VALUE_F32: u16 = 4;
32const VALUE_BYTES: u16 = 5;
35
36#[derive(Clone, Debug, PartialEq)]
37pub enum PropertyValue<'a> {
38 None,
39 String(&'a str),
40 Bool(bool),
41 Integer(i64),
42 Float(f32),
43 Bytes(&'a [u8]),
44}
45
46#[derive(Clone, Debug, PartialEq)]
47pub enum Mutation<'a> {
48 Create {
49 node_id: u32,
50 widget: WidgetKind,
51 },
52 SetProp {
53 node_id: u32,
54 property: PropertyKind,
55 value: PropertyValue<'a>,
56 },
57 SetModifier {
58 node_id: u32,
59 index: u16,
60 modifier: Modifier,
61 },
62 Insert {
63 parent_id: u32,
64 node_id: u32,
65 index: u32,
66 },
67 Move {
68 parent_id: u32,
69 node_id: u32,
70 index: u32,
71 },
72 Remove {
73 node_id: u32,
74 },
75 SetText {
76 node_id: u32,
77 text: &'a str,
78 selection: Option<Selection>,
79 },
80 AppendText {
82 node_id: u32,
83 text: &'a str,
84 },
85 SetTheme(Theme),
87 SetWindow(crate::schema::Window),
95 RegisterAsset {
99 asset_id: u32,
100 kind: AssetKind,
101 bytes: &'a [u8],
102 },
103 ReleaseAsset {
105 asset_id: u32,
106 },
107 ShowMessage {
115 handler_id: u64,
116 text: &'a str,
117 action: &'a str,
118 duration: MessageDuration,
119 },
120}
121
122#[derive(Clone, Debug, Eq, PartialEq)]
123pub enum ProtocolError {
124 Truncated,
125 InvalidEnvelope,
126 InvalidRecordLength,
127 InvalidTag(u16),
128 InvalidWidget(u16),
129 InvalidProperty(u16),
130 InvalidValueKind(u16),
131 InvalidModifier(u16),
132 InvalidTheme(u16),
133 InvalidAssetKind(u16),
134 InvalidMessageDuration(u16),
135 InvalidStringRange,
136 InvalidUtf8,
137 LengthOverflow,
138}
139
140#[derive(Clone, Debug, PartialEq)]
141pub struct HostEvent<'a> {
142 pub node_id: u32,
143 pub handler_id: u64,
144 pub payload: crate::schema::EventPayload<'a>,
145}
146
147const EVENT_CLICK: u16 = 1;
148const EVENT_TEXT_CHANGED: u16 = 2;
149const EVENT_TEXT_SUBMITTED: u16 = 3;
150const EVENT_FOCUS_LOST: u16 = 4;
151const EVENT_PROTOCOL_ERROR: u16 = 5;
152const EVENT_KEY_DOWN: u16 = 6;
153const EVENT_RANGE_REQUESTED: u16 = 7;
154const EVENT_VALUE_CHANGED: u16 = 16;
157const EVENT_WINDOW_SIZE_CHANGED: u16 = 17;
158const EVENT_RESYNC: u16 = 18;
159const EVENT_LIFECYCLE_START: u16 = 19;
160const EVENT_LIFECYCLE_STOP: u16 = 20;
161const EVENT_DESIGN_SYSTEM_RESOLVED: u16 = 21;
162const EVENT_FILES_ENTERED: u16 = 22;
163const EVENT_FILES_DROPPED: u16 = 23;
164
165const MODIFIER_SHIFT: u8 = 1 << 0;
166const MODIFIER_CTRL: u8 = 1 << 1;
167const MODIFIER_ALT: u8 = 1 << 2;
168const MODIFIER_META: u8 = 1 << 3;
169
170pub fn decode_event(bytes: &[u8]) -> Result<HostEvent<'_>, ProtocolError> {
172 if bytes.len() < 16 {
173 return Err(ProtocolError::Truncated);
174 }
175 let tag = read_u16(bytes, 0)?;
176 let record_len = usize::from(read_u16(bytes, 2)?);
177 if record_len < 16 || record_len % 4 != 0 || record_len > bytes.len() {
178 return Err(ProtocolError::InvalidRecordLength);
179 }
180 let node_id = read_u32(bytes, 4)?;
181 let handler_id = read_u64(bytes, 8)?;
182 let payload = match tag {
183 EVENT_CLICK if record_len == 16 => crate::schema::EventPayload::Clicked,
184 EVENT_TEXT_CHANGED if record_len == 24 => {
185 crate::schema::EventPayload::TextChanged(read_string(bytes, 16, record_len)?)
186 }
187 EVENT_TEXT_SUBMITTED if record_len == 24 => {
188 crate::schema::EventPayload::TextSubmitted(read_string(bytes, 16, record_len)?)
189 }
190 EVENT_FOCUS_LOST if record_len == 16 => crate::schema::EventPayload::FocusLost,
191 EVENT_PROTOCOL_ERROR if record_len == 28 => crate::schema::EventPayload::ProtocolError {
192 code: read_u32(bytes, 16)?,
193 message: read_string(bytes, 20, record_len)?,
194 },
195 EVENT_KEY_DOWN if record_len == 20 => {
196 let raw_key = read_u16(bytes, 16)?;
197 let modifiers = *bytes.get(18).ok_or(ProtocolError::Truncated)?;
198 crate::schema::EventPayload::KeyDown {
199 key: Key::try_from(raw_key)
200 .map_err(|()| ProtocolError::InvalidValueKind(raw_key))?,
201 shift_key: modifiers & MODIFIER_SHIFT != 0,
202 ctrl_key: modifiers & MODIFIER_CTRL != 0,
203 alt_key: modifiers & MODIFIER_ALT != 0,
204 meta_key: modifiers & MODIFIER_META != 0,
205 }
206 }
207 EVENT_RANGE_REQUESTED if record_len == 24 => crate::schema::EventPayload::RangeRequested {
208 start: read_u32(bytes, 16)?,
209 count: read_u32(bytes, 20)?,
210 },
211 EVENT_VALUE_CHANGED if record_len == 24 => {
212 crate::schema::EventPayload::ValueChanged(f64::from_bits(read_u64(bytes, 16)?))
213 }
214 EVENT_WINDOW_SIZE_CHANGED if record_len == 32 => {
215 let raw_class = read_u32(bytes, 24)?;
216 let class = u16::try_from(raw_class)
217 .ok()
218 .and_then(|value| crate::schema::WindowSizeClass::try_from(value).ok())
219 .ok_or(ProtocolError::InvalidValueKind(raw_class as u16))?;
220 let raw_height_class = read_u32(bytes, 28)?;
221 let height_class = u16::try_from(raw_height_class)
222 .ok()
223 .and_then(|value| crate::schema::WindowHeightClass::try_from(value).ok())
224 .ok_or(ProtocolError::InvalidValueKind(raw_height_class as u16))?;
225 crate::schema::EventPayload::WindowSizeChanged {
226 width_dp: f32::from_bits(read_u32(bytes, 16)?),
227 height_dp: f32::from_bits(read_u32(bytes, 20)?),
228 class,
229 height_class,
230 }
231 }
232 EVENT_DESIGN_SYSTEM_RESOLVED if record_len == 20 => {
233 let raw = read_u32(bytes, 16)?;
234 let system = u16::try_from(raw)
235 .ok()
236 .and_then(|tag| crate::schema::DesignSystem::try_from(tag).ok())
237 .ok_or(ProtocolError::InvalidValueKind(raw as u16))?;
238 crate::schema::EventPayload::DesignSystemResolved(system)
239 }
240 EVENT_FILES_ENTERED if record_len == 16 => crate::schema::EventPayload::FilesEntered,
241 EVENT_FILES_DROPPED if record_len == 24 => {
242 crate::schema::EventPayload::FilesDropped(read_string(bytes, 16, record_len)?)
243 }
244 EVENT_RESYNC if record_len == 16 => crate::schema::EventPayload::Resync,
245 EVENT_LIFECYCLE_START if record_len == 16 => crate::schema::EventPayload::LifecycleStart,
246 EVENT_LIFECYCLE_STOP if record_len == 16 => crate::schema::EventPayload::LifecycleStop,
247 EVENT_CLICK..=EVENT_RANGE_REQUESTED | EVENT_VALUE_CHANGED..=EVENT_LIFECYCLE_STOP => {
248 return Err(ProtocolError::InvalidRecordLength);
249 }
250 other => return Err(ProtocolError::InvalidTag(other)),
251 };
252 Ok(HostEvent {
253 node_id,
254 handler_id,
255 payload,
256 })
257}
258
259pub fn encode_event(event: &HostEvent<'_>, output: &mut Vec<u8>) -> Result<(), ProtocolError> {
261 output.clear();
262 if let crate::schema::EventPayload::KeyDown {
263 key,
264 shift_key,
265 ctrl_key,
266 alt_key,
267 meta_key,
268 } = event.payload
269 {
270 output.extend_from_slice(&EVENT_KEY_DOWN.to_le_bytes());
271 output.extend_from_slice(&20_u16.to_le_bytes());
272 output.extend_from_slice(&event.node_id.to_le_bytes());
273 output.extend_from_slice(&event.handler_id.to_le_bytes());
274 output.extend_from_slice(&(key as u16).to_le_bytes());
275 let modifiers = (u8::from(shift_key) * MODIFIER_SHIFT)
276 | (u8::from(ctrl_key) * MODIFIER_CTRL)
277 | (u8::from(alt_key) * MODIFIER_ALT)
278 | (u8::from(meta_key) * MODIFIER_META);
279 output.push(modifiers);
280 output.push(0);
281 return Ok(());
282 }
283 if let crate::schema::EventPayload::ValueChanged(value) = event.payload {
284 output.extend_from_slice(&EVENT_VALUE_CHANGED.to_le_bytes());
285 output.extend_from_slice(&24_u16.to_le_bytes());
286 output.extend_from_slice(&event.node_id.to_le_bytes());
287 output.extend_from_slice(&event.handler_id.to_le_bytes());
288 output.extend_from_slice(&value.to_bits().to_le_bytes());
289 return Ok(());
290 }
291 if let crate::schema::EventPayload::WindowSizeChanged {
292 width_dp,
293 height_dp,
294 class,
295 height_class,
296 } = event.payload
297 {
298 output.extend_from_slice(&EVENT_WINDOW_SIZE_CHANGED.to_le_bytes());
299 output.extend_from_slice(&32_u16.to_le_bytes());
300 output.extend_from_slice(&event.node_id.to_le_bytes());
301 output.extend_from_slice(&event.handler_id.to_le_bytes());
302 output.extend_from_slice(&width_dp.to_bits().to_le_bytes());
303 output.extend_from_slice(&height_dp.to_bits().to_le_bytes());
304 output.extend_from_slice(&u32::from(u16::from(class)).to_le_bytes());
305 output.extend_from_slice(&u32::from(u16::from(height_class)).to_le_bytes());
306 return Ok(());
307 }
308 if let crate::schema::EventPayload::DesignSystemResolved(system) = event.payload {
309 output.extend_from_slice(&EVENT_DESIGN_SYSTEM_RESOLVED.to_le_bytes());
310 output.extend_from_slice(&20_u16.to_le_bytes());
311 output.extend_from_slice(&event.node_id.to_le_bytes());
312 output.extend_from_slice(&event.handler_id.to_le_bytes());
313 output.extend_from_slice(&u32::from(u16::from(system)).to_le_bytes());
316 return Ok(());
317 }
318 if let crate::schema::EventPayload::RangeRequested { start, count } = event.payload {
319 output.extend_from_slice(&EVENT_RANGE_REQUESTED.to_le_bytes());
320 output.extend_from_slice(&24_u16.to_le_bytes());
321 output.extend_from_slice(&event.node_id.to_le_bytes());
322 output.extend_from_slice(&event.handler_id.to_le_bytes());
323 output.extend_from_slice(&start.to_le_bytes());
324 output.extend_from_slice(&count.to_le_bytes());
325 return Ok(());
326 }
327 let (tag, record_len, text, error_code) = match &event.payload {
328 crate::schema::EventPayload::Clicked => (EVENT_CLICK, 16_u16, None, None),
329 crate::schema::EventPayload::TextChanged(value) => {
330 (EVENT_TEXT_CHANGED, 24, Some(*value), None)
331 }
332 crate::schema::EventPayload::TextSubmitted(value) => {
333 (EVENT_TEXT_SUBMITTED, 24, Some(*value), None)
334 }
335 crate::schema::EventPayload::FocusLost => (EVENT_FOCUS_LOST, 16, None, None),
336 crate::schema::EventPayload::FilesEntered => (EVENT_FILES_ENTERED, 16, None, None),
337 crate::schema::EventPayload::FilesDropped(paths) => {
338 (EVENT_FILES_DROPPED, 24, Some(*paths), None)
339 }
340 crate::schema::EventPayload::Resync => (EVENT_RESYNC, 16, None, None),
341 crate::schema::EventPayload::LifecycleStart => (EVENT_LIFECYCLE_START, 16, None, None),
342 crate::schema::EventPayload::LifecycleStop => (EVENT_LIFECYCLE_STOP, 16, None, None),
343 crate::schema::EventPayload::ProtocolError { code, message } => {
344 (EVENT_PROTOCOL_ERROR, 28, Some(*message), Some(*code))
345 }
346 crate::schema::EventPayload::KeyDown { .. }
347 | crate::schema::EventPayload::RangeRequested { .. }
348 | crate::schema::EventPayload::ValueChanged(_)
349 | crate::schema::EventPayload::WindowSizeChanged { .. }
350 | crate::schema::EventPayload::DesignSystemResolved(_) => unreachable!(),
351 };
352 output.extend_from_slice(&tag.to_le_bytes());
353 output.extend_from_slice(&record_len.to_le_bytes());
354 output.extend_from_slice(&event.node_id.to_le_bytes());
355 output.extend_from_slice(&event.handler_id.to_le_bytes());
356 if let Some(code) = error_code {
357 output.extend_from_slice(&code.to_le_bytes());
358 }
359 if let Some(text) = text {
360 let offset = u32::from(record_len);
361 let len = u32::try_from(text.len()).map_err(|_| ProtocolError::LengthOverflow)?;
362 output.extend_from_slice(&offset.to_le_bytes());
363 output.extend_from_slice(&len.to_le_bytes());
364 output.extend_from_slice(text.as_bytes());
365 }
366 Ok(())
367}
368
369impl fmt::Display for ProtocolError {
370 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
371 write!(formatter, "protocol error: {self:?}")
372 }
373}
374
375impl std::error::Error for ProtocolError {}
376
377pub struct BatchEncoder {
379 records: Vec<u8>,
380 strings: Vec<u8>,
381 string_fixups: Vec<usize>,
382 arena: Vec<u8>,
383 record_count: u32,
384}
385
386impl Default for BatchEncoder {
387 fn default() -> Self {
388 Self::with_capacity(4096, 1024, 64)
389 }
390}
391
392impl BatchEncoder {
393 pub fn with_capacity(record_bytes: usize, string_bytes: usize, string_count: usize) -> Self {
394 let mut encoder = Self {
395 records: Vec::with_capacity(record_bytes),
396 strings: Vec::with_capacity(string_bytes),
397 string_fixups: Vec::with_capacity(string_count),
398 arena: Vec::with_capacity(record_bytes + string_bytes),
399 record_count: 0,
400 };
401 encoder.clear();
402 encoder
403 }
404
405 pub fn clear(&mut self) {
406 self.records.clear();
407 self.strings.clear();
408 self.string_fixups.clear();
409 self.arena.clear();
410 self.record_count = 0;
411 self.begin_record(TAG_ENVELOPE, 8);
412 self.put_u32(0);
413 self.put_u32(0);
414 }
415
416 pub fn encode(&mut self, mutation: &Mutation<'_>) -> Result<(), ProtocolError> {
417 match mutation {
418 Mutation::Create { node_id, widget } => {
419 self.begin_record(TAG_CREATE, 8);
420 self.put_u32(*node_id);
421 self.put_u16(*widget as u16);
422 self.put_u16(0);
423 }
424 Mutation::SetProp {
425 node_id,
426 property,
427 value,
428 } => {
429 self.begin_record(TAG_SET_PROP, 20);
430 self.put_u32(*node_id);
431 self.put_u16(*property as u16);
432 match value {
433 PropertyValue::None => {
434 self.put_u16(VALUE_NONE);
435 self.put_u64(0);
436 self.put_u32(0);
437 }
438 PropertyValue::String(value) => {
439 self.put_u16(VALUE_STRING);
440 self.put_string_ref(value)?;
441 self.put_u32(0);
442 }
443 PropertyValue::Bool(value) => {
444 self.put_u16(VALUE_BOOL);
445 self.put_u64(u64::from(*value));
446 self.put_u32(0);
447 }
448 PropertyValue::Integer(value) => {
449 self.put_u16(VALUE_I64);
450 self.put_u64(*value as u64);
451 self.put_u32(0);
452 }
453 PropertyValue::Float(value) => {
454 self.put_u16(VALUE_F32);
455 self.put_u64(u64::from(value.to_bits()));
456 self.put_u32(0);
457 }
458 PropertyValue::Bytes(value) => {
459 self.put_u16(VALUE_BYTES);
460 self.put_bytes_ref(value)?;
461 self.put_u32(0);
462 }
463 }
464 }
465 Mutation::SetModifier {
466 node_id,
467 index,
468 modifier,
469 } => {
470 self.begin_record(TAG_SET_MODIFIER, 24);
471 self.put_u32(*node_id);
472 self.put_u16(*index);
473 let (tag, first, second) = modifier_fields(modifier);
474 self.put_u16(tag);
475 self.put_u64(first);
476 self.put_u64(second);
477 }
478 Mutation::Insert {
479 parent_id,
480 node_id,
481 index,
482 } => {
483 self.begin_record(TAG_INSERT, 12);
484 self.put_u32(*parent_id);
485 self.put_u32(*node_id);
486 self.put_u32(*index);
487 }
488 Mutation::Move {
489 parent_id,
490 node_id,
491 index,
492 } => {
493 self.begin_record(TAG_MOVE, 12);
494 self.put_u32(*parent_id);
495 self.put_u32(*node_id);
496 self.put_u32(*index);
497 }
498 Mutation::Remove { node_id } => {
499 self.begin_record(TAG_REMOVE, 4);
500 self.put_u32(*node_id);
501 }
502 Mutation::SetText {
503 node_id,
504 text,
505 selection,
506 } => {
507 self.begin_record(TAG_SET_TEXT, 20);
508 self.put_u32(*node_id);
509 self.put_string_ref(text)?;
510 let (start, end) =
511 selection.map_or((u32::MAX, u32::MAX), |value| (value.start, value.end));
512 self.put_u32(start);
513 self.put_u32(end);
514 }
515 Mutation::AppendText { node_id, text } => {
516 self.begin_record(TAG_APPEND_TEXT, 12);
517 self.put_u32(*node_id);
518 self.put_string_ref(text)?;
519 }
520 Mutation::RegisterAsset {
521 asset_id,
522 kind,
523 bytes,
524 } => {
525 self.begin_record(TAG_REGISTER_ASSET, 16);
526 self.put_u32(*asset_id);
527 self.put_u16(*kind as u16);
528 self.put_u16(0);
529 self.put_bytes_ref(bytes)?;
530 }
531 Mutation::ReleaseAsset { asset_id } => {
532 self.begin_record(TAG_RELEASE_ASSET, 4);
533 self.put_u32(*asset_id);
534 }
535 Mutation::ShowMessage {
536 handler_id,
537 text,
538 action,
539 duration,
540 } => {
541 self.begin_record(TAG_SHOW_MESSAGE, 28);
542 self.put_u64(*handler_id);
543 self.put_string_ref(text)?;
544 self.put_string_ref(action)?;
545 self.put_u16(*duration as u16);
546 self.put_u16(0);
547 }
548 Mutation::SetTheme(theme) => {
549 self.begin_record(TAG_SET_THEME, 8 + 4 * TYPE_ROLE_COUNT as u16);
550 self.put_u16(theme.design_system as u16);
551 self.put_u16(theme.fallback as u16);
552 self.put_u16(theme.color_scheme as u16);
553 self.put_u16(u16::from(theme.adaptive));
554 for asset in theme.fonts {
558 self.put_u32(asset);
559 }
560 }
561 Mutation::SetWindow(window) => {
562 self.begin_record(TAG_SET_WINDOW, 28);
563 self.put_u16(window.chrome as u16);
564 self.put_u16(window.title_bar as u16);
565 self.put_u16(window.width);
566 self.put_u16(window.height);
567 self.put_u16(window.min_width);
568 self.put_u16(window.min_height);
569 self.put_u16(u16::from(window.resizable));
570 self.put_u16(0);
573 self.put_string_ref(window.title)?;
574 self.put_u32(window.icon);
575 }
576 }
577 self.record_count = self
578 .record_count
579 .checked_add(1)
580 .ok_or(ProtocolError::LengthOverflow)?;
581 Ok(())
582 }
583
584 pub fn finish(&mut self) -> Result<&[u8], ProtocolError> {
585 let records_len =
586 u32::try_from(self.records.len()).map_err(|_| ProtocolError::LengthOverflow)?;
587 self.records[4..8].copy_from_slice(&records_len.to_le_bytes());
588 self.records[8..12].copy_from_slice(&self.record_count.to_le_bytes());
589 let total = self
590 .records
591 .len()
592 .checked_add(self.strings.len())
593 .ok_or(ProtocolError::LengthOverflow)?;
594 self.arena.clear();
595 if self.arena.capacity() < total {
596 self.arena.reserve(total);
597 }
598 self.arena.extend_from_slice(&self.records);
599 self.arena.extend_from_slice(&self.strings);
600 for &position in &self.string_fixups {
601 let relative = read_u32(&self.arena, position)?;
602 let absolute = records_len
603 .checked_add(relative)
604 .ok_or(ProtocolError::LengthOverflow)?;
605 self.arena[position..position + 4].copy_from_slice(&absolute.to_le_bytes());
606 }
607 Ok(&self.arena)
608 }
609
610 pub fn arena(&self) -> (*const u8, usize) {
617 (self.arena.as_ptr(), self.arena.capacity())
618 }
619
620 pub fn capacity(&self) -> usize {
621 self.records.capacity() + self.strings.capacity() + self.arena.capacity()
622 }
623
624 fn begin_record(&mut self, tag: u16, payload_len: u16) {
625 self.put_u16(tag);
626 self.put_u16(payload_len + 4);
627 }
628
629 fn put_string_ref(&mut self, value: &str) -> Result<(), ProtocolError> {
630 self.put_bytes_ref(value.as_bytes())
631 }
632
633 fn put_bytes_ref(&mut self, value: &[u8]) -> Result<(), ProtocolError> {
636 let offset =
637 u32::try_from(self.strings.len()).map_err(|_| ProtocolError::LengthOverflow)?;
638 let len = u32::try_from(value.len()).map_err(|_| ProtocolError::LengthOverflow)?;
639 self.string_fixups.push(self.records.len());
640 self.put_u32(offset);
641 self.put_u32(len);
642 self.strings.extend_from_slice(value);
643 Ok(())
644 }
645
646 fn put_u16(&mut self, value: u16) {
647 self.records.extend_from_slice(&value.to_le_bytes());
648 }
649
650 fn put_u32(&mut self, value: u32) {
651 self.records.extend_from_slice(&value.to_le_bytes());
652 }
653
654 fn put_u64(&mut self, value: u64) {
655 self.records.extend_from_slice(&value.to_le_bytes());
656 }
657}
658
659pub fn decode_batch(bytes: &[u8]) -> Result<Vec<Mutation<'_>>, ProtocolError> {
660 if bytes.len() < ENVELOPE_LEN
661 || read_u16(bytes, 0)? != TAG_ENVELOPE
662 || read_u16(bytes, 2)? != 12
663 {
664 return Err(ProtocolError::InvalidEnvelope);
665 }
666 let records_len =
667 usize::try_from(read_u32(bytes, 4)?).map_err(|_| ProtocolError::LengthOverflow)?;
668 let count = usize::try_from(read_u32(bytes, 8)?).map_err(|_| ProtocolError::LengthOverflow)?;
669 if records_len < ENVELOPE_LEN || records_len > bytes.len() || records_len % 4 != 0 {
670 return Err(ProtocolError::InvalidEnvelope);
671 }
672 let capacity = count.min((records_len - ENVELOPE_LEN) / MIN_RECORD_LEN);
677 let mut output = Vec::with_capacity(capacity);
678 let mut position = ENVELOPE_LEN;
679 while position < records_len {
680 let tag = read_u16(bytes, position)?;
681 let len = usize::from(read_u16(bytes, position + 2)?);
682 if len < 4 || len % 4 != 0 || position + len > records_len {
683 return Err(ProtocolError::InvalidRecordLength);
684 }
685 let payload = position + 4;
686 let mutation = match tag {
687 TAG_CREATE if len == 12 => Mutation::Create {
688 node_id: read_u32(bytes, payload)?,
689 widget: WidgetKind::try_from(read_u16(bytes, payload + 4)?).map_err(|()| {
690 ProtocolError::InvalidWidget(read_u16(bytes, payload + 4).unwrap_or(0))
691 })?,
692 },
693 TAG_SET_PROP if len == 24 => {
694 let property_raw = read_u16(bytes, payload + 4)?;
695 let value_kind = read_u16(bytes, payload + 6)?;
696 let value = match value_kind {
697 VALUE_NONE => PropertyValue::None,
698 VALUE_STRING => {
699 PropertyValue::String(read_string(bytes, payload + 8, records_len)?)
700 }
701 VALUE_BOOL => PropertyValue::Bool(read_u64(bytes, payload + 8)? != 0),
702 VALUE_I64 => PropertyValue::Integer(read_u64(bytes, payload + 8)? as i64),
703 VALUE_F32 => {
704 PropertyValue::Float(f32::from_bits(read_u64(bytes, payload + 8)? as u32))
705 }
706 VALUE_BYTES => {
707 PropertyValue::Bytes(read_bytes(bytes, payload + 8, records_len)?)
708 }
709 other => return Err(ProtocolError::InvalidValueKind(other)),
710 };
711 Mutation::SetProp {
712 node_id: read_u32(bytes, payload)?,
713 property: PropertyKind::try_from(property_raw)
714 .map_err(|()| ProtocolError::InvalidProperty(property_raw))?,
715 value,
716 }
717 }
718 TAG_SET_MODIFIER if len == 28 => Mutation::SetModifier {
719 node_id: read_u32(bytes, payload)?,
720 index: read_u16(bytes, payload + 4)?,
721 modifier: decode_modifier(
722 read_u16(bytes, payload + 6)?,
723 read_u64(bytes, payload + 8)?,
724 read_u64(bytes, payload + 16)?,
725 )?,
726 },
727 TAG_INSERT if len == 16 => Mutation::Insert {
728 parent_id: read_u32(bytes, payload)?,
729 node_id: read_u32(bytes, payload + 4)?,
730 index: read_u32(bytes, payload + 8)?,
731 },
732 TAG_MOVE if len == 16 => Mutation::Move {
733 parent_id: read_u32(bytes, payload)?,
734 node_id: read_u32(bytes, payload + 4)?,
735 index: read_u32(bytes, payload + 8)?,
736 },
737 TAG_REMOVE if len == 8 => Mutation::Remove {
738 node_id: read_u32(bytes, payload)?,
739 },
740 TAG_SET_TEXT if len == 24 => {
741 let start = read_u32(bytes, payload + 12)?;
742 let end = read_u32(bytes, payload + 16)?;
743 Mutation::SetText {
744 node_id: read_u32(bytes, payload)?,
745 text: read_string(bytes, payload + 4, records_len)?,
746 selection: (start != u32::MAX || end != u32::MAX)
747 .then_some(Selection { start, end }),
748 }
749 }
750 TAG_APPEND_TEXT if len == 16 => Mutation::AppendText {
751 node_id: read_u32(bytes, payload)?,
752 text: read_string(bytes, payload + 4, records_len)?,
753 },
754 TAG_SET_THEME if len == 12 + 4 * TYPE_ROLE_COUNT => {
755 let design_system = read_u16(bytes, payload)?;
756 let fallback = read_u16(bytes, payload + 2)?;
757 let color_scheme = read_u16(bytes, payload + 4)?;
758 let adaptive = read_u16(bytes, payload + 6)?;
759 if adaptive > 1 {
760 return Err(ProtocolError::InvalidTheme(adaptive));
761 }
762 let mut fonts = [0u32; TYPE_ROLE_COUNT];
763 for (index, slot) in fonts.iter_mut().enumerate() {
764 *slot = read_u32(bytes, payload + 8 + 4 * index)?;
765 }
766 Mutation::SetTheme(Theme {
767 design_system: DesignSystem::try_from(design_system)
768 .map_err(|()| ProtocolError::InvalidTheme(design_system))?,
769 fallback: DesignSystem::try_from(fallback)
770 .map_err(|()| ProtocolError::InvalidTheme(fallback))?,
771 color_scheme: ColorScheme::try_from(color_scheme)
772 .map_err(|()| ProtocolError::InvalidTheme(color_scheme))?,
773 adaptive: adaptive == 1,
774 fonts,
775 })
776 }
777 TAG_SET_WINDOW if len == 32 => {
778 let chrome = read_u16(bytes, payload)?;
779 let title_bar = read_u16(bytes, payload + 2)?;
780 let resizable = read_u16(bytes, payload + 12)?;
781 if resizable > 1 {
782 return Err(ProtocolError::InvalidValueKind(resizable));
783 }
784 Mutation::SetWindow(crate::schema::Window {
785 chrome: crate::schema::Chrome::try_from(chrome)
786 .map_err(|()| ProtocolError::InvalidValueKind(chrome))?,
787 title_bar: crate::schema::TitleBar::try_from(title_bar)
788 .map_err(|()| ProtocolError::InvalidValueKind(title_bar))?,
789 width: read_u16(bytes, payload + 4)?,
790 height: read_u16(bytes, payload + 6)?,
791 min_width: read_u16(bytes, payload + 8)?,
792 min_height: read_u16(bytes, payload + 10)?,
793 resizable: resizable == 1,
794 icon: read_u32(bytes, payload + 24)?,
798 title: Box::leak(
799 read_string(bytes, payload + 16, records_len)?
800 .to_owned()
801 .into_boxed_str(),
802 ),
803 })
804 }
805 TAG_REGISTER_ASSET if len == 20 => {
806 let raw_kind = read_u16(bytes, payload + 4)?;
807 Mutation::RegisterAsset {
808 asset_id: read_u32(bytes, payload)?,
809 kind: AssetKind::try_from(raw_kind)
810 .map_err(|()| ProtocolError::InvalidAssetKind(raw_kind))?,
811 bytes: read_bytes(bytes, payload + 8, records_len)?,
812 }
813 }
814 TAG_RELEASE_ASSET if len == 8 => Mutation::ReleaseAsset {
815 asset_id: read_u32(bytes, payload)?,
816 },
817 TAG_SHOW_MESSAGE if len == 32 => {
818 let raw_duration = read_u16(bytes, payload + 24)?;
819 Mutation::ShowMessage {
820 handler_id: read_u64(bytes, payload)?,
821 text: read_string(bytes, payload + 8, records_len)?,
822 action: read_string(bytes, payload + 16, records_len)?,
823 duration: MessageDuration::try_from(raw_duration)
824 .map_err(|()| ProtocolError::InvalidMessageDuration(raw_duration))?,
825 }
826 }
827 TAG_CREATE..=TAG_SHOW_MESSAGE => {
828 return Err(ProtocolError::InvalidRecordLength);
829 }
830 other => return Err(ProtocolError::InvalidTag(other)),
831 };
832 output.push(mutation);
833 position += len;
834 }
835 if output.len() != count {
836 return Err(ProtocolError::InvalidEnvelope);
837 }
838 Ok(output)
839}
840
841const fn pack_floats(low: f32, high: f32) -> u64 {
843 (low.to_bits() as u64) | ((high.to_bits() as u64) << 32)
844}
845
846const fn unpack_low(word: u64) -> f32 {
847 f32::from_bits(word as u32)
848}
849
850const fn unpack_high(word: u64) -> f32 {
851 f32::from_bits((word >> 32) as u32)
852}
853
854fn modifier_fields(modifier: &Modifier) -> (u16, u64, u64) {
855 match modifier {
856 Modifier::Empty => (0, 0, 0),
857 Modifier::Padding(value) => (1, u64::from(value.to_bits()), 0),
858 Modifier::FillMaxWidth => (2, 0, 0),
859 Modifier::FillMaxHeight => (3, 0, 0),
860 Modifier::Width(value) => (4, u64::from(value.to_bits()), 0),
861 Modifier::Height(value) => (5, u64::from(value.to_bits()), 0),
862 Modifier::Size { width, height } => {
863 (6, u64::from(width.to_bits()), u64::from(height.to_bits()))
864 }
865 Modifier::Background(paint) => (7, paint.to_bits(), 0),
866 Modifier::Clickable { handler_id } => (8, *handler_id, 0),
867 Modifier::PaddingRole(role) => (9, *role as u64, 0),
868 Modifier::PaddingEach {
869 start,
870 top,
871 end,
872 bottom,
873 } => (10, pack_floats(*start, *top), pack_floats(*end, *bottom)),
874 Modifier::Weight(value) => (11, u64::from(value.to_bits()), 0),
875 Modifier::Shape {
876 top_start,
877 top_end,
878 bottom_end,
879 bottom_start,
880 } => (
881 12,
882 pack_floats(*top_start, *top_end),
883 pack_floats(*bottom_end, *bottom_start),
884 ),
885 Modifier::ShapeRole(role) => (13, *role as u64, 0),
886 Modifier::Border { width, paint } => (14, u64::from(width.to_bits()), paint.to_bits()),
887 Modifier::Elevation(dp) => (15, u64::from(dp.to_bits()), 0),
888 Modifier::ObserveSize { token } => (16, u64::from(*token), 0),
889 Modifier::Motion(role) => (17, *role as u64, 0),
890 Modifier::Material(role) => (18, *role as u64, 0),
891 }
892}
893
894fn decode_role<T: TryFrom<u16, Error = ()>>(first: u64) -> Result<T, ProtocolError> {
895 let tag = u16::try_from(first).map_err(|_| ProtocolError::InvalidModifier(0))?;
896 T::try_from(tag).map_err(|()| ProtocolError::InvalidModifier(tag))
897}
898
899fn decode_paint(bits: u64) -> Result<Paint, ProtocolError> {
900 Paint::from_bits(bits).ok_or(ProtocolError::InvalidModifier(0))
901}
902
903fn decode_modifier(tag: u16, first: u64, second: u64) -> Result<Modifier, ProtocolError> {
904 match tag {
905 0 => Ok(Modifier::Empty),
906 1 => Ok(Modifier::Padding(f32::from_bits(first as u32))),
907 2 => Ok(Modifier::FillMaxWidth),
908 3 => Ok(Modifier::FillMaxHeight),
909 4 => Ok(Modifier::Width(f32::from_bits(first as u32))),
910 5 => Ok(Modifier::Height(f32::from_bits(first as u32))),
911 6 => Ok(Modifier::Size {
912 width: f32::from_bits(first as u32),
913 height: f32::from_bits(second as u32),
914 }),
915 7 => Ok(Modifier::Background(decode_paint(first)?)),
916 8 => Ok(Modifier::Clickable { handler_id: first }),
917 9 => Ok(Modifier::PaddingRole(decode_role::<SpaceRole>(first)?)),
918 10 => Ok(Modifier::PaddingEach {
919 start: unpack_low(first),
920 top: unpack_high(first),
921 end: unpack_low(second),
922 bottom: unpack_high(second),
923 }),
924 11 => Ok(Modifier::Weight(f32::from_bits(first as u32))),
925 12 => Ok(Modifier::Shape {
926 top_start: unpack_low(first),
927 top_end: unpack_high(first),
928 bottom_end: unpack_low(second),
929 bottom_start: unpack_high(second),
930 }),
931 13 => Ok(Modifier::ShapeRole(decode_role::<ShapeRole>(first)?)),
932 14 => Ok(Modifier::Border {
933 width: f32::from_bits(first as u32),
934 paint: decode_paint(second)?,
935 }),
936 15 => Ok(Modifier::Elevation(f32::from_bits(first as u32))),
937 16 => Ok(Modifier::ObserveSize {
938 token: first as u32,
939 }),
940 17 => Ok(Modifier::Motion(decode_role::<MotionRole>(first)?)),
941 18 => Ok(Modifier::Material(decode_role::<MaterialRole>(first)?)),
942 other => Err(ProtocolError::InvalidModifier(other)),
943 }
944}
945
946fn read_string(bytes: &[u8], position: usize, arena_start: usize) -> Result<&str, ProtocolError> {
953 let value = read_bytes(bytes, position, arena_start)?;
954 std::str::from_utf8(value).map_err(|_| ProtocolError::InvalidUtf8)
955}
956
957fn read_bytes(bytes: &[u8], position: usize, arena_start: usize) -> Result<&[u8], ProtocolError> {
959 let offset =
960 usize::try_from(read_u32(bytes, position)?).map_err(|_| ProtocolError::LengthOverflow)?;
961 let len = usize::try_from(read_u32(bytes, position + 4)?)
962 .map_err(|_| ProtocolError::LengthOverflow)?;
963 let end = offset
964 .checked_add(len)
965 .ok_or(ProtocolError::InvalidStringRange)?;
966 if offset < arena_start {
967 return Err(ProtocolError::InvalidStringRange);
968 }
969 bytes
970 .get(offset..end)
971 .ok_or(ProtocolError::InvalidStringRange)
972}
973
974fn read_u16(bytes: &[u8], position: usize) -> Result<u16, ProtocolError> {
975 let value = bytes
976 .get(position..position + 2)
977 .ok_or(ProtocolError::Truncated)?;
978 Ok(u16::from_le_bytes([value[0], value[1]]))
979}
980
981fn read_u32(bytes: &[u8], position: usize) -> Result<u32, ProtocolError> {
982 let value = bytes
983 .get(position..position + 4)
984 .ok_or(ProtocolError::Truncated)?;
985 Ok(u32::from_le_bytes([value[0], value[1], value[2], value[3]]))
986}
987
988fn read_u64(bytes: &[u8], position: usize) -> Result<u64, ProtocolError> {
989 let value = bytes
990 .get(position..position + 8)
991 .ok_or(ProtocolError::Truncated)?;
992 Ok(u64::from_le_bytes(
993 value.try_into().map_err(|_| ProtocolError::Truncated)?,
994 ))
995}
996
997#[cfg(test)]
998mod tests {
999 use super::*;
1000 use crate::schema::TypeRole;
1001
1002 #[test]
1003 fn round_trips_all_m0_mutations() {
1004 let mutations = [
1005 Mutation::Create {
1006 node_id: 1,
1007 widget: WidgetKind::Column,
1008 },
1009 Mutation::SetProp {
1010 node_id: 2,
1011 property: PropertyKind::Text,
1012 value: PropertyValue::String("μλ
"),
1013 },
1014 Mutation::SetProp {
1015 node_id: 3,
1016 property: PropertyKind::Enabled,
1017 value: PropertyValue::Bool(true),
1018 },
1019 Mutation::SetModifier {
1020 node_id: 1,
1021 index: 0,
1022 modifier: Modifier::Padding(16.0),
1023 },
1024 Mutation::Insert {
1025 parent_id: 1,
1026 node_id: 2,
1027 index: 0,
1028 },
1029 Mutation::Move {
1030 parent_id: 1,
1031 node_id: 2,
1032 index: 1,
1033 },
1034 Mutation::Remove { node_id: 3 },
1035 Mutation::SetText {
1036 node_id: 4,
1037 text: "compose",
1038 selection: Some(Selection { start: 1, end: 4 }),
1039 },
1040 Mutation::AppendText {
1041 node_id: 4,
1042 text: " token",
1043 },
1044 ];
1045 let mut encoder = BatchEncoder::default();
1046 for mutation in &mutations {
1047 encoder.encode(mutation).unwrap();
1048 }
1049 let decoded = decode_batch(encoder.finish().unwrap()).unwrap();
1050 assert_eq!(decoded, mutations);
1051 }
1052
1053 #[test]
1056 fn fr13_paint_role_and_literal_share_one_record_length() {
1057 let encode_one = |modifier| {
1058 let mut encoder = BatchEncoder::default();
1059 encoder
1060 .encode(&Mutation::SetModifier {
1061 node_id: 1,
1062 index: 0,
1063 modifier,
1064 })
1065 .unwrap();
1066 encoder.finish().unwrap().to_vec()
1067 };
1068 let role = encode_one(Modifier::Background(Paint::Role(
1069 crate::schema::ColorRole::Surface,
1070 )));
1071 let literal = encode_one(Modifier::Background(Paint::Literal(
1072 crate::schema::Color::rgb(0x1b1b1f),
1073 )));
1074 assert_eq!(role.len(), literal.len());
1075 assert_eq!(
1076 decode_batch(&role).unwrap(),
1077 [Mutation::SetModifier {
1078 node_id: 1,
1079 index: 0,
1080 modifier: Modifier::Background(Paint::Role(crate::schema::ColorRole::Surface)),
1081 }]
1082 );
1083 assert_eq!(
1084 decode_batch(&literal).unwrap(),
1085 [Mutation::SetModifier {
1086 node_id: 1,
1087 index: 0,
1088 modifier: Modifier::Background(Paint::Literal(crate::schema::Color::rgb(0x1b1b1f))),
1089 }]
1090 );
1091 }
1092
1093 #[test]
1095 fn fr13_design_modifiers_round_trip_in_two_words() {
1096 let modifiers = [
1097 Modifier::PaddingRole(SpaceRole::Md),
1098 Modifier::PaddingEach {
1099 start: 1.0,
1100 top: 2.0,
1101 end: 3.0,
1102 bottom: 4.0,
1103 },
1104 Modifier::Weight(0.25),
1105 Modifier::Shape {
1106 top_start: 4.0,
1107 top_end: 8.0,
1108 bottom_end: 12.0,
1109 bottom_start: 16.0,
1110 },
1111 Modifier::ShapeRole(ShapeRole::Full),
1112 Modifier::Border {
1113 width: 2.0,
1114 paint: Paint::Role(crate::schema::ColorRole::Outline),
1115 },
1116 Modifier::Elevation(6.0),
1117 ];
1118 let mut encoder = BatchEncoder::default();
1119 let expected: Vec<_> = modifiers
1120 .iter()
1121 .enumerate()
1122 .map(|(index, modifier)| Mutation::SetModifier {
1123 node_id: 1,
1124 index: index as u16,
1125 modifier: modifier.clone(),
1126 })
1127 .collect();
1128 for mutation in &expected {
1129 encoder.encode(mutation).unwrap();
1130 }
1131 let bytes = encoder.finish().unwrap();
1132 assert_eq!(bytes.len(), ENVELOPE_LEN + expected.len() * 28);
1134 assert_eq!(decode_batch(bytes).unwrap(), expected);
1135 }
1136
1137 #[test]
1143 fn fr23_set_theme_round_trips_with_a_slot_for_every_type_role() {
1144 let theme = Theme::adaptive(DesignSystem::Cupertino)
1145 .with_color_scheme(ColorScheme::Dark)
1146 .with_font(TypeRole::Display, 7);
1147 let length = 12 + 4 * TYPE_ROLE_COUNT as u16;
1148 let mut encoder = BatchEncoder::default();
1149 encoder.encode(&Mutation::SetTheme(theme)).unwrap();
1150 let bytes = encoder.finish().unwrap();
1151 assert_eq!(bytes.len(), ENVELOPE_LEN + length as usize);
1152 assert_eq!(read_u16(bytes, ENVELOPE_LEN).unwrap(), TAG_SET_THEME);
1153 assert_eq!(read_u16(bytes, ENVELOPE_LEN + 2).unwrap(), length);
1154 assert_eq!(decode_batch(bytes).unwrap(), [Mutation::SetTheme(theme)]);
1155 }
1156
1157 #[test]
1159 fn fr23_a_font_is_named_for_one_role_and_no_others() {
1160 let theme = Theme::unified(DesignSystem::Material3).with_font(TypeRole::Display, 3);
1161 assert_eq!(theme.font(TypeRole::Display), Some(3));
1162 for role in [
1163 TypeRole::Body,
1164 TypeRole::Title,
1165 TypeRole::Mono,
1166 TypeRole::Caption,
1167 ] {
1168 assert_eq!(theme.font(role), None);
1169 }
1170 }
1171
1172 #[test]
1174 fn fr14_rejects_unknown_theme_tags() {
1175 let mut encoder = BatchEncoder::default();
1176 encoder
1177 .encode(&Mutation::SetTheme(Theme::default()))
1178 .unwrap();
1179 let mut bytes = encoder.finish().unwrap().to_vec();
1180 bytes[ENVELOPE_LEN + 4..ENVELOPE_LEN + 6].copy_from_slice(&99_u16.to_le_bytes());
1181 assert_eq!(decode_batch(&bytes), Err(ProtocolError::InvalidTheme(99)));
1182 }
1183
1184 #[test]
1185 fn rejects_unknown_tags_without_panicking() {
1186 let mut bytes = vec![0, 0, 12, 0, 20, 0, 0, 0, 1, 0, 0, 0];
1187 bytes.extend_from_slice(&999_u16.to_le_bytes());
1188 bytes.extend_from_slice(&8_u16.to_le_bytes());
1189 bytes.extend_from_slice(&0_u32.to_le_bytes());
1190 assert_eq!(decode_batch(&bytes), Err(ProtocolError::InvalidTag(999)));
1191 }
1192
1193 #[test]
1194 fn pr4_renderer_events_round_trip_fixed_layout() {
1195 let events = [
1196 HostEvent {
1197 node_id: 7,
1198 handler_id: 11,
1199 payload: crate::EventPayload::Clicked,
1200 },
1201 HostEvent {
1202 node_id: 8,
1203 handler_id: 12,
1204 payload: crate::EventPayload::TextChanged("νκΈ"),
1205 },
1206 HostEvent {
1207 node_id: 8,
1208 handler_id: 13,
1209 payload: crate::EventPayload::TextSubmitted("done"),
1210 },
1211 HostEvent {
1212 node_id: 8,
1213 handler_id: 14,
1214 payload: crate::EventPayload::FocusLost,
1215 },
1216 HostEvent {
1217 node_id: 9,
1218 handler_id: 16,
1219 payload: crate::EventPayload::RangeRequested {
1220 start: 100,
1221 count: 20,
1222 },
1223 },
1224 HostEvent {
1225 node_id: 8,
1226 handler_id: 15,
1227 payload: crate::EventPayload::KeyDown {
1228 key: Key::Enter,
1229 shift_key: true,
1230 ctrl_key: false,
1231 alt_key: true,
1232 meta_key: false,
1233 },
1234 },
1235 ];
1236 let mut bytes = Vec::new();
1237 for event in events {
1238 encode_event(&event, &mut bytes).unwrap();
1239 assert_eq!(decode_event(&bytes).unwrap(), event);
1240 }
1241 }
1242
1243 #[test]
1244 fn fr20_window_size_changed_round_trips_in_thirty_two_bytes() {
1245 let event = HostEvent {
1246 node_id: 0,
1247 handler_id: 0,
1248 payload: crate::EventPayload::WindowSizeChanged {
1249 width_dp: 841.5,
1250 height_dp: 600.25,
1251 class: crate::WindowSizeClass::Expanded,
1252 height_class: crate::WindowHeightClass::Medium,
1253 },
1254 };
1255 let mut bytes = Vec::new();
1256 encode_event(&event, &mut bytes).unwrap();
1257 assert_eq!(bytes.len(), 32);
1258 assert_eq!(decode_event(&bytes).unwrap(), event);
1259 }
1260
1261 #[test]
1262 fn fr20_unknown_window_size_class_is_a_protocol_error() {
1263 let event = HostEvent {
1264 node_id: 0,
1265 handler_id: 0,
1266 payload: crate::EventPayload::WindowSizeChanged {
1267 width_dp: 320.0,
1268 height_dp: 640.0,
1269 class: crate::WindowSizeClass::Compact,
1270 height_class: crate::WindowHeightClass::Medium,
1271 },
1272 };
1273 let mut bytes = Vec::new();
1274 encode_event(&event, &mut bytes).unwrap();
1275 bytes[24..28].copy_from_slice(&99_u32.to_le_bytes());
1276 assert_eq!(
1277 decode_event(&bytes),
1278 Err(ProtocolError::InvalidValueKind(99))
1279 );
1280 }
1281
1282 #[test]
1283 fn fr20_unknown_window_height_class_is_a_protocol_error() {
1284 let event = HostEvent {
1285 node_id: 0,
1286 handler_id: 0,
1287 payload: crate::EventPayload::WindowSizeChanged {
1288 width_dp: 320.0,
1289 height_dp: 640.0,
1290 class: crate::WindowSizeClass::Compact,
1291 height_class: crate::WindowHeightClass::Medium,
1292 },
1293 };
1294 let mut bytes = Vec::new();
1295 encode_event(&event, &mut bytes).unwrap();
1296 bytes[28..32].copy_from_slice(&99_u32.to_le_bytes());
1297 assert_eq!(
1298 decode_event(&bytes),
1299 Err(ProtocolError::InvalidValueKind(99))
1300 );
1301 }
1302
1303 #[test]
1304 fn fr20_window_size_classes_follow_the_material_boundaries() {
1305 use crate::WindowSizeClass;
1306 assert_eq!(
1307 WindowSizeClass::from_width_dp(599.9),
1308 WindowSizeClass::Compact
1309 );
1310 assert_eq!(
1311 WindowSizeClass::from_width_dp(600.0),
1312 WindowSizeClass::Medium
1313 );
1314 assert_eq!(
1315 WindowSizeClass::from_width_dp(839.9),
1316 WindowSizeClass::Medium
1317 );
1318 assert_eq!(
1319 WindowSizeClass::from_width_dp(840.0),
1320 WindowSizeClass::Expanded
1321 );
1322 }
1323
1324 #[test]
1325 fn fr20_window_height_classes_follow_the_material_boundaries() {
1326 use crate::WindowHeightClass;
1327 assert_eq!(
1328 WindowHeightClass::from_height_dp(479.9),
1329 WindowHeightClass::Compact
1330 );
1331 assert_eq!(
1332 WindowHeightClass::from_height_dp(480.0),
1333 WindowHeightClass::Medium
1334 );
1335 assert_eq!(
1336 WindowHeightClass::from_height_dp(899.9),
1337 WindowHeightClass::Medium
1338 );
1339 assert_eq!(
1340 WindowHeightClass::from_height_dp(900.0),
1341 WindowHeightClass::Expanded
1342 );
1343 }
1344}