240 lines
6.7 KiB
C
240 lines
6.7 KiB
C
#include <string.h>
|
|
#include <sys/socket.h>
|
|
#include <unistd.h>
|
|
#include <cmocka.h>
|
|
|
|
#include "../src/rcon.h"
|
|
|
|
static void assert_packet_equals(const rcon_packet_t *packet,
|
|
int32_t expected_id, int32_t expected_type,
|
|
const char *expected_body) {
|
|
assert_int_equal(packet->id, expected_id);
|
|
assert_int_equal(packet->type, expected_type);
|
|
assert_string_equal(packet->body, expected_body);
|
|
assert_int_equal(packet->_padding, 0);
|
|
assert_int_equal(packet->size,
|
|
(int32_t)(sizeof(int32_t) * 2 + strlen(expected_body) + 2));
|
|
}
|
|
|
|
static void send_raw_packet(int sockfd, int32_t id, int32_t type,
|
|
const char *body) {
|
|
size_t body_len = strlen(body);
|
|
size_t packet_size = sizeof(int32_t) * 3 + body_len + 2;
|
|
uint8_t packet[sizeof(int32_t) * 3 + 4096 + 2] = {0};
|
|
|
|
assert_true(packet_size <= sizeof(packet));
|
|
|
|
int32_t size = htole32(packet_size - sizeof(int32_t));
|
|
memcpy(packet, &size, sizeof(int32_t));
|
|
|
|
int32_t id_le = htole32(id);
|
|
memcpy(packet + 4, &id_le, sizeof(int32_t));
|
|
|
|
int32_t type_le = htole32(type);
|
|
memcpy(packet + 8, &type_le, sizeof(int32_t));
|
|
|
|
memcpy(packet + 12, body, body_len);
|
|
packet[12 + body_len] = 0;
|
|
packet[12 + body_len + 1] = 0;
|
|
|
|
assert_int_equal((int)send(sockfd, packet, packet_size, 0), (int)packet_size);
|
|
}
|
|
|
|
typedef struct {
|
|
int client;
|
|
int server;
|
|
} socketpair_t;
|
|
|
|
static socketpair_t create_socketpair() {
|
|
int sv[2];
|
|
assert_int_equal(socketpair(AF_UNIX, SOCK_STREAM, 0, sv), 0);
|
|
socketpair_t sp = {.client = sv[0], .server = sv[1]};
|
|
return sp;
|
|
}
|
|
|
|
static void close_socketpair(socketpair_t *sockets) {
|
|
close(sockets->client);
|
|
close(sockets->server);
|
|
}
|
|
|
|
/// Serialization and deserialization ///
|
|
|
|
static void test_serialization_roundtrip_command_packet(void **state) {
|
|
(void)state;
|
|
|
|
socketpair_t sockets = create_socketpair();
|
|
|
|
const int32_t id = 123;
|
|
const int32_t type = RCON_SERVERDATA_EXECCOMMAND;
|
|
const char *body = "execute as @a run data get entity @s Pos";
|
|
|
|
send_packet(sockets.client, id, type, body);
|
|
|
|
rcon_packet_t packet = {0};
|
|
assert_int_equal(recv_packet(sockets.server, &packet), 0);
|
|
assert_packet_equals(&packet, id, type, body);
|
|
|
|
close_socketpair(&sockets);
|
|
}
|
|
|
|
static void test_serialization_roundtrip_auth_packet(void **state) {
|
|
(void)state;
|
|
|
|
socketpair_t sockets = create_socketpair();
|
|
|
|
const int32_t id = 1;
|
|
const int32_t type = RCON_SERVERDATA_AUTH;
|
|
const char *body = "correct horse battery staple";
|
|
|
|
send_packet(sockets.client, id, type, body);
|
|
|
|
rcon_packet_t packet = {0};
|
|
assert_int_equal(recv_packet(sockets.server, &packet), 0);
|
|
assert_packet_equals(&packet, id, type, body);
|
|
|
|
close_socketpair(&sockets);
|
|
}
|
|
|
|
static void test_serialization_roundtrip_empty_body_packet(void **state) {
|
|
(void)state;
|
|
|
|
socketpair_t sockets = create_socketpair();
|
|
|
|
const int32_t id = 101;
|
|
const int32_t type = RCON_SERVERDATA_EXECCOMMAND;
|
|
const char *body = "";
|
|
|
|
send_packet(sockets.client, id, type, body);
|
|
|
|
rcon_packet_t packet = {0};
|
|
assert_int_equal(recv_packet(sockets.server, &packet), 0);
|
|
assert_packet_equals(&packet, id, type, body);
|
|
|
|
close_socketpair(&sockets);
|
|
}
|
|
|
|
static void test_serialization_roundtrip_max_supported_body(void **state) {
|
|
(void)state;
|
|
|
|
socketpair_t sockets = create_socketpair();
|
|
|
|
char body[4095];
|
|
memset(body, 'A', sizeof(body) - 1);
|
|
body[sizeof(body) - 1] = '\0';
|
|
|
|
send_packet(sockets.client, 777, RCON_SERVERDATA_RESPONSE_VALUE, body);
|
|
|
|
rcon_packet_t packet = {0};
|
|
assert_int_equal(recv_packet(sockets.server, &packet), 0);
|
|
assert_packet_equals(&packet, 777, RCON_SERVERDATA_RESPONSE_VALUE, body);
|
|
|
|
close_socketpair(&sockets);
|
|
}
|
|
|
|
static void test_recv_packet_errors_on_unexpected_close(void **state) {
|
|
(void)state;
|
|
|
|
socketpair_t sockets = create_socketpair();
|
|
|
|
close(sockets.server);
|
|
|
|
rcon_packet_t packet = {0};
|
|
assert_int_equal(recv_packet(sockets.client, &packet), -1);
|
|
|
|
close_socketpair(&sockets);
|
|
}
|
|
|
|
/// End of serialization and deserialization ///
|
|
|
|
/// Authentication logic ///
|
|
|
|
static void receive_auth_response(int sockfd, const char *expected_password) {
|
|
rcon_packet_t packet = {0};
|
|
|
|
assert_int_equal(recv_packet(sockfd, &packet), 0);
|
|
assert_packet_equals(&packet, 1, RCON_SERVERDATA_AUTH, expected_password);
|
|
}
|
|
|
|
static void test_authenticate_successful_flow(void **state) {
|
|
(void)state;
|
|
|
|
socketpair_t sockets = create_socketpair();
|
|
|
|
const char *password = "hunter2";
|
|
|
|
send_raw_packet(sockets.server, 1, RCON_SERVERDATA_RESPONSE_VALUE, "");
|
|
send_raw_packet(sockets.server, 1, RCON_SERVERDATA_AUTH_RESPONSE, "");
|
|
|
|
assert_int_equal(rcon_authenticate(sockets.client, password), 0);
|
|
receive_auth_response(sockets.server, password);
|
|
|
|
close(sockets.client);
|
|
close(sockets.server);
|
|
}
|
|
|
|
static void test_authenticate_invalid_password(void **state) {
|
|
(void)state;
|
|
|
|
socketpair_t sockets = create_socketpair();
|
|
|
|
const char *password = "wrong-password";
|
|
|
|
send_raw_packet(sockets.server, -1, RCON_SERVERDATA_RESPONSE_VALUE, "");
|
|
|
|
assert_int_equal(rcon_authenticate(sockets.client, password), -1);
|
|
receive_auth_response(sockets.server, password);
|
|
|
|
close(sockets.client);
|
|
close(sockets.server);
|
|
}
|
|
|
|
static void test_authenticate_unexpected_packet_type(void **state) {
|
|
(void)state;
|
|
|
|
socketpair_t sockets = create_socketpair();
|
|
|
|
const char *password = "hunter2";
|
|
|
|
send_raw_packet(sockets.server, 1, RCON_SERVERDATA_EXECCOMMAND, "");
|
|
|
|
assert_int_equal(rcon_authenticate(sockets.client, password), -1);
|
|
receive_auth_response(sockets.server, password);
|
|
|
|
close(sockets.client);
|
|
close(sockets.server);
|
|
}
|
|
|
|
static void test_authenticate_mismatched_response_id(void **state) {
|
|
(void)state;
|
|
|
|
socketpair_t sockets = create_socketpair();
|
|
|
|
const char *password = "hunter2";
|
|
|
|
send_raw_packet(sockets.server, 2, RCON_SERVERDATA_RESPONSE_VALUE, "");
|
|
|
|
assert_int_equal(rcon_authenticate(sockets.client, password), -1);
|
|
receive_auth_response(sockets.server, password);
|
|
|
|
close(sockets.client);
|
|
close(sockets.server);
|
|
}
|
|
|
|
/// End of authentication logic ///
|
|
|
|
int main(void) {
|
|
const struct CMUnitTest tests[] = {
|
|
cmocka_unit_test(test_serialization_roundtrip_command_packet),
|
|
cmocka_unit_test(test_serialization_roundtrip_auth_packet),
|
|
cmocka_unit_test(test_serialization_roundtrip_empty_body_packet),
|
|
cmocka_unit_test(test_serialization_roundtrip_max_supported_body),
|
|
cmocka_unit_test(test_recv_packet_errors_on_unexpected_close),
|
|
cmocka_unit_test(test_authenticate_successful_flow),
|
|
cmocka_unit_test(test_authenticate_invalid_password),
|
|
cmocka_unit_test(test_authenticate_unexpected_packet_type),
|
|
cmocka_unit_test(test_authenticate_mismatched_response_id),
|
|
};
|
|
|
|
return cmocka_run_group_tests(tests, NULL, NULL);
|
|
}
|