Nilorea Library
C utilities for networking, threading, graphics
Loading...
Searching...
No Matches
ex_network_ws.c
Go to the documentation of this file.
1/*
2 * Nilorea Library
3 * Copyright (C) 2005-2026 Castagnier Mickael
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
14 * implied. See the License for the specific language governing
15 * permissions and limitations under the License.
16 *
17 * SPDX-License-Identifier: Apache-2.0
18 */
19
28#include "nilorea/n_str.h"
29#include "nilorea/n_log.h"
30#include "nilorea/n_network.h"
31
32#include <getopt.h>
33#include <string.h>
34
35void usage(void) {
36 fprintf(stderr,
37 " -V 'log level' : set the log level (LOG_NULL, LOG_NOTICE, LOG_INFO, LOG_ERR, LOG_DEBUG)\n"
38 " -h : help\n");
39}
40
41/* Stateless frame-codec regression (no network): build then parse, both masked
42 and not, with extended lengths, partial buffers, and a back-to-back pair. */
43static int test_frame_codec(void) {
44 int fails = 0;
45 unsigned char out[512];
46 unsigned char mask[4] = {0x12, 0x34, 0x56, 0x78};
47 N_WS_FRAME f;
48 size_t consumed = 0;
49
50 /* unmasked text frame round-trips */
51 {
52 size_t w = n_ws_frame_build(1, N_WS_OP_TEXT, 0, (const unsigned char*)"hello", 5, NULL, out, sizeof(out));
53 if (w != 7) fails++; /* 2 header + 5 payload */
54 if (n_ws_frame_parse(out, w, &f, &consumed) != 1) fails++;
55 if (f.fin != 1 || f.opcode != N_WS_OP_TEXT || f.masked != 0 || f.payload_len != 5) fails++;
56 if (consumed != 7 || memcmp(f.payload, "hello", 5) != 0) fails++;
57 }
58 /* masked frame: parse then unmask recovers the plaintext */
59 {
60 unsigned char plain[8];
61 size_t w = n_ws_frame_build(1, N_WS_OP_BINARY, 1, (const unsigned char*)"abcd", 4, mask, out, sizeof(out));
62 if (w != 10) fails++; /* 2 header + 4 mask + 4 payload */
63 if (n_ws_frame_parse(out, w, &f, &consumed) != 1) fails++;
64 if (!f.masked || f.opcode != N_WS_OP_BINARY || f.payload_len != 4) fails++;
65 n_ws_unmask(plain, f.payload, (size_t)f.payload_len, f.mask);
66 if (memcmp(plain, "abcd", 4) != 0) fails++;
67 }
68 /* extended 16-bit length path (>125 bytes) */
69 {
70 unsigned char big[200];
71 size_t i, w;
72 for (i = 0; i < sizeof(big); i++) big[i] = (unsigned char)(i & 0xFF);
73 w = n_ws_frame_build(1, N_WS_OP_BINARY, 0, big, sizeof(big), NULL, out, sizeof(out));
74 if (w != 4 + sizeof(big)) fails++; /* 2 header + 2 ext len */
75 if (n_ws_frame_parse(out, w, &f, &consumed) != 1) fails++;
76 if (f.payload_len != sizeof(big) || memcmp(f.payload, big, sizeof(big)) != 0) fails++;
77 }
78 /* a truncated buffer needs more data (returns 0) */
79 {
80 size_t w = n_ws_frame_build(1, N_WS_OP_TEXT, 0, (const unsigned char*)"hello", 5, NULL, out, sizeof(out));
81 if (n_ws_frame_parse(out, w - 2, &f, &consumed) != 0) fails++;
82 if (n_ws_frame_parse(out, 1, &f, &consumed) != 0) fails++;
83 }
84 /* two frames back-to-back: parse the first, advance by consumed, parse the second */
85 {
86 size_t w1 = n_ws_frame_build(1, N_WS_OP_TEXT, 0, (const unsigned char*)"one", 3, NULL, out, sizeof(out));
87 size_t w2 = n_ws_frame_build(1, N_WS_OP_PING, 0, (const unsigned char*)"pp", 2, NULL, out + w1, sizeof(out) - w1);
88 size_t total = w1 + w2;
89 if (n_ws_frame_parse(out, total, &f, &consumed) != 1 || consumed != w1) fails++;
90 if (memcmp(f.payload, "one", 3) != 0) fails++;
91 if (n_ws_frame_parse(out + consumed, total - consumed, &f, &consumed) != 1) fails++;
92 if (f.opcode != N_WS_OP_PING || memcmp(f.payload, "pp", 2) != 0) fails++;
93 }
94
95 if (fails)
96 n_log(LOG_ERR, "ws frame codec: %d failure(s)", fails);
97 else
98 n_log(LOG_NOTICE, "ws frame codec: all checks passed");
99 return fails;
100}
101
102int main(int argc, char* argv[]) {
103 int log_level = LOG_ERR;
104 int getoptret = 0;
105
106 while ((getoptret = getopt(argc, argv, "hV:")) != EOF) {
107 switch (getoptret) {
108 case 'V':
109 if (!strncmp("LOG_NULL", optarg, 8)) {
111 } else if (!strncmp("LOG_NOTICE", optarg, 10)) {
113 } else if (!strncmp("LOG_INFO", optarg, 8)) {
115 } else if (!strncmp("LOG_ERR", optarg, 7)) {
117 } else if (!strncmp("LOG_DEBUG", optarg, 9)) {
119 } else {
120 fprintf(stderr, "%s is not a valid log level.\n", optarg);
121 exit(1);
122 }
123 break;
124 case 'h':
125 default:
126 usage();
127 exit(1);
128 }
129 }
131
132 /* the stateless frame codec runs without a network and must always pass */
133 if (test_frame_codec() != 0)
134 exit(1);
135
136#ifdef HAVE_OPENSSL
137 n_log(LOG_INFO, "Connecting to wss://echo.websocket.org:443/ ...");
138
139 N_WS_CONN* ws = n_ws_connect("echo.websocket.org", "443", "/", 1);
140 if (!ws) {
141 fprintf(stdout, "WebSocket echo server unreachable, skipping\n");
142 exit(0);
143 }
144
145 /* read the welcome/greeting message the server sends on connect */
146 N_WS_MESSAGE greeting;
147 memset(&greeting, 0, sizeof(greeting));
148 if (n_ws_recv(ws, &greeting) == 0) {
149 n_log(LOG_INFO, "Server greeting (opcode %d): %s", greeting.opcode, greeting.payload ? greeting.payload->data : "(empty)");
150 free_nstr(&greeting.payload);
151 }
152
153 const char* test_msg = "nilorea ws test";
154 n_log(LOG_INFO, "Sending: %s", test_msg);
155 if (n_ws_send(ws, test_msg, strlen(test_msg), N_WS_OP_TEXT) != 0) {
156 n_log(LOG_ERR, "Failed to send WebSocket message");
157 n_ws_conn_free(&ws);
158 exit(1);
159 }
160
161 N_WS_MESSAGE msg;
162 memset(&msg, 0, sizeof(msg));
163 if (n_ws_recv(ws, &msg) == 0) {
164 n_log(LOG_INFO, "Received (opcode %d): %s", msg.opcode, msg.payload ? msg.payload->data : "(empty)");
165 if (msg.payload && strcmp(msg.payload->data, test_msg) == 0) {
166 fprintf(stdout, "WebSocket echo test PASSED\n");
167 } else {
168 fprintf(stdout, "WebSocket echo test: got response (opcode %d)\n", msg.opcode);
169 }
170 free_nstr(&msg.payload);
171 } else {
172 n_log(LOG_ERR, "Failed to receive WebSocket message");
173 n_ws_conn_free(&ws);
174 exit(1);
175 }
176
177 n_ws_conn_free(&ws);
178 n_log(LOG_INFO, "WebSocket example completed successfully");
179#else
180 (void)log_level;
181 fprintf(stdout, "OpenSSL not available, skipping WebSocket test\n");
182#endif
183
184 exit(0);
185}
static void usage(void)
int main(void)
int getoptret
Definition ex_fluid.c:59
int log_level
Definition ex_fluid.c:60
static int test_frame_codec(void)
#define n_log(__LEVEL__,...)
Logging function wrapper to get line and func.
Definition n_log.h:89
#define LOG_DEBUG
debug-level messages
Definition n_log.h:84
#define LOG_ERR
error conditions
Definition n_log.h:76
void set_log_level(const int log_level)
Set the global log level value ( static int LOG_LEVEL )
Definition n_log.c:121
#define LOG_NOTICE
normal but significant condition
Definition n_log.h:80
#define LOG_NULL
no log output
Definition n_log.h:46
#define LOG_INFO
informational
Definition n_log.h:82
char * data
the string
Definition n_str.h:63
#define free_nstr(__ptr)
free a N_STR structure and set the pointer to NULL
Definition n_str.h:203
int masked
MASK bit (client-to-server frames are masked)
Definition n_network.h:966
int opcode
opcode (N_WS_OP_*)
Definition n_network.h:965
int fin
FIN bit (1 = final fragment)
Definition n_network.h:963
uint64_t payload_len
payload length in bytes
Definition n_network.h:968
const unsigned char * payload
pointer into the input buffer (still masked)
Definition n_network.h:969
N_STR * payload
message payload
Definition n_network.h:948
int opcode
frame opcode
Definition n_network.h:947
unsigned char mask[4]
masking key when masked, else zeroed
Definition n_network.h:967
#define N_WS_OP_PING
Definition n_network.h:942
void n_ws_unmask(unsigned char *dst, const unsigned char *src, size_t len, const unsigned char mask[4])
XOR-unmask len bytes of src into dst using a 4-byte WebSocket mask key.
Definition n_network.c:6952
int n_ws_frame_parse(const unsigned char *buf, size_t len, N_WS_FRAME *frame, size_t *consumed)
Parse one WebSocket frame from a byte buffer (stateless, no allocation).
Definition n_network.c:6905
N_WS_CONN * n_ws_connect(const char *host, const char *port, const char *path, int use_ssl)
Connect to a WebSocket server (ws:// or wss://).
Definition n_network.c:6208
#define N_WS_OP_TEXT
WebSocket opcodes per RFC 6455.
Definition n_network.h:939
int n_ws_recv(N_WS_CONN *conn, N_WS_MESSAGE *msg_out)
Receive one WebSocket frame.
Definition n_network.c:6479
size_t n_ws_frame_build(int fin, int opcode, int do_mask, const unsigned char *payload, size_t len, const unsigned char mask_key[4], unsigned char *out, size_t out_cap)
build a WebSocket frame into out (optionally masking the payload).
Definition n_network.c:6960
void n_ws_conn_free(N_WS_CONN **conn)
Free a WebSocket connection structure.
Definition n_network.c:6581
int n_ws_send(N_WS_CONN *conn, const char *payload, size_t len, int opcode)
Send a WebSocket frame (client always masks).
Definition n_network.c:6412
#define N_WS_OP_BINARY
Definition n_network.h:940
WebSocket connection.
Definition n_network.h:953
A single parsed WebSocket frame (RFC 6455).
Definition n_network.h:962
WebSocket message.
Definition n_network.h:946
Generic log system.
Network Engine.
N_STR and string function declaration.