From: Jeffrey Czyz Date: Thu, 9 Jun 2022 20:17:01 +0000 (-0500) Subject: Define NodeAlias struct and Display impl X-Git-Tag: v0.0.109~13^2 X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=commitdiff_plain;h=21aff6f7016e9b9ae4cd21f7ce591dd2a5c39763;p=rust-lightning Define NodeAlias struct and Display impl Provide a wrapper struct for 32-byte node aliases, which implements Display for printing. Support the UTF-8 character encoding, but replace control characters and terminate at the first null character. Fall back to ASCII if the byte sequence is an invalid encoding. --- diff --git a/lightning/src/routing/gossip.rs b/lightning/src/routing/gossip.rs index f404e77b..1e3be5dd 100644 --- a/lightning/src/routing/gossip.rs +++ b/lightning/src/routing/gossip.rs @@ -904,7 +904,7 @@ pub struct NodeAnnouncementInfo { /// Moniker assigned to the node. /// May be invalid or malicious (eg control chars), /// should not be exposed to the user. - pub alias: [u8; 32], + pub alias: NodeAlias, /// Internet-level addresses via which one can connect to the node pub addresses: Vec, /// An initial announcement of the node @@ -923,6 +923,51 @@ impl_writeable_tlv_based!(NodeAnnouncementInfo, { (10, addresses, vec_type), }); +/// A user-defined name for a node, which may be used when displaying the node in a graph. +/// +/// Since node aliases are provided by third parties, they are a potential avenue for injection +/// attacks. Care must be taken when processing. +#[derive(Clone, Debug, PartialEq)] +pub struct NodeAlias(pub [u8; 32]); + +impl fmt::Display for NodeAlias { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + let control_symbol = core::char::REPLACEMENT_CHARACTER; + let first_null = self.0.iter().position(|b| *b == 0).unwrap_or(self.0.len()); + let bytes = self.0.split_at(first_null).0; + match core::str::from_utf8(bytes) { + Ok(alias) => { + for c in alias.chars() { + let mut bytes = [0u8; 4]; + let c = if !c.is_control() { c } else { control_symbol }; + f.write_str(c.encode_utf8(&mut bytes))?; + } + }, + Err(_) => { + for c in bytes.iter().map(|b| *b as char) { + // Display printable ASCII characters + let mut bytes = [0u8; 4]; + let c = if c >= '\x20' && c <= '\x7e' { c } else { control_symbol }; + f.write_str(c.encode_utf8(&mut bytes))?; + } + }, + }; + Ok(()) + } +} + +impl Writeable for NodeAlias { + fn write(&self, w: &mut W) -> Result<(), io::Error> { + self.0.write(w) + } +} + +impl Readable for NodeAlias { + fn read(r: &mut R) -> Result { + Ok(NodeAlias(Readable::read(r)?)) + } +} + #[derive(Clone, Debug, PartialEq)] /// Details about a node in the network, known from the network announcement. pub struct NodeInfo { @@ -1126,7 +1171,7 @@ impl NetworkGraph where L::Target: Logger { features: msg.features.clone(), last_update: msg.timestamp, rgb: msg.rgb, - alias: msg.alias, + alias: NodeAlias(msg.alias), addresses: msg.addresses.clone(), announcement_message: if should_relay { full_msg.cloned() } else { None }, }); @@ -1627,7 +1672,7 @@ mod tests { use chain; use ln::PaymentHash; use ln::features::{ChannelFeatures, InitFeatures, NodeFeatures}; - use routing::gossip::{P2PGossipSync, NetworkGraph, NetworkUpdate, MAX_EXCESS_BYTES_FOR_RELAY}; + use routing::gossip::{P2PGossipSync, NetworkGraph, NetworkUpdate, NodeAlias, MAX_EXCESS_BYTES_FOR_RELAY}; use ln::msgs::{Init, OptionalField, RoutingMessageHandler, UnsignedNodeAnnouncement, NodeAnnouncement, UnsignedChannelAnnouncement, ChannelAnnouncement, UnsignedChannelUpdate, ChannelUpdate, ReplyChannelRange, QueryChannelRange, QueryShortChannelIds, MAX_VALUE_MSAT}; @@ -2731,6 +2776,29 @@ mod tests { }); assert!(result.is_err()); } + + #[test] + fn displays_node_alias() { + let format_str_alias = |alias: &str| { + let mut bytes = [0u8; 32]; + bytes[..alias.as_bytes().len()].copy_from_slice(alias.as_bytes()); + format!("{}", NodeAlias(bytes)) + }; + + assert_eq!(format_str_alias("I\u{1F496}LDK! \u{26A1}"), "I\u{1F496}LDK! \u{26A1}"); + assert_eq!(format_str_alias("I\u{1F496}LDK!\0\u{26A1}"), "I\u{1F496}LDK!"); + assert_eq!(format_str_alias("I\u{1F496}LDK!\t\u{26A1}"), "I\u{1F496}LDK!\u{FFFD}\u{26A1}"); + + let format_bytes_alias = |alias: &[u8]| { + let mut bytes = [0u8; 32]; + bytes[..alias.len()].copy_from_slice(alias); + format!("{}", NodeAlias(bytes)) + }; + + assert_eq!(format_bytes_alias(b"\xFFI LDK!"), "\u{FFFD}I LDK!"); + assert_eq!(format_bytes_alias(b"\xFFI \0LDK!"), "\u{FFFD}I "); + assert_eq!(format_bytes_alias(b"\xFFI \tLDK!"), "\u{FFFD}I \u{FFFD}LDK!"); + } } #[cfg(all(test, feature = "_bench_unstable"))]