diff --git a/src/modules/module-avb/avb-transport-loopback.h b/src/modules/module-avb/avb-transport-loopback.h new file mode 100644 index 000000000..d783ad55c --- /dev/null +++ b/src/modules/module-avb/avb-transport-loopback.h @@ -0,0 +1,149 @@ +/* AVB support */ +/* SPDX-FileCopyrightText: Copyright © 2026 PipeWire contributors */ +/* SPDX-License-Identifier: MIT */ + +#ifndef AVB_TRANSPORT_LOOPBACK_H +#define AVB_TRANSPORT_LOOPBACK_H + +#include +#include +#include +#include +#include + +#include "internal.h" +#include "packets.h" + +#define AVB_LOOPBACK_MAX_PACKETS 64 +#define AVB_LOOPBACK_MAX_PACKET_SIZE 2048 + +struct avb_loopback_packet { + uint8_t dest[6]; + uint16_t type; + size_t size; + uint8_t data[AVB_LOOPBACK_MAX_PACKET_SIZE]; +}; + +struct avb_loopback_transport { + struct avb_loopback_packet packets[AVB_LOOPBACK_MAX_PACKETS]; + int packet_count; + int packet_read; +}; + +static inline int avb_loopback_setup(struct server *server) +{ + struct avb_loopback_transport *t; + static const uint8_t test_mac[6] = { 0x02, 0x00, 0x00, 0x00, 0x00, 0x01 }; + + t = calloc(1, sizeof(*t)); + if (t == NULL) + return -errno; + + server->transport_data = t; + + memcpy(server->mac_addr, test_mac, 6); + server->ifindex = 1; + server->entity_id = (uint64_t)server->mac_addr[0] << 56 | + (uint64_t)server->mac_addr[1] << 48 | + (uint64_t)server->mac_addr[2] << 40 | + (uint64_t)0xff << 32 | + (uint64_t)0xfe << 24 | + (uint64_t)server->mac_addr[3] << 16 | + (uint64_t)server->mac_addr[4] << 8 | + (uint64_t)server->mac_addr[5]; + + return 0; +} + +static inline int avb_loopback_send_packet(struct server *server, + const uint8_t dest[6], uint16_t type, void *data, size_t size) +{ + struct avb_loopback_transport *t = server->transport_data; + struct avb_loopback_packet *pkt; + struct avb_ethernet_header *hdr = (struct avb_ethernet_header*)data; + + if (t->packet_count >= AVB_LOOPBACK_MAX_PACKETS) + return -ENOSPC; + if (size > AVB_LOOPBACK_MAX_PACKET_SIZE) + return -EMSGSIZE; + + /* Fill in the ethernet header like the raw transport does */ + memcpy(hdr->dest, dest, 6); + memcpy(hdr->src, server->mac_addr, 6); + hdr->type = htons(type); + + pkt = &t->packets[t->packet_count % AVB_LOOPBACK_MAX_PACKETS]; + memcpy(pkt->dest, dest, 6); + pkt->type = type; + pkt->size = size; + memcpy(pkt->data, data, size); + t->packet_count++; + + return 0; +} + +/** + * Return a dummy fd for protocol handlers that create their own sockets. + * Uses eventfd so pw_loop_add_io() has a valid fd to work with. + */ +static inline int avb_loopback_make_socket(struct server *server, + uint16_t type, const uint8_t mac[6]) +{ + int fd; + + fd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC); + if (fd < 0) + return -errno; + + return fd; +} + +static inline void avb_loopback_destroy(struct server *server) +{ + free(server->transport_data); + server->transport_data = NULL; +} + +static const struct avb_transport_ops avb_transport_loopback = { + .setup = avb_loopback_setup, + .send_packet = avb_loopback_send_packet, + .make_socket = avb_loopback_make_socket, + .destroy = avb_loopback_destroy, +}; + +/** Get the number of captured sent packets */ +static inline int avb_loopback_get_packet_count(struct server *server) +{ + struct avb_loopback_transport *t = server->transport_data; + return t->packet_count - t->packet_read; +} + +/** Read the next captured sent packet, returns packet size or -1 */ +static inline int avb_loopback_get_packet(struct server *server, + void *buf, size_t bufsize) +{ + struct avb_loopback_transport *t = server->transport_data; + struct avb_loopback_packet *pkt; + + if (t->packet_read >= t->packet_count) + return -1; + + pkt = &t->packets[t->packet_read % AVB_LOOPBACK_MAX_PACKETS]; + t->packet_read++; + + if (pkt->size > bufsize) + return -1; + + memcpy(buf, pkt->data, pkt->size); + return pkt->size; +} + +/** Clear all captured packets */ +static inline void avb_loopback_clear_packets(struct server *server) +{ + struct avb_loopback_transport *t = server->transport_data; + t->packet_count = 0; + t->packet_read = 0; +} + +#endif /* AVB_TRANSPORT_LOOPBACK_H */ diff --git a/test/meson.build b/test/meson.build index 5e38db383..55443bad3 100644 --- a/test/meson.build +++ b/test/meson.build @@ -163,3 +163,42 @@ if valgrind.found() env : valgrind_env, timeout_multiplier : 3) endif + +if build_module_avb + avb_test_inc = [pwtest_inc, include_directories('../src/modules')] + avb_module_sources = [ + '../src/modules/module-avb/avb.c', + '../src/modules/module-avb/adp.c', + '../src/modules/module-avb/acmp.c', + '../src/modules/module-avb/aecp.c', + '../src/modules/module-avb/aecp-aem.c', + '../src/modules/module-avb/aecp-aem-cmds-resps/cmd-available.c', + '../src/modules/module-avb/aecp-aem-cmds-resps/cmd-get-set-control.c', + '../src/modules/module-avb/aecp-aem-cmds-resps/cmd-get-set-name.c', + '../src/modules/module-avb/aecp-aem-cmds-resps/cmd-get-set-clock-source.c', + '../src/modules/module-avb/aecp-aem-cmds-resps/cmd-get-set-sampling-rate.c', + '../src/modules/module-avb/aecp-aem-cmds-resps/cmd-deregister-unsolicited-notifications.c', + '../src/modules/module-avb/aecp-aem-cmds-resps/cmd-register-unsolicited-notifications.c', + '../src/modules/module-avb/aecp-aem-cmds-resps/cmd-get-set-stream-format.c', + '../src/modules/module-avb/aecp-aem-cmds-resps/cmd-lock-entity.c', + '../src/modules/module-avb/aecp-aem-cmds-resps/cmd-get-set-configuration.c', + '../src/modules/module-avb/aecp-aem-cmds-resps/reply-unsol-helpers.c', + '../src/modules/module-avb/es-builder.c', + '../src/modules/module-avb/avdecc.c', + '../src/modules/module-avb/descriptors.c', + '../src/modules/module-avb/maap.c', + '../src/modules/module-avb/mmrp.c', + '../src/modules/module-avb/mrp.c', + '../src/modules/module-avb/msrp.c', + '../src/modules/module-avb/mvrp.c', + '../src/modules/module-avb/srp.c', + '../src/modules/module-avb/stream.c', + ] + test('test-avb', + executable('test-avb', + ['test-avb.c'] + avb_module_sources, + include_directories: avb_test_inc, + dependencies: [spa_dep, pipewire_dep, mathlib, dl_lib, rt_lib], + link_with: pwtest_lib) + ) +endif diff --git a/test/test-avb-utils.h b/test/test-avb-utils.h new file mode 100644 index 000000000..d1a6345bd --- /dev/null +++ b/test/test-avb-utils.h @@ -0,0 +1,230 @@ +/* AVB test utilities */ +/* SPDX-FileCopyrightText: Copyright © 2026 PipeWire contributors */ +/* SPDX-License-Identifier: MIT */ + +#ifndef TEST_AVB_UTILS_H +#define TEST_AVB_UTILS_H + +#include + +#include "module-avb/internal.h" +#include "module-avb/packets.h" +#include "module-avb/adp.h" +#include "module-avb/acmp.h" +#include "module-avb/mrp.h" +#include "module-avb/msrp.h" +#include "module-avb/mvrp.h" +#include "module-avb/mmrp.h" +#include "module-avb/maap.h" +#include "module-avb/aecp.h" +#include "module-avb/aecp-aem-descriptors.h" +#include "module-avb/descriptors.h" +#include "module-avb/avb-transport-loopback.h" + +#define server_emit_message(s,n,m,l) \ + spa_hook_list_call(&(s)->listener_list, struct server_events, message, 0, n, m, l) +#define server_emit_periodic(s,n) \ + spa_hook_list_call(&(s)->listener_list, struct server_events, periodic, 0, n) + +/** + * Create a test AVB server with loopback transport. + * All protocol handlers are registered. No network access required. + */ +static inline struct server *avb_test_server_new(struct impl *impl) +{ + struct server *server; + + server = calloc(1, sizeof(*server)); + if (server == NULL) + return NULL; + + server->impl = impl; + server->ifname = strdup("test0"); + server->avb_mode = AVB_MODE_LEGACY; + server->transport = &avb_transport_loopback; + + spa_list_append(&impl->servers, &server->link); + spa_hook_list_init(&server->listener_list); + spa_list_init(&server->descriptors); + + if (server->transport->setup(server) < 0) + goto error; + + server->mrp = avb_mrp_new(server); + if (server->mrp == NULL) + goto error; + + 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 a minimal entity descriptor so ADP can advertise. + * We skip init_descriptors() because it creates streams that + * need a pw_core connection. */ + { + 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.entity_capabilities = htonl( + AVB_ADP_ENTITY_CAPABILITY_AEM_SUPPORTED | + AVB_ADP_ENTITY_CAPABILITY_CLASS_A_SUPPORTED | + AVB_ADP_ENTITY_CAPABILITY_GPTP_SUPPORTED); + entity.talker_stream_sources = htons(1); + entity.talker_capabilities = htons( + AVB_ADP_TALKER_CAPABILITY_IMPLEMENTED | + AVB_ADP_TALKER_CAPABILITY_AUDIO_SOURCE); + entity.listener_stream_sinks = htons(1); + entity.listener_capabilities = htons( + AVB_ADP_LISTENER_CAPABILITY_IMPLEMENTED | + AVB_ADP_LISTENER_CAPABILITY_AUDIO_SINK); + entity.configurations_count = htons(1); + server_add_descriptor(server, AVB_AEM_DESC_ENTITY, 0, + sizeof(entity), &entity); + } + + return server; + +error: + free(server->ifname); + free(server); + return NULL; +} + +static inline void avb_test_server_free(struct server *server) +{ + avdecc_server_free(server); +} + +/** + * Inject a raw packet into the server's protocol dispatch. + * This simulates receiving a packet from the network. + */ +static inline void avb_test_inject_packet(struct server *server, + uint64_t now, const void *data, int len) +{ + server_emit_message(server, now, data, len); +} + +/** + * Trigger the periodic callback with a given timestamp. + * Use this to advance time and test timeout/readvertise logic. + */ +static inline void avb_test_tick(struct server *server, uint64_t now) +{ + server_emit_periodic(server, now); +} + +/** + * Build an ADP entity available packet. + * Returns the packet size, or -1 on error. + */ +static inline int avb_test_build_adp_entity_available( + uint8_t *buf, size_t bufsize, + const uint8_t src_mac[6], + uint64_t entity_id, + int valid_time) +{ + struct avb_ethernet_header *h; + struct avb_packet_adp *p; + size_t len = sizeof(*h) + sizeof(*p); + static const uint8_t bmac[6] = AVB_BROADCAST_MAC; + + if (bufsize < len) + return -1; + + memset(buf, 0, len); + + h = (struct avb_ethernet_header *)buf; + memcpy(h->dest, bmac, 6); + memcpy(h->src, src_mac, 6); + h->type = htons(AVB_TSN_ETH); + + p = (struct avb_packet_adp *)(buf + sizeof(*h)); + AVB_PACKET_SET_SUBTYPE(&p->hdr, AVB_SUBTYPE_ADP); + AVB_PACKET_SET_LENGTH(&p->hdr, AVB_ADP_CONTROL_DATA_LENGTH); + AVB_PACKET_ADP_SET_MESSAGE_TYPE(p, AVB_ADP_MESSAGE_TYPE_ENTITY_AVAILABLE); + AVB_PACKET_ADP_SET_VALID_TIME(p, valid_time); + p->entity_id = htobe64(entity_id); + + return len; +} + +/** + * Build an ADP entity departing packet. + */ +static inline int avb_test_build_adp_entity_departing( + uint8_t *buf, size_t bufsize, + const uint8_t src_mac[6], + uint64_t entity_id) +{ + struct avb_ethernet_header *h; + struct avb_packet_adp *p; + size_t len = sizeof(*h) + sizeof(*p); + static const uint8_t bmac[6] = AVB_BROADCAST_MAC; + + if (bufsize < len) + return -1; + + memset(buf, 0, len); + + h = (struct avb_ethernet_header *)buf; + memcpy(h->dest, bmac, 6); + memcpy(h->src, src_mac, 6); + h->type = htons(AVB_TSN_ETH); + + p = (struct avb_packet_adp *)(buf + sizeof(*h)); + AVB_PACKET_SET_SUBTYPE(&p->hdr, AVB_SUBTYPE_ADP); + AVB_PACKET_SET_LENGTH(&p->hdr, AVB_ADP_CONTROL_DATA_LENGTH); + AVB_PACKET_ADP_SET_MESSAGE_TYPE(p, AVB_ADP_MESSAGE_TYPE_ENTITY_DEPARTING); + p->entity_id = htobe64(entity_id); + + return len; +} + +/** + * Build an ADP entity discover packet. + */ +static inline int avb_test_build_adp_entity_discover( + uint8_t *buf, size_t bufsize, + const uint8_t src_mac[6], + uint64_t entity_id) +{ + struct avb_ethernet_header *h; + struct avb_packet_adp *p; + size_t len = sizeof(*h) + sizeof(*p); + static const uint8_t bmac[6] = AVB_BROADCAST_MAC; + + if (bufsize < len) + return -1; + + memset(buf, 0, len); + + h = (struct avb_ethernet_header *)buf; + memcpy(h->dest, bmac, 6); + memcpy(h->src, src_mac, 6); + h->type = htons(AVB_TSN_ETH); + + p = (struct avb_packet_adp *)(buf + sizeof(*h)); + AVB_PACKET_SET_SUBTYPE(&p->hdr, AVB_SUBTYPE_ADP); + AVB_PACKET_SET_LENGTH(&p->hdr, AVB_ADP_CONTROL_DATA_LENGTH); + AVB_PACKET_ADP_SET_MESSAGE_TYPE(p, AVB_ADP_MESSAGE_TYPE_ENTITY_DISCOVER); + p->entity_id = htobe64(entity_id); + + return len; +} + +#endif /* TEST_AVB_UTILS_H */ diff --git a/test/test-avb.c b/test/test-avb.c new file mode 100644 index 000000000..f804b1e8a --- /dev/null +++ b/test/test-avb.c @@ -0,0 +1,312 @@ +/* AVB tests */ +/* SPDX-FileCopyrightText: Copyright © 2026 PipeWire contributors */ +/* SPDX-License-Identifier: MIT */ + +#include "pwtest.h" + +#include + +#include "module-avb/aecp-aem-descriptors.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; +} + +PWTEST_SUITE(avb) +{ + 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); + + return PWTEST_PASS; +}