viva_camctl/
cmd_set_ip.rs1use std::net::Ipv4Addr;
2use std::time::Duration;
3
4use anyhow::{Context, Result, anyhow};
5use tracing::info;
6
7use viva_gige::nic::IfaceSelector;
8
9use crate::common;
10
11fn parse_mac(s: &str) -> Result<[u8; 6]> {
13 let parts: Vec<&str> = s.split(':').collect();
14 if parts.len() != 6 {
15 return Err(anyhow!(
16 "invalid MAC address '{s}': expected 6 colon-separated hex bytes"
17 ));
18 }
19 let mut mac = [0u8; 6];
20 for (i, part) in parts.iter().enumerate() {
21 mac[i] = u8::from_str_radix(part, 16)
22 .with_context(|| format!("invalid hex byte '{part}' in MAC address"))?;
23 }
24 Ok(mac)
25}
26
27pub async fn run(
28 mac: &str,
29 ip: Ipv4Addr,
30 subnet: Ipv4Addr,
31 gateway: Ipv4Addr,
32 force: bool,
33 iface: Option<IfaceSelector>,
34) -> Result<()> {
35 let mac = parse_mac(mac)?;
36
37 if force {
38 let iface_obj = common::resolve_iface(iface.as_ref())?;
40 viva_gige::force_ip(mac, ip, subnet, gateway, iface_obj.as_ref())
41 .await
42 .context("FORCEIP command failed")?;
43 println!(
44 "FORCEIP sent: {} -> {} (subnet {}, gateway {})",
45 common::format_mac(&mac),
46 ip,
47 subnet,
48 gateway,
49 );
50 } else {
51 let timeout = Duration::from_millis(common::DEFAULT_DISCOVERY_TIMEOUT_MS);
53 let devices = common::discover_devices(timeout, iface.as_ref()).await?;
54 let device = devices.iter().find(|d| d.mac == mac).ok_or_else(|| {
55 anyhow!(
56 "no device with MAC {} found (use --force for offline assignment)",
57 common::format_mac(&mac),
58 )
59 })?;
60
61 info!(ip = %device.ip, "found device, opening control connection");
62 let mut control = common::open_control(device).await?;
63 control.claim_control().await.context("claim CCP")?;
64
65 control
66 .write_persistent_ip(ip, subnet, gateway)
67 .await
68 .context("write persistent IP registers")?;
69 control
70 .enable_persistent_ip()
71 .await
72 .context("enable persistent IP mode")?;
73
74 control.release_control().await.context("release CCP")?;
75 println!(
76 "Persistent IP configured: {} -> {} (subnet {}, gateway {})",
77 common::format_mac(&mac),
78 ip,
79 subnet,
80 gateway,
81 );
82 println!("Power-cycle the device to apply the new IP address.");
83 }
84
85 Ok(())
86}
87
88#[cfg(test)]
89mod tests {
90 use super::*;
91
92 #[test]
93 fn parse_mac_valid() {
94 let mac = parse_mac("DE:AD:BE:EF:CA:FE").unwrap();
95 assert_eq!(mac, [0xDE, 0xAD, 0xBE, 0xEF, 0xCA, 0xFE]);
96 }
97
98 #[test]
99 fn parse_mac_lowercase() {
100 let mac = parse_mac("de:ad:be:ef:ca:fe").unwrap();
101 assert_eq!(mac, [0xDE, 0xAD, 0xBE, 0xEF, 0xCA, 0xFE]);
102 }
103
104 #[test]
105 fn parse_mac_invalid_format() {
106 assert!(parse_mac("DEADBEEFCAFE").is_err());
107 assert!(parse_mac("DE:AD:BE:EF:CA").is_err());
108 assert!(parse_mac("DE:AD:BE:EF:CA:FE:00").is_err());
109 }
110
111 #[test]
112 fn parse_mac_invalid_hex() {
113 assert!(parse_mac("GG:AD:BE:EF:CA:FE").is_err());
114 }
115}