pipewire/test/test-avb.c

2115 lines
61 KiB
C
Raw Normal View History

/* AVB tests */
/* SPDX-FileCopyrightText: Copyright © 2026 PipeWire contributors */
/* SPDX-License-Identifier: MIT */
#include "pwtest.h"
#include <pipewire/pipewire.h>
#include "module-avb/aecp-aem-descriptors.h"
#include "module-avb/aecp-aem.h"
#include "module-avb/aecp-aem-types.h"
#include "module-avb/aecp-aem-cmds-resps/cmd-lock-entity.h"
#include "test-avb-utils.h"
static struct impl *test_impl_new(void)
{
struct impl *impl;
struct pw_main_loop *ml;
struct pw_context *context;
pw_init(0, NULL);
ml = pw_main_loop_new(NULL);
pwtest_ptr_notnull(ml);
context = pw_context_new(pw_main_loop_get_loop(ml),
pw_properties_new(
PW_KEY_CONFIG_NAME, "null",
NULL), 0);
pwtest_ptr_notnull(context);
impl = calloc(1, sizeof(*impl));
pwtest_ptr_notnull(impl);
impl->loop = pw_main_loop_get_loop(ml);
impl->timer_queue = pw_context_get_timer_queue(context);
impl->context = context;
spa_list_init(&impl->servers);
return impl;
}
static void test_impl_free(struct impl *impl)
{
struct server *s;
spa_list_consume(s, &impl->servers, link)
avb_test_server_free(s);
free(impl);
pw_deinit();
}
/*
* Test: inject an ADP ENTITY_AVAILABLE packet and verify
* that the server processes it without error.
*/
PWTEST(avb_adp_entity_available)
{
struct impl *impl;
struct server *server;
uint8_t pkt[256];
int len;
static const uint8_t remote_mac[6] = { 0x02, 0x00, 0x00, 0x00, 0x00, 0x02 };
uint64_t remote_entity_id = 0x020000fffe000002ULL;
impl = test_impl_new();
server = avb_test_server_new(impl);
pwtest_ptr_notnull(server);
/* Build and inject an entity available packet from a remote device */
len = avb_test_build_adp_entity_available(pkt, sizeof(pkt),
remote_mac, remote_entity_id, 10);
pwtest_int_gt(len, 0);
avb_test_inject_packet(server, 1 * SPA_NSEC_PER_SEC, pkt, len);
/* The packet should have been processed without crashing.
* We can't easily inspect ADP internal state without exposing it,
* but we can verify the server is still functional by doing another
* inject and triggering periodic. */
avb_test_tick(server, 2 * SPA_NSEC_PER_SEC);
test_impl_free(impl);
return PWTEST_PASS;
}
/*
* Test: inject ENTITY_AVAILABLE then ENTITY_DEPARTING for the same entity.
*/
PWTEST(avb_adp_entity_departing)
{
struct impl *impl;
struct server *server;
uint8_t pkt[256];
int len;
static const uint8_t remote_mac[6] = { 0x02, 0x00, 0x00, 0x00, 0x00, 0x03 };
uint64_t remote_entity_id = 0x020000fffe000003ULL;
impl = test_impl_new();
server = avb_test_server_new(impl);
pwtest_ptr_notnull(server);
/* First make the entity known */
len = avb_test_build_adp_entity_available(pkt, sizeof(pkt),
remote_mac, remote_entity_id, 10);
avb_test_inject_packet(server, 1 * SPA_NSEC_PER_SEC, pkt, len);
/* Now send departing */
len = avb_test_build_adp_entity_departing(pkt, sizeof(pkt),
remote_mac, remote_entity_id);
pwtest_int_gt(len, 0);
avb_test_inject_packet(server, 2 * SPA_NSEC_PER_SEC, pkt, len);
avb_test_tick(server, 3 * SPA_NSEC_PER_SEC);
test_impl_free(impl);
return PWTEST_PASS;
}
/*
* Test: inject ENTITY_DISCOVER with entity_id=0 (discover all).
* The server should respond with its own entity advertisement
* once it has one (after periodic runs check_advertise).
*/
PWTEST(avb_adp_entity_discover)
{
struct impl *impl;
struct server *server;
uint8_t pkt[256];
int len;
static const uint8_t remote_mac[6] = { 0x02, 0x00, 0x00, 0x00, 0x00, 0x04 };
impl = test_impl_new();
server = avb_test_server_new(impl);
pwtest_ptr_notnull(server);
/* Trigger periodic to let the server advertise its own entity
* (check_advertise reads the entity descriptor) */
avb_test_tick(server, 1 * SPA_NSEC_PER_SEC);
avb_loopback_clear_packets(server);
/* Send discover-all (entity_id = 0) */
len = avb_test_build_adp_entity_discover(pkt, sizeof(pkt),
remote_mac, 0);
pwtest_int_gt(len, 0);
avb_test_inject_packet(server, 2 * SPA_NSEC_PER_SEC, pkt, len);
/* The server should have sent an advertise response */
pwtest_int_gt(avb_loopback_get_packet_count(server), 0);
test_impl_free(impl);
return PWTEST_PASS;
}
/*
* Test: entity timeout add an entity, then advance time past
* valid_time + 2 seconds and verify periodic cleans it up.
*/
PWTEST(avb_adp_entity_timeout)
{
struct impl *impl;
struct server *server;
uint8_t pkt[256];
int len;
static const uint8_t remote_mac[6] = { 0x02, 0x00, 0x00, 0x00, 0x00, 0x05 };
uint64_t remote_entity_id = 0x020000fffe000005ULL;
int valid_time = 10; /* seconds */
impl = test_impl_new();
server = avb_test_server_new(impl);
pwtest_ptr_notnull(server);
/* Add entity */
len = avb_test_build_adp_entity_available(pkt, sizeof(pkt),
remote_mac, remote_entity_id, valid_time);
avb_test_inject_packet(server, 1 * SPA_NSEC_PER_SEC, pkt, len);
/* Tick at various times before timeout — entity should survive */
avb_test_tick(server, 5 * SPA_NSEC_PER_SEC);
avb_test_tick(server, 10 * SPA_NSEC_PER_SEC);
/* Tick past valid_time + 2 seconds from last_time (1s + 12s = 13s) */
avb_test_tick(server, 14 * SPA_NSEC_PER_SEC);
/* The entity should have been timed out and cleaned up.
* If the entity was still present and had advertise=true, a departing
* packet would be sent. Inject a discover to verify: if the entity
* is gone, no response for that specific entity_id. */
avb_loopback_clear_packets(server);
len = avb_test_build_adp_entity_discover(pkt, sizeof(pkt),
remote_mac, remote_entity_id);
avb_test_inject_packet(server, 15 * SPA_NSEC_PER_SEC, pkt, len);
/* Remote entities don't have advertise=true, so even before timeout
* a discover for them wouldn't generate a response. But at least
* the timeout path was exercised without crashes. */
test_impl_free(impl);
return PWTEST_PASS;
}
/*
* Test: basic MRP attribute lifecycle create, begin, join.
*/
PWTEST(avb_mrp_attribute_lifecycle)
{
struct impl *impl;
struct server *server;
struct avb_msrp_attribute *attr;
impl = test_impl_new();
server = avb_test_server_new(impl);
pwtest_ptr_notnull(server);
/* Create an MSRP talker attribute */
attr = avb_msrp_attribute_new(server->msrp,
AVB_MSRP_ATTRIBUTE_TYPE_TALKER_ADVERTISE);
pwtest_ptr_notnull(attr);
pwtest_ptr_notnull(attr->mrp);
/* Begin and join the attribute */
avb_mrp_attribute_begin(attr->mrp, 0);
avb_mrp_attribute_join(attr->mrp, 0, true);
/* Tick to process the MRP state machine */
avb_test_tick(server, 1 * SPA_NSEC_PER_SEC);
avb_test_tick(server, 2 * SPA_NSEC_PER_SEC);
test_impl_free(impl);
return PWTEST_PASS;
}
/*
* Test: server with Milan v1.2 mode.
*/
PWTEST(avb_milan_server_create)
{
struct impl *impl;
struct server *server;
impl = test_impl_new();
/* Create a Milan-mode server manually */
server = calloc(1, sizeof(*server));
pwtest_ptr_notnull(server);
server->impl = impl;
server->ifname = strdup("test0");
server->avb_mode = AVB_MODE_MILAN_V12;
server->transport = &avb_transport_loopback;
spa_list_append(&impl->servers, &server->link);
spa_hook_list_init(&server->listener_list);
spa_list_init(&server->descriptors);
pwtest_int_eq(server->transport->setup(server), 0);
server->mrp = avb_mrp_new(server);
pwtest_ptr_notnull(server->mrp);
avb_aecp_register(server);
server->maap = avb_maap_register(server);
server->mmrp = avb_mmrp_register(server);
server->msrp = avb_msrp_register(server);
server->mvrp = avb_mvrp_register(server);
avb_adp_register(server);
avb_acmp_register(server);
server->domain_attr = avb_msrp_attribute_new(server->msrp,
AVB_MSRP_ATTRIBUTE_TYPE_DOMAIN);
server->domain_attr->attr.domain.sr_class_id = AVB_MSRP_CLASS_ID_DEFAULT;
server->domain_attr->attr.domain.sr_class_priority = AVB_MSRP_PRIORITY_DEFAULT;
server->domain_attr->attr.domain.sr_class_vid = htons(AVB_DEFAULT_VLAN);
avb_mrp_attribute_begin(server->domain_attr->mrp, 0);
avb_mrp_attribute_join(server->domain_attr->mrp, 0, true);
/* Add minimal entity descriptor (skip init_descriptors which needs pw_core) */
{
struct avb_aem_desc_entity entity;
memset(&entity, 0, sizeof(entity));
entity.entity_id = htobe64(server->entity_id);
entity.entity_model_id = htobe64(0x0001000000000001ULL);
entity.configurations_count = htons(1);
server_add_descriptor(server, AVB_AEM_DESC_ENTITY, 0,
sizeof(entity), &entity);
}
/* Verify Milan mode was set correctly */
pwtest_str_eq(get_avb_mode_str(server->avb_mode), "Milan V1.2");
/* Tick to exercise periodic handlers with Milan descriptors */
avb_test_tick(server, 1 * SPA_NSEC_PER_SEC);
test_impl_free(impl);
return PWTEST_PASS;
}
/*
* =====================================================================
* Phase 3: MRP State Machine Tests
* =====================================================================
*/
/*
* Test: MRP attribute begin sets initial state, join(new=true) enables
* pending_send after TX event via periodic tick.
*/
PWTEST(avb_mrp_begin_join_new_tx)
{
struct impl *impl;
struct server *server;
struct avb_msrp_attribute *attr;
impl = test_impl_new();
server = avb_test_server_new(impl);
pwtest_ptr_notnull(server);
/* Create a talker attribute */
attr = avb_msrp_attribute_new(server->msrp,
AVB_MSRP_ATTRIBUTE_TYPE_TALKER_ADVERTISE);
pwtest_ptr_notnull(attr);
/* After begin, pending_send should be 0 */
avb_mrp_attribute_begin(attr->mrp, 0);
pwtest_int_eq(attr->mrp->pending_send, 0);
/* Join with new=true */
avb_mrp_attribute_join(attr->mrp, 0, true);
/* Tick to let timers initialize (first periodic skips events) */
avb_test_tick(server, 1 * SPA_NSEC_PER_SEC);
/* Tick past join timer (100ms) to trigger TX event */
avb_test_tick(server, 1 * SPA_NSEC_PER_SEC + 200 * SPA_NSEC_PER_MSEC);
/* After TX, pending_send should be set (NEW=0 encoded as non-zero
* only if the state machine decided to send). The VN state on TX
* produces SEND_NEW. But pending_send is only written if joined=true. */
/* We mainly verify no crash and that the state machine ran. */
test_impl_free(impl);
return PWTEST_PASS;
}
/*
* Test: MRP attribute join then leave cycle.
* After leave, the attribute should eventually stop sending.
*/
PWTEST(avb_mrp_join_leave_cycle)
{
struct impl *impl;
struct server *server;
struct avb_msrp_attribute *attr;
impl = test_impl_new();
server = avb_test_server_new(impl);
pwtest_ptr_notnull(server);
attr = avb_msrp_attribute_new(server->msrp,
AVB_MSRP_ATTRIBUTE_TYPE_TALKER_ADVERTISE);
pwtest_ptr_notnull(attr);
avb_mrp_attribute_begin(attr->mrp, 0);
avb_mrp_attribute_join(attr->mrp, 0, true);
/* Let the state machine run a few cycles */
avb_test_tick(server, 1 * SPA_NSEC_PER_SEC);
avb_test_tick(server, 1 * SPA_NSEC_PER_SEC + 200 * SPA_NSEC_PER_MSEC);
/* Now leave */
avb_mrp_attribute_leave(attr->mrp, 2 * SPA_NSEC_PER_SEC);
/* After leave, pending_send should reflect leaving state.
* The next TX event should send LV and transition to VO. */
avb_test_tick(server, 2 * SPA_NSEC_PER_SEC + 200 * SPA_NSEC_PER_MSEC);
/* After the TX, pending_send should be 0 (joined is false,
* so pending_send is not updated by the state machine). */
pwtest_int_eq(attr->mrp->pending_send, 0);
test_impl_free(impl);
return PWTEST_PASS;
}
/*
* Test: MRP attribute receives RX_NEW, which triggers a registrar
* notification (NOTIFY_NEW). Verify via a notification tracker.
*/
struct notify_tracker {
int new_count;
int join_count;
int leave_count;
uint8_t last_notify;
};
static void track_mrp_notify(void *data, uint64_t now,
struct avb_mrp_attribute *attr, uint8_t notify)
{
struct notify_tracker *t = data;
t->last_notify = notify;
switch (notify) {
case AVB_MRP_NOTIFY_NEW:
t->new_count++;
break;
case AVB_MRP_NOTIFY_JOIN:
t->join_count++;
break;
case AVB_MRP_NOTIFY_LEAVE:
t->leave_count++;
break;
}
}
static const struct avb_mrp_events test_mrp_events = {
AVB_VERSION_MRP_EVENTS,
.notify = track_mrp_notify,
};
PWTEST(avb_mrp_rx_new_notification)
{
struct impl *impl;
struct server *server;
struct avb_msrp_attribute *attr;
struct spa_hook listener;
struct notify_tracker tracker = { 0 };
impl = test_impl_new();
server = avb_test_server_new(impl);
pwtest_ptr_notnull(server);
/* Register a global MRP listener to track notifications */
avb_mrp_add_listener(server->mrp, &listener, &test_mrp_events, &tracker);
attr = avb_msrp_attribute_new(server->msrp,
AVB_MSRP_ATTRIBUTE_TYPE_TALKER_ADVERTISE);
pwtest_ptr_notnull(attr);
avb_mrp_attribute_begin(attr->mrp, 0);
avb_mrp_attribute_join(attr->mrp, 0, true);
/* Simulate receiving NEW from a peer */
avb_mrp_attribute_rx_event(attr->mrp, 1 * SPA_NSEC_PER_SEC,
AVB_MRP_ATTRIBUTE_EVENT_NEW);
/* RX_NEW should trigger NOTIFY_NEW on the registrar */
pwtest_int_eq(tracker.new_count, 1);
pwtest_int_eq(tracker.last_notify, AVB_MRP_NOTIFY_NEW);
/* Simulate receiving JOININ from a peer (already IN, no new notification) */
avb_mrp_attribute_rx_event(attr->mrp, 2 * SPA_NSEC_PER_SEC,
AVB_MRP_ATTRIBUTE_EVENT_JOININ);
/* Registrar was already IN, so no additional JOIN notification */
pwtest_int_eq(tracker.join_count, 0);
spa_hook_remove(&listener);
test_impl_free(impl);
return PWTEST_PASS;
}
/*
* Test: MRP registrar leave timer after RX_LV, the registrar enters
* LV state. After MRP_LVTIMER_MS (1000ms), LV_TIMER fires and
* registrar transitions to MT with NOTIFY_LEAVE.
*/
PWTEST(avb_mrp_registrar_leave_timer)
{
struct impl *impl;
struct server *server;
struct avb_msrp_attribute *attr;
struct spa_hook listener;
struct notify_tracker tracker = { 0 };
impl = test_impl_new();
server = avb_test_server_new(impl);
pwtest_ptr_notnull(server);
avb_mrp_add_listener(server->mrp, &listener, &test_mrp_events, &tracker);
attr = avb_msrp_attribute_new(server->msrp,
AVB_MSRP_ATTRIBUTE_TYPE_TALKER_ADVERTISE);
avb_mrp_attribute_begin(attr->mrp, 0);
avb_mrp_attribute_join(attr->mrp, 0, true);
/* Get registrar to IN state via RX_NEW */
avb_mrp_attribute_rx_event(attr->mrp, 1 * SPA_NSEC_PER_SEC,
AVB_MRP_ATTRIBUTE_EVENT_NEW);
pwtest_int_eq(tracker.new_count, 1);
/* RX_LV transitions registrar IN -> LV, sets leave_timeout */
avb_mrp_attribute_rx_event(attr->mrp, 2 * SPA_NSEC_PER_SEC,
AVB_MRP_ATTRIBUTE_EVENT_LV);
/* Tick before the leave timer expires — no LEAVE notification yet */
avb_test_tick(server, 2 * SPA_NSEC_PER_SEC + 500 * SPA_NSEC_PER_MSEC);
pwtest_int_eq(tracker.leave_count, 0);
/* Tick after the leave timer expires (1000ms after RX_LV at 2s = 3s) */
avb_test_tick(server, 3 * SPA_NSEC_PER_SEC + 100 * SPA_NSEC_PER_MSEC);
pwtest_int_eq(tracker.leave_count, 1);
spa_hook_remove(&listener);
test_impl_free(impl);
return PWTEST_PASS;
}
/*
* Test: Multiple MRP attributes coexist events applied to all.
*/
PWTEST(avb_mrp_multiple_attributes)
{
struct impl *impl;
struct server *server;
struct avb_msrp_attribute *attr1, *attr2;
impl = test_impl_new();
server = avb_test_server_new(impl);
pwtest_ptr_notnull(server);
attr1 = avb_msrp_attribute_new(server->msrp,
AVB_MSRP_ATTRIBUTE_TYPE_TALKER_ADVERTISE);
attr2 = avb_msrp_attribute_new(server->msrp,
AVB_MSRP_ATTRIBUTE_TYPE_LISTENER);
pwtest_ptr_notnull(attr1);
pwtest_ptr_notnull(attr2);
avb_mrp_attribute_begin(attr1->mrp, 0);
avb_mrp_attribute_join(attr1->mrp, 0, true);
avb_mrp_attribute_begin(attr2->mrp, 0);
avb_mrp_attribute_join(attr2->mrp, 0, false);
/* Periodic tick should apply to both attributes without crash */
avb_test_tick(server, 1 * SPA_NSEC_PER_SEC);
avb_test_tick(server, 1 * SPA_NSEC_PER_SEC + 200 * SPA_NSEC_PER_MSEC);
avb_test_tick(server, 2 * SPA_NSEC_PER_SEC);
test_impl_free(impl);
return PWTEST_PASS;
}
/*
* =====================================================================
* Phase 3: MSRP Tests
* =====================================================================
*/
/*
* Test: Create each MSRP attribute type and verify fields.
*/
PWTEST(avb_msrp_attribute_types)
{
struct impl *impl;
struct server *server;
struct avb_msrp_attribute *talker, *talker_fail, *listener_attr, *domain;
impl = test_impl_new();
server = avb_test_server_new(impl);
pwtest_ptr_notnull(server);
/* Create all four MSRP attribute types */
talker = avb_msrp_attribute_new(server->msrp,
AVB_MSRP_ATTRIBUTE_TYPE_TALKER_ADVERTISE);
pwtest_ptr_notnull(talker);
pwtest_int_eq(talker->type, AVB_MSRP_ATTRIBUTE_TYPE_TALKER_ADVERTISE);
talker_fail = avb_msrp_attribute_new(server->msrp,
AVB_MSRP_ATTRIBUTE_TYPE_TALKER_FAILED);
pwtest_ptr_notnull(talker_fail);
pwtest_int_eq(talker_fail->type, AVB_MSRP_ATTRIBUTE_TYPE_TALKER_FAILED);
listener_attr = avb_msrp_attribute_new(server->msrp,
AVB_MSRP_ATTRIBUTE_TYPE_LISTENER);
pwtest_ptr_notnull(listener_attr);
pwtest_int_eq(listener_attr->type, AVB_MSRP_ATTRIBUTE_TYPE_LISTENER);
domain = avb_msrp_attribute_new(server->msrp,
AVB_MSRP_ATTRIBUTE_TYPE_DOMAIN);
pwtest_ptr_notnull(domain);
pwtest_int_eq(domain->type, AVB_MSRP_ATTRIBUTE_TYPE_DOMAIN);
/* Configure talker with stream parameters */
talker->attr.talker.stream_id = htobe64(0x020000fffe000001ULL);
talker->attr.talker.vlan_id = htons(AVB_DEFAULT_VLAN);
talker->attr.talker.tspec_max_frame_size = htons(256);
talker->attr.talker.tspec_max_interval_frames = htons(
AVB_MSRP_TSPEC_MAX_INTERVAL_FRAMES_DEFAULT);
talker->attr.talker.priority = AVB_MSRP_PRIORITY_DEFAULT;
talker->attr.talker.rank = AVB_MSRP_RANK_DEFAULT;
/* Configure listener for same stream */
listener_attr->attr.listener.stream_id = htobe64(0x020000fffe000001ULL);
listener_attr->param = AVB_MSRP_LISTENER_PARAM_READY;
/* Begin and join all attributes */
avb_mrp_attribute_begin(talker->mrp, 0);
avb_mrp_attribute_join(talker->mrp, 0, true);
avb_mrp_attribute_begin(talker_fail->mrp, 0);
avb_mrp_attribute_join(talker_fail->mrp, 0, true);
avb_mrp_attribute_begin(listener_attr->mrp, 0);
avb_mrp_attribute_join(listener_attr->mrp, 0, true);
avb_mrp_attribute_begin(domain->mrp, 0);
avb_mrp_attribute_join(domain->mrp, 0, true);
/* Tick to exercise all attribute types through the state machine */
avb_test_tick(server, 1 * SPA_NSEC_PER_SEC);
avb_test_tick(server, 1 * SPA_NSEC_PER_SEC + 200 * SPA_NSEC_PER_MSEC);
test_impl_free(impl);
return PWTEST_PASS;
}
/*
* Test: MSRP domain attribute encode/transmit via loopback.
* After join+TX, the domain attribute should produce a packet.
*/
PWTEST(avb_msrp_domain_transmit)
{
struct impl *impl;
struct server *server;
struct avb_msrp_attribute *domain;
impl = test_impl_new();
server = avb_test_server_new(impl);
pwtest_ptr_notnull(server);
/* The test server already has a domain_attr, but create another
* to test independent domain attribute behavior */
domain = avb_msrp_attribute_new(server->msrp,
AVB_MSRP_ATTRIBUTE_TYPE_DOMAIN);
domain->attr.domain.sr_class_id = 7;
domain->attr.domain.sr_class_priority = 2;
domain->attr.domain.sr_class_vid = htons(100);
avb_mrp_attribute_begin(domain->mrp, 0);
avb_mrp_attribute_join(domain->mrp, 0, true);
/* Let timers initialize and then trigger TX */
avb_test_tick(server, 1 * SPA_NSEC_PER_SEC);
avb_loopback_clear_packets(server);
avb_test_tick(server, 1 * SPA_NSEC_PER_SEC + 200 * SPA_NSEC_PER_MSEC);
/* MSRP should have transmitted a packet with domain data */
pwtest_int_gt(avb_loopback_get_packet_count(server), 0);
test_impl_free(impl);
return PWTEST_PASS;
}
/*
* Test: MSRP talker advertise encode/transmit via loopback.
*/
PWTEST(avb_msrp_talker_transmit)
{
struct impl *impl;
struct server *server;
struct avb_msrp_attribute *talker;
uint64_t stream_id = 0x020000fffe000001ULL;
impl = test_impl_new();
server = avb_test_server_new(impl);
pwtest_ptr_notnull(server);
talker = avb_msrp_attribute_new(server->msrp,
AVB_MSRP_ATTRIBUTE_TYPE_TALKER_ADVERTISE);
pwtest_ptr_notnull(talker);
talker->attr.talker.stream_id = htobe64(stream_id);
talker->attr.talker.vlan_id = htons(AVB_DEFAULT_VLAN);
talker->attr.talker.tspec_max_frame_size = htons(256);
talker->attr.talker.tspec_max_interval_frames = htons(1);
talker->attr.talker.priority = AVB_MSRP_PRIORITY_DEFAULT;
talker->attr.talker.rank = AVB_MSRP_RANK_DEFAULT;
avb_mrp_attribute_begin(talker->mrp, 0);
avb_mrp_attribute_join(talker->mrp, 0, true);
/* Let timers initialize */
avb_test_tick(server, 1 * SPA_NSEC_PER_SEC);
avb_loopback_clear_packets(server);
/* Trigger TX */
avb_test_tick(server, 1 * SPA_NSEC_PER_SEC + 200 * SPA_NSEC_PER_MSEC);
/* Should have transmitted the talker advertise */
pwtest_int_gt(avb_loopback_get_packet_count(server), 0);
/* Read the packet and verify it contains valid MSRP data */
{
uint8_t buf[2048];
int len;
struct avb_packet_mrp *mrp_pkt;
len = avb_loopback_get_packet(server, buf, sizeof(buf));
pwtest_int_gt(len, (int)sizeof(struct avb_packet_mrp));
mrp_pkt = (struct avb_packet_mrp *)buf;
pwtest_int_eq(mrp_pkt->version, AVB_MRP_PROTOCOL_VERSION);
}
test_impl_free(impl);
return PWTEST_PASS;
}
/*
* =====================================================================
* Phase 3: MRP Packet Parsing Tests
* =====================================================================
*/
struct parse_tracker {
int check_header_count;
int attr_event_count;
int process_count;
uint8_t last_attr_type;
uint8_t last_event;
uint8_t last_param;
};
static bool test_check_header(void *data, const void *hdr,
size_t *hdr_size, bool *has_params)
{
struct parse_tracker *t = data;
const struct avb_packet_mrp_hdr *h = hdr;
t->check_header_count++;
/* Accept attribute types 1-4 (MSRP-like) */
if (h->attribute_type < 1 || h->attribute_type > 4)
return false;
*hdr_size = sizeof(struct avb_packet_msrp_msg);
*has_params = (h->attribute_type == AVB_MSRP_ATTRIBUTE_TYPE_LISTENER);
return true;
}
static int test_attr_event(void *data, uint64_t now,
uint8_t attribute_type, uint8_t event)
{
struct parse_tracker *t = data;
t->attr_event_count++;
return 0;
}
static int test_process(void *data, uint64_t now,
uint8_t attribute_type, const void *value,
uint8_t event, uint8_t param, int index)
{
struct parse_tracker *t = data;
t->process_count++;
t->last_attr_type = attribute_type;
t->last_event = event;
t->last_param = param;
return 0;
}
static const struct avb_mrp_parse_info test_parse_info = {
AVB_VERSION_MRP_PARSE_INFO,
.check_header = test_check_header,
.attr_event = test_attr_event,
.process = test_process,
};
/*
* Test: Parse a minimal MRP packet with a single domain value.
*/
PWTEST(avb_mrp_parse_single_domain)
{
struct impl *impl;
struct server *server;
struct parse_tracker tracker = { 0 };
uint8_t buf[256];
int pos = 0;
int res;
impl = test_impl_new();
server = avb_test_server_new(impl);
pwtest_ptr_notnull(server);
memset(buf, 0, sizeof(buf));
/* Build MRP packet manually:
* [ethernet header + version] already at offset 0 */
{
struct avb_packet_mrp *mrp = (struct avb_packet_mrp *)buf;
mrp->version = AVB_MRP_PROTOCOL_VERSION;
pos = sizeof(struct avb_packet_mrp);
}
/* MSRP message header for domain (type=4, length=4) */
{
struct avb_packet_msrp_msg *msg =
(struct avb_packet_msrp_msg *)(buf + pos);
struct avb_packet_mrp_vector *v;
struct avb_packet_msrp_domain *d;
uint8_t *ev;
msg->attribute_type = AVB_MSRP_ATTRIBUTE_TYPE_DOMAIN;
msg->attribute_length = sizeof(struct avb_packet_msrp_domain);
v = (struct avb_packet_mrp_vector *)msg->attribute_list;
v->lva = 0;
AVB_MRP_VECTOR_SET_NUM_VALUES(v, 1);
d = (struct avb_packet_msrp_domain *)v->first_value;
d->sr_class_id = AVB_MSRP_CLASS_ID_DEFAULT;
d->sr_class_priority = AVB_MSRP_PRIORITY_DEFAULT;
d->sr_class_vid = htons(AVB_DEFAULT_VLAN);
/* Event byte: 1 value, event=JOININ(1), packed as 1*36 = 36 */
ev = (uint8_t *)(d + 1);
*ev = AVB_MRP_ATTRIBUTE_EVENT_JOININ * 36;
msg->attribute_list_length = htons(
sizeof(*v) + sizeof(*d) + 1 + 2); /* +2 for vector end mark */
/* Vector end mark */
pos += sizeof(*msg) + sizeof(*v) + sizeof(*d) + 1;
buf[pos++] = 0;
buf[pos++] = 0;
/* Attribute end mark */
buf[pos++] = 0;
buf[pos++] = 0;
}
res = avb_mrp_parse_packet(server->mrp, 1 * SPA_NSEC_PER_SEC,
buf, pos, &test_parse_info, &tracker);
pwtest_int_eq(res, 0);
pwtest_int_eq(tracker.check_header_count, 1);
pwtest_int_eq(tracker.process_count, 1);
pwtest_int_eq(tracker.last_attr_type, AVB_MSRP_ATTRIBUTE_TYPE_DOMAIN);
pwtest_int_eq(tracker.last_event, AVB_MRP_ATTRIBUTE_EVENT_JOININ);
test_impl_free(impl);
return PWTEST_PASS;
}
/*
* Test: Parse MRP packet with LVA (leave-all) flag set.
*/
PWTEST(avb_mrp_parse_with_lva)
{
struct impl *impl;
struct server *server;
struct parse_tracker tracker = { 0 };
uint8_t buf[256];
int pos = 0;
int res;
impl = test_impl_new();
server = avb_test_server_new(impl);
pwtest_ptr_notnull(server);
memset(buf, 0, sizeof(buf));
{
struct avb_packet_mrp *mrp = (struct avb_packet_mrp *)buf;
mrp->version = AVB_MRP_PROTOCOL_VERSION;
pos = sizeof(struct avb_packet_mrp);
}
{
struct avb_packet_msrp_msg *msg =
(struct avb_packet_msrp_msg *)(buf + pos);
struct avb_packet_mrp_vector *v;
struct avb_packet_msrp_domain *d;
uint8_t *ev;
msg->attribute_type = AVB_MSRP_ATTRIBUTE_TYPE_DOMAIN;
msg->attribute_length = sizeof(struct avb_packet_msrp_domain);
v = (struct avb_packet_mrp_vector *)msg->attribute_list;
v->lva = 1; /* Set LVA flag */
AVB_MRP_VECTOR_SET_NUM_VALUES(v, 1);
d = (struct avb_packet_msrp_domain *)v->first_value;
d->sr_class_id = AVB_MSRP_CLASS_ID_DEFAULT;
d->sr_class_priority = AVB_MSRP_PRIORITY_DEFAULT;
d->sr_class_vid = htons(AVB_DEFAULT_VLAN);
ev = (uint8_t *)(d + 1);
*ev = AVB_MRP_ATTRIBUTE_EVENT_NEW * 36;
msg->attribute_list_length = htons(
sizeof(*v) + sizeof(*d) + 1 + 2);
pos += sizeof(*msg) + sizeof(*v) + sizeof(*d) + 1;
buf[pos++] = 0;
buf[pos++] = 0;
buf[pos++] = 0;
buf[pos++] = 0;
}
res = avb_mrp_parse_packet(server->mrp, 1 * SPA_NSEC_PER_SEC,
buf, pos, &test_parse_info, &tracker);
pwtest_int_eq(res, 0);
pwtest_int_eq(tracker.check_header_count, 1);
pwtest_int_eq(tracker.attr_event_count, 1); /* LVA event fired */
pwtest_int_eq(tracker.process_count, 1);
pwtest_int_eq(tracker.last_event, AVB_MRP_ATTRIBUTE_EVENT_NEW);
test_impl_free(impl);
return PWTEST_PASS;
}
/*
* Test: Parse MRP packet with multiple values (3 values per event byte).
* Verifies the base-6 event decoding logic.
*/
PWTEST(avb_mrp_parse_three_values)
{
struct impl *impl;
struct server *server;
struct parse_tracker tracker = { 0 };
uint8_t buf[256];
int pos = 0;
int res;
uint8_t ev0 = AVB_MRP_ATTRIBUTE_EVENT_NEW; /* 0 */
uint8_t ev1 = AVB_MRP_ATTRIBUTE_EVENT_JOININ; /* 1 */
uint8_t ev2 = AVB_MRP_ATTRIBUTE_EVENT_MT; /* 4 */
impl = test_impl_new();
server = avb_test_server_new(impl);
pwtest_ptr_notnull(server);
memset(buf, 0, sizeof(buf));
{
struct avb_packet_mrp *mrp = (struct avb_packet_mrp *)buf;
mrp->version = AVB_MRP_PROTOCOL_VERSION;
pos = sizeof(struct avb_packet_mrp);
}
{
struct avb_packet_msrp_msg *msg =
(struct avb_packet_msrp_msg *)(buf + pos);
struct avb_packet_mrp_vector *v;
struct avb_packet_msrp_domain *d;
uint8_t *ev;
msg->attribute_type = AVB_MSRP_ATTRIBUTE_TYPE_DOMAIN;
msg->attribute_length = sizeof(struct avb_packet_msrp_domain);
v = (struct avb_packet_mrp_vector *)msg->attribute_list;
v->lva = 0;
AVB_MRP_VECTOR_SET_NUM_VALUES(v, 3);
/* First value (domain data) — all 3 values share the same
* first_value pointer in the parse callback */
d = (struct avb_packet_msrp_domain *)v->first_value;
d->sr_class_id = AVB_MSRP_CLASS_ID_DEFAULT;
d->sr_class_priority = AVB_MSRP_PRIORITY_DEFAULT;
d->sr_class_vid = htons(AVB_DEFAULT_VLAN);
/* Pack 3 events into 1 byte: ev0*36 + ev1*6 + ev2 */
ev = (uint8_t *)(d + 1);
*ev = ev0 * 36 + ev1 * 6 + ev2;
msg->attribute_list_length = htons(
sizeof(*v) + sizeof(*d) + 1 + 2);
pos += sizeof(*msg) + sizeof(*v) + sizeof(*d) + 1;
buf[pos++] = 0;
buf[pos++] = 0;
buf[pos++] = 0;
buf[pos++] = 0;
}
res = avb_mrp_parse_packet(server->mrp, 1 * SPA_NSEC_PER_SEC,
buf, pos, &test_parse_info, &tracker);
pwtest_int_eq(res, 0);
pwtest_int_eq(tracker.process_count, 3);
/* The last value processed should have event MT (4) */
pwtest_int_eq(tracker.last_event, AVB_MRP_ATTRIBUTE_EVENT_MT);
test_impl_free(impl);
return PWTEST_PASS;
}
/*
* Test: MSRP talker-failed attribute with notification.
* This tests the NULL notify crash that was fixed.
*/
PWTEST(avb_msrp_talker_failed_notify)
{
struct impl *impl;
struct server *server;
struct avb_msrp_attribute *talker_fail;
impl = test_impl_new();
server = avb_test_server_new(impl);
pwtest_ptr_notnull(server);
talker_fail = avb_msrp_attribute_new(server->msrp,
AVB_MSRP_ATTRIBUTE_TYPE_TALKER_FAILED);
pwtest_ptr_notnull(talker_fail);
talker_fail->attr.talker_fail.talker.stream_id =
htobe64(0x020000fffe000001ULL);
talker_fail->attr.talker_fail.failure_code = AVB_MRP_FAIL_BANDWIDTH;
avb_mrp_attribute_begin(talker_fail->mrp, 0);
avb_mrp_attribute_join(talker_fail->mrp, 0, true);
/* Simulate receiving NEW from a peer — this triggers NOTIFY_NEW
* which calls msrp_notify -> dispatch[TALKER_FAILED].notify.
* Before the fix, this would crash with NULL pointer dereference. */
avb_mrp_attribute_rx_event(talker_fail->mrp, 1 * SPA_NSEC_PER_SEC,
AVB_MRP_ATTRIBUTE_EVENT_NEW);
/* If we get here without crashing, the NULL check fix works */
/* Also exercise periodic to verify full lifecycle */
avb_test_tick(server, 2 * SPA_NSEC_PER_SEC);
test_impl_free(impl);
return PWTEST_PASS;
}
/*
* =====================================================================
* Phase 4: ACMP Integration Tests
* =====================================================================
*/
/**
* Build an ACMP packet for injection into a server.
* Returns packet size, or -1 on error.
*/
static int avb_test_build_acmp(uint8_t *buf, size_t bufsize,
const uint8_t src_mac[6],
uint8_t message_type,
uint64_t controller_guid,
uint64_t talker_guid,
uint64_t listener_guid,
uint16_t talker_unique_id,
uint16_t listener_unique_id,
uint16_t sequence_id)
{
struct avb_ethernet_header *h;
struct avb_packet_acmp *p;
size_t len = sizeof(*h) + sizeof(*p);
static const uint8_t acmp_mac[6] = AVB_BROADCAST_MAC;
if (bufsize < len)
return -1;
memset(buf, 0, len);
h = (struct avb_ethernet_header *)buf;
memcpy(h->dest, acmp_mac, 6);
memcpy(h->src, src_mac, 6);
h->type = htons(AVB_TSN_ETH);
p = (struct avb_packet_acmp *)(buf + sizeof(*h));
AVB_PACKET_SET_SUBTYPE(&p->hdr, AVB_SUBTYPE_ACMP);
AVB_PACKET_ACMP_SET_MESSAGE_TYPE(p, message_type);
AVB_PACKET_ACMP_SET_STATUS(p, AVB_ACMP_STATUS_SUCCESS);
p->controller_guid = htobe64(controller_guid);
p->talker_guid = htobe64(talker_guid);
p->listener_guid = htobe64(listener_guid);
p->talker_unique_id = htons(talker_unique_id);
p->listener_unique_id = htons(listener_unique_id);
p->sequence_id = htons(sequence_id);
return len;
}
/*
* Test: ACMP GET_TX_STATE_COMMAND should respond with NOT_SUPPORTED.
*/
PWTEST(avb_acmp_not_supported)
{
struct impl *impl;
struct server *server;
uint8_t pkt[256];
int len;
static const uint8_t remote_mac[6] = { 0x02, 0x00, 0x00, 0x00, 0x00, 0x10 };
uint64_t remote_entity_id = 0x020000fffe000010ULL;
impl = test_impl_new();
server = avb_test_server_new(impl);
pwtest_ptr_notnull(server);
avb_loopback_clear_packets(server);
/* Send GET_TX_STATE_COMMAND to our server as talker */
len = avb_test_build_acmp(pkt, sizeof(pkt), remote_mac,
AVB_ACMP_MESSAGE_TYPE_GET_TX_STATE_COMMAND,
remote_entity_id, /* controller */
server->entity_id, /* talker = us */
0, /* listener */
0, 0, 42);
pwtest_int_gt(len, 0);
avb_test_inject_packet(server, 1 * SPA_NSEC_PER_SEC, pkt, len);
/* Server should respond with NOT_SUPPORTED */
pwtest_int_gt(avb_loopback_get_packet_count(server), 0);
/* Read response and verify it's a GET_TX_STATE_RESPONSE with NOT_SUPPORTED */
{
uint8_t rbuf[256];
int rlen;
struct avb_packet_acmp *resp;
rlen = avb_loopback_get_packet(server, rbuf, sizeof(rbuf));
pwtest_int_gt(rlen, (int)sizeof(struct avb_ethernet_header));
resp = (struct avb_packet_acmp *)(rbuf + sizeof(struct avb_ethernet_header));
pwtest_int_eq((int)AVB_PACKET_ACMP_GET_MESSAGE_TYPE(resp),
AVB_ACMP_MESSAGE_TYPE_GET_TX_STATE_RESPONSE);
pwtest_int_eq((int)AVB_PACKET_ACMP_GET_STATUS(resp),
AVB_ACMP_STATUS_NOT_SUPPORTED);
}
test_impl_free(impl);
return PWTEST_PASS;
}
/*
* Test: ACMP CONNECT_TX_COMMAND to our server with no streams
* should respond with TALKER_NO_STREAM_INDEX.
*/
PWTEST(avb_acmp_connect_tx_no_stream)
{
struct impl *impl;
struct server *server;
uint8_t pkt[256];
int len;
static const uint8_t remote_mac[6] = { 0x02, 0x00, 0x00, 0x00, 0x00, 0x11 };
uint64_t remote_entity_id = 0x020000fffe000011ULL;
impl = test_impl_new();
server = avb_test_server_new(impl);
pwtest_ptr_notnull(server);
avb_loopback_clear_packets(server);
/* Send CONNECT_TX_COMMAND — we have no streams configured */
len = avb_test_build_acmp(pkt, sizeof(pkt), remote_mac,
AVB_ACMP_MESSAGE_TYPE_CONNECT_TX_COMMAND,
remote_entity_id, /* controller */
server->entity_id, /* talker = us */
remote_entity_id, /* listener */
0, 0, 1);
avb_test_inject_packet(server, 1 * SPA_NSEC_PER_SEC, pkt, len);
/* Should respond with CONNECT_TX_RESPONSE + TALKER_NO_STREAM_INDEX */
pwtest_int_gt(avb_loopback_get_packet_count(server), 0);
{
uint8_t rbuf[256];
int rlen;
struct avb_packet_acmp *resp;
rlen = avb_loopback_get_packet(server, rbuf, sizeof(rbuf));
pwtest_int_gt(rlen, (int)sizeof(struct avb_ethernet_header));
resp = (struct avb_packet_acmp *)(rbuf + sizeof(struct avb_ethernet_header));
pwtest_int_eq((int)AVB_PACKET_ACMP_GET_MESSAGE_TYPE(resp),
AVB_ACMP_MESSAGE_TYPE_CONNECT_TX_RESPONSE);
pwtest_int_eq((int)AVB_PACKET_ACMP_GET_STATUS(resp),
AVB_ACMP_STATUS_TALKER_NO_STREAM_INDEX);
}
test_impl_free(impl);
return PWTEST_PASS;
}
/*
* Test: ACMP message addressed to a different entity_id is ignored.
*/
PWTEST(avb_acmp_wrong_entity_ignored)
{
struct impl *impl;
struct server *server;
uint8_t pkt[256];
int len;
static const uint8_t remote_mac[6] = { 0x02, 0x00, 0x00, 0x00, 0x00, 0x12 };
uint64_t other_entity = 0xDEADBEEFCAFE0001ULL;
uint64_t controller_entity = 0x020000fffe000012ULL;
impl = test_impl_new();
server = avb_test_server_new(impl);
pwtest_ptr_notnull(server);
avb_loopback_clear_packets(server);
/* CONNECT_TX_COMMAND addressed to a different talker — should be ignored */
len = avb_test_build_acmp(pkt, sizeof(pkt), remote_mac,
AVB_ACMP_MESSAGE_TYPE_CONNECT_TX_COMMAND,
controller_entity,
other_entity, /* talker = NOT us */
controller_entity, /* listener */
0, 0, 1);
avb_test_inject_packet(server, 1 * SPA_NSEC_PER_SEC, pkt, len);
/* No response should be sent since the GUID doesn't match */
pwtest_int_eq(avb_loopback_get_packet_count(server), 0);
/* CONNECT_RX_COMMAND addressed to a different listener — also ignored */
len = avb_test_build_acmp(pkt, sizeof(pkt), remote_mac,
AVB_ACMP_MESSAGE_TYPE_CONNECT_RX_COMMAND,
controller_entity,
other_entity, /* talker */
other_entity, /* listener = NOT us */
0, 0, 2);
avb_test_inject_packet(server, 2 * SPA_NSEC_PER_SEC, pkt, len);
/* Still no response */
pwtest_int_eq(avb_loopback_get_packet_count(server), 0);
test_impl_free(impl);
return PWTEST_PASS;
}
/*
* Test: ACMP CONNECT_RX_COMMAND to our server as listener.
* Should create a pending request and forward CONNECT_TX_COMMAND to talker.
*/
PWTEST(avb_acmp_connect_rx_forward)
{
struct impl *impl;
struct server *server;
uint8_t pkt[256];
int len;
static const uint8_t controller_mac[6] = { 0x02, 0x00, 0x00, 0x00, 0x00, 0x20 };
uint64_t controller_entity = 0x020000fffe000020ULL;
uint64_t talker_entity = 0x020000fffe000030ULL;
impl = test_impl_new();
server = avb_test_server_new(impl);
pwtest_ptr_notnull(server);
avb_loopback_clear_packets(server);
/* Send CONNECT_RX_COMMAND to us as listener */
len = avb_test_build_acmp(pkt, sizeof(pkt), controller_mac,
AVB_ACMP_MESSAGE_TYPE_CONNECT_RX_COMMAND,
controller_entity,
talker_entity, /* talker = remote */
server->entity_id, /* listener = us */
0, 0, 100);
avb_test_inject_packet(server, 1 * SPA_NSEC_PER_SEC, pkt, len);
/* We should have forwarded a CONNECT_TX_COMMAND to the talker */
pwtest_int_gt(avb_loopback_get_packet_count(server), 0);
{
uint8_t rbuf[256];
int rlen;
struct avb_packet_acmp *cmd;
rlen = avb_loopback_get_packet(server, rbuf, sizeof(rbuf));
pwtest_int_gt(rlen, (int)sizeof(struct avb_ethernet_header));
cmd = (struct avb_packet_acmp *)(rbuf + sizeof(struct avb_ethernet_header));
pwtest_int_eq((int)AVB_PACKET_ACMP_GET_MESSAGE_TYPE(cmd),
AVB_ACMP_MESSAGE_TYPE_CONNECT_TX_COMMAND);
}
test_impl_free(impl);
return PWTEST_PASS;
}
/*
* Test: ACMP pending timeout and retry behavior.
* After CONNECT_RX_COMMAND, the listener creates a pending request.
* After timeout (2000ms for CONNECT_TX), it should retry once.
* After second timeout, it should be cleaned up.
*/
PWTEST(avb_acmp_pending_timeout)
{
struct impl *impl;
struct server *server;
uint8_t pkt[256];
int len;
static const uint8_t controller_mac[6] = { 0x02, 0x00, 0x00, 0x00, 0x00, 0x21 };
uint64_t controller_entity = 0x020000fffe000021ULL;
uint64_t talker_entity = 0x020000fffe000031ULL;
int pkt_count_after_forward;
impl = test_impl_new();
server = avb_test_server_new(impl);
pwtest_ptr_notnull(server);
avb_loopback_clear_packets(server);
/* Create a pending request via CONNECT_RX_COMMAND */
len = avb_test_build_acmp(pkt, sizeof(pkt), controller_mac,
AVB_ACMP_MESSAGE_TYPE_CONNECT_RX_COMMAND,
controller_entity,
talker_entity,
server->entity_id,
0, 0, 200);
avb_test_inject_packet(server, 1 * SPA_NSEC_PER_SEC, pkt, len);
/* Count packets after initial forward */
pkt_count_after_forward = avb_loopback_get_packet_count(server);
pwtest_int_gt(pkt_count_after_forward, 0);
/* Drain the packet queue */
avb_loopback_clear_packets(server);
/* Tick before timeout (2000ms) — no retry yet */
avb_test_tick(server, 2 * SPA_NSEC_PER_SEC);
pwtest_int_eq(avb_loopback_get_packet_count(server), 0);
/* Tick after timeout (1s + 2000ms = 3s) — should retry */
avb_test_tick(server, 3 * SPA_NSEC_PER_SEC + 100 * SPA_NSEC_PER_MSEC);
pwtest_int_gt(avb_loopback_get_packet_count(server), 0);
avb_loopback_clear_packets(server);
/* Tick after second timeout — should give up (no more retries) */
avb_test_tick(server, 5 * SPA_NSEC_PER_SEC + 200 * SPA_NSEC_PER_MSEC);
/* The pending was freed, no more retries */
/* Tick again — should be clean, no crashes */
avb_test_tick(server, 6 * SPA_NSEC_PER_SEC);
test_impl_free(impl);
return PWTEST_PASS;
}
/*
* Test: ACMP message with wrong EtherType or subtype is filtered.
*/
PWTEST(avb_acmp_packet_filtering)
{
struct impl *impl;
struct server *server;
uint8_t pkt[256];
int len;
static const uint8_t remote_mac[6] = { 0x02, 0x00, 0x00, 0x00, 0x00, 0x13 };
struct avb_ethernet_header *h;
struct avb_packet_acmp *p;
impl = test_impl_new();
server = avb_test_server_new(impl);
pwtest_ptr_notnull(server);
/* Build a valid-looking ACMP packet but with wrong EtherType */
len = avb_test_build_acmp(pkt, sizeof(pkt), remote_mac,
AVB_ACMP_MESSAGE_TYPE_GET_TX_STATE_COMMAND,
0, server->entity_id, 0, 0, 0, 1);
h = (struct avb_ethernet_header *)pkt;
h->type = htons(0x1234); /* Wrong EtherType */
avb_loopback_clear_packets(server);
avb_test_inject_packet(server, 1 * SPA_NSEC_PER_SEC, pkt, len);
pwtest_int_eq(avb_loopback_get_packet_count(server), 0);
/* Build packet with wrong subtype */
len = avb_test_build_acmp(pkt, sizeof(pkt), remote_mac,
AVB_ACMP_MESSAGE_TYPE_GET_TX_STATE_COMMAND,
0, server->entity_id, 0, 0, 0, 2);
p = (struct avb_packet_acmp *)(pkt + sizeof(struct avb_ethernet_header));
AVB_PACKET_SET_SUBTYPE(&p->hdr, AVB_SUBTYPE_ADP); /* Wrong subtype */
avb_test_inject_packet(server, 2 * SPA_NSEC_PER_SEC, pkt, len);
pwtest_int_eq(avb_loopback_get_packet_count(server), 0);
/* Build packet with correct parameters — should get response */
len = avb_test_build_acmp(pkt, sizeof(pkt), remote_mac,
AVB_ACMP_MESSAGE_TYPE_GET_TX_STATE_COMMAND,
0, server->entity_id, 0, 0, 0, 3);
avb_test_inject_packet(server, 3 * SPA_NSEC_PER_SEC, pkt, len);
pwtest_int_gt(avb_loopback_get_packet_count(server), 0);
test_impl_free(impl);
return PWTEST_PASS;
}
/*
* =====================================================================
* Phase 5: AECP/AEM Entity Model Tests
* =====================================================================
*/
/*
* Test: AECP READ_DESCRIPTOR for the entity descriptor.
* Verifies that a valid READ_DESCRIPTOR command returns SUCCESS
* with the entity descriptor data.
*/
PWTEST(avb_aecp_read_descriptor_entity)
{
struct impl *impl;
struct server *server;
uint8_t pkt[512];
int len;
static const uint8_t controller_mac[6] = { 0x02, 0x00, 0x00, 0x00, 0x00, 0x40 };
uint64_t controller_id = 0x020000fffe000040ULL;
struct avb_packet_aecp_aem_read_descriptor rd;
impl = test_impl_new();
server = avb_test_server_new(impl);
pwtest_ptr_notnull(server);
memset(&rd, 0, sizeof(rd));
rd.configuration = 0;
rd.descriptor_type = htons(AVB_AEM_DESC_ENTITY);
rd.descriptor_id = htons(0);
len = avb_test_build_aecp_aem(pkt, sizeof(pkt), controller_mac,
server->entity_id, controller_id, 1,
AVB_AECP_AEM_CMD_READ_DESCRIPTOR,
&rd, sizeof(rd));
pwtest_int_gt(len, 0);
avb_loopback_clear_packets(server);
avb_test_inject_packet(server, 1 * SPA_NSEC_PER_SEC, pkt, len);
/* Should get a response */
pwtest_int_gt(avb_loopback_get_packet_count(server), 0);
{
uint8_t rbuf[2048];
int rlen;
struct avb_packet_aecp_aem *resp;
rlen = avb_loopback_get_packet(server, rbuf, sizeof(rbuf));
pwtest_int_gt(rlen, (int)(sizeof(struct avb_ethernet_header) +
sizeof(struct avb_packet_aecp_aem)));
resp = SPA_PTROFF(rbuf, sizeof(struct avb_ethernet_header), void);
/* Should be AEM_RESPONSE with SUCCESS */
pwtest_int_eq((int)AVB_PACKET_AECP_GET_MESSAGE_TYPE(&resp->aecp),
AVB_AECP_MESSAGE_TYPE_AEM_RESPONSE);
pwtest_int_eq((int)AVB_PACKET_AECP_GET_STATUS(&resp->aecp),
AVB_AECP_AEM_STATUS_SUCCESS);
/* Response should include the descriptor data, making it
* larger than just the header + read_descriptor payload */
pwtest_int_gt(rlen, (int)(sizeof(struct avb_ethernet_header) +
sizeof(struct avb_packet_aecp_aem) +
sizeof(struct avb_packet_aecp_aem_read_descriptor)));
}
test_impl_free(impl);
return PWTEST_PASS;
}
/*
* Test: AECP READ_DESCRIPTOR for a non-existent descriptor.
* Should return NO_SUCH_DESCRIPTOR error.
*/
PWTEST(avb_aecp_read_descriptor_not_found)
{
struct impl *impl;
struct server *server;
uint8_t pkt[512];
int len;
static const uint8_t controller_mac[6] = { 0x02, 0x00, 0x00, 0x00, 0x00, 0x41 };
uint64_t controller_id = 0x020000fffe000041ULL;
struct avb_packet_aecp_aem_read_descriptor rd;
impl = test_impl_new();
server = avb_test_server_new(impl);
pwtest_ptr_notnull(server);
/* Request a descriptor type that doesn't exist */
memset(&rd, 0, sizeof(rd));
rd.descriptor_type = htons(AVB_AEM_DESC_AUDIO_UNIT);
rd.descriptor_id = htons(0);
len = avb_test_build_aecp_aem(pkt, sizeof(pkt), controller_mac,
server->entity_id, controller_id, 1,
AVB_AECP_AEM_CMD_READ_DESCRIPTOR,
&rd, sizeof(rd));
avb_loopback_clear_packets(server);
avb_test_inject_packet(server, 1 * SPA_NSEC_PER_SEC, pkt, len);
pwtest_int_gt(avb_loopback_get_packet_count(server), 0);
{
uint8_t rbuf[2048];
int rlen;
struct avb_packet_aecp_aem *resp;
rlen = avb_loopback_get_packet(server, rbuf, sizeof(rbuf));
pwtest_int_gt(rlen, (int)sizeof(struct avb_ethernet_header));
resp = SPA_PTROFF(rbuf, sizeof(struct avb_ethernet_header), void);
pwtest_int_eq((int)AVB_PACKET_AECP_GET_MESSAGE_TYPE(&resp->aecp),
AVB_AECP_MESSAGE_TYPE_AEM_RESPONSE);
pwtest_int_eq((int)AVB_PACKET_AECP_GET_STATUS(&resp->aecp),
AVB_AECP_AEM_STATUS_NO_SUCH_DESCRIPTOR);
}
test_impl_free(impl);
return PWTEST_PASS;
}
/*
* Test: AECP message filtering wrong EtherType and subtype.
*/
PWTEST(avb_aecp_packet_filtering)
{
struct impl *impl;
struct server *server;
uint8_t pkt[512];
int len;
static const uint8_t controller_mac[6] = { 0x02, 0x00, 0x00, 0x00, 0x00, 0x42 };
uint64_t controller_id = 0x020000fffe000042ULL;
struct avb_packet_aecp_aem_read_descriptor rd;
struct avb_ethernet_header *h;
struct avb_packet_aecp_aem *p;
impl = test_impl_new();
server = avb_test_server_new(impl);
pwtest_ptr_notnull(server);
memset(&rd, 0, sizeof(rd));
rd.descriptor_type = htons(AVB_AEM_DESC_ENTITY);
rd.descriptor_id = htons(0);
/* Wrong EtherType — should be filtered */
len = avb_test_build_aecp_aem(pkt, sizeof(pkt), controller_mac,
server->entity_id, controller_id, 1,
AVB_AECP_AEM_CMD_READ_DESCRIPTOR,
&rd, sizeof(rd));
h = (struct avb_ethernet_header *)pkt;
h->type = htons(0x1234);
avb_loopback_clear_packets(server);
avb_test_inject_packet(server, 1 * SPA_NSEC_PER_SEC, pkt, len);
pwtest_int_eq(avb_loopback_get_packet_count(server), 0);
/* Wrong subtype — should be filtered */
len = avb_test_build_aecp_aem(pkt, sizeof(pkt), controller_mac,
server->entity_id, controller_id, 2,
AVB_AECP_AEM_CMD_READ_DESCRIPTOR,
&rd, sizeof(rd));
p = SPA_PTROFF(pkt, sizeof(struct avb_ethernet_header), void);
AVB_PACKET_SET_SUBTYPE(&p->aecp.hdr, AVB_SUBTYPE_ADP);
avb_test_inject_packet(server, 2 * SPA_NSEC_PER_SEC, pkt, len);
pwtest_int_eq(avb_loopback_get_packet_count(server), 0);
/* Correct packet — should get a response */
len = avb_test_build_aecp_aem(pkt, sizeof(pkt), controller_mac,
server->entity_id, controller_id, 3,
AVB_AECP_AEM_CMD_READ_DESCRIPTOR,
&rd, sizeof(rd));
avb_test_inject_packet(server, 3 * SPA_NSEC_PER_SEC, pkt, len);
pwtest_int_gt(avb_loopback_get_packet_count(server), 0);
test_impl_free(impl);
return PWTEST_PASS;
}
/*
* Test: AECP unsupported message types (ADDRESS_ACCESS, AVC, VENDOR_UNIQUE).
* Should return NOT_IMPLEMENTED.
*/
PWTEST(avb_aecp_unsupported_message_types)
{
struct impl *impl;
struct server *server;
uint8_t pkt[512];
int len;
static const uint8_t controller_mac[6] = { 0x02, 0x00, 0x00, 0x00, 0x00, 0x43 };
uint64_t controller_id = 0x020000fffe000043ULL;
struct avb_packet_aecp_aem *p;
impl = test_impl_new();
server = avb_test_server_new(impl);
pwtest_ptr_notnull(server);
/* Build a basic AECP packet, then change message type to ADDRESS_ACCESS */
len = avb_test_build_aecp_aem(pkt, sizeof(pkt), controller_mac,
server->entity_id, controller_id, 1,
AVB_AECP_AEM_CMD_READ_DESCRIPTOR,
NULL, 0);
p = SPA_PTROFF(pkt, sizeof(struct avb_ethernet_header), void);
AVB_PACKET_AECP_SET_MESSAGE_TYPE(&p->aecp,
AVB_AECP_MESSAGE_TYPE_ADDRESS_ACCESS_COMMAND);
avb_loopback_clear_packets(server);
avb_test_inject_packet(server, 1 * SPA_NSEC_PER_SEC, pkt, len);
pwtest_int_gt(avb_loopback_get_packet_count(server), 0);
{
uint8_t rbuf[2048];
int rlen;
struct avb_packet_aecp_header *resp;
rlen = avb_loopback_get_packet(server, rbuf, sizeof(rbuf));
pwtest_int_gt(rlen, (int)sizeof(struct avb_ethernet_header));
resp = SPA_PTROFF(rbuf, sizeof(struct avb_ethernet_header), void);
pwtest_int_eq((int)AVB_PACKET_AECP_GET_STATUS(resp),
AVB_AECP_STATUS_NOT_IMPLEMENTED);
}
test_impl_free(impl);
return PWTEST_PASS;
}
/*
* Test: AEM command not in the legacy command table.
* Should return NOT_IMPLEMENTED.
*/
PWTEST(avb_aecp_aem_not_implemented)
{
struct impl *impl;
struct server *server;
uint8_t pkt[512];
int len;
static const uint8_t controller_mac[6] = { 0x02, 0x00, 0x00, 0x00, 0x00, 0x44 };
uint64_t controller_id = 0x020000fffe000044ULL;
impl = test_impl_new();
server = avb_test_server_new(impl);
pwtest_ptr_notnull(server);
/* REBOOT command is not in the legacy table */
len = avb_test_build_aecp_aem(pkt, sizeof(pkt), controller_mac,
server->entity_id, controller_id, 1,
AVB_AECP_AEM_CMD_REBOOT,
NULL, 0);
avb_loopback_clear_packets(server);
avb_test_inject_packet(server, 1 * SPA_NSEC_PER_SEC, pkt, len);
pwtest_int_gt(avb_loopback_get_packet_count(server), 0);
{
uint8_t rbuf[2048];
int rlen;
struct avb_packet_aecp_aem *resp;
rlen = avb_loopback_get_packet(server, rbuf, sizeof(rbuf));
pwtest_int_gt(rlen, (int)sizeof(struct avb_ethernet_header));
resp = SPA_PTROFF(rbuf, sizeof(struct avb_ethernet_header), void);
pwtest_int_eq((int)AVB_PACKET_AECP_GET_STATUS(&resp->aecp),
AVB_AECP_AEM_STATUS_NOT_IMPLEMENTED);
}
test_impl_free(impl);
return PWTEST_PASS;
}
/*
* Test: AECP ACQUIRE_ENTITY (legacy) with valid entity descriptor.
* Tests the fix for the pointer offset bug in handle_acquire_entity_avb_legacy.
*/
PWTEST(avb_aecp_acquire_entity_legacy)
{
struct impl *impl;
struct server *server;
uint8_t pkt[512];
int len;
static const uint8_t controller_mac[6] = { 0x02, 0x00, 0x00, 0x00, 0x00, 0x45 };
uint64_t controller_id = 0x020000fffe000045ULL;
struct avb_packet_aecp_aem_acquire acq;
impl = test_impl_new();
server = avb_test_server_new(impl);
pwtest_ptr_notnull(server);
/* Acquire the entity descriptor */
memset(&acq, 0, sizeof(acq));
acq.flags = 0;
acq.owner_guid = htobe64(controller_id);
acq.descriptor_type = htons(AVB_AEM_DESC_ENTITY);
acq.descriptor_id = htons(0);
len = avb_test_build_aecp_aem(pkt, sizeof(pkt), controller_mac,
server->entity_id, controller_id, 1,
AVB_AECP_AEM_CMD_ACQUIRE_ENTITY,
&acq, sizeof(acq));
avb_loopback_clear_packets(server);
avb_test_inject_packet(server, 1 * SPA_NSEC_PER_SEC, pkt, len);
pwtest_int_gt(avb_loopback_get_packet_count(server), 0);
{
uint8_t rbuf[2048];
int rlen;
struct avb_packet_aecp_aem *resp;
rlen = avb_loopback_get_packet(server, rbuf, sizeof(rbuf));
pwtest_int_gt(rlen, (int)sizeof(struct avb_ethernet_header));
resp = SPA_PTROFF(rbuf, sizeof(struct avb_ethernet_header), void);
pwtest_int_eq((int)AVB_PACKET_AECP_GET_MESSAGE_TYPE(&resp->aecp),
AVB_AECP_MESSAGE_TYPE_AEM_RESPONSE);
pwtest_int_eq((int)AVB_PACKET_AECP_GET_STATUS(&resp->aecp),
AVB_AECP_AEM_STATUS_SUCCESS);
}
test_impl_free(impl);
return PWTEST_PASS;
}
/*
* Test: AECP LOCK_ENTITY (legacy) with valid entity descriptor.
* Tests the fix for the pointer offset bug in handle_lock_entity_avb_legacy.
*/
PWTEST(avb_aecp_lock_entity_legacy)
{
struct impl *impl;
struct server *server;
uint8_t pkt[512];
int len;
static const uint8_t controller_mac[6] = { 0x02, 0x00, 0x00, 0x00, 0x00, 0x46 };
uint64_t controller_id = 0x020000fffe000046ULL;
struct avb_packet_aecp_aem_acquire lock_pkt;
impl = test_impl_new();
server = avb_test_server_new(impl);
pwtest_ptr_notnull(server);
/* Lock the entity descriptor (lock uses same struct as acquire) */
memset(&lock_pkt, 0, sizeof(lock_pkt));
lock_pkt.flags = 0;
lock_pkt.owner_guid = htobe64(controller_id);
lock_pkt.descriptor_type = htons(AVB_AEM_DESC_ENTITY);
lock_pkt.descriptor_id = htons(0);
len = avb_test_build_aecp_aem(pkt, sizeof(pkt), controller_mac,
server->entity_id, controller_id, 1,
AVB_AECP_AEM_CMD_LOCK_ENTITY,
&lock_pkt, sizeof(lock_pkt));
avb_loopback_clear_packets(server);
avb_test_inject_packet(server, 1 * SPA_NSEC_PER_SEC, pkt, len);
pwtest_int_gt(avb_loopback_get_packet_count(server), 0);
{
uint8_t rbuf[2048];
int rlen;
struct avb_packet_aecp_aem *resp;
rlen = avb_loopback_get_packet(server, rbuf, sizeof(rbuf));
pwtest_int_gt(rlen, (int)sizeof(struct avb_ethernet_header));
resp = SPA_PTROFF(rbuf, sizeof(struct avb_ethernet_header), void);
pwtest_int_eq((int)AVB_PACKET_AECP_GET_MESSAGE_TYPE(&resp->aecp),
AVB_AECP_MESSAGE_TYPE_AEM_RESPONSE);
pwtest_int_eq((int)AVB_PACKET_AECP_GET_STATUS(&resp->aecp),
AVB_AECP_AEM_STATUS_SUCCESS);
}
test_impl_free(impl);
return PWTEST_PASS;
}
/*
* Test: Milan ENTITY_AVAILABLE command.
* Verifies the entity available handler returns lock status.
*/
PWTEST(avb_aecp_entity_available_milan)
{
struct impl *impl;
struct server *server;
uint8_t pkt[512];
int len;
static const uint8_t controller_mac[6] = { 0x02, 0x00, 0x00, 0x00, 0x00, 0x47 };
uint64_t controller_id = 0x020000fffe000047ULL;
impl = test_impl_new();
server = avb_test_server_new_milan(impl);
pwtest_ptr_notnull(server);
/* ENTITY_AVAILABLE has no payload */
len = avb_test_build_aecp_aem(pkt, sizeof(pkt), controller_mac,
server->entity_id, controller_id, 1,
AVB_AECP_AEM_CMD_ENTITY_AVAILABLE,
NULL, 0);
avb_loopback_clear_packets(server);
avb_test_inject_packet(server, 1 * SPA_NSEC_PER_SEC, pkt, len);
pwtest_int_gt(avb_loopback_get_packet_count(server), 0);
{
uint8_t rbuf[2048];
int rlen;
struct avb_packet_aecp_aem *resp;
rlen = avb_loopback_get_packet(server, rbuf, sizeof(rbuf));
pwtest_int_gt(rlen, (int)sizeof(struct avb_ethernet_header));
resp = SPA_PTROFF(rbuf, sizeof(struct avb_ethernet_header), void);
pwtest_int_eq((int)AVB_PACKET_AECP_GET_MESSAGE_TYPE(&resp->aecp),
AVB_AECP_MESSAGE_TYPE_AEM_RESPONSE);
pwtest_int_eq((int)AVB_PACKET_AECP_GET_STATUS(&resp->aecp),
AVB_AECP_AEM_STATUS_SUCCESS);
}
test_impl_free(impl);
return PWTEST_PASS;
}
/*
* Test: Milan LOCK_ENTITY lock, verify locked, unlock.
* Tests lock semantics and the reply_status pointer fix.
*/
PWTEST(avb_aecp_lock_entity_milan)
{
struct impl *impl;
struct server *server;
uint8_t pkt[512];
int len;
static const uint8_t controller_mac[6] = { 0x02, 0x00, 0x00, 0x00, 0x00, 0x48 };
uint64_t controller_id = 0x020000fffe000048ULL;
uint64_t other_controller = 0x020000fffe000049ULL;
struct avb_packet_aecp_aem_lock lock_payload;
impl = test_impl_new();
server = avb_test_server_new_milan(impl);
pwtest_ptr_notnull(server);
/* Lock the entity */
memset(&lock_payload, 0, sizeof(lock_payload));
lock_payload.descriptor_type = htons(AVB_AEM_DESC_ENTITY);
lock_payload.descriptor_id = htons(0);
len = avb_test_build_aecp_aem(pkt, sizeof(pkt), controller_mac,
server->entity_id, controller_id, 1,
AVB_AECP_AEM_CMD_LOCK_ENTITY,
&lock_payload, sizeof(lock_payload));
avb_loopback_clear_packets(server);
avb_test_inject_packet(server, 1 * SPA_NSEC_PER_SEC, pkt, len);
/* Should get SUCCESS for the lock */
pwtest_int_gt(avb_loopback_get_packet_count(server), 0);
{
uint8_t rbuf[2048];
struct avb_packet_aecp_aem *resp;
avb_loopback_get_packet(server, rbuf, sizeof(rbuf));
resp = SPA_PTROFF(rbuf, sizeof(struct avb_ethernet_header), void);
pwtest_int_eq((int)AVB_PACKET_AECP_GET_STATUS(&resp->aecp),
AVB_AECP_AEM_STATUS_SUCCESS);
}
/* Another controller tries to lock — should get ENTITY_LOCKED */
avb_loopback_clear_packets(server);
len = avb_test_build_aecp_aem(pkt, sizeof(pkt), controller_mac,
server->entity_id, other_controller, 2,
AVB_AECP_AEM_CMD_LOCK_ENTITY,
&lock_payload, sizeof(lock_payload));
avb_test_inject_packet(server, 2 * SPA_NSEC_PER_SEC, pkt, len);
pwtest_int_gt(avb_loopback_get_packet_count(server), 0);
{
uint8_t rbuf[2048];
struct avb_packet_aecp_aem *resp;
avb_loopback_get_packet(server, rbuf, sizeof(rbuf));
resp = SPA_PTROFF(rbuf, sizeof(struct avb_ethernet_header), void);
pwtest_int_eq((int)AVB_PACKET_AECP_GET_STATUS(&resp->aecp),
AVB_AECP_AEM_STATUS_ENTITY_LOCKED);
}
/* Original controller unlocks */
avb_loopback_clear_packets(server);
lock_payload.flags = htonl(AECP_AEM_LOCK_ENTITY_FLAG_UNLOCK);
len = avb_test_build_aecp_aem(pkt, sizeof(pkt), controller_mac,
server->entity_id, controller_id, 3,
AVB_AECP_AEM_CMD_LOCK_ENTITY,
&lock_payload, sizeof(lock_payload));
avb_test_inject_packet(server, 3 * SPA_NSEC_PER_SEC, pkt, len);
pwtest_int_gt(avb_loopback_get_packet_count(server), 0);
{
uint8_t rbuf[2048];
struct avb_packet_aecp_aem *resp;
avb_loopback_get_packet(server, rbuf, sizeof(rbuf));
resp = SPA_PTROFF(rbuf, sizeof(struct avb_ethernet_header), void);
pwtest_int_eq((int)AVB_PACKET_AECP_GET_STATUS(&resp->aecp),
AVB_AECP_AEM_STATUS_SUCCESS);
}
test_impl_free(impl);
return PWTEST_PASS;
}
/*
* Test: Milan LOCK_ENTITY for non-entity descriptor returns NOT_SUPPORTED.
*/
PWTEST(avb_aecp_lock_non_entity_milan)
{
struct impl *impl;
struct server *server;
uint8_t pkt[512];
int len;
static const uint8_t controller_mac[6] = { 0x02, 0x00, 0x00, 0x00, 0x00, 0x4A };
uint64_t controller_id = 0x020000fffe00004AULL;
struct avb_packet_aecp_aem_lock lock_payload;
impl = test_impl_new();
server = avb_test_server_new_milan(impl);
pwtest_ptr_notnull(server);
/* Try to lock AUDIO_UNIT descriptor (not entity) */
memset(&lock_payload, 0, sizeof(lock_payload));
lock_payload.descriptor_type = htons(AVB_AEM_DESC_AUDIO_UNIT);
lock_payload.descriptor_id = htons(0);
len = avb_test_build_aecp_aem(pkt, sizeof(pkt), controller_mac,
server->entity_id, controller_id, 1,
AVB_AECP_AEM_CMD_LOCK_ENTITY,
&lock_payload, sizeof(lock_payload));
avb_loopback_clear_packets(server);
avb_test_inject_packet(server, 1 * SPA_NSEC_PER_SEC, pkt, len);
/* Should get NO_SUCH_DESCRIPTOR (audio_unit doesn't exist) */
pwtest_int_gt(avb_loopback_get_packet_count(server), 0);
{
uint8_t rbuf[2048];
struct avb_packet_aecp_aem *resp;
avb_loopback_get_packet(server, rbuf, sizeof(rbuf));
resp = SPA_PTROFF(rbuf, sizeof(struct avb_ethernet_header), void);
/* Bug fix verified: reply_status now gets the full frame pointer,
* so the response is correctly formed */
pwtest_int_eq((int)AVB_PACKET_AECP_GET_MESSAGE_TYPE(&resp->aecp),
AVB_AECP_MESSAGE_TYPE_AEM_RESPONSE);
}
test_impl_free(impl);
return PWTEST_PASS;
}
/*
* Test: Milan ACQUIRE_ENTITY returns NOT_SUPPORTED.
* Milan v1.2 does not implement acquire.
*/
PWTEST(avb_aecp_acquire_entity_milan)
{
struct impl *impl;
struct server *server;
uint8_t pkt[512];
int len;
static const uint8_t controller_mac[6] = { 0x02, 0x00, 0x00, 0x00, 0x00, 0x4B };
uint64_t controller_id = 0x020000fffe00004BULL;
struct avb_packet_aecp_aem_acquire acq;
impl = test_impl_new();
server = avb_test_server_new_milan(impl);
pwtest_ptr_notnull(server);
memset(&acq, 0, sizeof(acq));
acq.descriptor_type = htons(AVB_AEM_DESC_ENTITY);
acq.descriptor_id = htons(0);
len = avb_test_build_aecp_aem(pkt, sizeof(pkt), controller_mac,
server->entity_id, controller_id, 1,
AVB_AECP_AEM_CMD_ACQUIRE_ENTITY,
&acq, sizeof(acq));
avb_loopback_clear_packets(server);
avb_test_inject_packet(server, 1 * SPA_NSEC_PER_SEC, pkt, len);
pwtest_int_gt(avb_loopback_get_packet_count(server), 0);
{
uint8_t rbuf[2048];
struct avb_packet_aecp_aem *resp;
avb_loopback_get_packet(server, rbuf, sizeof(rbuf));
resp = SPA_PTROFF(rbuf, sizeof(struct avb_ethernet_header), void);
pwtest_int_eq((int)AVB_PACKET_AECP_GET_STATUS(&resp->aecp),
AVB_AECP_AEM_STATUS_NOT_SUPPORTED);
}
test_impl_free(impl);
return PWTEST_PASS;
}
/*
* Test: Milan READ_DESCRIPTOR works the same as legacy.
*/
PWTEST(avb_aecp_read_descriptor_milan)
{
struct impl *impl;
struct server *server;
uint8_t pkt[512];
int len;
static const uint8_t controller_mac[6] = { 0x02, 0x00, 0x00, 0x00, 0x00, 0x4C };
uint64_t controller_id = 0x020000fffe00004CULL;
struct avb_packet_aecp_aem_read_descriptor rd;
impl = test_impl_new();
server = avb_test_server_new_milan(impl);
pwtest_ptr_notnull(server);
memset(&rd, 0, sizeof(rd));
rd.descriptor_type = htons(AVB_AEM_DESC_ENTITY);
rd.descriptor_id = htons(0);
len = avb_test_build_aecp_aem(pkt, sizeof(pkt), controller_mac,
server->entity_id, controller_id, 1,
AVB_AECP_AEM_CMD_READ_DESCRIPTOR,
&rd, sizeof(rd));
avb_loopback_clear_packets(server);
avb_test_inject_packet(server, 1 * SPA_NSEC_PER_SEC, pkt, len);
pwtest_int_gt(avb_loopback_get_packet_count(server), 0);
{
uint8_t rbuf[2048];
struct avb_packet_aecp_aem *resp;
avb_loopback_get_packet(server, rbuf, sizeof(rbuf));
resp = SPA_PTROFF(rbuf, sizeof(struct avb_ethernet_header), void);
pwtest_int_eq((int)AVB_PACKET_AECP_GET_STATUS(&resp->aecp),
AVB_AECP_AEM_STATUS_SUCCESS);
}
test_impl_free(impl);
return PWTEST_PASS;
}
PWTEST_SUITE(avb)
{
/* Phase 2: ADP and basic tests */
pwtest_add(avb_adp_entity_available, PWTEST_NOARG);
pwtest_add(avb_adp_entity_departing, PWTEST_NOARG);
pwtest_add(avb_adp_entity_discover, PWTEST_NOARG);
pwtest_add(avb_adp_entity_timeout, PWTEST_NOARG);
pwtest_add(avb_mrp_attribute_lifecycle, PWTEST_NOARG);
pwtest_add(avb_milan_server_create, PWTEST_NOARG);
/* Phase 3: MRP state machine tests */
pwtest_add(avb_mrp_begin_join_new_tx, PWTEST_NOARG);
pwtest_add(avb_mrp_join_leave_cycle, PWTEST_NOARG);
pwtest_add(avb_mrp_rx_new_notification, PWTEST_NOARG);
pwtest_add(avb_mrp_registrar_leave_timer, PWTEST_NOARG);
pwtest_add(avb_mrp_multiple_attributes, PWTEST_NOARG);
/* Phase 3: MSRP tests */
pwtest_add(avb_msrp_attribute_types, PWTEST_NOARG);
pwtest_add(avb_msrp_domain_transmit, PWTEST_NOARG);
pwtest_add(avb_msrp_talker_transmit, PWTEST_NOARG);
pwtest_add(avb_msrp_talker_failed_notify, PWTEST_NOARG);
/* Phase 3: MRP packet parsing tests */
pwtest_add(avb_mrp_parse_single_domain, PWTEST_NOARG);
pwtest_add(avb_mrp_parse_with_lva, PWTEST_NOARG);
pwtest_add(avb_mrp_parse_three_values, PWTEST_NOARG);
/* Phase 4: ACMP integration tests */
pwtest_add(avb_acmp_not_supported, PWTEST_NOARG);
pwtest_add(avb_acmp_connect_tx_no_stream, PWTEST_NOARG);
pwtest_add(avb_acmp_wrong_entity_ignored, PWTEST_NOARG);
pwtest_add(avb_acmp_connect_rx_forward, PWTEST_NOARG);
pwtest_add(avb_acmp_pending_timeout, PWTEST_NOARG);
pwtest_add(avb_acmp_packet_filtering, PWTEST_NOARG);
/* Phase 5: AECP/AEM entity model tests */
pwtest_add(avb_aecp_read_descriptor_entity, PWTEST_NOARG);
pwtest_add(avb_aecp_read_descriptor_not_found, PWTEST_NOARG);
pwtest_add(avb_aecp_packet_filtering, PWTEST_NOARG);
pwtest_add(avb_aecp_unsupported_message_types, PWTEST_NOARG);
pwtest_add(avb_aecp_aem_not_implemented, PWTEST_NOARG);
pwtest_add(avb_aecp_acquire_entity_legacy, PWTEST_NOARG);
pwtest_add(avb_aecp_lock_entity_legacy, PWTEST_NOARG);
pwtest_add(avb_aecp_entity_available_milan, PWTEST_NOARG);
pwtest_add(avb_aecp_lock_entity_milan, PWTEST_NOARG);
pwtest_add(avb_aecp_lock_non_entity_milan, PWTEST_NOARG);
pwtest_add(avb_aecp_acquire_entity_milan, PWTEST_NOARG);
pwtest_add(avb_aecp_read_descriptor_milan, PWTEST_NOARG);
return PWTEST_PASS;
}