# Usage
This guide covers the default UDP receive API. Optional raw packet, metrics,
Python, and detailed IPv6 guidance live in their own docs.
## Core Flow
```rust
use mcrx_core::{Context, SubscriptionConfig};
let mut ctx = Context::new();
let config = SubscriptionConfig::asm(group, port);
let id = ctx.add_subscription(config)?;
ctx.join_subscription(id)?;
if let Some(packet) = ctx.try_recv_any()? {
println!("received {} bytes", packet.payload_len());
}
```
For SSM:
```rust
let config = SubscriptionConfig::ssm(group, source, port);
```
To join on a specific local interface:
```rust
let mut config = SubscriptionConfig::asm(group, port);
config.interface = Some(interface.into());
```
For IPv6 link-local or otherwise ambiguous joins, set an interface index too:
```rust
let mut config = SubscriptionConfig::asm_v6(group, port);
config.interface = Some("fe80::1".parse()?);
config.interface_index = Some(7);
```
See [IPv6 Multicast](ipv6.md) for group scoping and interface-selection rules.
## Existing Sockets
Use `add_subscription_with_socket()` when an integration needs to create or
bind the UDP socket itself. The socket must already be bound to
`config.dst_port`.
```rust
use socket2::{Domain, Protocol, SockAddr, Socket, Type};
use std::net::{Ipv4Addr, SocketAddrV4};
let config = SubscriptionConfig::asm(Ipv4Addr::new(239, 1, 2, 3), 5000);
let socket = Socket::new(Domain::IPV4, Type::DGRAM, Some(Protocol::UDP))?;
socket.set_reuse_address(true)?;
socket.bind(&SockAddr::from(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 5000)))?;
let id = ctx.add_subscription_with_socket(config, socket)?;
ctx.join_subscription(id)?;
```
The supplied socket is switched to non-blocking mode. Multicast join and leave
still flow through `join_subscription()` and `leave_subscription()`.
## Receiving
From any joined subscription:
```rust
if let Some(packet) = ctx.try_recv_any()? {
println!("received {} bytes", packet.payload_len());
}
```
From one specific subscription:
```rust
let subscription = ctx.get_subscription(id).unwrap();
if let Some(packet) = subscription.try_recv()? {
println!("received {} bytes", packet.payload_len());
}
```
`try_recv_any()` uses round-robin style fairness across joined subscriptions.
All receive APIs are non-blocking: `Ok(None)` means no packet is currently
available.
## Receive Metadata
Use the metadata-aware APIs when you need local destination or interface
context:
```rust
let subscription = ctx.get_subscription(id).unwrap();
if let Some(packet) = subscription.try_recv_with_metadata()? {
println!("destination ip: {:?}", packet.metadata.destination_local_ip);
println!("ingress interface: {:?}", packet.metadata.ingress_interface_index);
}
```
The plain `Packet` type remains small and stable. `PacketWithMetadata` wraps it
with optional platform receive metadata.
## Batch Receive
Bounded batch:
```rust
let mut packets = Vec::new();
ctx.try_recv_batch_into(&mut packets, 64)?;
```
Drain everything currently available:
```rust
let mut packets = Vec::new();
ctx.try_recv_all_into(&mut packets)?;
```
## Event Loop Integration
Borrow a live socket while the subscription stays inside the `Context`:
```rust
let subscription = ctx.get_subscription(id).unwrap();
let socket = subscription.socket();
#[cfg(unix)]
let raw = subscription.as_raw_fd();
```
Extract ownership for handoff into another runtime:
```rust
let subscription = ctx.take_subscription(id).unwrap();
let parts = subscription.into_parts();
let socket = parts.socket;
```
`take_subscription()` preserves the lifecycle state. A joined subscription can
continue receiving after handoff.
## Optional Extensions
- Tokio: enable `tokio` and use `TokioSubscription`; see [Demo Binaries](demo.md).
- Metrics: enable `metrics`; see [Metrics](metrics.md).
- Raw IP datagrams: enable `raw-packets`; see [Raw Packet Receive](raw-packets.md).
- Python bindings: see [Python Bindings](python.md).
## Leaving, Removing, and Taking Ownership
```rust
ctx.leave_subscription(id)?;
ctx.remove_subscription(id);
```
If you need the owned subscription back:
```rust
if let Some(subscription) = ctx.take_subscription(id) {
let socket = subscription.into_socket();
drop(socket);
}
```
## Multiple Subscriptions
```rust
let mut ctx = Context::new();
let id1 = ctx.add_subscription(SubscriptionConfig::asm(group1, 5000))?;
let id2 = ctx.add_subscription(SubscriptionConfig::asm(group2, 5001))?;
ctx.join_subscription(id1)?;
ctx.join_subscription(id2)?;
if let Some(packet) = ctx.try_recv_any()? {
println!("received on subscription {}", packet.subscription_id.0);
}
```