#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <assert.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "liburing.h"
#include "helpers.h"
static int no_buf_ring, no_recv_mshot;
#define TWO_KB (1024 * 2)
#define BUFFER_SIZE 1024
#define BUFFER_COUNT 4
#define QUEUE_DEPTH 16
#define min(a, b) (((a) < (b)) ? (a) : (b))
struct buf_data {
void *addr;
uint32_t len;
};
struct buf_ring_data {
struct io_uring_buf_ring *buf_ring;
void *buffer_memory;
uint16_t ring_entries;
uint32_t buf_size;
};
struct read_buf {
void *buffer_memory;
size_t cursor;
};
static int setup_buf_ring(struct buf_ring_data *data, struct io_uring *ring,
uint16_t entries, uint32_t buf_size, int bgid)
{
data->ring_entries = entries;
data->buf_size = buf_size;
size_t total_size = entries * buf_size;
int page_size = sysconf(_SC_PAGESIZE);
size_t aligned_size = (total_size + page_size - 1) & ~(page_size - 1);
void *buffer_memory = mmap(NULL, aligned_size, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
assert(buffer_memory != MAP_FAILED);
data->buffer_memory = buffer_memory;
void *mapped;
struct io_uring_buf_ring *buf_ring;
int ring_size = entries * sizeof(struct io_uring_buf);
ring_size = (ring_size + page_size - 1) & ~(page_size - 1);
mapped = mmap(NULL, ring_size, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
assert(mapped != MAP_FAILED);
buf_ring = (struct io_uring_buf_ring *)mapped;
io_uring_buf_ring_init(buf_ring);
data->buf_ring = buf_ring;
struct io_uring_buf_reg reg = {
.ring_addr = (unsigned long)buf_ring,
.ring_entries = entries,
.bgid = 0
};
int ret = io_uring_register_buf_ring(ring, ®, IOU_PBUF_RING_INC);
if (ret) {
if (ret == -EINVAL) {
no_buf_ring = 1;
return T_EXIT_SKIP;
}
fprintf(stderr, "Buffer ring setup: %d\n", ret);
return T_EXIT_FAIL;
}
for (int i = 0; i < entries; i++) {
void *buf_addr = buffer_memory + i * buf_size;
io_uring_buf_ring_add(buf_ring, buf_addr, buf_size, i,
io_uring_buf_ring_mask(entries), i);
}
io_uring_buf_ring_advance(buf_ring, entries);
return T_EXIT_PASS;
}
static int verify_received_buffer(struct buf_data *buf, uint8_t *expected_data_start)
{
uint8_t *data = buf->addr;
for (uint32_t i = 0; i < buf->len; i++) {
if (data[i] != expected_data_start[i]) {
fprintf(stderr, "Recv data ordering mismatch. expected: %d, get: %d\n", expected_data_start[i], data[i]);
return 1;
}
}
return 0;
}
static int process_completion(struct io_uring_cqe *cqe, struct read_buf *rb,
uint8_t **current_expect)
{
usleep(1);
if (cqe->res <= 0) {
if (cqe->res == 0) {
fprintf(stderr, "EOF reached\n");
} else if (cqe->res == -EINVAL) {
no_recv_mshot = 1;
} else if (cqe->res != -ENOBUFS) {
fprintf(stderr, "CQE res %d\n", cqe->res);
return 1;
}
return 0;
}
uint32_t total_len = cqe->res;
assert(total_len <= BUFFER_SIZE);
void *buffer_addr = (uint8_t*)rb->buffer_memory + (rb->cursor);
struct buf_data buf = {
.addr = buffer_addr,
.len = total_len
};
if (verify_received_buffer(&buf, *current_expect))
return T_EXIT_FAIL;
rb->cursor += total_len;
return 0;
}
static void write_all(int fd, const void *data, size_t size)
{
const uint8_t *buf = data;
size_t bytes_sent = 0;
while (bytes_sent < size) {
ssize_t sent;
sent = write(fd, buf + bytes_sent, size - bytes_sent);
assert(sent > 0);
bytes_sent += sent;
}
}
static int test_recv_incr(int queue_flags)
{
struct io_uring ring;
struct io_uring_params params = { .flags = queue_flags, };
int ret, eret;
ret = t_create_ring_params(QUEUE_DEPTH, &ring, ¶ms);
if (ret == T_SETUP_SKIP)
return T_EXIT_SKIP;
else if (ret != T_SETUP_OK)
return T_EXIT_FAIL;
struct buf_ring_data br_data;
ret = setup_buf_ring(&br_data, &ring, BUFFER_COUNT, BUFFER_SIZE, 0);
if (ret == T_EXIT_SKIP)
return T_EXIT_SKIP;
else if (ret != T_EXIT_PASS)
return T_EXIT_FAIL;
int socket_fds[2];
ret = t_create_socket_pair(socket_fds, true);
assert(ret == 0);
int receiver_fd = socket_fds[0];
int sender_fd = socket_fds[1];
size_t data_received = 0;
struct io_uring_sqe *sqe = io_uring_get_sqe(&ring);
io_uring_prep_recv_multishot(sqe, receiver_fd, NULL, 0, 0);
sqe->flags |= IOSQE_BUFFER_SELECT;
sqe->ioprio |= IORING_RECVSEND_BUNDLE;
sqe->buf_group = 0;
io_uring_submit(&ring);
size_t total_bytes = TWO_KB;
uint8_t *test_data = malloc(total_bytes);
for (int i = 0; i < total_bytes; i++)
test_data[i] = i % 256;
size_t chunk_sizes[] = {512, 333, 777, 426};
size_t sent_offset = 0;
size_t num_chunks = sizeof(chunk_sizes) / sizeof(chunk_sizes[0]);
struct read_buf rb = {
.buffer_memory = br_data.buffer_memory,
.cursor = 0
};
for (size_t i = 0; i < num_chunks; i++) {
size_t chunk_size = chunk_sizes[i];
size_t end_offset = min(sent_offset + chunk_size, total_bytes);
size_t chunk_len = end_offset - sent_offset;
uint8_t* chunk = test_data + sent_offset;
write_all(sender_fd, chunk, chunk_len);
struct io_uring_cqe *cqe;
size_t remaining_chunk_len = chunk_len;
while (remaining_chunk_len) {
ret = io_uring_wait_cqe(&ring, &cqe);
if (ret) {
fprintf(stderr, "wait_cqe=%d\n", ret);
eret = T_EXIT_FAIL;
goto exit;
}
uint8_t *current_expect = chunk + (chunk_len - remaining_chunk_len);
if (process_completion(cqe, &rb, ¤t_expect)) {
eret = T_EXIT_FAIL;
goto exit;
}
if (no_recv_mshot) {
eret = T_EXIT_SKIP;
goto exit;
}
remaining_chunk_len -= cqe->res;
data_received += cqe->res;
io_uring_cq_advance(&ring, 1);
}
sent_offset = end_offset;
if (sent_offset >= total_bytes)
break;
}
close(sender_fd);
if (data_received != total_bytes) {
fprintf(stderr, "Received %u, wanted %u\n", (int) data_received, TWO_KB);
return T_EXIT_FAIL;
}
eret = T_EXIT_PASS;
exit:
close(receiver_fd);
io_uring_queue_exit(&ring);
munmap(br_data.buffer_memory, BUFFER_COUNT * BUFFER_SIZE);
munmap(br_data.buf_ring, BUFFER_COUNT * sizeof(struct io_uring_buf));
free(test_data);
return eret;
}
int main(int argc, char *argv[])
{
int ret;
if (argc > 1)
return T_EXIT_SKIP;
ret = test_recv_incr(0);
if (ret == T_EXIT_FAIL) {
fprintf(stderr, "test 0 failed\n");
return ret;
}
if (no_buf_ring || no_recv_mshot)
return T_EXIT_SKIP;
ret = test_recv_incr(IORING_SETUP_SINGLE_ISSUER | IORING_SETUP_DEFER_TASKRUN);
if (ret == T_EXIT_FAIL) {
fprintf(stderr, "test defer failed\n");
return ret;
}
ret = test_recv_incr(IORING_SETUP_SQPOLL);
if (ret == T_EXIT_FAIL) {
fprintf(stderr, "test sqpoll failed\n");
return ret;
}
ret = test_recv_incr(IORING_SETUP_COOP_TASKRUN);
if (ret == T_EXIT_FAIL) {
fprintf(stderr, "test coop failed\n");
return ret;
}
return T_EXIT_PASS;
}