test: add AECP/AEM entity model tests and document new bugs

Add 12 Phase 5 tests for the AECP/AEM entity model:
- READ_DESCRIPTOR for existing and non-existent descriptors
- AECP packet filtering (wrong EtherType, wrong subtype)
- Unsupported AECP message types (ADDRESS_ACCESS, etc.)
- Unimplemented AEM commands (REBOOT, etc.)
- ACQUIRE_ENTITY and LOCK_ENTITY for legacy mode
- Milan ENTITY_AVAILABLE, LOCK_ENTITY (lock/contention/unlock)
- Milan LOCK_ENTITY for non-entity descriptors
- Milan ACQUIRE_ENTITY returns NOT_SUPPORTED
- Milan READ_DESCRIPTOR

Also adds Milan test server helper with properly sized entity
descriptor for lock state, and AECP/AEM packet builder utility.

Updates avb-bugs.md with 3 new bugs found (bugs #6-#8).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Christian F.K. Schaller 2026-04-07 13:07:36 -04:00 committed by Wim Taymans
parent e661c72272
commit fdfede8b96
3 changed files with 854 additions and 0 deletions

View file

@ -87,3 +87,57 @@ as a response to its command and would eventually time out.
shutdown.
**Fix:** Change `<` to `<=` to include the controller list.
## 6. Legacy AECP handlers read payload at wrong offset
**File:** `src/modules/module-avb/aecp-aem.c`
`handle_acquire_entity_avb_legacy()` and `handle_lock_entity_avb_legacy()`
assign `const struct avb_packet_aecp_aem *p = m;` where `m` is the full
ethernet frame (starting with `struct avb_ethernet_header`). The handlers
then access `p->payload` to read the acquire/lock fields, but this reads
from `m + offsetof(avb_packet_aecp_aem, payload)` instead of the correct
`m + sizeof(avb_ethernet_header) + offsetof(avb_packet_aecp_aem, payload)`.
This causes `descriptor_type` and `descriptor_id` to be read from the
wrong position, leading to incorrect descriptor lookups.
All other AEM command handlers (e.g., `handle_read_descriptor_common`)
correctly derive `p` via `SPA_PTROFF(h, sizeof(*h), void)`.
**Fix:** Change `const struct avb_packet_aecp_aem *p = m;` to properly
skip the ethernet header:
```c
const struct avb_ethernet_header *h = m;
const struct avb_packet_aecp_aem *p = SPA_PTROFF(h, sizeof(*h), void);
```
## 7. Milan LOCK_ENTITY error response uses wrong packet pointer
**File:** `src/modules/module-avb/aecp-aem-cmds-resps/cmd-lock-entity.c`
In `handle_cmd_lock_entity_milan_v12()`, when `server_find_descriptor()`
returns NULL, `reply_status()` is called with `p` (the AEM packet pointer
past the ethernet header) instead of `m` (the full ethernet frame).
`reply_status()` assumes its third argument is the full frame and casts
it as `struct avb_ethernet_header *`. With the wrong pointer, the
response ethernet header (including destination MAC) is corrupted.
**Fix:** Change `reply_status(aecp, ..., p, len)` to
`reply_status(aecp, ..., m, len)`.
## 8. Lock entity re-lock timeout uses wrong units
**File:** `src/modules/module-avb/aecp-aem-cmds-resps/cmd-lock-entity.c`
When a controller that already holds the lock sends another lock request
(to refresh it), the expire timeout is extended by:
```c
lock->base_info.expire_timeout +=
AECP_AEM_LOCK_ENTITY_EXPIRE_TIMEOUT_SECOND;
```
`AECP_AEM_LOCK_ENTITY_EXPIRE_TIMEOUT_SECOND` is `60` (raw seconds),
but `expire_timeout` is in nanoseconds. This adds only 60 nanoseconds
instead of 60 seconds. The initial lock correctly uses
`AECP_AEM_LOCK_ENTITY_EXPIRE_TIMEOUT_SECOND * SPA_NSEC_PER_SEC`.
**Fix:** Multiply by `SPA_NSEC_PER_SEC` to match the nanosecond units.

View file

@ -17,7 +17,9 @@
#include "module-avb/mmrp.h"
#include "module-avb/maap.h"
#include "module-avb/aecp.h"
#include "module-avb/aecp-aem.h"
#include "module-avb/aecp-aem-descriptors.h"
#include "module-avb/aecp-aem-state.h"
#include "module-avb/descriptors.h"
#include "module-avb/avb-transport-loopback.h"
@ -227,4 +229,123 @@ static inline int avb_test_build_adp_entity_discover(
return len;
}
/**
* Build an AECP AEM command packet for injection.
* Returns packet size, or -1 on error.
*/
static inline int avb_test_build_aecp_aem(
uint8_t *buf, size_t bufsize,
const uint8_t src_mac[6],
uint64_t target_guid,
uint64_t controller_guid,
uint16_t sequence_id,
uint16_t command_type,
const void *payload, size_t payload_size)
{
struct avb_ethernet_header *h;
struct avb_packet_aecp_aem *p;
size_t len = sizeof(*h) + sizeof(*p) + payload_size;
if (bufsize < len)
return -1;
memset(buf, 0, len);
h = (struct avb_ethernet_header *)buf;
memcpy(h->dest, (uint8_t[]){ 0x91, 0xe0, 0xf0, 0x01, 0x00, 0x00 }, 6);
memcpy(h->src, src_mac, 6);
h->type = htons(AVB_TSN_ETH);
p = SPA_PTROFF(h, sizeof(*h), void);
AVB_PACKET_SET_SUBTYPE(&p->aecp.hdr, AVB_SUBTYPE_AECP);
AVB_PACKET_AECP_SET_MESSAGE_TYPE(&p->aecp, AVB_AECP_MESSAGE_TYPE_AEM_COMMAND);
AVB_PACKET_AECP_SET_STATUS(&p->aecp, 0);
AVB_PACKET_SET_LENGTH(&p->aecp.hdr, payload_size + 12);
p->aecp.target_guid = htobe64(target_guid);
p->aecp.controller_guid = htobe64(controller_guid);
p->aecp.sequence_id = htons(sequence_id);
AVB_PACKET_AEM_SET_COMMAND_TYPE(p, command_type);
if (payload && payload_size > 0)
memcpy(p->payload, payload, payload_size);
return len;
}
/**
* Create a test AVB server in Milan v1.2 mode with loopback transport.
* The entity descriptor is properly sized for Milan state (lock, unsol).
*/
static inline struct server *avb_test_server_new_milan(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_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);
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 Milan-sized entity descriptor with lock/unsol state */
{
struct aecp_aem_entity_milan_state entity_state;
memset(&entity_state, 0, sizeof(entity_state));
entity_state.state.desc.entity_id = htobe64(server->entity_id);
entity_state.state.desc.entity_model_id = htobe64(0x0001000000000001ULL);
entity_state.state.desc.entity_capabilities = htonl(
AVB_ADP_ENTITY_CAPABILITY_AEM_SUPPORTED |
AVB_ADP_ENTITY_CAPABILITY_CLASS_A_SUPPORTED |
AVB_ADP_ENTITY_CAPABILITY_GPTP_SUPPORTED);
entity_state.state.desc.talker_stream_sources = htons(1);
entity_state.state.desc.talker_capabilities = htons(
AVB_ADP_TALKER_CAPABILITY_IMPLEMENTED |
AVB_ADP_TALKER_CAPABILITY_AUDIO_SOURCE);
entity_state.state.desc.listener_stream_sinks = htons(1);
entity_state.state.desc.listener_capabilities = htons(
AVB_ADP_LISTENER_CAPABILITY_IMPLEMENTED |
AVB_ADP_LISTENER_CAPABILITY_AUDIO_SINK);
entity_state.state.desc.configurations_count = htons(1);
server_add_descriptor(server, AVB_AEM_DESC_ENTITY, 0,
sizeof(entity_state), &entity_state);
}
return server;
error:
free(server->ifname);
free(server);
return NULL;
}
#endif /* TEST_AVB_UTILS_H */

View file

@ -7,6 +7,9 @@
#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)
@ -1395,6 +1398,668 @@ PWTEST(avb_acmp_packet_filtering)
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 */
@ -1431,5 +2096,19 @@ PWTEST_SUITE(avb)
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;
}