aboutsummaryrefslogtreecommitdiff
path: root/src/bin/mcast_test.rs
blob: 782610c0cc4b1ceef660edd9404e808415c06be1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
use anyhow::{Context, Result};
use clap::Parser;
use log::{error, info};
use socket2::{Domain, Protocol, Socket, Type};
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use std::str::FromStr;
use std::time::{Duration, Instant};
use tokio::net::UdpSocket;

#[derive(Parser, Debug)]
#[command(author, version, about = "Multicast packet generator for testing CastRepeat")]
struct Args {
    #[arg(short, long, default_value = "239.192.55.1")]
    multicast_addr: String,

    #[arg(short, long, default_value = "1681")]
    port: u16,

    #[arg(short, long, default_value = "1000")]
    interval_ms: u64,

    #[arg(short, long, default_value = "60")]
    duration_sec: u64,

    #[arg(short, long, default_value = "Test packet")]
    message: String,
    
    #[arg(short, long)]
    interface: Option<String>,
    
    #[arg(short = 'b', long, action)]
    binary_mode: bool,
}

#[tokio::main]
async fn main() -> Result<()> {
    env_logger::init();
    let args = Args::parse();

    info!("CastRepeat Multicast Packet Generator");
    info!("--------------------------------");
    info!("Multicast Address: {}", args.multicast_addr);
    info!("Port:              {}", args.port);
    info!("Interval:          {} ms", args.interval_ms);
    info!("Duration:          {} sec", args.duration_sec);
    if let Some(interface) = &args.interface {
        info!("Interface:         {}", interface);
    }
    info!("Mode:              {}", if args.binary_mode { "Binary" } else { "Text" });
    info!("--------------------------------");

    // Verify multicast address
    let mcast_addr = match IpAddr::from_str(&args.multicast_addr) {
        Ok(IpAddr::V4(addr)) if addr.is_multicast() => addr,
        _ => {
            error!("Invalid multicast address: {}", args.multicast_addr);
            return Ok(());
        }
    };

    // Create socket
    let socket = Socket::new(Domain::IPV4, Type::DGRAM, Some(Protocol::UDP))
        .context("Failed to create socket")?;

    socket.set_multicast_ttl_v4(4)?;
    socket.set_nonblocking(true)?;
    
    // Set the multicast interface if specified
    if let Some(if_str) = &args.interface {
        if let Ok(if_addr) = Ipv4Addr::from_str(if_str) {
            socket.set_multicast_if_v4(&if_addr)?;
            info!("Using interface: {}", if_addr);
        } else {
            error!("Invalid interface address: {}", if_str);
            return Ok(());
        }
    }

    let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0);
    socket.bind(&addr.into())?;

    let socket = UdpSocket::from_std(socket.into())?;
    let dest_addr = SocketAddr::new(IpAddr::V4(mcast_addr), args.port);

    // Start sending packets
    info!("Sending packets to {}...", dest_addr);

    let start = Instant::now();
    let end = start + Duration::from_secs(args.duration_sec);
    let mut counter: u64 = 0;  // Specify counter type as u64

    while Instant::now() < end {
        counter += 1;
        
        // Create either text or binary test data
        let data = if args.binary_mode {
            // Create binary test data (similar to what we might see in the field)
            let mut packet = Vec::with_capacity(16);
            packet.extend_from_slice(b"REL\0");          // 4 bytes header
            packet.extend_from_slice(&counter.to_be_bytes()[4..]); // 4 bytes counter 
            packet.extend_from_slice(&[0x00, 0x10, 0x02, 0x00]); // 4 bytes
            packet.extend_from_slice(&[0x00, 0x00, 0x00, 0x90]); // 4 bytes
            packet
        } else {
            // Create text test data
            format!("{} #{}", args.message, counter).into_bytes()
        };

        match socket.send_to(&data, &dest_addr).await {
            Ok(bytes) => {
                info!("Sent packet #{}: {} bytes", counter, bytes);
            }
            Err(e) => {
                error!("Error sending packet: {}", e);
            }
        }

        tokio::time::sleep(Duration::from_millis(args.interval_ms)).await;
    }

    info!("Done. Sent {} packets in {} seconds.", counter, args.duration_sec);
    Ok(())
}