247 lines
7.3 KiB
C
247 lines
7.3 KiB
C
// Native would-block and queue-timeout contract smoke test.
|
|
// Verifies that queue try, finite-timeout, and infinite-wait requests return distinct statuses.
|
|
|
|
#include <stdint.h>
|
|
#include <ntlite/syscall.h>
|
|
|
|
#define TEST_FILL_LIMIT 1024u
|
|
#define TEST_MESSAGE_WAKE 0x77616b65u
|
|
#define TEST_WAKE_VALUE 0x12345678u
|
|
|
|
typedef struct TEST_QUEUE_MESSAGE {
|
|
uint32_t Type;
|
|
uint32_t Flags;
|
|
uint64_t Param[6];
|
|
} TEST_QUEUE_MESSAGE;
|
|
|
|
typedef char TEST_QUEUE_MESSAGE_SIZE_CHECK[
|
|
(sizeof(TEST_QUEUE_MESSAGE) == SYS_QUEUE_MESSAGE_SIZE) ? 1 : -1
|
|
];
|
|
|
|
typedef struct TEST_SENDER_CONTEXT {
|
|
uint32_t Queue;
|
|
volatile uint32_t Status;
|
|
} TEST_SENDER_CONTEXT;
|
|
|
|
// Sends one message with the queue timeout request ABI.
|
|
static uint32_t send_queue(uint32_t queue, const TEST_QUEUE_MESSAGE* msg, uint64_t timeoutMilliseconds) {
|
|
SYS_QUEUE_SEND_REQUEST req = {
|
|
sizeof(req),
|
|
queue,
|
|
SYS_QUEUE_FLAG_NONE,
|
|
timeoutMilliseconds,
|
|
msg,
|
|
};
|
|
return SysSendQueue(&req);
|
|
}
|
|
|
|
// Receives one message with the queue timeout request ABI.
|
|
static uint32_t recv_queue(uint32_t queue, TEST_QUEUE_MESSAGE* msg, uint64_t timeoutMilliseconds) {
|
|
SYS_QUEUE_RECV_REQUEST req = {
|
|
sizeof(req),
|
|
queue,
|
|
SYS_QUEUE_FLAG_NONE,
|
|
timeoutMilliseconds,
|
|
msg,
|
|
};
|
|
return SysRecvQueue(&req);
|
|
}
|
|
|
|
// Prints one NUL-terminated string to the kernel console.
|
|
static void print(const char* s) {
|
|
SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) };
|
|
SysWriteConsole(&req);
|
|
}
|
|
|
|
// Prints one unsigned integer to the kernel console.
|
|
static void print_u64(uint64_t v) {
|
|
char buf[32];
|
|
int i = 0;
|
|
if (v == 0) {
|
|
print("0");
|
|
return;
|
|
}
|
|
while (v && i < (int)sizeof(buf)) {
|
|
buf[i++] = (char)('0' + (v % 10));
|
|
v /= 10;
|
|
}
|
|
while (i--) {
|
|
char c = buf[i];
|
|
SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromParts(&c, 1) };
|
|
SysWriteConsole(&req);
|
|
}
|
|
}
|
|
|
|
// Terminates the current test process.
|
|
static void exit_now(int code) {
|
|
for (;;) SysExitProcess(code);
|
|
}
|
|
|
|
// Fails the test with one plain message.
|
|
static void fail(const char* msg) {
|
|
print("would_block_smoke: ");
|
|
print(msg);
|
|
print("\n");
|
|
exit_now(1);
|
|
}
|
|
|
|
// Fails the test with one status value.
|
|
static void fail_status(const char* what, uint32_t status) {
|
|
print("would_block_smoke: ");
|
|
print(what);
|
|
print(" status=");
|
|
print_u64(status);
|
|
print("\n");
|
|
exit_now(1);
|
|
}
|
|
|
|
// Requires one exact status value.
|
|
static void expect_status(const char* what, uint32_t got, uint32_t expected) {
|
|
if (got == expected) return;
|
|
print("would_block_smoke: ");
|
|
print(what);
|
|
print(" got=");
|
|
print_u64(got);
|
|
print(" expected=");
|
|
print_u64(expected);
|
|
print("\n");
|
|
exit_now(1);
|
|
}
|
|
|
|
// Sends one wake message after the receiver has had a chance to block.
|
|
static void sender_thread(uintptr_t arg) {
|
|
TEST_SENDER_CONTEXT* ctx = (TEST_SENDER_CONTEXT*)arg;
|
|
if (!ctx) SysExitThread(1);
|
|
|
|
SysSleep(20);
|
|
|
|
TEST_QUEUE_MESSAGE msg = {0};
|
|
msg.Type = TEST_MESSAGE_WAKE;
|
|
msg.Param[0] = TEST_WAKE_VALUE;
|
|
ctx->Status = send_queue(ctx->Queue, &msg, SYS_WAIT_TIMEOUT_INFINITE);
|
|
SysExitThread(ctx->Status == NTLSTATUS_SUCCESS ? 0 : 1);
|
|
}
|
|
|
|
// Fills one queue with immediate sends until the queue reports would-block.
|
|
static void fill_until_would_block(uint32_t queue) {
|
|
TEST_QUEUE_MESSAGE msg = {0};
|
|
msg.Type = 1;
|
|
|
|
uint32_t sends = 0;
|
|
for (; sends < TEST_FILL_LIMIT; sends++) {
|
|
msg.Param[0] = sends;
|
|
uint32_t s = send_queue(queue, &msg, 0);
|
|
if (s == NTLSTATUS_WOULD_BLOCK)
|
|
break;
|
|
if (s != NTLSTATUS_SUCCESS)
|
|
fail_status("fill send", s);
|
|
}
|
|
|
|
if (sends == 0 || sends == TEST_FILL_LIMIT)
|
|
fail("queue fill did not reach would-block");
|
|
}
|
|
|
|
// Verifies queue try and finite-timeout status distinctions.
|
|
static void test_queue_empty_and_full(void) {
|
|
uint32_t queue = 0;
|
|
uint32_t s = SysCreateQueue(&queue);
|
|
if (s != NTLSTATUS_SUCCESS) fail_status("create empty queue", s);
|
|
|
|
TEST_QUEUE_MESSAGE msg = {0};
|
|
expect_status("empty nonblocking receive", recv_queue(queue, &msg, 0), NTLSTATUS_WOULD_BLOCK);
|
|
expect_status("empty finite receive", recv_queue(queue, &msg, 20), NTLSTATUS_TIMEOUT);
|
|
SysCloseHandle(queue);
|
|
|
|
queue = 0;
|
|
s = SysCreateQueue(&queue);
|
|
if (s != NTLSTATUS_SUCCESS) fail_status("create full queue", s);
|
|
|
|
fill_until_would_block(queue);
|
|
expect_status("full nonblocking send", send_queue(queue, &msg, 0), NTLSTATUS_WOULD_BLOCK);
|
|
expect_status("full finite send", send_queue(queue, &msg, 20), NTLSTATUS_TIMEOUT);
|
|
SysCloseHandle(queue);
|
|
}
|
|
|
|
// Verifies that an infinite receive composes with a sender wakeup.
|
|
static void test_queue_infinite_receive_wakeup(void) {
|
|
uint32_t queue = 0;
|
|
uint32_t s = SysCreateQueue(&queue);
|
|
if (s != NTLSTATUS_SUCCESS) fail_status("create wake queue", s);
|
|
|
|
TEST_SENDER_CONTEXT ctx = { queue, NTLSTATUS_PENDING };
|
|
uint32_t threadHandle = 0;
|
|
uint32_t threadId = 0;
|
|
SYS_CREATE_THREAD_REQUEST req = {
|
|
(uintptr_t)sender_thread,
|
|
(uintptr_t)&ctx,
|
|
1,
|
|
0,
|
|
&threadHandle,
|
|
&threadId
|
|
};
|
|
s = SysCreateThread(&req);
|
|
if (s != NTLSTATUS_SUCCESS) fail_status("create sender", s);
|
|
|
|
TEST_QUEUE_MESSAGE msg = {0};
|
|
s = recv_queue(queue, &msg, SYS_WAIT_TIMEOUT_INFINITE);
|
|
if (s != NTLSTATUS_SUCCESS) fail_status("infinite receive", s);
|
|
if (msg.Type != TEST_MESSAGE_WAKE || msg.Param[0] != TEST_WAKE_VALUE)
|
|
fail("wake message payload mismatch");
|
|
|
|
s = SysWait(threadHandle, SYS_WAIT_FLAG_NONE);
|
|
if (s != NTLSTATUS_SUCCESS) fail_status("wait sender", s);
|
|
if (ctx.Status != NTLSTATUS_SUCCESS) fail_status("sender status", ctx.Status);
|
|
|
|
SysCloseHandle(threadHandle);
|
|
SysCloseHandle(queue);
|
|
}
|
|
|
|
// Verifies that request metadata is part of the queue ABI contract.
|
|
static void test_queue_request_validation(void) {
|
|
uint32_t queue = 0;
|
|
uint32_t s = SysCreateQueue(&queue);
|
|
if (s != NTLSTATUS_SUCCESS) fail_status("create validation queue", s);
|
|
|
|
TEST_QUEUE_MESSAGE msg = {0};
|
|
expect_status("null send request", SysSendQueue((const SYS_QUEUE_SEND_REQUEST*)0), NTLSTATUS_INVALID_PARAMETER);
|
|
expect_status("null recv request", SysRecvQueue((const SYS_QUEUE_RECV_REQUEST*)0), NTLSTATUS_INVALID_PARAMETER);
|
|
|
|
SYS_QUEUE_SEND_REQUEST sendReq = {
|
|
0,
|
|
queue,
|
|
SYS_QUEUE_FLAG_NONE,
|
|
0,
|
|
&msg,
|
|
};
|
|
expect_status("bad send size", SysSendQueue(&sendReq), NTLSTATUS_INVALID_PARAMETER);
|
|
|
|
sendReq.Size = sizeof(sendReq);
|
|
sendReq.Flags = 1;
|
|
expect_status("bad send flags", SysSendQueue(&sendReq), NTLSTATUS_INVALID_PARAMETER);
|
|
|
|
sendReq.Flags = SYS_QUEUE_FLAG_NONE;
|
|
sendReq.Message = (const void*)0;
|
|
expect_status("bad send message", SysSendQueue(&sendReq), NTLSTATUS_INVALID_PARAMETER);
|
|
|
|
SYS_QUEUE_RECV_REQUEST recvReq = {
|
|
sizeof(recvReq),
|
|
queue,
|
|
SYS_QUEUE_FLAG_NONE,
|
|
0,
|
|
(void*)0,
|
|
};
|
|
expect_status("bad recv message", SysRecvQueue(&recvReq), NTLSTATUS_INVALID_PARAMETER);
|
|
|
|
SysCloseHandle(queue);
|
|
}
|
|
|
|
// Runs the native would-block smoke test.
|
|
int main(void) {
|
|
print("would_block_smoke: start\n");
|
|
test_queue_request_validation();
|
|
test_queue_empty_and_full();
|
|
test_queue_infinite_receive_wakeup();
|
|
print("would_block_smoke: PASS\n");
|
|
exit_now(0);
|
|
}
|