Skip to content

Instantly share code, notes, and snippets.

@aneury1
Created February 20, 2025 21:05
Show Gist options
  • Save aneury1/5e0feea132145894cd5dc58dfa1a2a1d to your computer and use it in GitHub Desktop.
Save aneury1/5e0feea132145894cd5dc58dfa1a2a1d to your computer and use it in GitHub Desktop.
#if 0
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <openssl/sha.h> // For SHA-1 (link with -lcrypto)
#include <openssl/bio.h> // For Base64 encoding
#include <openssl/evp.h>
#include <openssl/buffer.h>
#define PORT 8080
#define BUFFER_SIZE 1024
#define MAGIC_STRING "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
// Compute Sec-WebSocket-Accept header value (unchanged from previous example)
char* compute_accept_key(const char* client_key) {
char combined[100];
snprintf(combined, sizeof(combined), "%s%s", client_key, MAGIC_STRING);
unsigned char sha1[SHA_DIGEST_LENGTH];
SHA1((unsigned char*)combined, strlen(combined), sha1);
BIO* b64 = BIO_new(BIO_f_base64());
BIO* mem = BIO_new(BIO_s_mem());
b64 = BIO_push(b64, mem);
BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
BIO_write(b64, sha1, SHA_DIGEST_LENGTH);
BIO_flush(b64);
BUF_MEM* buffer;
BIO_get_mem_ptr(b64, &buffer);
char* accept_key = (char *)malloc(buffer->length + 1);
memcpy(accept_key, buffer->data, buffer->length);
accept_key[buffer->length] = '\0';
BIO_free_all(b64);
return accept_key;
}
// Send HTTP handshake response (unchanged)
void send_handshake(int client_fd, const char* client_key) {
char* accept_key = compute_accept_key(client_key);
char response[BUFFER_SIZE];
snprintf(response, sizeof(response),
"HTTP/1.1 101 Switching Protocols\r\n"
"Upgrade: websocket\r\n"
"Connection: Upgrade\r\n"
"Sec-WebSocket-Accept: %s\r\n\r\n",
accept_key);
write(client_fd, response, strlen(response));
free(accept_key);
}
// Extract Sec-WebSocket-Key from HTTP request (unchanged)
char* extract_key(const char* request) {
const char* key_start = strstr(request, "Sec-WebSocket-Key: ");
if (!key_start) return NULL;
key_start += 19;
char* key_end = (char *)strstr(key_start, "\r\n");
if (!key_end) return NULL;
int key_len = key_end - key_start;
char* key = (char *)malloc(key_len + 1);
strncpy(key, key_start, key_len);
key[key_len] = '\0';
return key;
}
// Decode a masked payload
void decode_payload(char* payload, unsigned char* mask_key, int payload_len) {
for (int i = 0; i < payload_len; i++) {
payload[i] = payload[i] ^ mask_key[i % 4];
}
}
int main() {
int server_fd, client_fd;
struct sockaddr_in address;
int addrlen = sizeof(address);
char buffer[BUFFER_SIZE] = {0};
// Socket setup (unchanged)
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
perror("Socket failed");
exit(EXIT_FAILURE);
}
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(PORT);
if (bind(server_fd, (struct sockaddr*)&address, sizeof(address)) < 0) {
perror("Bind failed");
exit(EXIT_FAILURE);
}
if (listen(server_fd, 3) < 0) {
perror("Listen failed");
exit(EXIT_FAILURE);
}
printf("Server listening on port %d\n", PORT);
// Accept client
if ((client_fd = accept(server_fd, (struct sockaddr*)&address, (socklen_t*)&addrlen)) < 0) {
perror("Accept failed");
exit(EXIT_FAILURE);
}
// Handle handshake
read(client_fd, buffer, BUFFER_SIZE);
char* client_key = extract_key(buffer);
if (client_key) {
send_handshake(client_fd, client_key);
free(client_key);
} else {
printf("Invalid WebSocket request\n");
close(client_fd);
close(server_fd);
return 1;
}
// WebSocket frame handling loop
while (1) {
memset(buffer, 0, BUFFER_SIZE);
int bytes_read = read(client_fd, buffer, BUFFER_SIZE);
if (bytes_read <= 0) break;
// Parse frame
unsigned char first_byte = buffer[0];
unsigned char second_byte = buffer[1];
int fin = (first_byte & 0x80) >> 7; // FIN bit
int opcode = first_byte & 0x0F; // Opcode (e.g., 0x1 for text)
int mask = (second_byte & 0x80) >> 7; // Mask bit
int payload_len = second_byte & 0x7F; // Payload length (basic case)
if (opcode == 0x1 && fin == 1) { // Text frame, FIN set
char* payload;
unsigned char* mask_key;
if (mask == 1) {
// Masked frame: mask key follows second byte
mask_key = (unsigned char*)buffer + 2;
payload = buffer + 6; // Payload starts after mask key
decode_payload(payload, mask_key, payload_len);
} else {
// Unmasked frame (shouldn’t happen from client, but handle for robustness)
payload = buffer + 2;
}
printf("Received: %.*s\n", payload_len, payload);
// Echo back (unmasked, FIN=1, opcode=0x1)
char frame[BUFFER_SIZE] = {(char)0x81, (char)payload_len};
memcpy(frame + 2, payload, payload_len);
write(client_fd, frame, 2 + payload_len);
} else {
printf("Unsupported or fragmented frame\n");
}
}
close(client_fd);
close(server_fd);
return 0;
}
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
#include <openssl/sha.h>
#include <openssl/bio.h>
#include <openssl/evp.h>
#include <openssl/buffer.h>
#define PORT 8080
#define BUFFER_SIZE 1024
#define MAGIC_STRING "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
#define MAX_EVENTS 10
#define MAX_CLIENTS 100
// Client state
typedef struct {
int fd; // Client socket FD
char buffer[BUFFER_SIZE]; // Incoming data buffer
int buf_len; // Bytes in buffer
int handshaked; // 0 = not handshaked, 1 = handshaked
} Client;
// Compute Sec-WebSocket-Accept
char* compute_accept_key(const char* client_key) {
char combined[100];
snprintf(combined, sizeof(combined), "%s%s", client_key, MAGIC_STRING);
unsigned char sha1[SHA_DIGEST_LENGTH];
SHA1((unsigned char*)combined, strlen(combined), sha1);
BIO* b64 = BIO_new(BIO_f_base64());
BIO* mem = BIO_new(BIO_s_mem());
b64 = BIO_push(b64, mem);
BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
BIO_write(b64, sha1, SHA_DIGEST_LENGTH);
BIO_flush(b64);
BUF_MEM* buffer;
BIO_get_mem_ptr(b64, &buffer);
char* accept_key =(char *)malloc(buffer->length + 1);
memcpy(accept_key, buffer->data, buffer->length);
accept_key[buffer->length] = '\0';
BIO_free_all(b64);
return accept_key;
}
// Send handshake response
void send_handshake(int client_fd, const char* client_key) {
char* accept_key = compute_accept_key(client_key);
char response[BUFFER_SIZE];
snprintf(response, sizeof(response),
"HTTP/1.1 101 Switching Protocols\r\n"
"Upgrade: websocket\r\n"
"Connection: Upgrade\r\n"
"Sec-WebSocket-Accept: %s\r\n\r\n",
accept_key);
write(client_fd, response, strlen(response));
free(accept_key);
}
// Extract Sec-WebSocket-Key
char* extract_key(const char* request) {
const char* key_start = strstr(request, "Sec-WebSocket-Key: ");
if (!key_start) return NULL;
key_start += 19;
char* key_end = (char *)strstr(key_start, "\r\n");
if (!key_end) return NULL;
int key_len = key_end - key_start;
char* key = (char *)malloc(key_len + 1);
strncpy(key, key_start, key_len);
key[key_len] = '\0';
return key;
}
// Decode masked payload
void decode_payload(char* payload, unsigned char* mask_key, int payload_len) {
for (int i = 0; i < payload_len; i++) {
payload[i] ^= mask_key[i % 4];
}
}
// Set socket to non-blocking
void set_nonblocking(int fd) {
int flags = fcntl(fd, F_GETFL, 0);
fcntl(fd, F_SETFL, flags | O_NONBLOCK);
}
int main() {
int server_fd, epoll_fd;
struct sockaddr_in address;
int addrlen = sizeof(address);
Client clients[MAX_CLIENTS] = {0}; // Array to track clients
// Create server socket
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
perror("Socket failed");
exit(EXIT_FAILURE);
}
set_nonblocking(server_fd);
// Set up address
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(PORT);
// Bind and listen
if (bind(server_fd, (struct sockaddr*)&address, sizeof(address)) < 0) {
perror("Bind failed");
exit(EXIT_FAILURE);
}
if (listen(server_fd, SOMAXCONN) < 0) {
perror("Listen failed");
exit(EXIT_FAILURE);
}
printf("Server listening on port %d\n", PORT);
// Create epoll instance
epoll_fd = epoll_create1(0);
if (epoll_fd == -1) {
perror("Epoll create failed");
exit(EXIT_FAILURE);
}
// Add server socket to epoll
struct epoll_event ev, events[MAX_EVENTS];
ev.events = EPOLLIN;
ev.data.fd = server_fd;
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_fd, &ev) == -1) {
perror("Epoll add server failed");
exit(EXIT_FAILURE);
}
// Main event loop
while (1) {
int nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);
if (nfds == -1) {
perror("Epoll wait failed");
continue;
}
for (int i = 0; i < nfds; i++) {
int fd = events[i].data.fd;
if (fd == server_fd) {
// Accept new connection
int client_fd = accept(server_fd, (struct sockaddr*)&address, (socklen_t*)&addrlen);
if (client_fd == -1) {
perror("Accept failed");
continue;
}
set_nonblocking(client_fd);
// Add client to epoll
ev.events = EPOLLIN | EPOLLET; // Edge-triggered
ev.data.fd = client_fd;
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_fd, &ev) == -1) {
perror("Epoll add client failed");
close(client_fd);
continue;
}
// Store client
for (int j = 0; j < MAX_CLIENTS; j++) {
if (clients[j].fd == 0) {
clients[j].fd = client_fd;
clients[j].handshaked = 0;
clients[j].buf_len = 0;
break;
}
}
printf("New client connected: %d\n", client_fd);
} else {
// Find client
Client* client = NULL;
for (int j = 0; j < MAX_CLIENTS; j++) {
if (clients[j].fd == fd) {
client = &clients[j];
break;
}
}
if (!client) continue;
// Read data
int bytes_read = read(fd, client->buffer + client->buf_len, BUFFER_SIZE - client->buf_len);
if (bytes_read <= 0) {
// Client disconnected or error
epoll_ctl(epoll_fd, EPOLL_CTL_DEL, fd, NULL);
close(fd);
memset(client, 0, sizeof(Client));
printf("Client disconnected: %d\n", fd);
continue;
}
client->buf_len += bytes_read;
if (!client->handshaked) {
// Process handshake
char* client_key = extract_key(client->buffer);
if (client_key) {
send_handshake(fd, client_key);
client->handshaked = 1;
client->buf_len = 0; // Clear buffer after handshake
free(client_key);
}
} else {
// Process WebSocket frame
if (client->buf_len >= 2) { // Minimum frame size
unsigned char* buf = (unsigned char*)client->buffer;
int opcode = buf[0] & 0x0F;
int mask = (buf[1] & 0x80) >> 7;
int payload_len = buf[1] & 0x7F;
if (opcode == 0x1 && client->buf_len >= (2 + (mask ? 4 : 0) + payload_len)) {
char* payload = mask ? client->buffer + 6 : client->buffer + 2;
if (mask) {
unsigned char* mask_key = (unsigned char*)client->buffer + 2;
decode_payload(payload, mask_key, payload_len);
}
printf("Received from %d: %.*s\n", fd, payload_len, payload);
// Echo back
char frame[BUFFER_SIZE] = {(char)0x81, (char)payload_len};
memcpy(frame + 2, payload, payload_len);
write(fd, frame, 2 + payload_len);
// Shift remaining data
int frame_len = 2 + (mask ? 4 : 0) + payload_len;
memmove(client->buffer, client->buffer + frame_len, client->buf_len - frame_len);
client->buf_len -= frame_len;
}
}
}
}
}
}
close(server_fd);
close(epoll_fd);
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment