const std = @import("std");
const net = std.net;
const posix = std.posix;
const linux = std.os.linux;
const echo = std.debug.print;
const html =
\\
\\
;
var g_buffer: [1024]u8 = undefined;
fn respondHttp(stream: std.net.Stream, msg: []const u8) !void {
_ = msg;
try std.fmt.format(
stream.writer(),
"HTTP/1.1 200 OK\r\nContent-Length: {}\r\n\r\n{s}",
.{ html.len, html },
);
}
fn respondWs(stream: std.net.Stream, msg: []const u8) !void {
// TODO: handle msg length (< 128)
var response_buffer = try std.BoundedArray(u8, 128).init(0);
var writer = response_buffer.writer();
_ = try writer.writeByte(0b10000001);
_ = try writer.writeByte(@truncate(msg.len));
_ = try writer.write(msg);
_ = try stream.write(response_buffer.constSlice());
}
fn handleHttp(stream: std.net.Stream, msg: []const u8) !bool {
echo("received: {s}\n", .{msg});
try respondHttp(stream, msg);
return true;
}
fn handleWsUpgrade(stream: std.net.Stream, msg: []const u8) !bool {
if (std.mem.indexOf(u8, msg, "Sec-WebSocket-Key")) |idx| {
const end = std.mem.indexOfScalarPos(u8, msg, idx, '\r') orelse return error.InvalidUpgradeRequest;
const key = msg[idx + 19 .. end];
echo("key: {s}\n", .{key});
var concat = try std.BoundedArray(u8, 128).init(0);
_ = try concat.writer().write(key);
_ = try concat.writer().write("258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
var hashed: [20]u8 = undefined;
std.crypto.hash.Sha1.hash(concat.constSlice(), &hashed, .{});
var encoded: [28]u8 = undefined;
_ = std.base64.standard.Encoder.encode(&encoded, &hashed);
try std.fmt.format(
stream.writer(),
"HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: {s}\r\n\r\n",
.{encoded},
);
return true;
}
return false;
}
fn handleWs(stream: std.net.Stream, frame: []const u8) !bool {
// TODO: handle ping/pong
// TODO: handle closing
// zig fmt: off
const FIN = (frame[0] & 0b10000000) >> 7;
const RSV1 = (frame[0] & 0b01000000) >> 6;
const RSV2 = (frame[0] & 0b00100000) >> 5;
const RSV3 = (frame[0] & 0b00010000) >> 4;
const OPCODE = (frame[0] & 0b00001111) >> 0;
const MASK = (frame[1] & 0b10000000) >> 7;
var msgIndex: usize = 2;
var payloadLength: usize = frame[1] & 0b01111111 >> 0;
if (payloadLength == 126) {
payloadLength = std.mem.readVarInt(u16, frame[2..4],.big);
msgIndex = 4;
}
else if (payloadLength == 127) {
payloadLength = std.mem.readVarInt(u16, frame[2..6],.big);
msgIndex = 6;
// const msb = payloadLength >> 63;
}
// zig fmt: on
const mask = frame[msgIndex .. msgIndex + 4];
const encoded = frame[msgIndex + 4 .. frame.len];
var decoded = try std.BoundedArray(u8, 1024).init(0);
for (0..encoded.len) |i| {
try decoded.append(encoded[i] ^ mask[i % 4]);
}
echo("tcp: [{}] {s}\n", .{ .{ FIN, RSV1, RSV2, RSV3, OPCODE, MASK, payloadLength, mask }, decoded.constSlice() });
try respondWs(stream, "yooooooo");
// _ = try stream.write(frame);
// std.debug.print("frame: {b}\n", .{frame});
return true;
}
fn handle(client: *Client) !bool {
var stream = std.net.Stream{ .handle = client.fd };
const bytesReceived = try stream.read(&g_buffer);
if (bytesReceived == 0) {
return false;
}
const msg = g_buffer[0..bytesReceived];
if (client.ws) {
return try handleWs(stream, msg);
} else if (std.mem.indexOf(u8, msg, "Sec-WebSocket-Key")) |_| {
const success = try handleWsUpgrade(stream, msg);
if (success) {
client.ws = true;
}
return success;
} else {
return try handleHttp(stream, msg);
}
}
const Client = struct {
fd: i32,
ws: bool = false,
};
pub fn main() !void {
const address = try std.net.Address.parseIp("127.0.0.1", 8080);
const tpe: u32 = posix.SOCK.STREAM | posix.SOCK.NONBLOCK;
const protocol = posix.IPPROTO.TCP;
const listener = try posix.socket(address.any.family, tpe, protocol);
defer posix.close(listener);
try posix.setsockopt(listener, posix.SOL.SOCKET, posix.SO.REUSEADDR, &std.mem.toBytes(@as(c_int, 1)));
try posix.bind(listener, &address.any, address.getOsSockLen());
try posix.listen(listener, 128);
// epoll_create1 takes flags. We aren't using any in these examples
const efd = try posix.epoll_create1(0);
defer posix.close(efd);
const listener_client = Client{ .fd = listener };
{
// monitor our listening socket
var event = linux.epoll_event{ .events = linux.EPOLL.IN, .data = .{ .ptr = @intFromPtr(&listener_client) } };
try posix.epoll_ctl(efd, linux.EPOLL.CTL_ADD, listener, &event);
}
var ready_list: [128]linux.epoll_event = undefined;
var client_list = try std.BoundedArray(Client, 128).init(0);
while (true) {
const ready_count = posix.epoll_wait(efd, &ready_list, -1);
for (ready_list[0..ready_count]) |ready| {
const client: *Client = @ptrFromInt(ready.data.ptr);
const ready_socket = client.fd;
if (ready_socket == listener) {
const client_socket = try posix.accept(listener, null, null, posix.SOCK.NONBLOCK);
errdefer posix.close(client_socket);
var new_client = try client_list.addOne();
new_client.fd = client_socket;
var event = linux.epoll_event{ .events = linux.EPOLL.IN, .data = .{ .ptr = @intFromPtr(new_client) } };
try posix.epoll_ctl(efd, linux.EPOLL.CTL_ADD, client_socket, &event);
} else {
const closed = !(handle(client) catch false);
if (closed or ready.events & linux.EPOLL.RDHUP == linux.EPOLL.RDHUP) {
posix.close(ready_socket);
}
}
}
}
}