Skip to content

Commit aa6f75b

Browse files
committed
fix:
Signed-off-by: Haobo Gu <haobogu@outlook.com>
1 parent 05f1e4d commit aa6f75b

10 files changed

Lines changed: 249 additions & 29 deletions

File tree

rmk-types/src/combo.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,9 @@ pub struct Combo {
2828
}
2929

3030
impl MaxSize for Combo {
31-
const POSTCARD_MAX_SIZE: usize = KeyAction::POSTCARD_MAX_SIZE * COMBO_SIZE
32-
+ crate::varint_max_size(COMBO_SIZE)
31+
const POSTCARD_MAX_SIZE: usize = crate::heapless_vec_max_size::<KeyAction, COMBO_SIZE>()
3332
+ KeyAction::POSTCARD_MAX_SIZE
34-
+ 1 // Option<u8> tag
35-
+ u8::POSTCARD_MAX_SIZE;
33+
+ Option::<u8>::POSTCARD_MAX_SIZE;
3634
}
3735

3836
impl Combo {

rmk-types/src/lib.rs

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,21 @@ pub(crate) const fn varint_max_size(max_n: usize) -> usize {
5757
roundup_bits / BITS_PER_VARINT_BYTE
5858
}
5959

60+
/// Worst-case postcard-encoded size of `heapless::Vec<T, N>`:
61+
/// every element at its own max, plus the widest varint for the length prefix.
62+
///
63+
/// Use this in manual `MaxSize` impls for structs whose fields contain
64+
/// `heapless::Vec<T, N>`, since `#[derive(MaxSize)]` doesn't support `heapless::Vec`.
65+
/// TODO: Use derived `MaxSize` after postcard updates its heapless version.
66+
pub(crate) const fn heapless_vec_max_size<T: postcard::experimental::max_size::MaxSize, const N: usize>() -> usize {
67+
T::POSTCARD_MAX_SIZE * N + varint_max_size(N)
68+
}
69+
6070
#[cfg(test)]
6171
mod tests {
62-
use super::varint_max_size;
72+
use heapless::Vec;
73+
74+
use super::{heapless_vec_max_size, varint_max_size};
6375

6476
/// Validate varint_max_size against known postcard varint encoding sizes
6577
/// and cross-check with actual postcard serialization.
@@ -86,4 +98,26 @@ mod tests {
8698
);
8799
}
88100
}
101+
102+
/// Worst-case `Vec<u32, 8>` (every element at `u32::MAX`, max-width varint
103+
/// length prefix) must encode to exactly `heapless_vec_max_size::<u32, 8>()`.
104+
#[test]
105+
fn heapless_vec_max_size_matches_postcard() {
106+
let mut v: Vec<u32, 8> = Vec::new();
107+
for _ in 0..8 {
108+
v.push(u32::MAX).unwrap();
109+
}
110+
let mut buf = [0u8; 64];
111+
let bytes = postcard::to_slice(&v, &mut buf).unwrap();
112+
assert_eq!(
113+
bytes.len(),
114+
heapless_vec_max_size::<u32, 8>(),
115+
"tight bound: 8 × u32::MAX + varint(8)",
116+
);
117+
118+
// Empty Vec is below the bound (loose check).
119+
let empty: Vec<u32, 8> = Vec::new();
120+
let bytes = postcard::to_slice(&empty, &mut buf).unwrap();
121+
assert!(bytes.len() <= heapless_vec_max_size::<u32, 8>());
122+
}
89123
}

rmk-types/src/morse.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -282,9 +282,11 @@ pub struct Morse {
282282
}
283283

284284
impl MaxSize for Morse {
285-
const POSTCARD_MAX_SIZE: usize = MorseProfile::POSTCARD_MAX_SIZE
286-
+ (<(u16, Action)>::POSTCARD_MAX_SIZE) * MORSE_SIZE
287-
+ crate::varint_max_size(MORSE_SIZE);
285+
// The custom serializer in `morse_actions_serde` (below) emits the
286+
// `LinearMap` as `Vec<(u16, Action), MORSE_SIZE>` on the wire — keep that
287+
// shape in sync with the helper type parameter here.
288+
const POSTCARD_MAX_SIZE: usize =
289+
MorseProfile::POSTCARD_MAX_SIZE + crate::heapless_vec_max_size::<(u16, Action), MORSE_SIZE>();
288290
}
289291

290292
#[cfg(feature = "defmt")]

rmk-types/src/protocol/rmk/combo.rs

Lines changed: 51 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,7 @@ pub struct SetComboBulkRequest {
4141

4242
#[cfg(feature = "bulk")]
4343
impl MaxSize for SetComboBulkRequest {
44-
const POSTCARD_MAX_SIZE: usize =
45-
u8::POSTCARD_MAX_SIZE + <Combo>::POSTCARD_MAX_SIZE * BULK_SIZE + crate::varint_max_size(BULK_SIZE);
44+
const POSTCARD_MAX_SIZE: usize = u8::POSTCARD_MAX_SIZE + crate::heapless_vec_max_size::<Combo, BULK_SIZE>();
4645
}
4746

4847
/// Bulk response for getting multiple combos at once.
@@ -54,14 +53,27 @@ pub struct GetComboBulkResponse {
5453

5554
#[cfg(feature = "bulk")]
5655
impl MaxSize for GetComboBulkResponse {
57-
const POSTCARD_MAX_SIZE: usize = <Combo>::POSTCARD_MAX_SIZE * BULK_SIZE + crate::varint_max_size(BULK_SIZE);
56+
const POSTCARD_MAX_SIZE: usize = crate::heapless_vec_max_size::<Combo, BULK_SIZE>();
5857
}
5958

6059
#[cfg(test)]
6160
mod tests {
6261
use super::*;
6362
use crate::action::KeyAction;
64-
use crate::protocol::rmk::test_utils::round_trip;
63+
use crate::constants::COMBO_SIZE;
64+
use crate::protocol::rmk::test_utils::{assert_max_size_bound, round_trip};
65+
66+
/// Build a `Combo` filled to `COMBO_SIZE` actions plus a `Some` layer —
67+
/// the worst case for the manual `MaxSize` impl on `Combo`.
68+
fn full_combo() -> Combo {
69+
let actions = core::iter::repeat_n(
70+
KeyAction::Single(crate::action::Action::Key(crate::keycode::KeyCode::Hid(
71+
crate::keycode::HidKeyCode::A,
72+
))),
73+
COMBO_SIZE,
74+
);
75+
Combo::new(actions, KeyAction::No, Some(u8::MAX))
76+
}
6577

6678
#[test]
6779
fn round_trip_combo() {
@@ -76,4 +88,39 @@ mod tests {
7688
config: Combo::new([KeyAction::No], KeyAction::No, Some(1)),
7789
});
7890
}
91+
92+
#[test]
93+
fn round_trip_combo_max_capacity() {
94+
let c = full_combo();
95+
assert_eq!(c.actions.len(), COMBO_SIZE);
96+
round_trip(&c);
97+
assert_max_size_bound(&c);
98+
}
99+
100+
#[cfg(feature = "bulk")]
101+
#[test]
102+
fn round_trip_set_combo_bulk_request_max_capacity() {
103+
let mut configs: Vec<Combo, BULK_SIZE> = Vec::new();
104+
for _ in 0..BULK_SIZE {
105+
configs.push(full_combo()).unwrap();
106+
}
107+
let req = SetComboBulkRequest {
108+
start_index: u8::MAX,
109+
configs,
110+
};
111+
round_trip(&req);
112+
assert_max_size_bound(&req);
113+
}
114+
115+
#[cfg(feature = "bulk")]
116+
#[test]
117+
fn round_trip_get_combo_bulk_response_max_capacity() {
118+
let mut configs: Vec<Combo, BULK_SIZE> = Vec::new();
119+
for _ in 0..BULK_SIZE {
120+
configs.push(full_combo()).unwrap();
121+
}
122+
let resp = GetComboBulkResponse { configs };
123+
round_trip(&resp);
124+
assert_max_size_bound(&resp);
125+
}
79126
}

rmk-types/src/protocol/rmk/keymap.rs

Lines changed: 51 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ pub struct GetKeymapBulkResponse {
5252

5353
#[cfg(feature = "bulk")]
5454
impl MaxSize for GetKeymapBulkResponse {
55-
const POSTCARD_MAX_SIZE: usize = KeyAction::POSTCARD_MAX_SIZE * BULK_SIZE + crate::varint_max_size(BULK_SIZE);
55+
const POSTCARD_MAX_SIZE: usize = crate::heapless_vec_max_size::<KeyAction, BULK_SIZE>();
5656
}
5757

5858
/// Request payload for `SetKeymapBulk` endpoint.
@@ -71,15 +71,33 @@ pub struct SetKeymapBulkRequest {
7171

7272
#[cfg(feature = "bulk")]
7373
impl MaxSize for SetKeymapBulkRequest {
74-
const POSTCARD_MAX_SIZE: usize = 3 // layer + start_row + start_col
75-
+ KeyAction::POSTCARD_MAX_SIZE * BULK_SIZE
76-
+ crate::varint_max_size(BULK_SIZE);
74+
// 3 bytes for layer + start_row + start_col (each `u8::POSTCARD_MAX_SIZE == 1`).
75+
const POSTCARD_MAX_SIZE: usize = 3 + crate::heapless_vec_max_size::<KeyAction, BULK_SIZE>();
7776
}
7877

7978
#[cfg(test)]
8079
mod tests {
8180
use super::*;
8281
use crate::protocol::rmk::test_utils::round_trip;
82+
#[cfg(feature = "bulk")]
83+
use crate::{
84+
action::Action,
85+
keycode::{HidKeyCode, KeyCode},
86+
modifier::ModifierCombination,
87+
morse::MorseProfile,
88+
protocol::rmk::test_utils::assert_max_size_bound,
89+
};
90+
91+
/// Largest-encoded `KeyAction` variant: `TapHold` wraps two multi-field
92+
/// `Action`s and a `MorseProfile(u32)`, many times the size of
93+
/// `KeyAction::No`. Using it in max-capacity bulk tests makes
94+
/// `assert_max_size_bound` exercise both the per-element and the
95+
/// length-prefix dimensions of the bound.
96+
#[cfg(feature = "bulk")]
97+
fn worst_key_action() -> KeyAction {
98+
let action = Action::KeyWithModifier(KeyCode::Hid(HidKeyCode::A), ModifierCombination::new());
99+
KeyAction::TapHold(action, action, MorseProfile::const_default())
100+
}
83101

84102
#[test]
85103
fn round_trip_key_position() {
@@ -125,4 +143,33 @@ mod tests {
125143
actions,
126144
});
127145
}
146+
147+
#[cfg(feature = "bulk")]
148+
#[test]
149+
fn round_trip_set_keymap_bulk_request_max_capacity() {
150+
let mut actions: Vec<KeyAction, BULK_SIZE> = Vec::new();
151+
for _ in 0..BULK_SIZE {
152+
actions.push(worst_key_action()).unwrap();
153+
}
154+
let req = SetKeymapBulkRequest {
155+
layer: u8::MAX,
156+
start_row: u8::MAX,
157+
start_col: u8::MAX,
158+
actions,
159+
};
160+
round_trip(&req);
161+
assert_max_size_bound(&req);
162+
}
163+
164+
#[cfg(feature = "bulk")]
165+
#[test]
166+
fn round_trip_get_keymap_bulk_response_max_capacity() {
167+
let mut actions: Vec<KeyAction, BULK_SIZE> = Vec::new();
168+
for _ in 0..BULK_SIZE {
169+
actions.push(worst_key_action()).unwrap();
170+
}
171+
let resp = GetKeymapBulkResponse { actions };
172+
round_trip(&resp);
173+
assert_max_size_bound(&resp);
174+
}
128175
}

rmk-types/src/protocol/rmk/macro_data.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ pub struct MacroData {
1414
}
1515

1616
impl MaxSize for MacroData {
17-
const POSTCARD_MAX_SIZE: usize = MACRO_DATA_SIZE + crate::varint_max_size(MACRO_DATA_SIZE);
17+
const POSTCARD_MAX_SIZE: usize = crate::heapless_vec_max_size::<u8, MACRO_DATA_SIZE>();
1818
}
1919

2020
/// Request payload for `GetMacro`.
@@ -41,7 +41,7 @@ pub struct SetMacroRequest {
4141
#[cfg(test)]
4242
mod tests {
4343
use super::*;
44-
use crate::protocol::rmk::test_utils::round_trip;
44+
use crate::protocol::rmk::test_utils::{assert_max_size_bound, round_trip};
4545

4646
#[test]
4747
fn round_trip_macro_data() {
@@ -56,7 +56,9 @@ mod tests {
5656
for i in 0..MACRO_DATA_SIZE {
5757
data.push(i as u8).unwrap();
5858
}
59-
round_trip(&MacroData { data });
59+
let full = MacroData { data };
60+
round_trip(&full);
61+
assert_max_size_bound(&full);
6062
}
6163

6264
#[test]

rmk-types/src/protocol/rmk/mod.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ pub type RmkResult = Result<(), RmkError>;
8686

8787
#[cfg(test)]
8888
pub(crate) mod test_utils {
89+
use postcard::experimental::max_size::MaxSize;
8990
use serde::{Deserialize, Serialize};
9091

9192
/// Postcard round-trip helper used by every submodule's tests.
@@ -99,6 +100,24 @@ pub(crate) mod test_utils {
99100
assert_eq!(&decoded, val);
100101
decoded
101102
}
103+
104+
/// Assert that `val` serializes within its declared `POSTCARD_MAX_SIZE`.
105+
/// Use alongside `round_trip` in max-capacity tests to catch under-counted
106+
/// manual `MaxSize` impls (the dangerous bug — buffer overflows downstream).
107+
pub fn assert_max_size_bound<T>(val: &T)
108+
where
109+
T: Serialize + MaxSize,
110+
{
111+
let mut buf = [0u8; 4096];
112+
let bytes = postcard::to_slice(val, &mut buf).expect("serialize");
113+
assert!(
114+
bytes.len() <= T::POSTCARD_MAX_SIZE,
115+
"{} encoded to {} bytes but POSTCARD_MAX_SIZE = {}",
116+
core::any::type_name::<T>(),
117+
bytes.len(),
118+
T::POSTCARD_MAX_SIZE,
119+
);
120+
}
102121
}
103122

104123
// ---------------------------------------------------------------------------

rmk-types/src/protocol/rmk/morse.rs

Lines changed: 64 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,7 @@ pub struct SetMorseBulkRequest {
4141

4242
#[cfg(feature = "bulk")]
4343
impl MaxSize for SetMorseBulkRequest {
44-
const POSTCARD_MAX_SIZE: usize =
45-
u8::POSTCARD_MAX_SIZE + <Morse>::POSTCARD_MAX_SIZE * BULK_SIZE + crate::varint_max_size(BULK_SIZE);
44+
const POSTCARD_MAX_SIZE: usize = u8::POSTCARD_MAX_SIZE + crate::heapless_vec_max_size::<Morse, BULK_SIZE>();
4645
}
4746

4847
/// Bulk response for getting multiple morse configs at once.
@@ -54,15 +53,41 @@ pub struct GetMorseBulkResponse {
5453

5554
#[cfg(feature = "bulk")]
5655
impl MaxSize for GetMorseBulkResponse {
57-
const POSTCARD_MAX_SIZE: usize = <Morse>::POSTCARD_MAX_SIZE * BULK_SIZE + crate::varint_max_size(BULK_SIZE);
56+
const POSTCARD_MAX_SIZE: usize = crate::heapless_vec_max_size::<Morse, BULK_SIZE>();
5857
}
5958

6059
#[cfg(test)]
6160
mod tests {
6261
use super::*;
6362
use crate::action::Action;
63+
use crate::constants::MORSE_SIZE;
64+
use crate::keycode::{HidKeyCode, KeyCode};
65+
use crate::modifier::ModifierCombination;
6466
use crate::morse::{MorsePattern, MorseProfile};
65-
use crate::protocol::rmk::test_utils::round_trip;
67+
use crate::protocol::rmk::test_utils::{assert_max_size_bound, round_trip};
68+
69+
/// Build a `Morse` whose `actions` `LinearMap` is filled to `MORSE_SIZE`
70+
/// distinct entries, each using a multi-field `Action` variant so both the
71+
/// entry count *and* the per-entry encoded size meaningfully exercise the
72+
/// manual `MaxSize` impl. `MorsePattern::from_u16(0)` panics (the empty
73+
/// pattern is `0b1`), so patterns start at 1.
74+
fn full_morse() -> Morse {
75+
// `KeyWithModifier` carries a nested `KeyCode` enum + a `ModifierCombination`
76+
// bitfield, so it encodes to several bytes rather than the 1 byte of
77+
// `Action::No` — enough slack for `assert_max_size_bound` to catch a
78+
// per-element under-count.
79+
let action = Action::KeyWithModifier(KeyCode::Hid(HidKeyCode::A), ModifierCombination::new());
80+
let mut m = Morse {
81+
profile: MorseProfile::const_default(),
82+
actions: heapless::LinearMap::new(),
83+
};
84+
for i in 0..MORSE_SIZE {
85+
m.actions
86+
.insert(MorsePattern::from_u16((i + 1) as u16), action)
87+
.unwrap();
88+
}
89+
m
90+
}
6691

6792
#[test]
6893
fn round_trip_morse() {
@@ -84,4 +109,39 @@ mod tests {
84109
config: morse,
85110
});
86111
}
112+
113+
#[test]
114+
fn round_trip_morse_max_capacity() {
115+
let m = full_morse();
116+
assert_eq!(m.actions.len(), MORSE_SIZE);
117+
round_trip(&m);
118+
assert_max_size_bound(&m);
119+
}
120+
121+
#[cfg(feature = "bulk")]
122+
#[test]
123+
fn round_trip_set_morse_bulk_request_max_capacity() {
124+
let mut configs: Vec<Morse, BULK_SIZE> = Vec::new();
125+
for _ in 0..BULK_SIZE {
126+
configs.push(full_morse()).unwrap();
127+
}
128+
let req = SetMorseBulkRequest {
129+
start_index: u8::MAX,
130+
configs,
131+
};
132+
round_trip(&req);
133+
assert_max_size_bound(&req);
134+
}
135+
136+
#[cfg(feature = "bulk")]
137+
#[test]
138+
fn round_trip_get_morse_bulk_response_max_capacity() {
139+
let mut configs: Vec<Morse, BULK_SIZE> = Vec::new();
140+
for _ in 0..BULK_SIZE {
141+
configs.push(full_morse()).unwrap();
142+
}
143+
let resp = GetMorseBulkResponse { configs };
144+
round_trip(&resp);
145+
assert_max_size_bound(&resp);
146+
}
87147
}

0 commit comments

Comments
 (0)