use std::fmt::Write as _;
use std::path::Path;
use tokio::fs;
use crate::config::{escape_xml, unescape_xml};
use crate::error::{ConfigurationError, Result};
use crate::protocol::address::{GroupAddress, IndividualAddress};
use crate::security::SecurityKey;
#[derive(Debug, Clone)]
pub struct KeyringConfig {
pub metadata: KeyringMetadata,
pub interfaces: Vec<KeyringInterface>,
pub devices: Vec<KeyringDevice>,
pub group_addresses: Vec<KeyringGroupAddress>,
}
#[derive(Debug, Clone)]
pub struct KeyringMetadata {
pub created: Option<String>,
pub creator: Option<String>,
pub project: Option<String>,
pub signature: Option<Vec<u8>>,
}
#[derive(Debug, Clone)]
pub struct KeyringInterface {
pub individual_address: IndividualAddress,
pub interface_type: String,
pub host: String,
pub user_id: u8,
pub user_password: SecurityKey,
pub device_authentication: Option<SecurityKey>,
pub backbone_key: Option<SecurityKey>,
}
#[derive(Debug, Clone)]
pub struct KeyringDevice {
pub individual_address: IndividualAddress,
pub serial_number: Option<String>,
pub tool_key: Option<SecurityKey>,
pub management_password: Option<SecurityKey>,
pub authentication_code: Option<SecurityKey>,
}
#[derive(Debug, Clone)]
pub struct KeyringGroupAddress {
pub address: GroupAddress,
pub key: SecurityKey,
pub description: Option<String>,
}
pub struct KeyringParser;
impl KeyringParser {
pub async fn parse_file<P: AsRef<Path>>(path: P) -> Result<KeyringConfig> {
let data = fs::read(path)
.await
.map_err(|e| ConfigurationError::ParseError {
file: "keyring file".to_string(),
reason: format!("Failed to read keyring file: {e}"),
})?;
Self::parse_bytes(&data)
}
pub fn parse_bytes(data: &[u8]) -> Result<KeyringConfig> {
if let Ok(config) = Self::parse_xml_keyring(data) {
return Ok(config);
}
if let Ok(config) = Self::parse_binary_keyring(data) {
return Ok(config);
}
Err(ConfigurationError::ParseError {
file: "keyring data".to_string(),
reason: "Unable to parse keyring data as XML or binary format".to_string(),
}
.into())
}
fn parse_xml_keyring(data: &[u8]) -> Result<KeyringConfig> {
let xml_str = std::str::from_utf8(data).map_err(|e| ConfigurationError::ParseError {
file: "keyring XML".to_string(),
reason: format!("Invalid UTF-8 in keyring XML: {e}"),
})?;
let mut config = KeyringConfig {
metadata: KeyringMetadata {
created: None,
creator: None,
project: None,
signature: None,
},
interfaces: Vec::new(),
devices: Vec::new(),
group_addresses: Vec::new(),
};
if let Some(keyring_start) = xml_str.find("<Keyring")
&& let Some(keyring_end) = xml_str[keyring_start..].find('>')
{
let keyring_tag = &xml_str[keyring_start..=(keyring_start + keyring_end)];
if let Some(created) = Self::extract_xml_attribute(keyring_tag, "Created") {
config.metadata.created = Some(created);
}
if let Some(creator) = Self::extract_xml_attribute(keyring_tag, "CreatedBy") {
config.metadata.creator = Some(creator);
}
if let Some(project) = Self::extract_xml_attribute(keyring_tag, "Project") {
config.metadata.project = Some(project);
}
}
config.interfaces = Self::parse_xml_interfaces(xml_str);
config.devices = Self::parse_xml_devices(xml_str);
config.group_addresses = Self::parse_xml_group_addresses(xml_str);
Ok(config)
}
fn parse_binary_keyring(data: &[u8]) -> Result<KeyringConfig> {
if data.len() < 16 {
return Err(ConfigurationError::ParseError {
file: "binary keyring".to_string(),
reason: "Binary keyring too short".to_string(),
}
.into());
}
if &data[0..4] != b"KNXK" {
return Err(ConfigurationError::ParseError {
file: "binary keyring".to_string(),
reason: "Invalid binary keyring magic bytes".to_string(),
}
.into());
}
Err(ConfigurationError::ParseError {
file: "binary keyring".to_string(),
reason: "binary .knxkeys parsing is not implemented; export the keyring as XML"
.to_string(),
}
.into())
}
fn extract_xml_attribute(xml: &str, attr_name: &str) -> Option<String> {
let pattern = format!("{attr_name}=\"");
if let Some(start) = xml.find(&pattern) {
let start = start + pattern.len();
if let Some(end) = xml[start..].find('"') {
return Some(unescape_xml(&xml[start..start + end]).into_owned());
}
}
None
}
fn parse_xml_interfaces(xml: &str) -> Vec<KeyringInterface> {
let mut interfaces = Vec::new();
let mut pos = 0;
while let Some(start) = xml[pos..].find("<Interface") {
let start = pos + start;
if let Some(end) = xml[start..].find(" />") {
let end = start + end + 3;
let interface_xml = &xml[start..end];
if let Ok(interface) = Self::parse_single_interface(interface_xml) {
interfaces.push(interface);
}
pos = end;
} else if let Some(end) = xml[start..].find("</Interface>") {
let end = start + end + "</Interface>".len();
let interface_xml = &xml[start..end];
if let Ok(interface) = Self::parse_single_interface(interface_xml) {
interfaces.push(interface);
}
pos = end;
} else {
break;
}
}
interfaces
}
fn parse_single_interface(xml: &str) -> Result<KeyringInterface> {
let individual_address = Self::extract_xml_attribute(xml, "IndividualAddress")
.and_then(|addr| addr.parse().ok())
.ok_or_else(|| ConfigurationError::ParseError {
file: "interface XML".to_string(),
reason: "Missing or invalid IndividualAddress in interface".to_string(),
})?;
let interface_type =
Self::extract_xml_attribute(xml, "Type").unwrap_or_else(|| "Unknown".to_string());
let host =
Self::extract_xml_attribute(xml, "Host").unwrap_or_else(|| "0.0.0.0".to_string());
let user_id = Self::extract_xml_attribute(xml, "UserID")
.and_then(|id| id.parse().ok())
.unwrap_or(1);
let user_password = Self::extract_xml_attribute(xml, "UserPassword")
.and_then(|pwd| SecurityKey::from_hex(&pwd).ok())
.unwrap_or_else(|| SecurityKey::new(vec![0; 16]));
let device_authentication = Self::extract_xml_attribute(xml, "DeviceAuthentication")
.and_then(|auth| SecurityKey::from_hex(&auth).ok());
let backbone_key = Self::extract_xml_attribute(xml, "BackboneKey")
.and_then(|key| SecurityKey::from_hex(&key).ok());
Ok(KeyringInterface {
individual_address,
interface_type,
host,
user_id,
user_password,
device_authentication,
backbone_key,
})
}
fn parse_xml_devices(xml: &str) -> Vec<KeyringDevice> {
let mut devices = Vec::new();
let mut pos = 0;
while let Some(start) = xml[pos..].find("<Device") {
let start = pos + start;
if let Some(end) = xml[start..].find(" />") {
let end = start + end + 3;
let device_xml = &xml[start..end];
if let Ok(device) = Self::parse_single_device(device_xml) {
devices.push(device);
}
pos = end;
} else if let Some(end) = xml[start..].find("</Device>") {
let end = start + end + "</Device>".len();
let device_xml = &xml[start..end];
if let Ok(device) = Self::parse_single_device(device_xml) {
devices.push(device);
}
pos = end;
} else {
break;
}
}
devices
}
fn parse_single_device(xml: &str) -> Result<KeyringDevice> {
let individual_address = Self::extract_xml_attribute(xml, "IndividualAddress")
.and_then(|addr| addr.parse().ok())
.ok_or_else(|| ConfigurationError::ParseError {
file: "device XML".to_string(),
reason: "Missing or invalid IndividualAddress in device".to_string(),
})?;
let serial_number = Self::extract_xml_attribute(xml, "SerialNumber");
let tool_key = Self::extract_xml_attribute(xml, "ToolKey")
.and_then(|key| SecurityKey::from_hex(&key).ok());
let management_password = Self::extract_xml_attribute(xml, "ManagementPassword")
.and_then(|pwd| SecurityKey::from_hex(&pwd).ok());
let authentication_code = Self::extract_xml_attribute(xml, "AuthenticationCode")
.and_then(|auth| SecurityKey::from_hex(&auth).ok());
Ok(KeyringDevice {
individual_address,
serial_number,
tool_key,
management_password,
authentication_code,
})
}
fn parse_xml_group_addresses(xml: &str) -> Vec<KeyringGroupAddress> {
let mut group_addresses = Vec::new();
let mut pos = 0;
while let Some(start) = xml[pos..].find("<GroupAddress") {
let start = pos + start;
if let Some(end) = xml[start..].find(" />") {
let end = start + end + 3;
let ga_xml = &xml[start..end];
if let Ok(ga) = Self::parse_single_group_address(ga_xml) {
group_addresses.push(ga);
}
pos = end;
} else if let Some(end) = xml[start..].find("</GroupAddress>") {
let end = start + end + "</GroupAddress>".len();
let ga_xml = &xml[start..end];
if let Ok(ga) = Self::parse_single_group_address(ga_xml) {
group_addresses.push(ga);
}
pos = end;
} else {
break;
}
}
group_addresses
}
fn parse_single_group_address(xml: &str) -> Result<KeyringGroupAddress> {
let address = Self::extract_xml_attribute(xml, "Address")
.and_then(|addr| addr.parse().ok())
.ok_or_else(|| ConfigurationError::ParseError {
file: "group address XML".to_string(),
reason: "Missing or invalid Address in group address".to_string(),
})?;
let key = Self::extract_xml_attribute(xml, "Key")
.and_then(|key| SecurityKey::from_hex(&key).ok())
.ok_or_else(|| ConfigurationError::ParseError {
file: "group address XML".to_string(),
reason: "Missing or invalid Key in group address".to_string(),
})?;
let description = Self::extract_xml_attribute(xml, "Description");
Ok(KeyringGroupAddress {
address,
key,
description,
})
}
#[must_use]
pub fn serialize(config: &KeyringConfig) -> Vec<u8> {
let mut xml = String::new();
xml.push_str("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
xml.push_str("<Keyring");
if let Some(ref created) = config.metadata.created {
let _ = write!(xml, " Created=\"{}\"", escape_xml(created));
}
if let Some(ref creator) = config.metadata.creator {
let _ = write!(xml, " CreatedBy=\"{}\"", escape_xml(creator));
}
if let Some(ref project) = config.metadata.project {
let _ = write!(xml, " Project=\"{}\"", escape_xml(project));
}
xml.push_str(">\n");
for interface in &config.interfaces {
xml.push_str(" <Interface");
let _ = write!(
xml,
" IndividualAddress=\"{}\"",
interface.individual_address
);
let _ = write!(xml, " Type=\"{}\"", interface.interface_type);
let _ = write!(xml, " Host=\"{}\"", escape_xml(&interface.host));
let _ = write!(xml, " UserID=\"{}\"", interface.user_id);
let _ = write!(
xml,
" UserPassword=\"{}\"",
hex::encode(interface.user_password.as_bytes())
);
if let Some(ref device_auth) = interface.device_authentication {
let _ = write!(
xml,
" DeviceAuthentication=\"{}\"",
hex::encode(device_auth.as_bytes())
);
}
if let Some(ref backbone_key) = interface.backbone_key {
let _ = write!(
xml,
" BackboneKey=\"{}\"",
hex::encode(backbone_key.as_bytes())
);
}
xml.push_str(" />\n");
}
for device in &config.devices {
xml.push_str(" <Device");
let _ = write!(xml, " IndividualAddress=\"{}\"", device.individual_address);
if let Some(ref serial) = device.serial_number {
let _ = write!(xml, " SerialNumber=\"{}\"", escape_xml(serial));
}
if let Some(ref tool_key) = device.tool_key {
let _ = write!(xml, " ToolKey=\"{}\"", hex::encode(tool_key.as_bytes()));
}
if let Some(ref mgmt_pwd) = device.management_password {
let _ = write!(
xml,
" ManagementPassword=\"{}\"",
hex::encode(mgmt_pwd.as_bytes())
);
}
if let Some(ref auth_code) = device.authentication_code {
let _ = write!(
xml,
" AuthenticationCode=\"{}\"",
hex::encode(auth_code.as_bytes())
);
}
xml.push_str(" />\n");
}
for ga in &config.group_addresses {
xml.push_str(" <GroupAddress");
let _ = write!(xml, " Address=\"{}\"", ga.address);
let _ = write!(xml, " Key=\"{}\"", hex::encode(ga.key.as_bytes()));
if let Some(ref desc) = ga.description {
let _ = write!(xml, " Description=\"{}\"", escape_xml(desc));
}
xml.push_str(" />\n");
}
xml.push_str("</Keyring>\n");
xml.into_bytes()
}
}