Skip to content

Commit

Permalink
feat: sanitize and set node alias
Browse files Browse the repository at this point in the history
What this commit does:
Implements a method `set_node_alias` on NodeBuilder to allow
callers customize/set the value of the node alias. This method
sanitizes the user-provided alias by ensuring the following:
 + Node alias is UTF-8-encoded String
 + Node alias is non-empty
 + Node alias cannot exceed 32 bytes
 + Node alias is only valid up to the first null byte. Every
   character after the null byte is discraded

Additionally, a test case is provided to cover sanitizing
empty node alias, as well as an alias with emojis (copied and
modified from rust-lightning) and a sandwiched null byte.
  • Loading branch information
enigbe committed Jul 25, 2024
1 parent 77a0bbe commit 9a606e2
Show file tree
Hide file tree
Showing 2 changed files with 88 additions and 1 deletion.
83 changes: 82 additions & 1 deletion src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ impl Default for LiquiditySourceConfig {
/// An error encountered during building a [`Node`].
///
/// [`Node`]: crate::Node
#[derive(Debug, Clone)]
#[derive(Debug, Clone, PartialEq)]
pub enum BuildError {
/// The given seed bytes are invalid, e.g., have invalid length.
InvalidSeedBytes,
Expand Down Expand Up @@ -132,6 +132,8 @@ pub enum BuildError {
WalletSetupFailed,
/// We failed to setup the logger.
LoggerSetupFailed,
/// The provided alias is invalid
InvalidNodeAlias(String),
}

impl fmt::Display for BuildError {
Expand All @@ -152,6 +154,9 @@ impl fmt::Display for BuildError {
Self::KVStoreSetupFailed => write!(f, "Failed to setup KVStore."),
Self::WalletSetupFailed => write!(f, "Failed to setup onchain wallet."),
Self::LoggerSetupFailed => write!(f, "Failed to setup the logger."),
Self::InvalidNodeAlias(ref reason) => {
write!(f, "Given node alias is invalid: {}", reason)
},
}
}
}
Expand Down Expand Up @@ -302,6 +307,17 @@ impl NodeBuilder {
self
}

/// Sets the alias the [`Node`] will use in its announcement. The provided
/// alias must be a valid UTF-8 string.
pub fn set_node_alias<T: Into<String>>(
&mut self, node_alias: T,
) -> Result<&mut Self, BuildError> {
let node_alias = sanitize_alias(node_alias).map_err(|e| e)?;

self.config.node_alias = Some(node_alias);
Ok(self)
}

/// Builds a [`Node`] instance with a [`SqliteStore`] backend and according to the options
/// previously configured.
pub fn build(&self) -> Result<Node, BuildError> {
Expand Down Expand Up @@ -1043,3 +1059,68 @@ fn seed_bytes_from_config(
},
}
}

/// Sanitize the user-provided node alias to ensure that it is a valid protocol-specified UTF-8 string.
fn sanitize_alias<T: Into<String>>(node_alias: T) -> Result<String, BuildError> {
// Alias is convertible into UTF-8 encoded string
let node_alias: String = node_alias.into();
let alias = node_alias.trim();

// Alias is non-empty
if alias.is_empty() {
return Err(BuildError::InvalidNodeAlias("Node alias cannot be empty.".to_string()));
}

// Alias valid up to first null byte
let first_null = alias.as_bytes().iter().position(|b| *b == 0).unwrap_or(alias.len());
let actual_alias = alias.split_at(first_null).0;

// Alias must be 32-bytes long or less
if actual_alias.as_bytes().len() > 32 {
return Err(BuildError::InvalidNodeAlias("Node alias cannot exceed 32 bytes.".to_string()));
}

Ok(actual_alias.to_string())
}

#[cfg(test)]
mod tests {
use crate::{BuildError, Node};

use super::NodeBuilder;

fn create_node_with_alias<T: Into<String>>(alias: T) -> Result<Node, BuildError> {
NodeBuilder::new().set_node_alias(&alias.into())?.build()
}

#[test]
fn empty_node_alias() {
// Empty node alias
let alias = "";
let node = create_node_with_alias(alias);
assert_eq!(
node.err().unwrap(),
BuildError::InvalidNodeAlias("Node alias cannot be empty.".to_string())
);
}

#[test]
fn node_alias_with_sandwiched_null() {
// Alias with emojis
let expected_alias = "I\u{1F496}LDK-Node!";
let user_provided_alias = "I\u{1F496}LDK-Node!\0\u{26A1}";
let node = create_node_with_alias(user_provided_alias).unwrap();

assert_eq!(expected_alias, node.config().node_alias.unwrap());
}

#[test]
fn node_alias_longer_than_32_bytes() {
let alias = "This is a string longer than thirty-two bytes!"; // 46 bytes
let node = create_node_with_alias(alias);
assert_eq!(
node.err().unwrap(),
BuildError::InvalidNodeAlias("Node alias cannot exceed 32 bytes.".to_string())
);
}
}
6 changes: 6 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,11 @@ pub struct Config {
/// closure. We *will* however still try to get the Anchor spending transactions confirmed
/// on-chain with the funds available.
pub anchor_channels_config: Option<AnchorChannelsConfig>,
/// The node alias to be used in announcements.
///
/// **Note**: This is required if, alongside a valid public socket address, node announcements
/// are to be broadcast.
pub node_alias: Option<String>,
}

impl Default for Config {
Expand All @@ -164,6 +169,7 @@ impl Default for Config {
probing_liquidity_limit_multiplier: DEFAULT_PROBING_LIQUIDITY_LIMIT_MULTIPLIER,
log_level: DEFAULT_LOG_LEVEL,
anchor_channels_config: Some(AnchorChannelsConfig::default()),
node_alias: None,
}
}
}
Expand Down

0 comments on commit 9a606e2

Please sign in to comment.