#![deny(warnings,
missing_docs,
missing_debug_implementations,
missing_copy_implementations,
clippy::all,
clippy::pedantic,
// Do cfg(test) right
clippy::cfg_not_test,
clippy::tests_outside_test_module,
// Guard against left-over debugging output
clippy::dbg_macro,
clippy::print_stderr,
clippy::print_stdout,
clippy::unimplemented,
clippy::use_debug,
clippy::todo,
// We should not exit here
clippy::exit,
// Don't panic carelessly
clippy::get_unwrap,
clippy::unused_result_ok,
clippy::unwrap_in_result,
clippy::indexing_slicing,
// Do not carelessly ignore errors
clippy::let_underscore_must_use,
clippy::let_underscore_untyped,
// Code smells
clippy::float_cmp_const,
clippy::if_then_some_else_none,
clippy::large_include_file,
// Disable as casts
clippy::as_conversions,
)]
#![forbid(unsafe_code)]
use std::error::Error;
use std::fmt::Display;
use std::io::Write;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, ToSocketAddrs, UdpSocket};
use std::str::FromStr;
#[cfg(feature = "file")]
pub mod file;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MacAddress([u8; 6]);
impl MacAddress {
#[must_use]
pub fn new(address: [u8; 6]) -> Self {
Self(address)
}
}
impl AsRef<[u8]> for MacAddress {
fn as_ref(&self) -> &[u8] {
&self.0
}
}
impl From<MacAddress> for [u8; 6] {
fn from(value: MacAddress) -> Self {
value.0
}
}
impl From<[u8; 6]> for MacAddress {
fn from(value: [u8; 6]) -> Self {
Self(value)
}
}
impl Display for MacAddress {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let sep = if f.sign_minus() { '-' } else { ':' };
write!(
f,
"{:02X}{sep}{:02X}{sep}{:02X}{sep}{:02X}{sep}{:02X}{sep}{:02X}",
self.0[0], self.0[1], self.0[2], self.0[3], self.0[4], self.0[5]
)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SecureOn([u8; 6]);
impl SecureOn {
#[must_use]
pub fn new(address: [u8; 6]) -> Self {
Self(address)
}
}
impl AsRef<[u8]> for SecureOn {
fn as_ref(&self) -> &[u8] {
&self.0
}
}
impl From<SecureOn> for [u8; 6] {
fn from(value: SecureOn) -> Self {
value.0
}
}
impl From<[u8; 6]> for SecureOn {
fn from(value: [u8; 6]) -> Self {
Self(value)
}
}
impl Display for SecureOn {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", MacAddress::new(self.0))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ParseErrorKind {
TooShort,
InvalidSeparator,
InvalidByteLiteral,
TrailingBytes,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ParseError {
kind: ParseErrorKind,
}
impl Display for ParseError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.kind {
ParseErrorKind::InvalidByteLiteral => "invalid byte literal found in string",
ParseErrorKind::TooShort => "input too short",
ParseErrorKind::InvalidSeparator => "invalid separator found in string",
ParseErrorKind::TrailingBytes => "trailing bytes found in string",
}
.fmt(f)
}
}
impl Error for ParseError {}
impl ParseError {
#[must_use]
pub fn kind(&self) -> ParseErrorKind {
self.kind
}
}
#[inline]
fn parse_eui48_with_sep(s: &str, sep: u8) -> Result<[u8; 6], ParseErrorKind> {
let mut addr = [0; 6];
let mut last_field = 0;
for (i, byte_literal) in s.as_bytes().split(|b| *b == sep).enumerate() {
match addr.get_mut(i) {
Some(byte) => {
last_field = i;
if byte_literal.len() != 2 {
return Err(ParseErrorKind::InvalidByteLiteral);
}
*byte = u8::from_str_radix(
std::str::from_utf8(byte_literal)
.map_err(|_| ParseErrorKind::InvalidByteLiteral)?,
16,
)
.map_err(|_| ParseErrorKind::InvalidByteLiteral)?;
}
None => return Err(ParseErrorKind::TrailingBytes),
}
}
if last_field == addr.len() - 1 {
Ok(addr)
} else {
Err(ParseErrorKind::TooShort)
}
}
fn parse_eui48(s: &str) -> Result<[u8; 6], ParseError> {
match s.as_bytes().get(2) {
None => Err(ParseErrorKind::TooShort),
Some(sep @ (b'-' | b':')) => parse_eui48_with_sep(s, *sep),
Some(_) => Err(ParseErrorKind::InvalidSeparator),
}
.map_err(|kind| ParseError { kind })
}
impl FromStr for MacAddress {
type Err = ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
parse_eui48(s).map(Self::new)
}
}
impl FromStr for SecureOn {
type Err = ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
parse_eui48(s).map(Self::new)
}
}
pub fn fill_magic_packet(buffer: &mut [u8; 102], mac_address: MacAddress) {
buffer[0..6].copy_from_slice(&[0xff; 6]);
for i in 0..16 {
let base = (i + 1) * 6;
#[allow(clippy::indexing_slicing)]
buffer[base..base + 6].copy_from_slice(mac_address.as_ref());
}
}
#[allow(clippy::missing_panics_doc)]
pub fn fill_magic_packet_secure_on(
buffer: &mut [u8; 108],
mac_address: MacAddress,
secure_on: SecureOn,
) {
fill_magic_packet((&mut buffer[..102]).try_into().unwrap(), mac_address);
buffer[102..].copy_from_slice(secure_on.as_ref());
}
pub fn write_magic_packet<W: Write>(
sink: &mut W,
mac_address: MacAddress,
secure_on: Option<SecureOn>,
) -> std::io::Result<()> {
sink.write_all(&[0xff; 6])?;
for _ in 0..16 {
sink.write_all(mac_address.as_ref())?;
}
if let Some(secure_on) = secure_on {
sink.write_all(secure_on.as_ref())?;
}
Ok(())
}
pub trait SendMagicPacket {
fn send_magic_packet<A: ToSocketAddrs>(
&self,
mac_address: MacAddress,
secure_on: Option<SecureOn>,
addr: A,
) -> std::io::Result<()>;
}
impl SendMagicPacket for UdpSocket {
fn send_magic_packet<A: ToSocketAddrs>(
&self,
mac_address: MacAddress,
secure_on: Option<SecureOn>,
addr: A,
) -> std::io::Result<()> {
if let Some(secure_on) = secure_on {
let mut packet = [0; 108];
fill_magic_packet_secure_on(&mut packet, mac_address, secure_on);
let size = self.send_to(&packet, addr)?;
assert!(size == packet.len());
} else {
let mut packet = [0; 102];
fill_magic_packet(&mut packet, mac_address);
let size = self.send_to(&packet, addr)?;
assert!(size == packet.len());
}
Ok(())
}
}
pub fn send_magic_packet(
mac_address: MacAddress,
secure_on: Option<SecureOn>,
addr: SocketAddr,
) -> std::io::Result<()> {
let bind_address = if addr.is_ipv4() {
IpAddr::from(Ipv4Addr::UNSPECIFIED)
} else {
IpAddr::from(Ipv6Addr::UNSPECIFIED)
};
let socket = UdpSocket::bind((bind_address, 0))?;
socket.set_broadcast(true)?;
socket.send_magic_packet(mac_address, secure_on, addr)
}
#[cfg(test)]
mod tests {
use crate::{fill_magic_packet, fill_magic_packet_secure_on};
use super::{MacAddress, write_magic_packet};
mod parse {
use super::super::*;
#[test]
fn valid_eui48() {
assert_eq!(
parse_eui48("12-13-14-15-16-17").unwrap(),
[0x12, 0x13, 0x14, 0x15, 0x16, 0x17]
);
assert_eq!(
parse_eui48("aa:BB:cc:DD:ee:FF").unwrap(),
[0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF]
);
}
#[test]
fn invalid_eui48() {
let cases = [
"12|13-14-15-16-17", "12:13-14-15-16-17", "12-13-4-15-16-17", "12-13-z1-15-16-17", "12-13-1z-15-16-17", "12-15-16-17", "12-15-16-17-3",
"12-13-14-15-16-17-18", ];
for case in cases {
let result = parse_eui48(case);
assert!(result.is_err(), "{case}: {result:?}");
}
}
}
#[test]
fn test_fill_magic_packet() {
let mac_address = MacAddress::from([0x26, 0xCE, 0x55, 0xA5, 0xC2, 0x33]);
let mut buffer = [0; 102];
fill_magic_packet(&mut buffer, mac_address);
let expected_packet: [u8; 102] = [
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x26, 0xCE, 0x55, 0xA5, 0xC2, 0x33, 0x26, 0xCE, 0x55, 0xA5, 0xC2, 0x33, 0x26, 0xCE, 0x55, 0xA5, 0xC2, 0x33, 0x26, 0xCE, 0x55, 0xA5, 0xC2, 0x33, 0x26, 0xCE, 0x55, 0xA5, 0xC2, 0x33, 0x26, 0xCE, 0x55, 0xA5, 0xC2, 0x33, 0x26, 0xCE, 0x55, 0xA5, 0xC2, 0x33, 0x26, 0xCE, 0x55, 0xA5, 0xC2, 0x33, 0x26, 0xCE, 0x55, 0xA5, 0xC2, 0x33, 0x26, 0xCE, 0x55, 0xA5, 0xC2, 0x33, 0x26, 0xCE, 0x55, 0xA5, 0xC2, 0x33, 0x26, 0xCE, 0x55, 0xA5, 0xC2, 0x33, 0x26, 0xCE, 0x55, 0xA5, 0xC2, 0x33, 0x26, 0xCE, 0x55, 0xA5, 0xC2, 0x33, 0x26, 0xCE, 0x55, 0xA5, 0xC2, 0x33, 0x26, 0xCE, 0x55, 0xA5, 0xC2, 0x33, ];
assert_eq!(buffer, expected_packet);
}
#[test]
fn test_fill_magic_packet_secure_on() {
let secure_on = [0x12, 0x13, 0x14, 0x15, 0x16, 0x42];
let mac_address = MacAddress::from([0x26, 0xCE, 0x55, 0xA5, 0xC2, 0x33]);
let mut buffer = [0; 108];
fill_magic_packet_secure_on(&mut buffer, mac_address, secure_on.into());
let expected_packet: [u8; 108] = [
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x26, 0xCE, 0x55, 0xA5, 0xC2, 0x33, 0x26, 0xCE, 0x55, 0xA5, 0xC2, 0x33, 0x26, 0xCE, 0x55, 0xA5, 0xC2, 0x33, 0x26, 0xCE, 0x55, 0xA5, 0xC2, 0x33, 0x26, 0xCE, 0x55, 0xA5, 0xC2, 0x33, 0x26, 0xCE, 0x55, 0xA5, 0xC2, 0x33, 0x26, 0xCE, 0x55, 0xA5, 0xC2, 0x33, 0x26, 0xCE, 0x55, 0xA5, 0xC2, 0x33, 0x26, 0xCE, 0x55, 0xA5, 0xC2, 0x33, 0x26, 0xCE, 0x55, 0xA5, 0xC2, 0x33, 0x26, 0xCE, 0x55, 0xA5, 0xC2, 0x33, 0x26, 0xCE, 0x55, 0xA5, 0xC2, 0x33, 0x26, 0xCE, 0x55, 0xA5, 0xC2, 0x33, 0x26, 0xCE, 0x55, 0xA5, 0xC2, 0x33, 0x26, 0xCE, 0x55, 0xA5, 0xC2, 0x33, 0x26, 0xCE, 0x55, 0xA5, 0xC2, 0x33, 0x12, 0x13, 0x14, 0x15, 0x16, 0x42, ];
assert_eq!(buffer, expected_packet);
}
#[test]
fn test_write_magic_packet() {
let mac_address = MacAddress::from([0x26, 0xCE, 0x55, 0xA5, 0xC2, 0x33]);
let mut buffer = Vec::new();
write_magic_packet(&mut buffer, mac_address, None).unwrap();
let expected_packet: [u8; 102] = [
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x26, 0xCE, 0x55, 0xA5, 0xC2, 0x33, 0x26, 0xCE, 0x55, 0xA5, 0xC2, 0x33, 0x26, 0xCE, 0x55, 0xA5, 0xC2, 0x33, 0x26, 0xCE, 0x55, 0xA5, 0xC2, 0x33, 0x26, 0xCE, 0x55, 0xA5, 0xC2, 0x33, 0x26, 0xCE, 0x55, 0xA5, 0xC2, 0x33, 0x26, 0xCE, 0x55, 0xA5, 0xC2, 0x33, 0x26, 0xCE, 0x55, 0xA5, 0xC2, 0x33, 0x26, 0xCE, 0x55, 0xA5, 0xC2, 0x33, 0x26, 0xCE, 0x55, 0xA5, 0xC2, 0x33, 0x26, 0xCE, 0x55, 0xA5, 0xC2, 0x33, 0x26, 0xCE, 0x55, 0xA5, 0xC2, 0x33, 0x26, 0xCE, 0x55, 0xA5, 0xC2, 0x33, 0x26, 0xCE, 0x55, 0xA5, 0xC2, 0x33, 0x26, 0xCE, 0x55, 0xA5, 0xC2, 0x33, 0x26, 0xCE, 0x55, 0xA5, 0xC2, 0x33, ];
assert_eq!(buffer.as_slice(), expected_packet.as_slice());
}
#[test]
fn test_write_magic_packet_secure_on() {
let secure_on = [0x12, 0x13, 0x14, 0x15, 0x16, 0x42];
let mac_address = MacAddress::from([0x26, 0xCE, 0x55, 0xA5, 0xC2, 0x33]);
let mut buffer = Vec::new();
write_magic_packet(&mut buffer, mac_address, Some(secure_on.into())).unwrap();
let expected_packet: [u8; 108] = [
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x26, 0xCE, 0x55, 0xA5, 0xC2, 0x33, 0x26, 0xCE, 0x55, 0xA5, 0xC2, 0x33, 0x26, 0xCE, 0x55, 0xA5, 0xC2, 0x33, 0x26, 0xCE, 0x55, 0xA5, 0xC2, 0x33, 0x26, 0xCE, 0x55, 0xA5, 0xC2, 0x33, 0x26, 0xCE, 0x55, 0xA5, 0xC2, 0x33, 0x26, 0xCE, 0x55, 0xA5, 0xC2, 0x33, 0x26, 0xCE, 0x55, 0xA5, 0xC2, 0x33, 0x26, 0xCE, 0x55, 0xA5, 0xC2, 0x33, 0x26, 0xCE, 0x55, 0xA5, 0xC2, 0x33, 0x26, 0xCE, 0x55, 0xA5, 0xC2, 0x33, 0x26, 0xCE, 0x55, 0xA5, 0xC2, 0x33, 0x26, 0xCE, 0x55, 0xA5, 0xC2, 0x33, 0x26, 0xCE, 0x55, 0xA5, 0xC2, 0x33, 0x26, 0xCE, 0x55, 0xA5, 0xC2, 0x33, 0x26, 0xCE, 0x55, 0xA5, 0xC2, 0x33, 0x12, 0x13, 0x14, 0x15, 0x16, 0x42, ];
assert_eq!(buffer.as_slice(), expected_packet.as_slice());
}
}