fix(mesh): never show mojibake device names — strict decode with readable fallback
Name fields sit at firmware-version-dependent offsets; when an offset lands in binary data, from_utf8_lossy handed the UI replacement-character soup in the mesh modals and settings panel. decode_mesh_name(): strict UTF-8 to the first NUL, no control chars, else a readable fallback (short pubkey / node-<id>). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
be6f06a573
commit
e59ea1da69
@ -353,6 +353,28 @@ pub fn build_get_stats() -> Vec<u8> {
|
|||||||
|
|
||||||
// ─── Response parsers ───────────────────────────────────────────────────
|
// ─── Response parsers ───────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/// Decode a device/contact name from raw frame bytes, defensively.
|
||||||
|
///
|
||||||
|
/// Name fields sit at firmware-version-dependent offsets; when an offset
|
||||||
|
/// lands inside binary data (path/pubkey bytes), `from_utf8_lossy` used to
|
||||||
|
/// hand the UI replacement-character soup ("<22>\u{618}…"). Strict rules: valid
|
||||||
|
/// UTF-8 up to the first NUL, no control characters, at least one
|
||||||
|
/// non-whitespace char — anything else gets the caller's readable fallback.
|
||||||
|
fn decode_mesh_name(bytes: &[u8], fallback: &str) -> String {
|
||||||
|
let end = bytes.iter().position(|&b| b == 0).unwrap_or(bytes.len());
|
||||||
|
match std::str::from_utf8(&bytes[..end]) {
|
||||||
|
Ok(s) => {
|
||||||
|
let s = s.trim();
|
||||||
|
if !s.is_empty() && s.chars().all(|c| !c.is_control()) {
|
||||||
|
s.to_string()
|
||||||
|
} else {
|
||||||
|
fallback.to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(_) => fallback.to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Parse RESP_DEVICE_INFO (0x0D) response.
|
/// Parse RESP_DEVICE_INFO (0x0D) response.
|
||||||
/// Returns firmware version string and device capabilities.
|
/// Returns firmware version string and device capabilities.
|
||||||
pub fn parse_device_info(data: &[u8]) -> Result<(String, u16)> {
|
pub fn parse_device_info(data: &[u8]) -> Result<(String, u16)> {
|
||||||
@ -384,15 +406,12 @@ pub fn parse_self_info(data: &[u8]) -> Result<(u32, String)> {
|
|||||||
|
|
||||||
let node_id = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
|
let node_id = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
|
||||||
|
|
||||||
// Name follows after fixed fields — find it by scanning for printable ASCII
|
// Name follows after fixed fields. A firmware whose fixed-field layout
|
||||||
|
// differs would put binary here — decode defensively so the settings
|
||||||
|
// panel never shows byte soup.
|
||||||
let name_start = 4;
|
let name_start = 4;
|
||||||
let name = if data.len() > name_start {
|
let name = if data.len() > name_start {
|
||||||
let name_end = data[name_start..]
|
decode_mesh_name(&data[name_start..], &format!("node-{node_id:08x}"))
|
||||||
.iter()
|
|
||||||
.position(|&b| b == 0)
|
|
||||||
.map(|p| name_start + p)
|
|
||||||
.unwrap_or(data.len());
|
|
||||||
String::from_utf8_lossy(&data[name_start..name_end]).to_string()
|
|
||||||
} else {
|
} else {
|
||||||
String::new()
|
String::new()
|
||||||
};
|
};
|
||||||
@ -453,12 +472,11 @@ pub fn parse_contact(data: &[u8]) -> Result<ParsedContact> {
|
|||||||
// name at data[99..131] (32 bytes)
|
// name at data[99..131] (32 bytes)
|
||||||
let name_start = 99.min(data.len());
|
let name_start = 99.min(data.len());
|
||||||
let name_end = (name_start + 32).min(data.len());
|
let name_end = (name_start + 32).min(data.len());
|
||||||
|
let short_id = format!("{}...", &public_key_hex[..8]);
|
||||||
let advert_name = if data.len() > name_start {
|
let advert_name = if data.len() > name_start {
|
||||||
String::from_utf8_lossy(&data[name_start..name_end])
|
decode_mesh_name(&data[name_start..name_end], &short_id)
|
||||||
.trim_end_matches('\0')
|
|
||||||
.to_string()
|
|
||||||
} else {
|
} else {
|
||||||
format!("{}...", &public_key_hex[..8])
|
short_id
|
||||||
};
|
};
|
||||||
|
|
||||||
// last_advert at data[131..135]
|
// last_advert at data[131..135]
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user