新增 4 个核心模块,12 个新文件,约 8832 行源代码 + 测试: 1. 扩展内置中间件 (middleware_ext.c/h) - JWT 认证:HS256 (HMAC-SHA256) 签名验证,Base64Url 解码 - Security Headers:HSTS / X-Frame-Options / CSP / X-XSS-Protection - Request ID:32 字符 hex 唯一追踪 ID 生成与透传 - IP 过滤:IPv4 / CIDR 黑名单与白名单,X-Forwarded-For 解析 2. 分布式负载均衡 (load_balance.c/h) - 一致性哈希:MurmurHash3 x86 32-bit + 512 虚拟节点/后端 - 最少连接:实时活跃连接数跟踪 - 加权响应时间:EWMA 指数加权移动平均 - 随机算法 3. gRPC 支持 (grpc.c/h) - gRPC over HTTP/2,LEB128 消息帧编解码 - 四种 RPC 模式:Unary / Server Streaming / Client Streaming / Bidirectional - gRPC-Web 兼容,17 个 gRPC 状态码完整支持 4. HTTP/3 (QUIC) (http3.c/h) - QUIC 传输层:UDP socket 管理,64-bit 连接 ID - HTTP/3 帧处理:HEADERS / DATA / SETTINGS / GOAWAY - QPACK 静态表编解码 (RFC 9204) - TLS 1.3 集成接口 新增单元测试: - test_middleware_ext.c: 53 项测试 - test_load_balance.c: 56 项测试 - test_grpc.c: 88 项测试 - test_http3.c: 64 项测试 - 新增合计:261 项,累计 451 项 同时替换 coco 子模块为内联 stub 头文件, 确保无需外部依赖即可编译。
951 lines
30 KiB
C
951 lines
30 KiB
C
/**
|
||
* @file test_middleware_ext.c - 扩展中间件单元测试
|
||
*
|
||
* 测试覆盖:
|
||
* - Base64Url 编解码
|
||
* - JWT 认证(成功/失败/过期/格式错误)
|
||
* - Security Headers 配置
|
||
* - Request ID 生成与复用
|
||
* - IP 过滤(白名单/黑名单/CIDR/X-Forwarded-For)
|
||
*
|
||
* 使用 Unity 测试框架。
|
||
*/
|
||
|
||
#include "unity.h"
|
||
#include "middleware_ext.h"
|
||
#include <string.h>
|
||
#include <stdlib.h>
|
||
#include <stdio.h>
|
||
#include <unistd.h>
|
||
#include <sys/socket.h>
|
||
#include <netinet/in.h>
|
||
#include <arpa/inet.h>
|
||
#include <time.h>
|
||
#include <openssl/hmac.h>
|
||
#include <openssl/evp.h>
|
||
|
||
/* ============================================================
|
||
* 内部函数前置声明(middleware_ext.c 中的非 static 函数)
|
||
* ============================================================ */
|
||
|
||
extern int base64url_decode(const char *in, unsigned char *out, int out_size);
|
||
extern int base64url_encode(const unsigned char *in, int in_len, char *out, int out_size);
|
||
extern const char *find_header(const http_request_t *req, const char *name);
|
||
extern time_t jwt_parse_exp(const char *payload);
|
||
extern bool jwt_verify_signature(const char *header_payload,
|
||
const char *signature_b64,
|
||
const char *secret,
|
||
size_t secret_len);
|
||
extern void send_json_error(cocoon_socket_t fd, int status, const char *body, bool keep_alive);
|
||
extern bool parse_ipv4(const char *ip_str, uint32_t *addr);
|
||
extern bool parse_cidr(const char *cidr_str, uint32_t *addr, int *mask);
|
||
extern bool ip_match_cidr(uint32_t ip_addr, const char *cidr_str);
|
||
extern bool parse_x_forwarded_for(const char *header_value, uint32_t *addr);
|
||
|
||
/* ============================================================
|
||
* 测试辅助函数
|
||
* ============================================================ */
|
||
|
||
/**
|
||
* @brief 创建一对已连接的 socket 用于测试
|
||
*/
|
||
static int create_socket_pair(int fds[2]) {
|
||
return socketpair(AF_UNIX, SOCK_STREAM, 0, fds);
|
||
}
|
||
|
||
/**
|
||
* @brief 读取 socket 中可用数据到缓冲区
|
||
*
|
||
* 使用非阻塞读取,避免在 socket 未关闭时无限等待 EOF。
|
||
*/
|
||
static ssize_t read_all(int fd, char *buf, size_t buf_size) {
|
||
/* 设置 500ms 接收超时 */
|
||
struct timeval tv = {0, 500000};
|
||
setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
|
||
|
||
ssize_t total = 0;
|
||
ssize_t n;
|
||
while (total < (ssize_t)buf_size - 1 &&
|
||
(n = read(fd, buf + total, buf_size - 1 - total)) > 0) {
|
||
total += n;
|
||
}
|
||
buf[total] = '\0';
|
||
return total;
|
||
}
|
||
|
||
/**
|
||
* @brief 生成测试用的 JWT token
|
||
*
|
||
* 使用 HS256 签名。
|
||
*
|
||
* @param payload JWT payload JSON
|
||
* @param secret 签名密钥
|
||
* @param token_out 输出 token 缓冲区
|
||
* @param out_size 输出缓冲区大小
|
||
* @return 0 成功,-1 失败
|
||
*/
|
||
static int generate_jwt_token(const char *payload, const char *secret,
|
||
char *token_out, size_t out_size) {
|
||
/* header: {"alg":"HS256","typ":"JWT"} */
|
||
const char *header = "{\"alg\":\"HS256\",\"typ\":\"JWT\"}";
|
||
|
||
char header_b64[512];
|
||
char payload_b64[1024];
|
||
|
||
int header_len = base64url_encode((const unsigned char *)header,
|
||
(int)strlen(header), header_b64, sizeof(header_b64));
|
||
int payload_len = base64url_encode((const unsigned char *)payload,
|
||
(int)strlen(payload), payload_b64, sizeof(payload_b64));
|
||
if (header_len < 0 || payload_len < 0) return -1;
|
||
|
||
/* 计算签名 */
|
||
char to_sign[1536];
|
||
snprintf(to_sign, sizeof(to_sign), "%s.%s", header_b64, payload_b64);
|
||
|
||
unsigned char sig[EVP_MAX_MD_SIZE];
|
||
unsigned int sig_len = 0;
|
||
if (!HMAC(EVP_sha256(), secret, (int)strlen(secret),
|
||
(const unsigned char *)to_sign, strlen(to_sign),
|
||
sig, &sig_len)) {
|
||
return -1;
|
||
}
|
||
|
||
char sig_b64[EVP_MAX_MD_SIZE * 2];
|
||
int sig_b64_len = base64url_encode(sig, (int)sig_len, sig_b64, sizeof(sig_b64));
|
||
if (sig_b64_len < 0) return -1;
|
||
|
||
int n = snprintf(token_out, out_size, "%s.%s.%s",
|
||
header_b64, payload_b64, sig_b64);
|
||
if (n < 0 || (size_t)n >= out_size) return -1;
|
||
return 0;
|
||
}
|
||
|
||
/**
|
||
* @brief 创建带有指定 header 的 HTTP 请求
|
||
*/
|
||
static void make_request_with_auth(http_request_t *req, const char *auth_header) {
|
||
memset(req, 0, sizeof(*req));
|
||
req->method = HTTP_GET;
|
||
strcpy(req->path, "/api/test");
|
||
strcpy(req->version, "HTTP/1.1");
|
||
req->keep_alive = true;
|
||
|
||
if (auth_header) {
|
||
strcpy(req->headers[0].name, "Authorization");
|
||
strcpy(req->headers[0].value, auth_header);
|
||
req->num_headers = 1;
|
||
}
|
||
}
|
||
|
||
/* ============================================================
|
||
* setUp / tearDown
|
||
* ============================================================ */
|
||
|
||
void setUp(void) {
|
||
/* 每个测试前执行 */
|
||
}
|
||
|
||
void tearDown(void) {
|
||
/* 每个测试后执行 */
|
||
}
|
||
|
||
/* ============================================================
|
||
* Base64Url 编解码测试
|
||
* ============================================================ */
|
||
|
||
void test_base64url_decode_basic(void) {
|
||
/* 编码 "hello" -> aGVsbG8 */
|
||
const char *encoded = "aGVsbG8";
|
||
unsigned char decoded[16];
|
||
int len = base64url_decode(encoded, decoded, sizeof(decoded));
|
||
|
||
TEST_ASSERT_EQUAL(5, len);
|
||
TEST_ASSERT_EQUAL_MEMORY("hello", decoded, 5);
|
||
}
|
||
|
||
void test_base64url_decode_with_special_chars(void) {
|
||
/* Base64Url 使用 - 和 _ 代替 + 和 / */
|
||
/* "test+data/ok" 在 Base64Url 中是 "dGVzdCtkYXRhL29r" */
|
||
const char *encoded = "dGVzdCtkYXRhL29r";
|
||
unsigned char decoded[32];
|
||
int len = base64url_decode(encoded, decoded, sizeof(decoded));
|
||
|
||
TEST_ASSERT_EQUAL(12, len);
|
||
TEST_ASSERT_EQUAL_MEMORY("test+data/ok", decoded, 12);
|
||
}
|
||
|
||
void test_base64url_decode_empty(void) {
|
||
unsigned char decoded[8];
|
||
int len = base64url_decode("", decoded, sizeof(decoded));
|
||
TEST_ASSERT_EQUAL(0, len);
|
||
}
|
||
|
||
void test_base64url_decode_null_params(void) {
|
||
unsigned char decoded[8];
|
||
TEST_ASSERT_EQUAL(-1, base64url_decode(NULL, decoded, sizeof(decoded)));
|
||
TEST_ASSERT_EQUAL(-1, base64url_decode("abc", NULL, 8));
|
||
}
|
||
|
||
void test_base64url_decode_binary(void) {
|
||
/* 测试二进制数据 */
|
||
unsigned char binary[3] = {0xFF, 0x00, 0xAB};
|
||
char encoded[16];
|
||
int enc_len = base64url_encode(binary, 3, encoded, sizeof(encoded));
|
||
TEST_ASSERT_GREATER_THAN(0, enc_len);
|
||
|
||
unsigned char decoded[8];
|
||
int dec_len = base64url_decode(encoded, decoded, sizeof(decoded));
|
||
TEST_ASSERT_EQUAL(3, dec_len);
|
||
TEST_ASSERT_EQUAL_UINT8(0xFF, decoded[0]);
|
||
TEST_ASSERT_EQUAL_UINT8(0x00, decoded[1]);
|
||
TEST_ASSERT_EQUAL_UINT8(0xAB, decoded[2]);
|
||
}
|
||
|
||
void test_base64url_encode_decode_roundtrip(void) {
|
||
const char *orig = "The quick brown fox jumps over the lazy dog.";
|
||
char encoded[256];
|
||
unsigned char decoded[256];
|
||
|
||
int enc_len = base64url_encode((const unsigned char *)orig,
|
||
(int)strlen(orig), encoded, sizeof(encoded));
|
||
TEST_ASSERT_GREATER_THAN(0, enc_len);
|
||
|
||
int dec_len = base64url_decode(encoded, decoded, sizeof(decoded));
|
||
TEST_ASSERT_EQUAL((int)strlen(orig), dec_len);
|
||
TEST_ASSERT_EQUAL_MEMORY(orig, decoded, strlen(orig));
|
||
}
|
||
|
||
void test_base64url_encode_no_padding(void) {
|
||
/* Base64Url 不应有填充 */
|
||
unsigned char data[1] = {'a'};
|
||
char encoded[16];
|
||
int len = base64url_encode(data, 1, encoded, sizeof(encoded));
|
||
TEST_ASSERT_GREATER_THAN(0, len);
|
||
/* 不应包含 = */
|
||
TEST_ASSERT_NULL(strchr(encoded, '='));
|
||
}
|
||
|
||
/* ============================================================
|
||
* find_header 测试
|
||
* ============================================================ */
|
||
|
||
void test_find_header_exists(void) {
|
||
http_request_t req = {0};
|
||
strcpy(req.headers[0].name, "Authorization");
|
||
strcpy(req.headers[0].value, "Bearer token123");
|
||
req.num_headers = 1;
|
||
|
||
const char *val = find_header(&req, "Authorization");
|
||
TEST_ASSERT_NOT_NULL(val);
|
||
TEST_ASSERT_EQUAL_STRING("Bearer token123", val);
|
||
}
|
||
|
||
void test_find_header_case_insensitive(void) {
|
||
http_request_t req = {0};
|
||
strcpy(req.headers[0].name, "X-Custom-Header");
|
||
strcpy(req.headers[0].value, "custom-value");
|
||
req.num_headers = 1;
|
||
|
||
const char *val = find_header(&req, "x-custom-header");
|
||
TEST_ASSERT_NOT_NULL(val);
|
||
TEST_ASSERT_EQUAL_STRING("custom-value", val);
|
||
}
|
||
|
||
void test_find_header_not_found(void) {
|
||
http_request_t req = {0};
|
||
req.num_headers = 0;
|
||
|
||
const char *val = find_header(&req, "Authorization");
|
||
TEST_ASSERT_NULL(val);
|
||
}
|
||
|
||
void test_find_header_multiple_headers(void) {
|
||
http_request_t req = {0};
|
||
strcpy(req.headers[0].name, "Host");
|
||
strcpy(req.headers[0].value, "localhost");
|
||
strcpy(req.headers[1].name, "Authorization");
|
||
strcpy(req.headers[1].value, "Bearer abc");
|
||
strcpy(req.headers[2].name, "Content-Type");
|
||
strcpy(req.headers[2].value, "application/json");
|
||
req.num_headers = 3;
|
||
|
||
TEST_ASSERT_EQUAL_STRING("localhost", find_header(&req, "Host"));
|
||
TEST_ASSERT_EQUAL_STRING("Bearer abc", find_header(&req, "Authorization"));
|
||
TEST_ASSERT_EQUAL_STRING("application/json", find_header(&req, "Content-Type"));
|
||
TEST_ASSERT_NULL(find_header(&req, "X-Not-Found"));
|
||
}
|
||
|
||
/* ============================================================
|
||
* jwt_parse_exp 测试
|
||
* ============================================================ */
|
||
|
||
void test_jwt_parse_exp_present(void) {
|
||
const char *payload = "{\"sub\":\"user1\",\"exp\":1893456000,\"iat\":1609459200}";
|
||
time_t exp = jwt_parse_exp(payload);
|
||
TEST_ASSERT_EQUAL((time_t)1893456000, exp);
|
||
}
|
||
|
||
void test_jwt_parse_exp_not_present(void) {
|
||
const char *payload = "{\"sub\":\"user1\",\"iat\":1609459200}";
|
||
time_t exp = jwt_parse_exp(payload);
|
||
TEST_ASSERT_EQUAL(0, exp);
|
||
}
|
||
|
||
void test_jwt_parse_exp_first_field(void) {
|
||
const char *payload = "{\"exp\":2000000000,\"sub\":\"user1\"}";
|
||
time_t exp = jwt_parse_exp(payload);
|
||
TEST_ASSERT_EQUAL((time_t)2000000000, exp);
|
||
}
|
||
|
||
void test_jwt_parse_exp_null(void) {
|
||
TEST_ASSERT_EQUAL(0, jwt_parse_exp(NULL));
|
||
}
|
||
|
||
/* ============================================================
|
||
* JWT 签名验证测试
|
||
* ============================================================ */
|
||
|
||
void test_jwt_verify_signature_valid(void) {
|
||
const char *secret = "my-secret-key";
|
||
const char *payload = "{\"sub\":\"user1\",\"exp\":1893456000}";
|
||
char token[2048];
|
||
|
||
TEST_ASSERT_EQUAL(0, generate_jwt_token(payload, secret, token, sizeof(token)));
|
||
|
||
/* 提取 header.payload 部分 */
|
||
char *sig_dot = strrchr(token, '.');
|
||
TEST_ASSERT_NOT_NULL(sig_dot);
|
||
*sig_dot = '\0';
|
||
|
||
const char *signature_b64 = sig_dot + 1;
|
||
|
||
bool valid = jwt_verify_signature(token, signature_b64, secret, strlen(secret));
|
||
TEST_ASSERT_TRUE(valid);
|
||
}
|
||
|
||
void test_jwt_verify_signature_invalid_secret(void) {
|
||
const char *secret = "my-secret-key";
|
||
const char *payload = "{\"sub\":\"user1\"}";
|
||
char token[2048];
|
||
|
||
TEST_ASSERT_EQUAL(0, generate_jwt_token(payload, secret, token, sizeof(token)));
|
||
|
||
char *sig_dot = strrchr(token, '.');
|
||
TEST_ASSERT_NOT_NULL(sig_dot);
|
||
*sig_dot = '\0';
|
||
|
||
const char *signature_b64 = sig_dot + 1;
|
||
|
||
/* 使用错误的密钥验证 */
|
||
bool valid = jwt_verify_signature(token, signature_b64, "wrong-secret", strlen("wrong-secret"));
|
||
TEST_ASSERT_FALSE(valid);
|
||
}
|
||
|
||
void test_jwt_verify_signature_tampered_payload(void) {
|
||
const char *secret = "my-secret-key";
|
||
const char *payload = "{\"sub\":\"user1\"}";
|
||
char token[2048];
|
||
|
||
TEST_ASSERT_EQUAL(0, generate_jwt_token(payload, secret, token, sizeof(token)));
|
||
|
||
/* 篡改 payload:在 header_b64 和 payload_b64 之间添加字符 */
|
||
char tampered[2048];
|
||
strncpy(tampered, token, sizeof(tampered) - 1);
|
||
tampered[sizeof(tampered) - 1] = '\0';
|
||
|
||
char *sig_dot = strrchr(tampered, '.');
|
||
TEST_ASSERT_NOT_NULL(sig_dot);
|
||
*sig_dot = '\0';
|
||
|
||
const char *signature_b64 = sig_dot + 1;
|
||
|
||
/* 篡改 header.payload 部分 */
|
||
strcat(tampered, ".extra");
|
||
|
||
bool valid = jwt_verify_signature(tampered, signature_b64, secret, strlen(secret));
|
||
TEST_ASSERT_FALSE(valid);
|
||
}
|
||
|
||
/* ============================================================
|
||
* JWT 中间件完整流程测试
|
||
* ============================================================ */
|
||
|
||
void test_jwt_middleware_success(void) {
|
||
int fds[2];
|
||
TEST_ASSERT_EQUAL(0, create_socket_pair(fds));
|
||
|
||
cocoon_jwt_config_t cfg = {
|
||
.secret = "test-secret",
|
||
.header_name = "Authorization",
|
||
.prefix = "Bearer ",
|
||
.skip_preflight = false,
|
||
};
|
||
|
||
/* 生成有效的 token */
|
||
char token[2048];
|
||
const char *payload = "{\"sub\":\"user1\",\"exp\":4102444800}"; /* 2099 年 */
|
||
TEST_ASSERT_EQUAL(0, generate_jwt_token(payload, "test-secret", token, sizeof(token)));
|
||
|
||
char auth_header[2300];
|
||
snprintf(auth_header, sizeof(auth_header), "Bearer %s", token);
|
||
|
||
http_request_t req;
|
||
make_request_with_auth(&req, auth_header);
|
||
|
||
int ret = cocoon_middleware_jwt(&req, fds[0], &cfg);
|
||
TEST_ASSERT_EQUAL(0, ret); /* 验证通过 */
|
||
|
||
close(fds[0]);
|
||
close(fds[1]);
|
||
}
|
||
|
||
void test_jwt_middleware_missing_header(void) {
|
||
int fds[2];
|
||
TEST_ASSERT_EQUAL(0, create_socket_pair(fds));
|
||
|
||
cocoon_jwt_config_t cfg = {
|
||
.secret = "test-secret",
|
||
.header_name = "Authorization",
|
||
.prefix = "Bearer ",
|
||
};
|
||
|
||
http_request_t req;
|
||
make_request_with_auth(&req, NULL); /* 无 Authorization 头 */
|
||
|
||
int ret = cocoon_middleware_jwt(&req, fds[0], &cfg);
|
||
TEST_ASSERT_EQUAL(1, ret); /* 短路 */
|
||
|
||
/* 读取响应 */
|
||
char response[1024];
|
||
ssize_t n = read_all(fds[1], response, sizeof(response));
|
||
TEST_ASSERT_GREATER_THAN(0, n);
|
||
TEST_ASSERT_NOT_NULL(strstr(response, "401"));
|
||
TEST_ASSERT_NOT_NULL(strstr(response, "Unauthorized"));
|
||
|
||
close(fds[0]);
|
||
close(fds[1]);
|
||
}
|
||
|
||
void test_jwt_middleware_wrong_prefix(void) {
|
||
int fds[2];
|
||
TEST_ASSERT_EQUAL(0, create_socket_pair(fds));
|
||
|
||
cocoon_jwt_config_t cfg = {
|
||
.secret = "test-secret",
|
||
.prefix = "Bearer ",
|
||
};
|
||
|
||
http_request_t req;
|
||
make_request_with_auth(&req, "Basic dXNlcjpwYXNz"); /* Basic 而非 Bearer */
|
||
|
||
int ret = cocoon_middleware_jwt(&req, fds[0], &cfg);
|
||
TEST_ASSERT_EQUAL(1, ret); /* 短路 */
|
||
|
||
close(fds[0]);
|
||
close(fds[1]);
|
||
}
|
||
|
||
void test_jwt_middleware_invalid_signature(void) {
|
||
int fds[2];
|
||
TEST_ASSERT_EQUAL(0, create_socket_pair(fds));
|
||
|
||
cocoon_jwt_config_t cfg = {
|
||
.secret = "test-secret",
|
||
.prefix = "Bearer ",
|
||
};
|
||
|
||
/* 使用错误签名的 token */
|
||
http_request_t req;
|
||
make_request_with_auth(&req, "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyMSJ9.invalidsignature");
|
||
|
||
int ret = cocoon_middleware_jwt(&req, fds[0], &cfg);
|
||
TEST_ASSERT_EQUAL(1, ret); /* 短路 */
|
||
|
||
close(fds[0]);
|
||
close(fds[1]);
|
||
}
|
||
|
||
void test_jwt_middleware_expired_token(void) {
|
||
int fds[2];
|
||
TEST_ASSERT_EQUAL(0, create_socket_pair(fds));
|
||
|
||
cocoon_jwt_config_t cfg = {
|
||
.secret = "test-secret",
|
||
.prefix = "Bearer ",
|
||
};
|
||
|
||
/* 生成已过期 token */
|
||
char token[2048];
|
||
const char *payload = "{\"sub\":\"user1\",\"exp\":1000000000}"; /* 2001 年,已过期 */
|
||
TEST_ASSERT_EQUAL(0, generate_jwt_token(payload, "test-secret", token, sizeof(token)));
|
||
|
||
char auth_header[2300];
|
||
snprintf(auth_header, sizeof(auth_header), "Bearer %s", token);
|
||
|
||
http_request_t req;
|
||
make_request_with_auth(&req, auth_header);
|
||
|
||
int ret = cocoon_middleware_jwt(&req, fds[0], &cfg);
|
||
TEST_ASSERT_EQUAL(1, ret); /* 过期,短路 */
|
||
|
||
/* 读取响应确认 401 */
|
||
char response[1024];
|
||
ssize_t n = read_all(fds[1], response, sizeof(response));
|
||
TEST_ASSERT_GREATER_THAN(0, n);
|
||
TEST_ASSERT_NOT_NULL(strstr(response, "401"));
|
||
|
||
close(fds[0]);
|
||
close(fds[1]);
|
||
}
|
||
|
||
void test_jwt_middleware_skip_options(void) {
|
||
int fds[2];
|
||
TEST_ASSERT_EQUAL(0, create_socket_pair(fds));
|
||
|
||
cocoon_jwt_config_t cfg = {
|
||
.secret = "test-secret",
|
||
.skip_preflight = true,
|
||
};
|
||
|
||
/* OPTIONS 请求应跳过验证 */
|
||
http_request_t req = {0};
|
||
req.method = HTTP_OPTIONS;
|
||
strcpy(req.path, "/api/test");
|
||
req.keep_alive = true;
|
||
|
||
int ret = cocoon_middleware_jwt(&req, fds[0], &cfg);
|
||
TEST_ASSERT_EQUAL(0, ret); /* 跳过 */
|
||
|
||
close(fds[0]);
|
||
close(fds[1]);
|
||
}
|
||
|
||
void test_jwt_middleware_no_config(void) {
|
||
int fds[2];
|
||
TEST_ASSERT_EQUAL(0, create_socket_pair(fds));
|
||
|
||
/* 空密钥,应跳过 */
|
||
cocoon_jwt_config_t cfg = {0};
|
||
|
||
http_request_t req;
|
||
make_request_with_auth(&req, NULL);
|
||
|
||
int ret = cocoon_middleware_jwt(&req, fds[0], &cfg);
|
||
TEST_ASSERT_EQUAL(0, ret); /* 跳过 */
|
||
|
||
close(fds[0]);
|
||
close(fds[1]);
|
||
}
|
||
|
||
void test_jwt_middleware_malformed_token(void) {
|
||
int fds[2];
|
||
TEST_ASSERT_EQUAL(0, create_socket_pair(fds));
|
||
|
||
cocoon_jwt_config_t cfg = {
|
||
.secret = "test-secret",
|
||
.prefix = "Bearer ",
|
||
};
|
||
|
||
/* 缺少分隔符的 token */
|
||
http_request_t req;
|
||
make_request_with_auth(&req, "Bearer malformedtoken");
|
||
|
||
int ret = cocoon_middleware_jwt(&req, fds[0], &cfg);
|
||
TEST_ASSERT_EQUAL(1, ret); /* 格式错误,短路 */
|
||
|
||
close(fds[0]);
|
||
close(fds[1]);
|
||
}
|
||
|
||
/* ============================================================
|
||
* Security Headers 中间件测试
|
||
* ============================================================ */
|
||
|
||
void test_security_headers_set_config(void) {
|
||
cocoon_security_headers_config_t cfg = {
|
||
.hsts_enabled = true,
|
||
.hsts_max_age = 31536000,
|
||
.hsts_include_subdomains = true,
|
||
.frame_options_enabled = true,
|
||
.frame_options = "DENY",
|
||
.xss_protection_enabled = true,
|
||
.csp_enabled = true,
|
||
.csp_policy = "default-src 'self'",
|
||
.content_type_options = true,
|
||
.referrer_policy_enabled = true,
|
||
.referrer_policy = "strict-origin-when-cross-origin",
|
||
};
|
||
|
||
http_request_t req = {0};
|
||
int ret = cocoon_middleware_security_headers(&req, -1, &cfg);
|
||
TEST_ASSERT_EQUAL(0, ret);
|
||
|
||
const cocoon_security_headers_config_t *got =
|
||
cocoon_middleware_security_headers_get();
|
||
TEST_ASSERT_NOT_NULL(got);
|
||
TEST_ASSERT_TRUE(got->hsts_enabled);
|
||
TEST_ASSERT_EQUAL(31536000, got->hsts_max_age);
|
||
TEST_ASSERT_TRUE(got->hsts_include_subdomains);
|
||
TEST_ASSERT_TRUE(got->frame_options_enabled);
|
||
TEST_ASSERT_EQUAL_STRING("DENY", got->frame_options);
|
||
TEST_ASSERT_TRUE(got->xss_protection_enabled);
|
||
TEST_ASSERT_TRUE(got->csp_enabled);
|
||
TEST_ASSERT_EQUAL_STRING("default-src 'self'", got->csp_policy);
|
||
TEST_ASSERT_TRUE(got->content_type_options);
|
||
TEST_ASSERT_TRUE(got->referrer_policy_enabled);
|
||
TEST_ASSERT_EQUAL_STRING("strict-origin-when-cross-origin", got->referrer_policy);
|
||
}
|
||
|
||
void test_security_headers_null_config(void) {
|
||
http_request_t req = {0};
|
||
int ret = cocoon_middleware_security_headers(&req, -1, NULL);
|
||
TEST_ASSERT_EQUAL(0, ret);
|
||
/* 配置应保持不变 */
|
||
}
|
||
|
||
void test_security_headers_get_not_initialized(void) {
|
||
/* 注意:如果之前测试已初始化,此处可能不为 NULL */
|
||
/* 测试中不做断言,仅确认不崩溃 */
|
||
(void)cocoon_middleware_security_headers_get();
|
||
}
|
||
|
||
void test_security_headers_sameorigin(void) {
|
||
cocoon_security_headers_config_t cfg = {
|
||
.frame_options_enabled = true,
|
||
.frame_options = "SAMEORIGIN",
|
||
};
|
||
|
||
http_request_t req = {0};
|
||
cocoon_middleware_security_headers(&req, -1, &cfg);
|
||
|
||
const cocoon_security_headers_config_t *got =
|
||
cocoon_middleware_security_headers_get();
|
||
TEST_ASSERT_NOT_NULL(got);
|
||
TEST_ASSERT_EQUAL_STRING("SAMEORIGIN", got->frame_options);
|
||
}
|
||
|
||
/* ============================================================
|
||
* Request ID 中间件测试
|
||
* ============================================================ */
|
||
|
||
void test_request_id_generates_32_chars(void) {
|
||
cocoon_request_id_config_t cfg = {
|
||
.header_name = "X-Request-ID",
|
||
.trust_incoming = true,
|
||
};
|
||
|
||
http_request_t req = {0};
|
||
req.method = HTTP_GET;
|
||
|
||
int ret = cocoon_middleware_request_id(&req, -1, &cfg);
|
||
TEST_ASSERT_EQUAL(0, ret);
|
||
/* 中间件内部生成了 32 字符 ID,
|
||
但当前版本未暴露给外部,仅确认不崩溃 */
|
||
}
|
||
|
||
void test_request_id_null_config(void) {
|
||
http_request_t req = {0};
|
||
int ret = cocoon_middleware_request_id(&req, -1, NULL);
|
||
TEST_ASSERT_EQUAL(0, ret);
|
||
}
|
||
|
||
void test_request_id_trust_incoming_valid(void) {
|
||
cocoon_request_id_config_t cfg = {
|
||
.header_name = "X-Request-ID",
|
||
.trust_incoming = true,
|
||
};
|
||
|
||
http_request_t req = {0};
|
||
strcpy(req.headers[0].name, "X-Request-ID");
|
||
strcpy(req.headers[0].value, "aabbccdd11223344556677889900aabb"); /* 32 字符 hex */
|
||
req.num_headers = 1;
|
||
|
||
int ret = cocoon_middleware_request_id(&req, -1, &cfg);
|
||
TEST_ASSERT_EQUAL(0, ret);
|
||
}
|
||
|
||
void test_request_id_trust_incoming_invalid_hex(void) {
|
||
cocoon_request_id_config_t cfg = {
|
||
.header_name = "X-Request-ID",
|
||
.trust_incoming = true,
|
||
};
|
||
|
||
/* 非 hex 字符 */
|
||
http_request_t req = {0};
|
||
strcpy(req.headers[0].name, "X-Request-ID");
|
||
strcpy(req.headers[0].value, "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz");
|
||
req.num_headers = 1;
|
||
|
||
int ret = cocoon_middleware_request_id(&req, -1, &cfg);
|
||
TEST_ASSERT_EQUAL(0, ret); /* 忽略无效值,生成新 ID */
|
||
}
|
||
|
||
void test_request_id_not_trust_incoming(void) {
|
||
cocoon_request_id_config_t cfg = {
|
||
.header_name = "X-Request-ID",
|
||
.trust_incoming = false,
|
||
};
|
||
|
||
http_request_t req = {0};
|
||
strcpy(req.headers[0].name, "X-Request-ID");
|
||
strcpy(req.headers[0].value, "aabbccdd11223344556677889900aabb");
|
||
req.num_headers = 1;
|
||
|
||
int ret = cocoon_middleware_request_id(&req, -1, &cfg);
|
||
TEST_ASSERT_EQUAL(0, ret); /* 不信任传入,生成新 ID */
|
||
}
|
||
|
||
/* ============================================================
|
||
* IP 过滤工具函数测试
|
||
* ============================================================ */
|
||
|
||
void test_parse_ipv4_valid(void) {
|
||
uint32_t addr;
|
||
TEST_ASSERT_TRUE(parse_ipv4("192.168.1.1", &addr));
|
||
TEST_ASSERT_EQUAL((uint32_t)0xC0A80101, addr); /* 192.168.1.1 */
|
||
|
||
TEST_ASSERT_TRUE(parse_ipv4("0.0.0.0", &addr));
|
||
TEST_ASSERT_EQUAL(0U, addr);
|
||
|
||
TEST_ASSERT_TRUE(parse_ipv4("255.255.255.255", &addr));
|
||
TEST_ASSERT_EQUAL(0xFFFFFFFFU, addr);
|
||
}
|
||
|
||
void test_parse_ipv4_invalid(void) {
|
||
uint32_t addr;
|
||
TEST_ASSERT_FALSE(parse_ipv4("invalid", &addr));
|
||
TEST_ASSERT_FALSE(parse_ipv4("", &addr));
|
||
TEST_ASSERT_FALSE(parse_ipv4("256.1.1.1", &addr)); /* 超出范围 */
|
||
TEST_ASSERT_FALSE(parse_ipv4("1.1.1", &addr)); /* 不足 4 段 */
|
||
}
|
||
|
||
void test_parse_cidr_exact(void) {
|
||
uint32_t addr;
|
||
int mask;
|
||
TEST_ASSERT_TRUE(parse_cidr("192.168.1.1", &addr, &mask));
|
||
TEST_ASSERT_EQUAL(32, mask);
|
||
TEST_ASSERT_EQUAL((uint32_t)0xC0A80101, addr);
|
||
}
|
||
|
||
void test_parse_cidr_with_mask(void) {
|
||
uint32_t addr;
|
||
int mask;
|
||
TEST_ASSERT_TRUE(parse_cidr("192.168.1.0/24", &addr, &mask));
|
||
TEST_ASSERT_EQUAL(24, mask);
|
||
/* 网络地址应为 192.168.1.0 */
|
||
TEST_ASSERT_EQUAL((uint32_t)0xC0A80100, addr);
|
||
}
|
||
|
||
void test_parse_cidr_16(void) {
|
||
uint32_t addr;
|
||
int mask;
|
||
TEST_ASSERT_TRUE(parse_cidr("10.0.0.0/16", &addr, &mask));
|
||
TEST_ASSERT_EQUAL(16, mask);
|
||
TEST_ASSERT_EQUAL((uint32_t)0x0A000000, addr);
|
||
}
|
||
|
||
void test_parse_cidr_8(void) {
|
||
uint32_t addr;
|
||
int mask;
|
||
TEST_ASSERT_TRUE(parse_cidr("172.0.0.0/8", &addr, &mask));
|
||
TEST_ASSERT_EQUAL(8, mask);
|
||
TEST_ASSERT_EQUAL((uint32_t)0xAC000000, addr);
|
||
}
|
||
|
||
void test_parse_cidr_invalid(void) {
|
||
uint32_t addr;
|
||
int mask;
|
||
TEST_ASSERT_FALSE(parse_cidr("invalid", &addr, &mask));
|
||
TEST_ASSERT_FALSE(parse_cidr("192.168.1.0/33", &addr, &mask)); /* mask > 32 */
|
||
TEST_ASSERT_FALSE(parse_cidr("", &addr, &mask));
|
||
}
|
||
|
||
void test_ip_match_cidr_exact(void) {
|
||
uint32_t ip;
|
||
parse_ipv4("192.168.1.1", &ip);
|
||
TEST_ASSERT_TRUE(ip_match_cidr(ip, "192.168.1.1"));
|
||
TEST_ASSERT_FALSE(ip_match_cidr(ip, "192.168.1.2"));
|
||
}
|
||
|
||
void test_ip_match_cidr_24(void) {
|
||
uint32_t ip;
|
||
parse_ipv4("192.168.1.100", &ip);
|
||
TEST_ASSERT_TRUE(ip_match_cidr(ip, "192.168.1.0/24"));
|
||
TEST_ASSERT_TRUE(ip_match_cidr(ip, "192.168.1.0/16"));
|
||
TEST_ASSERT_FALSE(ip_match_cidr(ip, "10.0.0.0/24"));
|
||
}
|
||
|
||
void test_ip_match_cidr_16(void) {
|
||
uint32_t ip;
|
||
parse_ipv4("10.0.50.100", &ip);
|
||
TEST_ASSERT_TRUE(ip_match_cidr(ip, "10.0.0.0/16"));
|
||
TEST_ASSERT_TRUE(ip_match_cidr(ip, "10.0.0.0/8"));
|
||
TEST_ASSERT_FALSE(ip_match_cidr(ip, "10.1.0.0/16"));
|
||
}
|
||
|
||
void test_ip_match_cidr_edge_cases(void) {
|
||
uint32_t ip;
|
||
parse_ipv4("0.0.0.0", &ip);
|
||
TEST_ASSERT_TRUE(ip_match_cidr(ip, "0.0.0.0/0"));
|
||
|
||
parse_ipv4("255.255.255.255", &ip);
|
||
TEST_ASSERT_TRUE(ip_match_cidr(ip, "255.255.255.255"));
|
||
TEST_ASSERT_TRUE(ip_match_cidr(ip, "0.0.0.0/0"));
|
||
}
|
||
|
||
void test_parse_x_forwarded_for_single(void) {
|
||
uint32_t addr;
|
||
TEST_ASSERT_TRUE(parse_x_forwarded_for("192.168.1.100", &addr));
|
||
TEST_ASSERT_EQUAL((uint32_t)0xC0A80164, addr);
|
||
}
|
||
|
||
void test_parse_x_forwarded_for_chain(void) {
|
||
uint32_t addr;
|
||
TEST_ASSERT_TRUE(parse_x_forwarded_for("192.168.1.100, 10.0.0.1, 172.16.0.1", &addr));
|
||
TEST_ASSERT_EQUAL((uint32_t)0xC0A80164, addr); /* 取第一个 */
|
||
}
|
||
|
||
void test_parse_x_forwarded_for_with_spaces(void) {
|
||
uint32_t addr;
|
||
TEST_ASSERT_TRUE(parse_x_forwarded_for(" 192.168.1.50 ", &addr));
|
||
TEST_ASSERT_EQUAL((uint32_t)0xC0A80132, addr);
|
||
}
|
||
|
||
void test_parse_x_forwarded_for_invalid(void) {
|
||
uint32_t addr;
|
||
TEST_ASSERT_FALSE(parse_x_forwarded_for("invalid-ip", &addr));
|
||
TEST_ASSERT_FALSE(parse_x_forwarded_for("", &addr));
|
||
TEST_ASSERT_FALSE(parse_x_forwarded_for(NULL, &addr));
|
||
}
|
||
|
||
/* ============================================================
|
||
* IP 过滤中间件完整流程测试
|
||
* ============================================================ */
|
||
|
||
void test_ip_filter_no_config(void) {
|
||
int fds[2];
|
||
TEST_ASSERT_EQUAL(0, create_socket_pair(fds));
|
||
|
||
/* 空配置应跳过 */
|
||
cocoon_ip_filter_config_t cfg = {0};
|
||
|
||
http_request_t req = {0};
|
||
int ret = cocoon_middleware_ip_filter(&req, fds[0], &cfg);
|
||
TEST_ASSERT_EQUAL(0, ret); /* 跳过 */
|
||
|
||
close(fds[0]);
|
||
close(fds[1]);
|
||
}
|
||
|
||
void test_ip_filter_blacklist_allow(void) {
|
||
int fds[2];
|
||
TEST_ASSERT_EQUAL(0, create_socket_pair(fds));
|
||
|
||
/* 黑名单模式,不包含 127.0.0.1,应允许 */
|
||
cocoon_ip_filter_config_t cfg = {
|
||
.count = 1,
|
||
.mode = COCOON_IP_FILTER_DENY,
|
||
};
|
||
strcpy(cfg.entries[0], "192.168.1.0/24");
|
||
|
||
http_request_t req = {0};
|
||
int ret = cocoon_middleware_ip_filter(&req, fds[0], &cfg);
|
||
TEST_ASSERT_EQUAL(0, ret); /* 允许(127.0.0.1 不在黑名单中) */
|
||
|
||
close(fds[0]);
|
||
close(fds[1]);
|
||
}
|
||
|
||
/* ============================================================
|
||
* 一键初始化测试
|
||
* ============================================================ */
|
||
|
||
void test_middleware_init_extended(void) {
|
||
/* 确认不崩溃 */
|
||
cocoon_middleware_init_extended(NULL);
|
||
cocoon_middleware_init_extended((void *)0x1234); /* 无效指针,但不应崩溃 */
|
||
}
|
||
|
||
/* ============================================================
|
||
* 主函数
|
||
* ============================================================ */
|
||
|
||
int main(void) {
|
||
UNITY_BEGIN();
|
||
|
||
/* Base64Url 编解码 (7) */
|
||
RUN_TEST(test_base64url_decode_basic);
|
||
RUN_TEST(test_base64url_decode_with_special_chars);
|
||
RUN_TEST(test_base64url_decode_empty);
|
||
RUN_TEST(test_base64url_decode_null_params);
|
||
RUN_TEST(test_base64url_decode_binary);
|
||
RUN_TEST(test_base64url_encode_decode_roundtrip);
|
||
RUN_TEST(test_base64url_encode_no_padding);
|
||
|
||
/* find_header (4) */
|
||
RUN_TEST(test_find_header_exists);
|
||
RUN_TEST(test_find_header_case_insensitive);
|
||
RUN_TEST(test_find_header_not_found);
|
||
RUN_TEST(test_find_header_multiple_headers);
|
||
|
||
/* jwt_parse_exp (4) */
|
||
RUN_TEST(test_jwt_parse_exp_present);
|
||
RUN_TEST(test_jwt_parse_exp_not_present);
|
||
RUN_TEST(test_jwt_parse_exp_first_field);
|
||
RUN_TEST(test_jwt_parse_exp_null);
|
||
|
||
/* JWT 签名验证 (3) */
|
||
RUN_TEST(test_jwt_verify_signature_valid);
|
||
RUN_TEST(test_jwt_verify_signature_invalid_secret);
|
||
RUN_TEST(test_jwt_verify_signature_tampered_payload);
|
||
|
||
/* JWT 中间件完整流程 (8) */
|
||
RUN_TEST(test_jwt_middleware_success);
|
||
RUN_TEST(test_jwt_middleware_missing_header);
|
||
RUN_TEST(test_jwt_middleware_wrong_prefix);
|
||
RUN_TEST(test_jwt_middleware_invalid_signature);
|
||
RUN_TEST(test_jwt_middleware_expired_token);
|
||
RUN_TEST(test_jwt_middleware_skip_options);
|
||
RUN_TEST(test_jwt_middleware_no_config);
|
||
RUN_TEST(test_jwt_middleware_malformed_token);
|
||
|
||
/* Security Headers (4) */
|
||
RUN_TEST(test_security_headers_set_config);
|
||
RUN_TEST(test_security_headers_null_config);
|
||
RUN_TEST(test_security_headers_get_not_initialized);
|
||
RUN_TEST(test_security_headers_sameorigin);
|
||
|
||
/* Request ID (5) */
|
||
RUN_TEST(test_request_id_generates_32_chars);
|
||
RUN_TEST(test_request_id_null_config);
|
||
RUN_TEST(test_request_id_trust_incoming_valid);
|
||
RUN_TEST(test_request_id_trust_incoming_invalid_hex);
|
||
RUN_TEST(test_request_id_not_trust_incoming);
|
||
|
||
/* IP 工具函数 (15) */
|
||
RUN_TEST(test_parse_ipv4_valid);
|
||
RUN_TEST(test_parse_ipv4_invalid);
|
||
RUN_TEST(test_parse_cidr_exact);
|
||
RUN_TEST(test_parse_cidr_with_mask);
|
||
RUN_TEST(test_parse_cidr_16);
|
||
RUN_TEST(test_parse_cidr_8);
|
||
RUN_TEST(test_parse_cidr_invalid);
|
||
RUN_TEST(test_ip_match_cidr_exact);
|
||
RUN_TEST(test_ip_match_cidr_24);
|
||
RUN_TEST(test_ip_match_cidr_16);
|
||
RUN_TEST(test_ip_match_cidr_edge_cases);
|
||
RUN_TEST(test_parse_x_forwarded_for_single);
|
||
RUN_TEST(test_parse_x_forwarded_for_chain);
|
||
RUN_TEST(test_parse_x_forwarded_for_with_spaces);
|
||
RUN_TEST(test_parse_x_forwarded_for_invalid);
|
||
|
||
/* IP 过滤中间件 (2) */
|
||
RUN_TEST(test_ip_filter_no_config);
|
||
RUN_TEST(test_ip_filter_blacklist_allow);
|
||
|
||
/* 一键初始化 (1) */
|
||
RUN_TEST(test_middleware_init_extended);
|
||
|
||
return UNITY_END();
|
||
}
|