use rusteron_client::*;
use rusteron_media_driver::testing::{find_unused_udp_port, EmbeddedDriver};
use std::collections::HashSet;
use std::thread::sleep;
use std::time::{Duration, Instant};
const STREAM_ID: i32 = 1003;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let driver = EmbeddedDriver::launch()?;
let ctx = AeronContext::new()?;
ctx.set_dir(&cformat!("{}", driver.dir()))?;
ctx.set_error_handler(Some(|code: i32, msg: &str| eprintln!("aeron error {code}: {msg}")))?;
let aeron = Aeron::new(&ctx)?;
aeron.start()?;
let mds_channel = {
let builder = AeronUriStringBuilder::new_zeroed_on_heap();
builder.init_new()?;
builder.media(Media::Udp)?.control_mode(ControlMode::Manual)?;
builder.build(256)?
};
let subscription = aeron
.async_add_subscription(&cformat!("{mds_channel}"), STREAM_ID, Handlers::NONE, Handlers::NONE)?
.poll_blocking(Duration::from_secs(5))?;
let port_a = find_unused_udp_port(20200).expect("no free port");
let port_b = find_unused_udp_port(port_a + 1).expect("no free port");
let destination_a = AeronUriStringBuilder::udp(&format!("127.0.0.1:{port_a}"))?.build(256)?;
let destination_b = AeronUriStringBuilder::udp(&format!("127.0.0.1:{port_b}"))?.build(256)?;
subscription.add_destination(&cformat!("{destination_a}"), Duration::from_secs(5))?;
subscription.add_destination(&cformat!("{destination_b}"), Duration::from_secs(5))?;
println!("subscription aggregating {destination_a} + {destination_b}");
let publication_a = aeron
.async_add_publication(&cformat!("{destination_a}"), STREAM_ID)?
.poll_blocking(Duration::from_secs(5))?;
let publication_b = aeron
.async_add_publication(&cformat!("{destination_b}"), STREAM_ID)?
.poll_blocking(Duration::from_secs(5))?;
let start = Instant::now();
while (!publication_a.is_connected() || !publication_b.is_connected()) && start.elapsed() < Duration::from_secs(10)
{
sleep(Duration::from_millis(10));
}
assert!(
publication_a.is_connected() && publication_b.is_connected(),
"publications did not connect to the MDS destinations (a={}, b={})",
publication_a.is_connected(),
publication_b.is_connected()
);
for (publication, tag) in [(&publication_a, "feed-A"), (&publication_b, "feed-B")] {
let deadline = Instant::now() + Duration::from_secs(5);
loop {
match publication.offer(tag.as_bytes()) {
Ok(_) => break,
Err(e) if e.is_retryable() && Instant::now() < deadline => sleep(Duration::from_millis(1)),
Err(e) => return Err(format!("offer on {tag} failed: {e}").into()),
}
}
}
let mut seen: HashSet<String> = HashSet::new();
let deadline = Instant::now() + Duration::from_secs(5);
while seen.len() < 2 && Instant::now() < deadline {
subscription.poll_fn(
|buf, _hdr| {
let msg = String::from_utf8_lossy(buf).to_string();
println!("received {msg}");
seen.insert(msg);
},
16,
)?;
sleep(Duration::from_millis(1));
}
assert!(
seen.contains("feed-A") && seen.contains("feed-B"),
"expected both feeds through the MDS subscription, got {seen:?}"
);
println!("both destinations delivered through one subscription");
drop(publication_a);
drop(publication_b);
drop(subscription);
drop(aeron);
Ok(())
}