Nilorea Library
C utilities for networking, threading, graphics
Loading...
Searching...
No Matches
ex_network_ws.c

WebSocket client example using Nilorea network module.

WebSocket client example using Nilorea network module

Author
Castagnier Mickael
Version
1.0
Date
26/03/2026
/*
* Nilorea Library
* Copyright (C) 2005-2026 Castagnier Mickael
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
* implied. See the License for the specific language governing
* permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "nilorea/n_str.h"
#include "nilorea/n_log.h"
#include <getopt.h>
#include <string.h>
void usage(void) {
fprintf(stderr,
" -V 'log level' : set the log level (LOG_NULL, LOG_NOTICE, LOG_INFO, LOG_ERR, LOG_DEBUG)\n"
" -h : help\n");
}
/* Stateless frame-codec regression (no network): build then parse, both masked
and not, with extended lengths, partial buffers, and a back-to-back pair. */
static int test_frame_codec(void) {
int fails = 0;
unsigned char out[512];
unsigned char mask[4] = {0x12, 0x34, 0x56, 0x78};
size_t consumed = 0;
/* unmasked text frame round-trips */
{
size_t w = n_ws_frame_build(1, N_WS_OP_TEXT, 0, (const unsigned char*)"hello", 5, NULL, out, sizeof(out));
if (w != 7) fails++; /* 2 header + 5 payload */
if (n_ws_frame_parse(out, w, &f, &consumed) != 1) fails++;
if (f.fin != 1 || f.opcode != N_WS_OP_TEXT || f.masked != 0 || f.payload_len != 5) fails++;
if (consumed != 7 || memcmp(f.payload, "hello", 5) != 0) fails++;
}
/* masked frame: parse then unmask recovers the plaintext */
{
unsigned char plain[8];
size_t w = n_ws_frame_build(1, N_WS_OP_BINARY, 1, (const unsigned char*)"abcd", 4, mask, out, sizeof(out));
if (w != 10) fails++; /* 2 header + 4 mask + 4 payload */
if (n_ws_frame_parse(out, w, &f, &consumed) != 1) fails++;
if (!f.masked || f.opcode != N_WS_OP_BINARY || f.payload_len != 4) fails++;
n_ws_unmask(plain, f.payload, (size_t)f.payload_len, f.mask);
if (memcmp(plain, "abcd", 4) != 0) fails++;
}
/* extended 16-bit length path (>125 bytes) */
{
unsigned char big[200];
size_t i, w;
for (i = 0; i < sizeof(big); i++) big[i] = (unsigned char)(i & 0xFF);
w = n_ws_frame_build(1, N_WS_OP_BINARY, 0, big, sizeof(big), NULL, out, sizeof(out));
if (w != 4 + sizeof(big)) fails++; /* 2 header + 2 ext len */
if (n_ws_frame_parse(out, w, &f, &consumed) != 1) fails++;
if (f.payload_len != sizeof(big) || memcmp(f.payload, big, sizeof(big)) != 0) fails++;
}
/* a truncated buffer needs more data (returns 0) */
{
size_t w = n_ws_frame_build(1, N_WS_OP_TEXT, 0, (const unsigned char*)"hello", 5, NULL, out, sizeof(out));
if (n_ws_frame_parse(out, w - 2, &f, &consumed) != 0) fails++;
if (n_ws_frame_parse(out, 1, &f, &consumed) != 0) fails++;
}
/* two frames back-to-back: parse the first, advance by consumed, parse the second */
{
size_t w1 = n_ws_frame_build(1, N_WS_OP_TEXT, 0, (const unsigned char*)"one", 3, NULL, out, sizeof(out));
size_t w2 = n_ws_frame_build(1, N_WS_OP_PING, 0, (const unsigned char*)"pp", 2, NULL, out + w1, sizeof(out) - w1);
size_t total = w1 + w2;
if (n_ws_frame_parse(out, total, &f, &consumed) != 1 || consumed != w1) fails++;
if (memcmp(f.payload, "one", 3) != 0) fails++;
if (n_ws_frame_parse(out + consumed, total - consumed, &f, &consumed) != 1) fails++;
if (f.opcode != N_WS_OP_PING || memcmp(f.payload, "pp", 2) != 0) fails++;
}
if (fails)
n_log(LOG_ERR, "ws frame codec: %d failure(s)", fails);
else
n_log(LOG_NOTICE, "ws frame codec: all checks passed");
return fails;
}
int main(int argc, char* argv[]) {
int getoptret = 0;
while ((getoptret = getopt(argc, argv, "hV:")) != EOF) {
switch (getoptret) {
case 'V':
if (!strncmp("LOG_NULL", optarg, 8)) {
} else if (!strncmp("LOG_NOTICE", optarg, 10)) {
} else if (!strncmp("LOG_INFO", optarg, 8)) {
} else if (!strncmp("LOG_ERR", optarg, 7)) {
} else if (!strncmp("LOG_DEBUG", optarg, 9)) {
} else {
fprintf(stderr, "%s is not a valid log level.\n", optarg);
exit(1);
}
break;
case 'h':
default:
usage();
exit(1);
}
}
/* the stateless frame codec runs without a network and must always pass */
if (test_frame_codec() != 0)
exit(1);
#ifdef HAVE_OPENSSL
n_log(LOG_INFO, "Connecting to wss://echo.websocket.org:443/ ...");
N_WS_CONN* ws = n_ws_connect("echo.websocket.org", "443", "/", 1);
if (!ws) {
fprintf(stdout, "WebSocket echo server unreachable, skipping\n");
exit(0);
}
/* read the welcome/greeting message the server sends on connect */
N_WS_MESSAGE greeting;
memset(&greeting, 0, sizeof(greeting));
if (n_ws_recv(ws, &greeting) == 0) {
n_log(LOG_INFO, "Server greeting (opcode %d): %s", greeting.opcode, greeting.payload ? greeting.payload->data : "(empty)");
free_nstr(&greeting.payload);
}
const char* test_msg = "nilorea ws test";
n_log(LOG_INFO, "Sending: %s", test_msg);
if (n_ws_send(ws, test_msg, strlen(test_msg), N_WS_OP_TEXT) != 0) {
n_log(LOG_ERR, "Failed to send WebSocket message");
exit(1);
}
memset(&msg, 0, sizeof(msg));
if (n_ws_recv(ws, &msg) == 0) {
n_log(LOG_INFO, "Received (opcode %d): %s", msg.opcode, msg.payload ? msg.payload->data : "(empty)");
if (msg.payload && strcmp(msg.payload->data, test_msg) == 0) {
fprintf(stdout, "WebSocket echo test PASSED\n");
} else {
fprintf(stdout, "WebSocket echo test: got response (opcode %d)\n", msg.opcode);
}
} else {
n_log(LOG_ERR, "Failed to receive WebSocket message");
exit(1);
}
n_log(LOG_INFO, "WebSocket example completed successfully");
#else
(void)log_level;
fprintf(stdout, "OpenSSL not available, skipping WebSocket test\n");
#endif
exit(0);
}
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.