Compare commits

...

19 Commits

Author SHA1 Message Date
dc839e098c media: Update to zig 0.16.0, harden tests and add test build step 2026-05-13 00:11:38 +02:00
380145a986 vecmath: Update to zig 0.16.0, harden tests and add test build step 2026-05-12 23:33:33 +02:00
0cce9d9bce Add "zig-pkg" to .gitignore (for zig 0.16.0) 2026-05-12 23:11:34 +02:00
572f4be896 myid port (wip) 2026-05-12 23:11:15 +02:00
8d45a93e6e bad grammer 2026-05-12 23:10:12 +02:00
09266e678f README for the people 2026-03-13 14:44:41 +01:00
cbf0d6a9da Massive logging, improve connection closing/timeout handling 2026-03-13 02:47:36 +01:00
712e214f61 web: fix compiler errors 2026-03-12 03:07:05 +01:00
fe4a585b6b web: unnecessary refactor before compilation 2026-03-12 02:47:54 +01:00
9a4932e629 web: Great cleanup WIP 2026-03-09 16:59:29 +01:00
6315589fa1 web: SSL but bad 2026-03-08 23:37:22 +01:00
1f07cc38ba web: The beginnings of TLS 2026-03-08 22:08:25 +01:00
e09a00a4ba web: fix compile errors and critical runtime errors 2026-03-08 15:48:20 +01:00
738ba5bd37 web: before compile & fix marathon 2026-03-08 14:54:09 +01:00
66d49ea8d5 web: some stuff 2026-03-07 21:08:22 +01:00
f02ece22fa web: Secondary utils and helpers 2026-03-06 13:17:27 +01:00
2e97aef842 media: png chunk structs and decoders 2026-02-15 23:51:21 +01:00
9e7955495f Remove sciter and TCC from code workspace 2026-02-09 19:03:59 +01:00
ef15201f21 media: Format description struct, stubs for more formats 2026-02-09 19:02:02 +01:00
85 changed files with 43374 additions and 939 deletions

2
.gitattributes vendored
View File

@@ -1,3 +1 @@
*.dll filter=lfs diff=lfs merge=lfs -text
*.pdf filter=lfs diff=lfs merge=lfs -text
*.so filter=lfs diff=lfs merge=lfs -text

1
.gitignore vendored
View File

@@ -1,2 +1,3 @@
.zig-cache
zig-out
zig-pkg

8
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,8 @@
{
"files.exclude": {
"**/.zig-cache": true,
},
"files.associations": {
"**/*.{c,h}": "c",
},
}

88
README.md Normal file
View File

@@ -0,0 +1,88 @@
# Building my own castle
I'm building my own castle in Zig ⚡.
This is a collection of experimental, recreational or actually useful libraries,
which I may or may not use as a part of some other projects. Most of them are
work in progress and might remain so indefinitely.
## cjit
JIT compiler for the C language inspired by Fabrice Bellard's Tiny C Compiler
(TCC). Meant to compile x86_64 and aarch64 for Windows and Linux straight to
virtual memory, without the ability to output an executable file or library. C
standard library is not fully supported, only some includes and builtins (see
[src/includes](packages/cjit/src/includes)).
It's in very early stage and cannot be used as of now. Currently, parts of the
tokenizer, x86_64 emit code and relocation logic are implemented. Aarch64 would
come after x86_64 is fully implemented and actually works.
You can see an example of how this library is supposed to be used in
[test/root.zig](packages/cjit/test/root.zig).
## js
Zig bindings for Fabrice Bellard's QuickJS. Original C sources are included and
compiled with Zig's build system. The bindings are not complete, but usable.
## media
Set of utilities for decoding and encoding media files. As of now, no purely Zig
decoder/encoder exists, which would be the goal. There is fairly comprehensive
PNG chunk decoder, though.
Bindings for stb_image.h from Sean T. Barrett's single-file C libraries are
included. They can be used to decode PNG, JPEG and HDR images and support Zig's
allocator interface in a thread-safe manner.
This package is actually used by my other project, codenamed
[voxel-game](/:root/renati/voxel-game).
## myid
Remnants of a project meant to provide a simpler alternative to OpenID Connect
(OIDC). It contains an HTTP server, which has since been improved and resides
in *web* package.
The ideas behind *myid* were developed further in another project I created
using TypeScript and Bun as a runtime. I might backport them back to Zig when I
find the time and motivation, which would be implemented on top of the HTTP
server from the *web* package.
## vecmath
Vector math library with support for vectors, matrices and ×8 SIMD operations
(utilizing SOA layout).
This package is actually used by my other project, codenamed
[voxel-game](/:root/renati/voxel-game) and is probably the most complete package
in this repository.
## web
An HTTP server library and collection of other utilities commonly associated
with web technologies.
The HTTP server implements HTTP version 1.1 using worker threads and supports
SSL via OpenSSL library. The server is very low level and does not provide any
logic to handle proper HTTP semantics. It is up to the user of the HTTP server
library to respect (or not) all HTTP methods and headers. The server is designed
or Linux only and uses Linux syscalls directly in its implementation.
The other utilities include:
- HTTP/1.1 request parser, which is part of the HTTP server, but can be used
independently,
- OpenSSL bindings, which are used by the HTTP server, but can be used
independently (full mechanical C translation is provided and partial manual
Zig translation),
- UUID type and generators for v4, v5 and v7,
- Generic ID type, a wrapper around UUID (or anything that is 16 bytes long)
that can be instantiated for each distinct use of ID with a tag to help avoid
type confusion at compile time.
## x11
Zig bindings for Xlib (aka libX11), a C library implementing a client of the X
Window System protocol. The bindings are not complete.

View File

@@ -1,44 +0,0 @@
{
"folders": [
{
"name": "cjit",
"path": "packages/cjit"
},
{
"name": "js",
"path": "packages/js"
},
{
"name": "media",
"path": "packages/media"
},
{
"name": "myid",
"path": "packages/myid"
},
{
"name": "sciter",
"path": "packages/sciter"
},
{
"name": "tcc",
"path": "packages/tcc"
},
{
"name": "vecmath",
"path": "packages/vecmath"
},
{
"name": "x11",
"path": "packages/x11"
}
],
"settings": {
"files.exclude": {
"**/.zig-cache": true,
},
"files.associations": {
"**/packages/tcc/vendor/*.{def,h}": "c",
},
},
}

View File

@@ -195,7 +195,7 @@ const import = struct {
no_add: bool = false,
/// Internal use.
no_exotic: bool = false,
_pad14: u18 = 0,
_pad18: u14 = 0,
};
pub const JS_EVAL = packed struct(u32) {
@@ -1168,7 +1168,7 @@ pub const Value = extern struct {
}
pub fn getClassId(self: Value) ClassId {
return .{ .class_id = import.JS_GetClassID(self) };
return .{ .class_id = import.JS_GetClassID(self.value) };
}
};
@@ -1182,7 +1182,7 @@ pub const ClassId = extern struct {
pub const invalid: ClassId = .{ .class_id = 0 };
pub fn new() ClassId {
var class_id: import.JSClassId = 0;
var class_id: import.JSClassID = 0;
return .{ .class_id = import.JS_NewClassID(&class_id) };
}
};

View File

@@ -1,15 +1,23 @@
const std = @import("std");
pub fn build(b: *std.Build) void {
const vm_dep = b.dependency("vecmath", .{});
const vm_module = vm_dep.module("vecmath");
const target = b.standardTargetOptions(.{});
const root_module = b.addModule("media", .{
const vm_dep = b.dependency("vecmath", .{
.target = target,
});
const vm_mod = vm_dep.module("vecmath");
const mod = b.addModule("media", .{
.root_source_file = b.path("src/root.zig"),
.target = target,
.link_libc = true,
.imports = &.{
.{ .name = "vecmath", .module = vm_mod },
},
});
root_module.addCSourceFile(.{
mod.addCSourceFile(.{
.file = b.path("src/stbi/stb_image.c"),
.flags = &.{
"-std=c17",
@@ -19,5 +27,12 @@ pub fn build(b: *std.Build) void {
.language = .c,
});
root_module.addImport("vecmath", vm_module);
const mod_tests = b.addTest(.{
.root_module = mod,
});
const run_mod_tests = b.addRunArtifact(mod_tests);
const test_step = b.step("test", "Run tests");
test_step.dependOn(&run_mod_tests.step);
}

View File

@@ -1,7 +1,7 @@
.{
.name = .media,
.version = "0.0.0",
.minimum_zig_version = "0.15.2",
.minimum_zig_version = "0.16.0",
.paths = .{
"src",
"build.zig",

BIN
packages/media/jpeg-specification.pdf (Stored with Git LFS) Normal file

Binary file not shown.

BIN
packages/media/png-specification.pdf (Stored with Git LFS) Normal file

Binary file not shown.

View File

@@ -0,0 +1,25 @@
const std = @import("std");
const Self = @This();
/// The number of bytes necessary to confirm that a buffer contains this media
/// format.
magic_length: usize,
/// The number of bytes necessary to confirm that a buffer contains this media
/// format and to extract metadata stored at the beggining, if any. Must be at
/// least as big as `magic_length`.
info_length: usize,
/// The file extension usually associated with this media format; all in
/// lowercase and without the dot character.
extension: []const u8,
/// The media type (aka MIME type or Content-Type) usually associated with this
/// media format; not necessarily officially registered.
media_type: []const u8,
/// Confirm whether a buffer contains this media format. The buffer doesn't have
/// to contain the entire file, only its beginning. The caller asserts that
/// `buffer` is at least `magic_length` bytes long.
isFormat: *const fn (buffer: []const u8) bool,
test "refAllDecls" {
std.testing.refAllDecls(@This());
}

View File

@@ -9,14 +9,26 @@ pub const Static = struct {
const sample_rate: f64 = @floatFromInt(self.sample_rate);
return @floatCast(samples / sample_rate);
}
test "refAllDecls" {
std.testing.refAllDecls(@This());
}
};
pub const Sample = extern struct {
left: i16,
right: i16,
test "refAllDecls" {
std.testing.refAllDecls(@This());
}
};
pub const Stream = struct {
source: std.io.Reader,
source: *std.Io.Reader,
sample_rate: u32,
test "refAllDecls" {
std.testing.refAllDecls(@This());
}
};

View File

@@ -19,11 +19,15 @@ pub fn Static(comptime W: u32, comptime H: u32) type {
}
pub fn fill(self: *@This(), color: vm.Color) void {
@memset(self.data, color);
@memset(&self.data, color);
}
};
}
test "Static - refAllDecls" {
std.testing.refAllDecls(Static(16, 16));
}
pub const Dynamic = struct {
width: u32,
height: u32,
@@ -65,6 +69,10 @@ pub const Dynamic = struct {
pub fn fill(self: *@This(), color: vm.Color) void {
@memset(self.data[0 .. self.width * self.height], color);
}
test "refAllDecls" {
std.testing.refAllDecls(@This());
}
};
pub const Hdr = struct {
@@ -108,4 +116,8 @@ pub const Hdr = struct {
pub fn fill(self: *@This(), color: vm.ColorHdr) void {
@memset(self.data[0 .. self.width * self.height], color);
}
test "refAllDecls" {
std.testing.refAllDecls(@This());
}
};

151
packages/media/src/jpeg.zig Normal file
View File

@@ -0,0 +1,151 @@
const std = @import("std");
const Format = @import("Format.zig");
const format: Format = .{
.magic_length = magic.len,
// NOTE The information (like width and height) is not in a fixed position
.info_length = magic.len,
.extension = "jpeg",
.media_type = "image/jpeg",
.isFormat = isJpeg,
};
const magic = "\xFF\xD8\xFF";
const Info = union(enum) {
partial: void,
full: Header,
pub fn makeFull(full: Header) Info {
return .{ .full = full };
}
test "refAllDecls" {
std.testing.refAllDecls(@This());
}
};
const Header = struct {
width: u32,
height: u32,
};
const Marker = enum(u8) {
/// Start of frame, Huffman coding, Baseline DCT
SOF_0 = 0xC0,
/// Start of frame, Huffman coding, Extended sequential DCT
SOF_1 = 0xC1,
/// Start of frame, Huffman coding, Progressive DCT
SOF_2 = 0xC2,
/// Start of frame, Huffman coding, Lossless (sequential)
SOF_3 = 0xC3,
/// Start of frame, Huffman coding, Differential sequential DCT
SOF_5 = 0xC5,
/// Start of frame, Huffman coding, Differential progressive DCT
SOF_6 = 0xC6,
/// Start of frame, Huffman coding, Differential lossless (sequential)
SOF_7 = 0xC7,
/// Start of frame, arithmetic coding, Extended sequential DCT
SOF_9 = 0xC9,
/// Start of frame, arithmetic coding, Progressive DCT
SOF_10 = 0xCA,
/// Start of frame, arithmetic coding, Lossless (sequential)
SOF_11 = 0xCB,
/// Start of frame, arithmetic coding, Differential sequential DCT
SOF_13 = 0xCD,
/// Start of frame, arithmetic coding, Differential progressive DCT
SOF_14 = 0xCE,
/// Start of frame, arithmetic coding, Differential lossless (sequential)
SOF_15 = 0xCF,
/// Define Huffman table(s)
DHT = 0xC4,
/// Define arithmetic coding conditioning(s)
DAC = 0xCC,
/// Start of image
SOI = 0xD8,
/// End of image
EOI = 0xD9,
/// Start of scan
SOS = 0xDA,
/// Define quantization table(s)
DQT = 0xDB,
/// Define number of lines
DNL = 0xDC,
/// Define restart interval
DRI = 0xDD,
/// Define hierarchical progression
DHP = 0xDE,
/// Expand reference component(s)
EXP = 0xDF,
/// Comment
COM = 0xFE,
/// For temporary private use in arithmetic coding
TEM = 0x01,
_,
/// Restart with modulo 8 count `m`
pub fn RST(m: u3) Marker {
return @enumFromInt(0xD0 | m);
}
pub fn isRST(self: Marker) ?u3 {
return if (@intFromEnum(self) & 0b1111_1000 == 0xD0) @intCast(@intFromEnum(self) & 0b0000_0111) else null;
}
/// Reserved for application segments
pub fn APP(n: u4) Marker {
return @enumFromInt(0xE0 | n);
}
pub fn isAPP(self: Marker) ?u3 {
return if (@intFromEnum(self) & 0b1111_0000 == 0xE0) @intCast(@intFromEnum(self) & 0b0000_1111) else null;
}
/// A standalone marker has no content and is not followed by segment length
/// parameter.
pub fn isStandalone(self: Marker) bool {
return self.isRST() != null or
self == .SOI or
self == .EOI or
self == .TEM;
}
test "refAllDecls" {
std.testing.refAllDecls(@This());
}
};
/// The caller asserts that the buffer is at least `format.magic_length` bytes
/// long.
pub fn isJpeg(buffer: []const u8) bool {
return std.mem.eql(u8, buffer[0..format.magic_length], magic);
}
/// The caller asserts that the buffer is at least `format.info_length` bytes
/// long. The information is not in a fixed position, so you need to provide a
/// substantial amount of data, in the order of kilobytes. Depending on the
/// amount of metadata, the information might be in the first kilobyte or dozens
/// of kilobytes in.
///
/// This function returns:
///
/// - `null` when the buffer is not a JPEG or it's a malformed JPEG
/// - `.partial` when the buffer appears to be a part of a JPEG, but the
/// information could not be found within the buffer provided
/// - `.full` when the buffer appears to be a part of a JPEG and the information
/// was fully contained within the buffer provided
pub fn info(buffer: []const u8) ?Info {
std.debug.assert(buffer.len >= format.info_length);
if (!isJpeg(buffer)) {
return null;
}
@panic("TODO");
}
test "refAllDecls" {
std.testing.refAllDecls(@This());
}

View File

@@ -0,0 +1,41 @@
const std = @import("std");
const Format = @import("Format.zig");
const format: Format = .{
.magic_length = @max(magic_naked.len, magic_container.len),
.info_length = @max(magic_naked.len, magic_container.len),
.extension = "jxl",
.media_type = "image/jxl",
.isFormat = isJxl,
};
const magic_naked = "\xFF\x0A";
const magic_container = "\x00\x00\x00\x0CJXL \r\n\x87\n";
const Header = struct {};
/// The caller asserts that the buffer is at least `format.magic_length` bytes
/// long.
pub fn isJxl(buffer: []const u8) bool {
std.debug.assert(buffer.len >= format.magic_length);
return std.mem.startsWith(u8, buffer, magic_naked) or
std.mem.startsWith(u8, buffer, magic_container);
}
/// The caller asserts that the buffer is at least `format.info_length` bytes
/// long.
pub fn info(buffer: []const u8) ?Header {
std.debug.assert(buffer.len >= format.info_length);
if (!isJxl(buffer)) {
return null;
}
@panic("TODO");
}
test "refAllDecls" {
std.testing.refAllDecls(@This());
}

645
packages/media/src/png.zig Normal file
View File

@@ -0,0 +1,645 @@
const std = @import("std");
const Format = @import("Format.zig");
pub const format: Format = .{
.magic_length = magic.len,
// 4B - chunk length
// 4B - chunk type
// 13B - IHDR data
// 4B - checksum
.info_length = magic.len + 25,
.extension = "png",
.media_type = "image/png",
.isFormat = isPng,
};
const magic = "\x89PNG\r\n\x1A\n";
// --- IHDR --------------------------------------------------------------------
pub const Header = struct {
width: u32,
height: u32,
bit_depth: BitDepth,
color_type: ColorType,
compression_method: CompressionMethod,
filter_method: FilterMethod,
interlace_method: InterlaceMethod,
pub fn decode(chunk: Chunk) !Header {
std.debug.assert(chunk.chunk_type == .IHDR);
if (chunk.data.len != 13) return error.InvalidPng;
const width = std.mem.readInt(u32, chunk.data[0..4], .big);
const height = std.mem.readInt(u32, chunk.data[4..8], .big);
const bit_depth = chunk.data[8];
const color_type = chunk.data[9];
const compression_method = chunk.data[10];
const filter_method = chunk.data[11];
const interlace_method = chunk.data[12];
if (width == 0 or width > 0x7FFF_FFFF or
height == 0 or height > 0x7FFF_FFFF or
bit_depth == 0 or bit_depth > 16 or !std.math.isPowerOfTwo(bit_depth) or
color_type == 1 or color_type == 5 or color_type > 6 or
compression_method != 0 or
filter_method != 0 or
interlace_method > 1)
{
return error.InvalidPng;
}
switch (color_type) {
0 => {
// all bit depths allowed
},
2 => {
if (bit_depth < 8) return error.InvalidPng;
},
3 => {
if (bit_depth > 8) return error.InvalidPng;
},
4 => {
if (bit_depth < 8) return error.InvalidPng;
},
6 => {
if (bit_depth < 8) return error.InvalidPng;
},
else => unreachable,
}
return .{
.width = width,
.height = height,
.bit_depth = @enumFromInt(bit_depth),
.color_type = @enumFromInt(color_type),
.compression_method = @enumFromInt(compression_method),
.filter_method = @enumFromInt(filter_method),
.interlace_method = @enumFromInt(interlace_method),
};
}
test "refAllDecls" {
std.testing.refAllDecls(@This());
}
};
pub const BitDepth = enum(u8) {
@"1" = 1,
@"2" = 2,
@"4" = 4,
@"8" = 8,
@"16" = 16,
pub fn range(self: BitDepth) usize {
return @as(usize, 2) << @intCast(@intFromEnum(self));
}
test "refAllDecls" {
std.testing.refAllDecls(@This());
}
};
pub const ColorType = enum(u8) {
grayscale = 0,
rgb = 2,
palette = 3,
grayscale_alpha = 4,
rgba = 6,
pub fn paletteUsed(self: ColorType) bool {
return @intFromEnum(self) & 0b0000_0001 != 0;
}
pub fn colorUsed(self: ColorType) bool {
return @intFromEnum(self) & 0b0000_0010 != 0;
}
pub fn alphaChannelUsed(self: ColorType) bool {
return @intFromEnum(self) & 0b0000_0100 != 0;
}
test "refAllDecls" {
std.testing.refAllDecls(@This());
}
};
pub const CompressionMethod = enum(u8) {
flate = 0,
};
pub const FilterMethod = enum(u8) {
adaptive = 0,
};
pub const InterlaceMethod = enum(u8) {
none = 0,
adam7 = 1,
};
// --- PLTE --------------------------------------------------------------------
pub const Palette = struct {
len: u16,
entries: [256][3]u8,
pub fn asSlice(self: *const Palette) []const [3]u8 {
return self.entries[0..self.len];
}
pub fn decode(chunk: Chunk, ctx: Header) !Palette {
std.debug.assert(chunk.chunk_type == .PLTE);
const len = std.math.divExact(usize, chunk.data.len, 3) catch return error.InvalidPng;
switch (ctx.color_type) {
.grayscale => return error.InvalidPng,
.rgb => if (len < 1 or len > 256) return error.InvalidPng,
.palette => if (len < 1 or len > ctx.bit_depth.range()) return error.InvalidPng,
.grayscale_alpha => return error.InvalidPng,
.rgba => if (len < 1 or len > 256) return error.InvalidPng,
}
var entries: [256][3]u8 = undefined;
@memcpy(entries[0..len], @as([]const [3]u8, @ptrCast(chunk.data)));
return .{
.len = @intCast(len),
.entries = entries,
};
}
test "refAllDecls" {
std.testing.refAllDecls(@This());
}
};
// --- tRNS --------------------------------------------------------------------
pub const Transparency = union(enum) {
grayscale: u16,
rgb: [3]u16,
palette: TransparencyPalette,
pub fn initGrayscale(grayscale: u16) Transparency {
return .{ .grayscale = grayscale };
}
pub fn initRgb(rgb: [3]u16) Transparency {
return .{ .rgb = rgb };
}
pub fn initPalette(palette: TransparencyPalette) Transparency {
return .{ .palette = palette };
}
pub fn decode(chunk: Chunk, header: Header, maybe_palette: ?Palette) !Transparency {
std.debug.assert(chunk.chunk_type == .tRNS);
return switch (header.color_type) {
.grayscale => {
if (chunk.data.len != 2) return error.InvalidPng;
const grayscale = std.mem.readInt(u16, chunk.data[0..2], .big);
return .initGrayscale(grayscale);
},
.rgb => {
if (chunk.data.len != 6) return error.InvalidPng;
const r = std.mem.readInt(u16, chunk.data[0..2], .big);
const g = std.mem.readInt(u16, chunk.data[2..4], .big);
const b = std.mem.readInt(u16, chunk.data[4..6], .big);
return .initRgb(.{ r, g, b });
},
.palette => if (maybe_palette) |palette| {
const len = chunk.data.len;
if (len < 1 or len > palette.len) return error.InvalidPng;
var entries: [256]u8 = undefined;
@memcpy(entries[0..len], chunk.data[0..len]);
return .initPalette(.{
.len = @intCast(len),
.entries = entries,
});
} else {
return error.InvalidPng;
},
.grayscale_alpha => error.InvalidPng,
.rgba => error.InvalidPng,
};
}
test "refAllDecls" {
std.testing.refAllDecls(@This());
}
};
const TransparencyPalette = struct {
len: u16,
entries: [256]u8,
pub fn asSlice(self: *const TransparencyPalette) []const u8 {
return self.entries[0..self.len];
}
test "refAllDecls" {
std.testing.refAllDecls(@This());
}
};
// --- gAMA --------------------------------------------------------------------
const Gamma = struct {
/// Gamma times `scale`, i.e. 100000, For example, a gamma of 1/2.2 would be
/// stored as 45455.
gamma: u32,
const scale = 100000;
pub fn initExponent(exponent: f32) Gamma {
const gamma: u32 = @intFromFloat(@round(exponent * scale));
return .{ .gamma = gamma };
}
pub fn asExponent(self: Gamma) f32 {
return @as(f32, @floatFromInt(self.gamma)) / scale;
}
pub fn decode(chunk: Chunk) Gamma {
std.debug.assert(chunk.chunk_type == .gAMA);
if (chunk.data.len != 4) return error.InvalidPng;
const gamma = std.mem.readInt(u32, chunk.data[0..4], .big);
return .{ .gamma = gamma };
}
test "refAllDecls" {
std.testing.refAllDecls(@This());
}
};
// --- cHRM --------------------------------------------------------------------
const Chromaticities = struct {
white_point_x: u32,
white_point_y: u32,
red_x: u32,
red_y: u32,
green_x: u32,
green_y: u32,
blue_x: u32,
blue_y: u32,
pub fn decode(chunk: Chunk) !Chunk {
std.debug.assert(chunk.chunk_type == .cHRM);
if (chunk.data.len != 32) return error.InvalidPng;
return .{
.white_point_x = std.mem.readInt(u32, chunk.data[0..4], .big),
.white_point_y = std.mem.readInt(u32, chunk.data[4..8], .big),
.red_x = std.mem.readInt(u32, chunk.data[8..12], .big),
.red_y = std.mem.readInt(u32, chunk.data[12..16], .big),
.green_x = std.mem.readInt(u32, chunk.data[16..20], .big),
.green_y = std.mem.readInt(u32, chunk.data[20..24], .big),
.blue_x = std.mem.readInt(u32, chunk.data[24..28], .big),
.blue_y = std.mem.readInt(u32, chunk.data[28..32], .big),
};
}
test "refAllDecls" {
std.testing.refAllDecls(@This());
}
};
// --- sRGB --------------------------------------------------------------------
const RenderingIntent = enum(u8) {
/// Perceptual intent is for images preferring good adaptation to the output
/// device gamut at the expense of colorimetric accuracy, like photographs.
perceptual = 0,
/// Relative colorimetric intent is for images requiring color appearance
/// matching (relative to the output device white point), like logos.
relative_colorimetric = 1,
/// Saturation intent is for images preferring preservation of saturation at
/// the expense of hue and lightness, like charts and graphs.
saturation = 2,
/// Absolute colorimetric intent is for images requiring preservation of
/// absolute colorimetry, like proofs (previews of images destined for a
/// different output device).
absolute_colorimetric = 3,
pub fn decode(chunk: Chunk) RenderingIntent {
std.debug.assert(chunk.chunk_type == .sRGB);
if (chunk.data.len != 1) return error.InvalidPng;
const rendering_intent = chunk.data[0];
if (rendering_intent > 3) return error.InvalidPng;
return @enumFromInt(rendering_intent);
}
test "refAllDecls" {
std.testing.refAllDecls(@This());
}
};
// --- pHYs --------------------------------------------------------------------
pub const PhysicalPixelDimensions = struct {
pixels_per_unit_x: u32,
pixels_per_unit_y: u32,
unit: PhysicalUnit,
pub fn decode(chunk: Chunk) !PhysicalPixelDimensions {
std.debug.assert(chunk.chunk_type == .pHYs);
if (chunk.data.len != 9) return error.InvalidPng;
const pixels_per_unit_x = std.mem.readInt(u32, chunk.data[0..4], .big);
const pixels_per_unit_y = std.mem.readInt(u32, chunk.data[4..8], .big);
const unit = chunk.data[8];
if (pixels_per_unit_x == 0 or pixels_per_unit_y == 0 or unit > 1) {
return error.InvalidPng;
}
return .{
.pixels_per_unit_x = pixels_per_unit_x,
.pixels_per_unit_y = pixels_per_unit_y,
.unit = @enumFromInt(unit),
};
}
test "refAllDecls" {
std.testing.refAllDecls(@This());
}
};
pub const PhysicalUnit = enum(u8) {
unknown = 0,
meter = 1,
};
// --- tIME --------------------------------------------------------------------
pub const LastModificationTime = struct {
year: u16,
/// 1 - 12
month: u8,
/// 1 - 31
day: u8,
/// 0 - 23
hour: u8,
/// 0 - 59
minute: u8,
/// 0 - 60 (may include leap seconds)
second: u8,
pub fn decode(chunk: Chunk) !LastModificationTime {
std.debug.assert(chunk.chunk_type == .tIME);
if (chunk.data.len == 7) return error.InvalidPng;
const year = std.mem.readInt(u16, chunk.data[0..2], .big);
const month = chunk.data[2];
const day = chunk.data[3];
const hour = chunk.data[4];
const minute = chunk.data[5];
const second = chunk.data[6];
// NOTE This implementation is intentionally trying not to be too clever
// with validation.
if (month < 1 or month > 12 or
day < 1 or day > 31 or
hour > 23 or
minute > 59 or
second > 60)
{
return error.InvalidPng;
}
return .{
.year = year,
.month = month,
.day = day,
.hour = hour,
.minute = minute,
.second = second,
};
}
test "refAllDecls" {
std.testing.refAllDecls(@This());
}
};
// -----------------------------------------------------------------------------
pub const ChunkType = enum(u32) {
IHDR = fromName("IHDR"),
PLTE = fromName("PLTE"),
IDAT = fromName("IDAT"),
IEND = fromName("IEND"),
tRNS = fromName("tRNS"),
gAMA = fromName("gAMA"),
cHRM = fromName("cHRM"),
sRGB = fromName("sRGB"),
iCCP = fromName("iCCP"),
tEXt = fromName("tEXt"),
zTXt = fromName("zTXt"),
iTXt = fromName("iTXt"),
bKGD = fromName("bKGD"),
pHYs = fromName("pHYs"),
sBIT = fromName("sBIT"),
sPLT = fromName("sPLT"),
hIST = fromName("hIST"),
tIME = fromName("tIME"),
_,
fn fromName(name: *const [4]u8) u32 {
return @bitCast(name.*);
}
pub fn fromBytes(bytes: *const [4]u8) ChunkType {
return @enumFromInt(fromName(bytes));
}
pub fn toBytes(self: ChunkType) [4]u8 {
return @bitCast(@intFromEnum(self));
}
pub fn ancillary(self: ChunkType) bool {
return self.toBytes()[0] & 0b0010_0000 != 0;
}
pub fn private(self: ChunkType) bool {
return self.toBytes()[1] & 0b0010_0000 != 0;
}
pub fn safeToCopy(self: ChunkType) bool {
return self.toBytes()[3] & 0b0010_0000 != 0;
}
test "refAllDecls" {
std.testing.refAllDecls(@This());
}
};
pub const Chunk = struct {
chunk_type: ChunkType,
data: []const u8,
};
pub const StandardKeyword = enum {
/// Short (one line) title or caption for image
Title,
/// Name of images creator
Author,
/// Description of image (possibly long)
Description,
/// Copyright notice
Copyright,
/// Time Time of original image creation
Creation,
/// Software used to create the image
Software,
/// Legal disclaimer
Disclaimer,
/// Warning of nature of content
Warning,
/// Device used to create the image
Source,
/// Miscellaneous comment; conversion from GIF comment
Comment,
pub const map: std.StaticStringMap(StandardKeyword) = blk: {
const fields = @typeInfo(StandardKeyword).@"enum".fields;
var kvs_list: [fields.len]struct { []const u8, StandardKeyword } = undefined;
for (fields, 0..) |field, i| {
kvs_list[i] = .{ field.name, @field(StandardKeyword, field.name) };
}
break :blk .initComptime(kvs_list);
};
pub fn isStandardKeyword(keyword: []const u8) ?StandardKeyword {
return map.get(keyword);
}
test "refAllDecls" {
std.testing.refAllDecls(@This());
}
};
/// The caller asserts that the buffer is at least `format.magic_length` bytes
/// long.
pub fn isPng(buffer: []const u8) bool {
return std.mem.eql(u8, buffer[0..format.magic_length], magic);
}
/// The caller asserts that the buffer is at least `format.info_length` bytes
/// long.
pub fn info(buffer: []const u8) ?Header {
std.debug.assert(buffer.len >= format.info_length);
if (!isPng(buffer)) {
return null;
}
const chunk, _ = (decodeChunk(buffer[format.magic_length..]) catch return null) orelse return null;
if (chunk.chunk_type != .IHDR) {
return null;
}
const header = Header.decode(chunk) catch return null;
return header;
}
pub fn decodeChunk(buffer: []const u8) !?struct { Chunk, []const u8 } {
var rest: []const u8 = buffer;
if (rest.len < 8) {
// Not enough data
return null;
}
const length = std.mem.readInt(u32, rest[0..4], .big);
const chunk_type: ChunkType = .fromBytes(rest[4..8]);
rest = rest[8..];
if (length > 0x7FFF_FFFF) {
return error.InvalidPng;
}
if (rest.len < length) {
// Not enough data
return null;
}
const data = rest[0..length];
rest = rest[length..];
if (rest.len < 4) {
// Not enough data
return null;
}
const crc = std.mem.readInt(u32, rest[0..4], .big);
rest = rest[4..];
if (std.hash.crc.Crc32IsoHdlc.hash(data) != crc) {
return error.InvalidPng;
}
return .{
.{ .chunk_type = chunk_type, .data = data },
rest,
};
}
pub fn decodeChunks(buffer: []const u8, chunks: ?[]Chunk) !usize {
if (buffer.len < format.magic_length or !isPng(buffer)) {
return error.InvalidPng;
}
var index: usize = 0;
var rest: []const u8 = buffer[format.magic_length..];
while (rest.len > 0) : (index += 1) {
const chunk, rest = try decodeChunk(rest) orelse return error.InvalidPng;
if (chunks) |ck| {
if (index < ck.len) {
ck[index] = chunk;
}
}
}
return index;
}
pub fn decodeChunksAlloc(buffer: []const u8, allocator: std.mem.Allocator) ![]Chunk {
const n = try decodeChunks(buffer, null);
const chunks = try allocator.alloc(Chunk, n);
errdefer allocator.free(chunks);
_ = try decodeChunks(buffer, chunks);
return chunks;
}
pub fn encodeChunks(chunks: []const Chunk, writer: *std.Io.Writer) !void {
try writer.writeAll(magic);
for (chunks) |chunk| {
var crc: std.hash.crc.Crc32IsoHdlc = .init();
var data: []const u8 = chunk.data;
while (data.len > 0) {
const bytes_written = try writer.write(data);
crc.update(data[0..bytes_written]);
data = data[bytes_written..];
}
try writer.writeInt(u32, crc.final(), .big);
}
}
test "refAllDecls" {
std.testing.refAllDecls(@This());
}

View File

@@ -1,8 +1,31 @@
const std = @import("std");
const Format = @import("Format.zig");
const format: Format = .{
.magic_length = magic.len,
// 4B - sample count
// 1B - channels
// 3B - sample rate
.info_length = magic.len + 8,
.extension = "qoa",
.media_type = "audio/qoa",
.isFormat = isQoa,
};
const magic = "qoaf";
const Header = union(enum) {
streaming: HeaderStreaming,
static: HeaderStatic,
pub fn initStatic(static: HeaderStatic) Header {
return .{ .static = static };
}
test "refAllDecls" {
std.testing.refAllDecls(@This());
}
};
const HeaderStreaming = void;
@@ -17,32 +40,48 @@ const HeaderStatic = struct {
const sample_rate: f64 = @floatFromInt(self.sample_rate);
return @floatCast(samples / sample_rate);
}
test "refAllDecls" {
std.testing.refAllDecls(@This());
}
};
/// The caller asserts that the buffer is at least 12 bytes long, which can
/// contain the entirety of a QOA file header and the relevant information in
/// the first frame header.
pub fn info(buffer: []const u8) ?Header {
std.debug.assert(buffer.len >= 12);
/// The caller asserts that the buffer is at least `format.magic_length` bytes
/// long.
pub fn isQoa(buffer: []const u8) bool {
return std.mem.eql(u8, buffer[0..format.magic_length], magic);
}
/// The caller asserts that the buffer is at least `format.info_length` bytes
/// long.
pub fn info(buffer: []const u8) ?Header {
std.debug.assert(buffer.len >= format.info_length);
if (!isQoa(buffer)) {
return null;
}
const magic = buffer[0..4];
const samples = std.mem.readInt(u32, buffer[4..8], .big);
const channels = buffer[8];
const sample_rate = std.mem.readInt(u24, buffer[9..12], .big);
if (!std.mem.eql(u8, magic, "qoaf") or channels == 0 or channels > 8 or sample_rate == 0) {
if (channels == 0 or channels > 8 or
sample_rate == 0)
{
return null;
}
if (samples == 0) {
return .streaming;
} else {
return .{
.static = .{
.samples = samples,
.channels = channels,
.sample_rate = sample_rate,
},
};
return .initStatic(.{
.samples = samples,
.channels = channels,
.sample_rate = sample_rate,
});
}
}
test "refAllDecls" {
std.testing.refAllDecls(@This());
}

View File

@@ -1,5 +1,21 @@
const std = @import("std");
const Format = @import("Format.zig");
const format: Format = .{
.magic_length = magic.len,
// 4B - width
// 4B - height
// 1B - channels
// 1B - color space
.info_length = magic.len + 10,
.extension = "qoi",
.media_type = "image/qoi",
.isFormat = isQoi,
};
const magic = "qoif";
const Channels = enum(u8) {
rgb = 3,
rgba = 4,
@@ -17,18 +33,31 @@ const Header = struct {
color_space: ColorSpace,
};
/// The caller asserts that the buffer is at least 14 bytes long, which can
/// contain the entirety of a QOI header.
pub fn info(buffer: []const u8) ?Header {
std.debug.assert(buffer.len >= 14);
/// The caller asserts that the buffer is at least `format.magic_length` bytes
/// long.
pub fn isQoi(buffer: []const u8) bool {
return std.mem.eql(u8, buffer[0..format.magic_length], magic);
}
/// The caller asserts that the buffer is at least `format.info_length` bytes
/// long.
pub fn info(buffer: []const u8) ?Header {
std.debug.assert(buffer.len >= format.info_length);
if (!isQoi(buffer)) {
return null;
}
const magic = buffer[0..4];
const width = std.mem.readInt(u32, buffer[4..8], .big);
const height = std.mem.readInt(u32, buffer[8..12], .big);
const channels = buffer[12];
const color_space = buffer[13];
if (!std.mem.eql(u8, magic, "qoif") or width == 0 or height == 0 or channels < 3 or channels > 4 or color_space > 1) {
if (width == 0 or
height == 0 or
channels < 3 or channels > 4 or
color_space > 1)
{
return null;
}
@@ -39,3 +68,7 @@ pub fn info(buffer: []const u8) ?Header {
.color_space = @enumFromInt(color_space),
};
}
test "refAllDecls" {
std.testing.refAllDecls(@This());
}

View File

@@ -1,5 +1,15 @@
const std = @import("std");
pub const audio = @import("audio.zig");
pub const Format = @import("Format.zig");
pub const image = @import("image.zig");
pub const jpeg = @import("jpeg.zig");
pub const jxl = @import("jxl.zig");
pub const png = @import("png.zig");
pub const qoa = @import("qoa.zig");
pub const qoi = @import("qoi.zig");
pub const stbi = @import("stbi.zig");
test "refAllDecls" {
std.testing.refAllDecls(@This());
}

View File

@@ -5,17 +5,20 @@ const image = @import("image.zig");
const vm = @import("vecmath");
allocator: std.mem.Allocator,
io: std.Io,
mutex: std.Io.Mutex = .init,
allocations: std.AutoHashMapUnmanaged(*anyopaque, usize) = .empty,
mutex: std.Thread.Mutex = .{},
allocated_bytes: usize = 0,
const alignment: std.mem.Alignment = .@"16";
const VoidPtr = ?*align(alignment.toByteUnits()) anyopaque;
const log = std.log.scoped(.stbi);
pub fn init(allocator: std.mem.Allocator) Self {
pub fn init(allocator: std.mem.Allocator, io: std.Io) Self {
return .{
.allocator = allocator,
.io = io,
};
}
@@ -76,7 +79,7 @@ pub fn loadStaticBuf(self: *Self, comptime W: u32, comptime H: u32, buf: []const
return .{ .data = @as(*const [W * H]vm.Color, @ptrCast(@alignCast(res))).* };
}
pub fn loadStaticIo(self: *Self, comptime W: u32, comptime H: u32, reader: *std.io.Reader) !image.Static(W, H) {
pub fn loadStaticIo(self: *Self, comptime W: u32, comptime H: u32, reader: *std.Io.Reader) !image.Static(W, H) {
current_self = self;
defer current_self = undefined;
@@ -109,7 +112,7 @@ pub fn loadDynamicBuf(self: *Self, buf: []const u8) !image.Dynamic {
}
/// On success, must free memory by calling `freeDynamic` method.
pub fn loadDynamicIo(self: *Self, reader: *std.io.Reader) !image.Dynamic {
pub fn loadDynamicIo(self: *Self, reader: *std.Io.Reader) !image.Dynamic {
current_self = self;
defer current_self = undefined;
@@ -155,7 +158,7 @@ pub fn loadHdrBuf(self: *Self, buf: []const u8) !image.Hdr {
}
/// On success, must free memory by calling `freeHdr` method.
pub fn loadHdrIo(self: *Self, reader: *std.io.Reader) !image.Hdr {
pub fn loadHdrIo(self: *Self, reader: *std.Io.Reader) !image.Hdr {
current_self = self;
defer current_self = undefined;
@@ -243,8 +246,8 @@ threadlocal var current_self: *Self = undefined;
export fn castle_media_stbi_malloc(size: usize) callconv(.c) VoidPtr {
const self = current_self;
self.mutex.lock();
defer self.mutex.unlock();
self.mutex.lock(self.io) catch return null;
defer self.mutex.unlock(self.io);
self.allocations.ensureUnusedCapacity(self.allocator, 1) catch return null;
const memory = self.allocator.alignedAlloc(u8, alignment, size) catch return null;
@@ -259,8 +262,8 @@ export fn castle_media_stbi_malloc(size: usize) callconv(.c) VoidPtr {
export fn castle_media_stbi_realloc(maybe_ptr: VoidPtr, size: usize) callconv(.c) VoidPtr {
const self = current_self;
self.mutex.lock();
defer self.mutex.unlock();
self.mutex.lock(self.io) catch return null;
defer self.mutex.unlock(self.io);
// NOTE If we were pedantic, we would consider the fact that we might not
// need unused capacity if the memory doesn't get relocated.
@@ -291,8 +294,8 @@ export fn castle_media_stbi_free(maybe_ptr: VoidPtr) callconv(.c) void {
const self = current_self;
if (maybe_ptr) |ptr| {
self.mutex.lock();
defer self.mutex.unlock();
self.mutex.lockUncancelable(self.io);
defer self.mutex.unlock(self.io);
const size = self.allocations.fetchRemove(ptr).?.value;
self.allocated_bytes -= size;
@@ -301,3 +304,7 @@ export fn castle_media_stbi_free(maybe_ptr: VoidPtr) callconv(.c) void {
self.allocator.free(memory);
}
}
test "refAllDecls" {
std.testing.refAllDecls(@This());
}

View File

@@ -11,11 +11,19 @@ pub fn build(b: *std.Build) void {
const sqlite_mod = sqlite_dep.module("sqlite");
const web_dep = b.dependency("web", .{
.target = target,
.optimize = optimize,
});
const web_mod = web_dep.module("web");
const myid_mod = b.addModule("myid", .{
.root_source_file = b.path("src/root.zig"),
.target = target,
.imports = &.{
.{ .name = "sqlite", .module = sqlite_mod },
.{ .name = "web", .module = web_mod },
},
});

View File

@@ -13,5 +13,8 @@
.url = "git+https://github.com/vrischmann/zig-sqlite#6d90ee900d186a7fbb6066f28ee13beeaf8be345",
.hash = "sqlite-3.48.0-F2R_a5yODgDFvwwsytm7ZONcSqYBo3qv1PmXOtw3tqLA",
},
.web = .{
.path = "../web",
},
},
}

259
packages/myid/src/data.zig Normal file
View File

@@ -0,0 +1,259 @@
const std = @import("std");
const sqlite = @import("sqlite");
const web = @import("web");
const id = @import("id.zig");
pub const callback_regex = "^https?://([a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\\.)+[a-zA-Z]{2,}/";
pub const App = struct {
aid: id.App,
name: []const u8,
callbacks: []const []const u8,
};
pub const AppWithSecret = struct {
aid: id.App,
name: []const u8,
callbacks: []const []const u8,
secret: []const u8,
};
pub const CreateAppData = struct {
name: []const u8,
callback: ?[]const []const u8,
};
pub const CreateAppResult = struct {
aid: id.App,
plainSecret: []const u8,
};
pub const User = struct {
uid: id.User,
name: []const u8,
email: []const u8,
admin: bool,
};
pub const UserWithPassword = struct {
uid: id.User,
name: []const u8,
email: []const u8,
admin: bool,
passworD: []const u8,
};
pub const InviteUserData = struct {
name: []const u8,
email: []const u8,
admin: bool,
};
pub const CreateUserData = struct {
name: []const u8,
email: []const u8,
plainPassword: []const u8,
admin: bool,
};
pub const All = struct {
users: []const User,
apps: []const App,
};
pub const Database = struct {
db: sqlite.Db,
allocator: std.mem.Allocator,
pub fn init(path: [:0]const u8, allocator: std.mem.Allocator) !Database {
var db = try sqlite.Db.init(.{
.mode = .{ .File = path },
.open_flags = .{
.create = true,
.write = true,
},
.threading_mode = .MultiThread,
});
errdefer db.deinit();
_ = try db.one(void, "PRAGMA journal_mode = WAL", .{}, .{});
_ = try db.one(void, "PRAGMA foreign_keys = ON", .{}, .{});
return .{
.db = db,
.allocator = allocator,
};
}
pub fn deinit(self: *Database) void {
self.db.deinit();
self.* = undefined;
}
// --- MIGRATION -----------------------------------------------------------
fn getUserVersion(self: *Database) !i32 {
const version = try self.db.one(i32, "PRAGMA user_version", .{}, .{});
return version.?;
}
fn setUserVersion(self: *Database, version: i32) !void {
var buf: [100]u8 = undefined;
const query = std.fmt.bufPrint(&buf, "PRAGMA user_version = {d}", .{version}) catch unreachable;
_ = try self.db.oneDynamic(void, query, .{}, .{});
}
pub fn migrate(self: *Database) !void {
var user_version = try self.getUserVersion();
if (user_version == 0) {
_ = try self.db.exec(
\\CREATE TABLE users (
\\ uid BLOB NOT NULL,
\\ name TEXT NOT NULL,
\\ email TEXT NOT NULL UNIQUE,
\\ password TEXT NOT NULL,
\\ PRIMARY KEY (uid)
\\)
, .{}, .{});
_ = try self.db.exec(
\\CREATE TABLE apps (
\\ aid BLOB NOT NULL,
\\ name TEXT NOT NULL,
\\ secret TEXT NOT NULL,
\\ PRIMARY KEY (aid)
\\)
, .{}, .{});
_ = try self.db.exec(
\\CREATE TABLE app_callbacks (
\\ aid BLOB NOT NULL,
\\ callback TEXT NOT NULL,
\\ PRIMARY KEY (aid, callback),
\\ FOREIGN KEY (aid) REFERENCES apps (aid)
\\ ON UPDATE CASCADE
\\ ON DELETE CASCADE
\\)
, .{}, .{});
user_version += 1;
try self.setUserVersion(user_version);
}
}
// --- USERS ---------------------------------------------------------------
pub fn createUser(self: *Database, name: []const u8, email: []const u8, plain_password: []const u8) !id.User {
const uid = id.User.init(web.UUID.v7());
var password_buf: [1000]u8 = undefined;
const password = try std.crypto.pwhash.argon2.strHash(plain_password, .{
.allocator = self.allocator,
.mode = .argon2id,
.params = .owasp_2id,
}, &password_buf);
try self.db.exec("INSERT INTO users (uid, name, email, password) VALUES (?, ?, ?, ?)", .{}, .{
.uid = uid.bytes,
.name = name,
.email = email,
.password = password,
});
return uid;
}
// --- APPS ----------------------------------------------------------------
pub fn createApp(self: *Database, name: []const u8, callbacks: []const []const u8) !CreateAppResult {
_ = try self.db.exec("BEGIN", .{}, .{});
errdefer self.db.exec("ROLLBACK", .{}, .{}) catch unreachable;
const secret_bytes = blk: {
var bytes: [24]u8 = undefined;
std.crypto.random.bytes(&bytes);
break :blk bytes;
};
var plain_secret_buf: [32]u8 = undefined;
const plain_secret = std.base64.url_safe_no_pad.Encoder.encode(&plain_secret_buf, &secret_bytes);
std.debug.assert(plain_secret_buf.len == plain_secret.len);
const aid = id.App.init(web.UUID.v7());
var secret_buf: [1000]u8 = undefined;
const secret = try std.crypto.pwhash.argon2.strHash(plain_secret, .{
.allocator = self.allocator,
.mode = .argon2id,
.params = .owasp_2id,
}, &secret_buf);
try self.db.exec("INSERT INTO apps (aid, name, secret) VALUES (?, ?, ?)", .{}, .{
.aid = aid.bytes,
.name = name,
.secret = secret,
});
var insert_callback = try self.db.prepare("INSERT INTO app_callbacks (aid, callback) VALUES (?, ?)");
defer insert_callback.deinit();
for (callbacks) |callback| {
try insert_callback.exec(.{}, .{ .aid = aid.bytes, .callback = callback });
insert_callback.reset();
}
try self.db.exec("COMMIT", .{}, .{});
return .{
.aid = aid,
.plain_secret = plain_secret_buf,
};
}
};
test "user version" {
var db = try Database.init(":memory:", std.testing.allocator);
defer db.deinit();
try std.testing.expectEqual(0, try db.getUserVersion());
try db.setUserVersion(1);
try std.testing.expectEqual(1, try db.getUserVersion());
}
test "migrate" {
var db = try Database.init(":memory:", std.testing.allocator);
defer db.deinit();
try db.migrate();
}
test "create user" {
var db = try Database.init(":memory:", std.testing.allocator);
defer db.deinit();
try db.migrate();
const uid = try db.createUser("admin", "admin@test.invalid", "admin");
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
const maybe_user = try db.db.oneAlloc(struct {
name: []const u8,
email: []const u8,
password: []const u8,
}, arena.allocator(), "SELECT name, email, password FROM users WHERE uid = ?", .{}, .{
.uid = uid.bytes,
});
defer arena.deinit();
try std.testing.expect(maybe_user != null);
if (maybe_user) |user| {
try std.testing.expectEqualSlices(u8, "admin", user.name);
}
}
test "create app" {
var db = try Database.init(":memory:", std.testing.allocator);
defer db.deinit();
try db.migrate();
_ = try db.createApp("app", &.{
"http://localhost:3000/callback",
"https://example.com/callback",
});
}

View File

@@ -1,2 +0,0 @@
pub const database_path = "db.sqlite3";
pub const socket_path = "myid.sock";

View File

@@ -1,206 +0,0 @@
const std = @import("std");
const main = @import("main.zig");
const Parser = @import("http/Parser.zig");
threadlocal var read_buffer: [2 * 1024 * 1024]u8 = undefined;
threadlocal var write_buffer: [2 * 1024 * 1024]u8 = undefined;
const log = std.log.scoped(.http);
const status = struct {
pub const ok = "HTTP/1.1 200 OK\r\n";
pub const created = "HTTP/1.1 201 Created\r\n";
pub const accepted = "HTTP/1.1 202 Accepted\r\n";
pub const non_authoritative_information = "HTTP/1.1 203 Non-Authoritative Information\r\n";
pub const no_content = "HTTP/1.1 204 No Content\r\n";
pub const reset_content = "HTTP/1.1 205 Reset Content\r\n";
pub const partial_content = "HTTP/1.1 206 Partial Content\r\n";
pub const multi_status = "HTTP/1.1 207 Multi-Status\r\n";
pub const already_reported = "HTTP/1.1 208 Already Reported\r\n";
pub const multiple_choices = "HTTP/1.1 300 Multiple Choices\r\n";
pub const moved_permanently = "HTTP/1.1 301 Moved Permanently\r\n";
pub const found = "HTTP/1.1 302 Found\r\n";
pub const see_other = "HTTP/1.1 303 See Other\r\n";
pub const not_modified = "HTTP/1.1 304 Not Modified\r\n";
pub const temporary_redirect = "HTTP/1.1 307 Temporary Redirect\r\n";
pub const permanent_redirect = "HTTP/1.1 308 Permanent Redirect\r\n";
pub const bad_request = "HTTP/1.1 400 Bad Request\r\n";
pub const unauthorized = "HTTP/1.1 401 Unauthorized\r\n";
pub const payment_required = "HTTP/1.1 402 Payment Required\r\n";
pub const forbidden = "HTTP/1.1 403 Forbidden\r\n";
pub const not_found = "HTTP/1.1 404 Not Found\r\n";
pub const method_not_allowed = "HTTP/1.1 405 Method Not Allowed\r\n";
pub const not_acceptable = "HTTP/1.1 406 Not Acceptable\r\n";
pub const proxy_authentication_required = "HTTP/1.1 407 Proxy Authentication Required\r\n";
pub const request_timeout = "HTTP/1.1 408 Request Timeout\r\n";
pub const conflict = "HTTP/1.1 409 Conflict\r\n";
pub const gone = "HTTP/1.1 410 Gone\r\n";
pub const length_required = "HTTP/1.1 411 Length Required\r\n";
pub const precondition_failed = "HTTP/1.1 412 Precondition Failed\r\n";
pub const content_too_large = "HTTP/1.1 413 Content Too Large\r\n";
pub const uri_too_long = "HTTP/1.1 414 URI Too Long\r\n";
pub const unsupported_media_type = "HTTP/1.1 415 Unsupported Media Type\r\n";
pub const range_not_satisfiable = "HTTP/1.1 416 Range Not Satisfiable\r\n";
pub const expectation_failed = "HTTP/1.1 417 Expectation Failed\r\n";
pub const im_a_teapot = "HTTP/1.1 418 I'm a teapot\r\n";
pub const misdirected_request = "HTTP/1.1 421 Misdirected Request\r\n";
pub const unprocessable_content = "HTTP/1.1 422 Unprocessable Content\r\n";
pub const locked = "HTTP/1.1 423 Locked\r\n";
pub const failed_dependency = "HTTP/1.1 424 Failed Dependency\r\n";
pub const upgrade_required = "HTTP/1.1 426 Upgrade Required\r\n";
pub const precondition_required = "HTTP/1.1 428 Precondition Required\r\n";
pub const too_many_requests = "HTTP/1.1 429 Too Many Requests\r\n";
pub const request_header_fields_too_large = "HTTP/1.1 431 Request Header Fields Too Large\r\n";
pub const unavailable_for_legal_reasons = "HTTP/1.1 451 Unavailable For Legal Reasons\r\n";
pub const internal_server_error = "HTTP/1.1 500 Internal Server Error\r\n";
pub const not_implemented = "HTTP/1.1 501 Not Implemented\r\n";
pub const bad_gateway = "HTTP/1.1 502 Bad Gateway\r\n";
pub const service_unavailable = "HTTP/1.1 503 Service Unavailable\r\n";
pub const gateway_timeout = "HTTP/1.1 504 Gateway Timeout\r\n";
pub const http_version_not_supported = "HTTP/1.1 505 HTTP Version Not Supported\r\n";
pub const variant_also_negotiates = "HTTP/1.1 506 Variant Also Negotiates\r\n";
pub const insufficient_storage = "HTTP/1.1 507 Insufficient Storage\r\n";
pub const loop_detected = "HTTP/1.1 508 Loop Detected\r\n";
pub const not_extended = "HTTP/1.1 510 Not Extended\r\n";
pub const network_authentication_required = "HTTP/1.1 511 Network Authentication Required\r\n";
};
const ResponseEmptyOptions = struct {
status_text: []const u8 = status.ok,
};
const ResponseOptions = struct {
status_text: []const u8 = status.ok,
media_type: []const u8 = "text/plain; charset=utf-8",
response_body: []const u8,
};
fn makeResponseEmpty(options: ResponseEmptyOptions) ![]const u8 {
var fbs = std.io.fixedBufferStream(&write_buffer);
const writer = fbs.writer();
try writer.print("{s}", .{options.status_text});
try writer.print("\r\n", .{});
return fbs.getWritten();
}
fn makeResponseClose(options: ResponseEmptyOptions) ![]const u8 {
var fbs = std.io.fixedBufferStream(&write_buffer);
const writer = fbs.writer();
try writer.print("{s}", .{options.status_text});
try writer.print("Connection: close\r\n", .{});
try writer.print("\r\n", .{});
return fbs.getWritten();
}
fn makeResponse(options: ResponseOptions) ![]const u8 {
var fbs = std.io.fixedBufferStream(&write_buffer);
const writer = fbs.writer();
try writer.print("{s}", .{options.status_text});
try writer.print("Content-Type: {s}\r\n", .{options.media_type});
try writer.print("Content-Length: {d}\r\n", .{options.response_body.len});
try writer.print("\r\n", .{});
try writer.print("{s}", .{options.response_body});
return fbs.getWritten();
}
pub fn process(conn: std.net.Server.Connection) !void {
defer conn.stream.close();
var leftover_bytes: usize = 0;
while (true) {
const start = try std.time.Instant.now();
var route: Parser.Route = undefined;
var parser = Parser.init(.{
.self = &route,
.route = routeCallback,
});
var total_bytes_read: usize = 0;
while (true) {
var bytes_read: usize = undefined;
var chars: []const u8 = undefined;
if (leftover_bytes > 0) {
bytes_read = leftover_bytes;
chars = read_buffer[0..leftover_bytes];
leftover_bytes = 0;
} else {
bytes_read = try conn.stream.read(read_buffer[total_bytes_read..]);
chars = read_buffer[total_bytes_read .. total_bytes_read + bytes_read];
}
total_bytes_read += bytes_read;
const res = parser.consume(chars) catch |err| switch (err) {
error.MethodNotSupported => {
const response = try makeResponseClose(.{ .status_text = status.method_not_allowed });
try conn.stream.writeAll(response);
return;
},
error.HttpVersionNotSupported => {
const response = try makeResponseClose(.{ .status_text = status.http_version_not_supported });
try conn.stream.writeAll(response);
return;
},
error.MissingLineFeed => {
const response = try makeResponseClose(.{ .status_text = status.bad_request });
try conn.stream.writeAll(response);
return;
},
error.InvalidContentLength => {
const response = try makeResponseClose(.{ .status_text = status.bad_request });
try conn.stream.writeAll(response);
return;
},
};
if (total_bytes_read >= read_buffer.len and !res.done) {
if (parser.state == .body) {
const response = try makeResponseClose(.{ .status_text = status.content_too_large });
try conn.stream.writeAll(response);
return;
} else {
const response = try makeResponseClose(.{ .status_text = status.request_header_fields_too_large });
try conn.stream.writeAll(response);
return;
}
}
if (res.done) {
leftover_bytes = bytes_read - res.consumed;
break;
}
}
const response = try makeResponse(.{ .response_body = "PONG\n" });
try conn.stream.writeAll(response);
if (leftover_bytes > 0) {
@memmove(&read_buffer, read_buffer[total_bytes_read - leftover_bytes .. total_bytes_read]);
}
const end = try std.time.Instant.now();
const time_ns = end.since(start);
const time_us = @divFloor(time_ns, std.time.ns_per_us);
log.info("{s} {s} ({} μs)", .{ @tagName(route.method), route.pathname, time_us });
}
}
fn routeCallback(self: ?*anyopaque, route: Parser.Route) void {
@as(*Parser.Route, @alignCast(@ptrCast(self))).* = route;
}

View File

@@ -1,356 +0,0 @@
const std = @import("std");
const Parser = @This();
const Callbacks = struct {
self: ?*anyopaque = null,
route: ?*const fn (self: ?*anyopaque, route: Route) void = null,
header: ?*const fn (self: ?*anyopaque, name: []const u8, value: []const u8) void = null,
body: ?*const fn (self: ?*anyopaque, body: []const u8) void = null,
pub const init: Callbacks = .{};
};
const Error = error{
MethodNotSupported,
HttpVersionNotSupported,
MissingLineFeed,
InvalidContentLength,
};
const State = union(enum) {
pub fn methodComplete(method: Method) State {
return .{
.method_complete = .{
.method = method,
},
};
}
pub fn pathname(method: Method, p: []const u8) State {
return .{
.pathname_state = .{
.method = method,
.pathname = p,
},
};
}
pub fn headerValue(name: []const u8, value: []const u8) State {
return .{
.header_value = .{
.name = name,
.value = value,
},
};
}
init: void,
method_d: void,
method_g: void,
method_h: void,
method_p: void,
method_de: void,
method_ge: void,
method_he: void,
method_pa: void,
method_po: void,
method_pu: void,
method_del: void,
method_hea: void,
method_pat: void,
method_pos: void,
method_dele: void,
method_patc: void,
method_delet: void,
method_complete: struct { method: Method },
pathname_state: struct { method: Method, pathname: []const u8 },
pathname_complete: void,
version_h: void,
version_ht: void,
version_htt: void,
version_http: void,
@"version_http/@": void,
@"version_http/1@": void,
@"version_http/1.@": void,
version_complete: void,
start_line_end: void,
header_name_start: void,
header_name: []const u8,
header_value: struct { name: []const u8, value: []const u8 },
header_line_end: void,
headers_end: void,
body: []const u8,
};
const ConsumeResult = struct {
consumed: usize,
done: bool,
};
const ConsumeCharResult = enum {
not_done,
done,
};
pub const Method = enum {
DELETE,
GET,
HEAD,
PATCH,
POST,
PUT,
};
pub const Route = struct {
method: Method,
pathname: []const u8,
};
callbacks: Callbacks,
state: State,
current_header_is_content_length: bool,
content_length: usize,
pub fn init(callbacks: Callbacks) Parser {
return .{
.callbacks = callbacks,
.state = .init,
.current_header_is_content_length = false,
.content_length = 0,
};
}
pub fn consume(self: *Parser, chars: []const u8) Error!ConsumeResult {
var i: usize = 0;
while (i < chars.len) {
switch (self.state) {
.body => |body| {
const to_consume = @min(chars.len - i, self.content_length - body.len);
const new_body = body.ptr[0 .. body.len + to_consume];
self.state = .{ .body = new_body };
i += to_consume;
const done = new_body.len >= self.content_length;
if (done) {
if (self.callbacks.body) |bodyCallback| {
bodyCallback(self.callbacks.self, new_body);
}
}
return .{
.consumed = i,
.done = done,
};
},
else => {
const res = try self.consumeChar(&chars[i]);
i += 1;
if (res == .done) return .{
.consumed = i,
.done = true,
};
},
}
}
return .{
.consumed = chars.len,
.done = false,
};
}
pub fn consumeChar(self: *Parser, c_ptr: *const u8) Error!ConsumeCharResult {
const c = c_ptr.*;
const c_slice = @as([*]const u8, @ptrCast(c_ptr))[0..1];
switch (self.state) {
.init => switch (c) {
'D' => self.state = .method_d,
'G' => self.state = .method_g,
'H' => self.state = .method_h,
'P' => self.state = .method_p,
else => return error.MethodNotSupported,
},
.method_d => switch (c) {
'E' => self.state = .method_de,
else => return error.MethodNotSupported,
},
.method_g => switch (c) {
'E' => self.state = .method_ge,
else => return error.MethodNotSupported,
},
.method_h => switch (c) {
'E' => self.state = .method_he,
else => return error.MethodNotSupported,
},
.method_p => switch (c) {
'A' => self.state = .method_pa,
'O' => self.state = .method_po,
'U' => self.state = .method_pu,
else => return error.MethodNotSupported,
},
.method_de => switch (c) {
'L' => self.state = .method_del,
else => return error.MethodNotSupported,
},
.method_ge => switch (c) {
'T' => self.state = .methodComplete(.GET),
else => return error.MethodNotSupported,
},
.method_he => switch (c) {
'A' => self.state = .method_hea,
else => return error.MethodNotSupported,
},
.method_pa => switch (c) {
'T' => self.state = .method_pat,
else => return error.MethodNotSupported,
},
.method_po => switch (c) {
'S' => self.state = .method_pos,
else => return error.MethodNotSupported,
},
.method_pu => switch (c) {
'T' => self.state = .methodComplete(.PUT),
else => return error.MethodNotSupported,
},
.method_del => switch (c) {
'E' => self.state = .method_dele,
else => return error.MethodNotSupported,
},
.method_hea => switch (c) {
'D' => self.state = .methodComplete(.HEAD),
else => return error.MethodNotSupported,
},
.method_pat => switch (c) {
'C' => self.state = .method_patc,
else => return error.MethodNotSupported,
},
.method_pos => switch (c) {
'T' => self.state = .methodComplete(.POST),
else => return error.MethodNotSupported,
},
.method_dele => switch (c) {
'T' => self.state = .method_delet,
else => return error.MethodNotSupported,
},
.method_patc => switch (c) {
'H' => self.state = .methodComplete(.PATCH),
else => return error.MethodNotSupported,
},
.method_delet => switch (c) {
'E' => self.state = .methodComplete(.DELETE),
else => return error.MethodNotSupported,
},
.method_complete => |s| switch (c) {
' ' => self.state = .pathname(s.method, @as([*]const u8, @ptrCast(c_ptr))[1..1]),
else => return error.MethodNotSupported,
},
.pathname_state => |s| switch (c) {
' ' => {
self.state = .pathname_complete;
if (self.callbacks.route) |routeCallback| {
routeCallback(self.callbacks.self, .{
.method = s.method,
.pathname = s.pathname,
});
}
},
else => self.state = .pathname(s.method, s.pathname.ptr[0 .. s.pathname.len + 1]),
},
.pathname_complete => switch (c) {
'H' => self.state = .version_h,
else => return error.HttpVersionNotSupported,
},
.version_h => switch (c) {
'T' => self.state = .version_ht,
else => return error.HttpVersionNotSupported,
},
.version_ht => switch (c) {
'T' => self.state = .version_htt,
else => return error.HttpVersionNotSupported,
},
.version_htt => switch (c) {
'P' => self.state = .version_http,
else => return error.HttpVersionNotSupported,
},
.version_http => switch (c) {
'/' => self.state = .@"version_http/@",
else => return error.HttpVersionNotSupported,
},
.@"version_http/@" => switch (c) {
'1' => self.state = .@"version_http/1@",
else => return error.HttpVersionNotSupported,
},
.@"version_http/1@" => switch (c) {
'.' => self.state = .@"version_http/1.@",
else => return error.HttpVersionNotSupported,
},
.@"version_http/1.@" => switch (c) {
'1' => self.state = .version_complete,
else => return error.HttpVersionNotSupported,
},
.version_complete => switch (c) {
'\r' => self.state = .start_line_end,
else => return error.HttpVersionNotSupported,
},
.start_line_end => switch (c) {
'\n' => self.state = .header_name_start,
else => return error.MissingLineFeed,
},
.header_name_start => switch (c) {
'\r' => self.state = .headers_end,
else => self.state = .{ .header_name = c_slice },
},
.header_name => |name| switch (c) {
':' => {
self.state = .headerValue(name, @as([*]const u8, @ptrCast(c_ptr))[1..1]);
self.current_header_is_content_length = std.ascii.eqlIgnoreCase(name, "Content-Length");
},
else => self.state = .{ .header_name = name.ptr[0 .. name.len + 1] },
},
.header_value => |s| switch (c) {
'\r' => {
self.state = .header_line_end;
const value_trimmed = std.mem.trim(u8, s.value, " \t");
if (self.current_header_is_content_length) {
self.content_length = std.fmt.parseInt(usize, value_trimmed, 10) catch return error.InvalidContentLength;
self.current_header_is_content_length = false;
}
if (self.callbacks.header) |headerCallback| {
headerCallback(self.callbacks.self, s.name, value_trimmed);
}
},
else => self.state = .headerValue(s.name, s.value.ptr[0 .. s.value.len + 1]),
},
.header_line_end => switch (c) {
'\n' => self.state = .header_name_start,
else => return error.MissingLineFeed,
},
.headers_end => switch (c) {
'\n' => {
if (self.content_length == 0) {
if (self.callbacks.body) |bodyCallback| {
bodyCallback(self.callbacks.self, &.{});
}
return .done;
}
self.state = .{ .body = @as([*]const u8, @ptrCast(c_ptr))[1..1] };
},
else => return error.MissingLineFeed,
},
.body => |body| {
const new_body = body.ptr[0 .. body.len + 1];
self.state = .{ .body = new_body };
if (new_body.len >= self.content_length) {
if (self.callbacks.body) |bodyCallback| {
bodyCallback(self.callbacks.self, new_body);
}
return .done;
}
},
}
return .not_done;
}

6
packages/myid/src/id.zig Normal file
View File

@@ -0,0 +1,6 @@
const std = @import("std");
const web = @import("web");
pub const App = web.Id(.app);
pub const Session = web.Id(.session);
pub const User = web.Id(.user);

View File

@@ -1,235 +1,3 @@
const std = @import("std");
const sqlite = @import("sqlite");
const uuid = @import("uuid.zig");
fn Id(comptime _tag: @Type(.enum_literal)) type {
return struct {
pub const tag = _tag;
bytes: [16]u8,
pub fn new() @This() {
return .{ .bytes = uuid.uuid_v7() };
}
pub fn eql(a: @This(), b: @This()) bool {
return std.mem.eql(u8, &a.bytes, &b.bytes);
}
pub fn decode(encoded: *const [22]u8) !@This() {
var bytes: [16]u8 = undefined;
try std.base64.url_safe_no_pad.Decoder.decode(&bytes, encoded);
return .{ .bytes = bytes };
}
pub fn encode(self: @This()) [22]u8 {
var text: [22]u8 = undefined;
std.base64.url_safe_no_pad.Encoder.encode(&text, self.bytes);
return text;
}
};
}
pub const AppId = Id(.app_id);
pub const UserId = Id(.user_id);
pub const CreateAppResult = struct {
aid: AppId,
plain_secret: [32]u8,
};
pub const Database = struct {
db: sqlite.Db,
allocator: std.mem.Allocator,
pub fn init(path: [:0]const u8, allocator: std.mem.Allocator) !Database {
var db = try sqlite.Db.init(.{
.mode = .{ .File = path },
.open_flags = .{
.create = true,
.write = true,
},
.threading_mode = .MultiThread,
});
errdefer db.deinit();
_ = try db.one(void, "PRAGMA journal_mode = WAL", .{}, .{});
_ = try db.one(void, "PRAGMA foreign_keys = ON", .{}, .{});
return .{
.db = db,
.allocator = allocator,
};
}
pub fn deinit(self: *Database) void {
self.db.deinit();
self.* = undefined;
}
// --- MIGRATION -----------------------------------------------------------
fn getUserVersion(self: *Database) !i32 {
const version = try self.db.one(i32, "PRAGMA user_version", .{}, .{});
return version.?;
}
fn setUserVersion(self: *Database, version: i32) !void {
var buf: [100]u8 = undefined;
const query = std.fmt.bufPrint(&buf, "PRAGMA user_version = {d}", .{version}) catch unreachable;
_ = try self.db.oneDynamic(void, query, .{}, .{});
}
pub fn migrate(self: *Database) !void {
var user_version = try self.getUserVersion();
if (user_version == 0) {
_ = try self.db.exec(
\\CREATE TABLE users (
\\ uid BLOB NOT NULL,
\\ name TEXT NOT NULL,
\\ email TEXT NOT NULL UNIQUE,
\\ password TEXT NOT NULL,
\\ PRIMARY KEY (uid)
\\)
, .{}, .{});
_ = try self.db.exec(
\\CREATE TABLE apps (
\\ aid BLOB NOT NULL,
\\ name TEXT NOT NULL,
\\ secret TEXT NOT NULL,
\\ PRIMARY KEY (aid)
\\)
, .{}, .{});
_ = try self.db.exec(
\\CREATE TABLE app_callbacks (
\\ aid BLOB NOT NULL,
\\ callback TEXT NOT NULL,
\\ PRIMARY KEY (aid, callback),
\\ FOREIGN KEY (aid) REFERENCES apps (aid)
\\ ON UPDATE CASCADE
\\ ON DELETE CASCADE
\\)
, .{}, .{});
user_version += 1;
try self.setUserVersion(user_version);
}
}
// --- USERS ---------------------------------------------------------------
pub fn createUser(self: *Database, name: []const u8, email: []const u8, plain_password: []const u8) !UserId {
const uid = UserId.new();
var password_buf: [1000]u8 = undefined;
const password = try std.crypto.pwhash.argon2.strHash(plain_password, .{
.allocator = self.allocator,
.mode = .argon2id,
.params = .owasp_2id,
}, &password_buf);
try self.db.exec("INSERT INTO users (uid, name, email, password) VALUES (?, ?, ?, ?)", .{}, .{
.uid = uid.bytes,
.name = name,
.email = email,
.password = password,
});
return uid;
}
// --- APPS ----------------------------------------------------------------
pub fn createApp(self: *Database, name: []const u8, callbacks: []const []const u8) !CreateAppResult {
_ = try self.db.exec("BEGIN", .{}, .{});
errdefer self.db.exec("ROLLBACK", .{}, .{}) catch unreachable;
const secret_bytes = blk: {
var bytes: [24]u8 = undefined;
std.crypto.random.bytes(&bytes);
break :blk bytes;
};
var plain_secret_buf: [32]u8 = undefined;
const plain_secret = std.base64.url_safe_no_pad.Encoder.encode(&plain_secret_buf, &secret_bytes);
std.debug.assert(plain_secret_buf.len == plain_secret.len);
const aid = AppId.new();
var secret_buf: [1000]u8 = undefined;
const secret = try std.crypto.pwhash.argon2.strHash(plain_secret, .{
.allocator = self.allocator,
.mode = .argon2id,
.params = .owasp_2id,
}, &secret_buf);
try self.db.exec("INSERT INTO apps (aid, name, secret) VALUES (?, ?, ?)", .{}, .{
.aid = aid.bytes,
.name = name,
.secret = secret,
});
var insert_callback = try self.db.prepare("INSERT INTO app_callbacks (aid, callback) VALUES (?, ?)");
defer insert_callback.deinit();
for (callbacks) |callback| {
try insert_callback.exec(.{}, .{ .aid = aid.bytes, .callback = callback });
insert_callback.reset();
}
try self.db.exec("COMMIT", .{}, .{});
return .{
.aid = aid,
.plain_secret = plain_secret_buf,
};
}
};
test "user version" {
var db = try Database.init(":memory:", std.testing.allocator);
defer db.deinit();
try std.testing.expectEqual(0, try db.getUserVersion());
try db.setUserVersion(1);
try std.testing.expectEqual(1, try db.getUserVersion());
}
test "migrate" {
var db = try Database.init(":memory:", std.testing.allocator);
defer db.deinit();
try db.migrate();
}
test "create user" {
var db = try Database.init(":memory:", std.testing.allocator);
defer db.deinit();
try db.migrate();
const uid = try db.createUser("admin", "admin@test.invalid", "admin");
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
const maybe_user = try db.db.oneAlloc(struct {
name: []const u8,
email: []const u8,
password: []const u8,
}, arena.allocator(), "SELECT name, email, password FROM users WHERE uid = ?", .{}, .{
.uid = uid.bytes,
});
defer arena.deinit();
try std.testing.expect(maybe_user != null);
if (maybe_user) |user| {
try std.testing.expectEqualSlices(u8, "admin", user.name);
}
}
test "create app" {
var db = try Database.init(":memory:", std.testing.allocator);
defer db.deinit();
try db.migrate();
_ = try db.createApp("app", &.{
"http://localhost:3000/callback",
"https://example.com/callback",
});
}
const web = @import("web");

View File

@@ -1,41 +0,0 @@
const std = @import("std");
var lock: std.Thread.Mutex = .{};
var last_timestamp: std.atomic.Value(u64) = .{ .raw = 0 };
var counter: std.atomic.Value(u32) = .{ .raw = 0 };
fn getCount(timestamp: u64) u32 {
lock.lock();
defer lock.unlock();
if (last_timestamp.swap(timestamp, .monotonic) != timestamp) {
counter.store(0, .monotonic);
}
return counter.fetchAdd(1, .monotonic) % 4096;
}
pub fn uuid_v7() [16]u8 {
const timestamp: u64 = @intCast(@max(0, std.time.milliTimestamp()));
const count = getCount(timestamp);
const random = blk: {
var bytes: [8]u8 = undefined;
std.crypto.random.bytes(&bytes);
break :blk bytes;
};
var res: [16]u8 = undefined;
res[0] = @truncate(timestamp >> 40);
res[1] = @truncate(timestamp >> 32);
res[2] = @truncate(timestamp >> 24);
res[3] = @truncate(timestamp >> 16);
res[4] = @truncate(timestamp >> 8);
res[5] = @truncate(timestamp);
res[6] = (@as(u8, 7) << 4) | @as(u8, @truncate((count >> 8) & 0x0F));
res[7] = @truncate(count);
res[8] = 0x80 | (random[0] & 0x3F);
@memcpy(res[9..16], random[1..8]);
return res;
}

View File

@@ -1,7 +1,19 @@
const std = @import("std");
pub fn build(b: *std.Build) void {
_ = b.addModule("vecmath", .{
const target = b.standardTargetOptions(.{});
const mod = b.addModule("vecmath", .{
.root_source_file = b.path("src/root.zig"),
.target = target,
});
const mod_tests = b.addTest(.{
.root_module = mod,
});
const run_mod_tests = b.addRunArtifact(mod_tests);
const test_step = b.step("test", "Run tests");
test_step.dependOn(&run_mod_tests.step);
}

View File

@@ -1,7 +1,7 @@
.{
.name = .vecmath,
.version = "0.0.0",
.minimum_zig_version = "0.15.2",
.minimum_zig_version = "0.16.0",
.paths = .{
"src",
"build.zig",

View File

@@ -93,7 +93,11 @@ pub const Color = extern struct {
return @bitCast(self);
}
pub fn format(self: Color, w: *std.io.Writer) !void {
pub fn format(self: Color, w: *std.Io.Writer) !void {
try w.print("#{X:0>2}{X:0>2}{X:0>2}{X:0>2}", .{ self.r, self.g, self.b, self.a });
}
test "refAllDecls" {
std.testing.refAllDecls(@This());
}
};

View File

@@ -24,7 +24,11 @@ pub const ColorHdr = extern struct {
return @bitCast(self);
}
pub fn format(self: ColorHdr, w: *std.io.Writer) !void {
pub fn format(self: ColorHdr, w: *std.Io.Writer) !void {
try w.print("ColorHdr[{d}, {d}, {d}, {d}]", .{ self.r, self.g, self.b, self.a });
}
test "refAllDecls" {
std.testing.refAllDecls(@This());
}
};

View File

@@ -252,4 +252,8 @@ pub const Matrix3x2 = extern struct {
.ty = -inv_det * (self.tx * iy + self.ty * jy),
};
}
test "refAllDecls" {
std.testing.refAllDecls(@This());
}
};

View File

@@ -406,4 +406,8 @@ pub const Matrix3x2x8 = struct {
.ty = -inv_det * (self.tx * iy + self.ty * jy),
};
}
test "refAllDecls" {
std.testing.refAllDecls(@This());
}
};

View File

@@ -507,4 +507,8 @@ pub const Matrix4x4 = extern struct {
// zig fmt: on
};
}
test "refAllDecls" {
std.testing.refAllDecls(@This());
}
};

View File

@@ -763,4 +763,8 @@ pub const Matrix4x4x8 = extern struct {
// zig fmt: on
};
}
test "refAllDecls" {
std.testing.refAllDecls(@This());
}
};

View File

@@ -110,5 +110,5 @@ pub inline fn unlerpInt64(a: i64, b: i64, x: i64) f32 {
}
test "refAllDecls" {
std.testing.refAllDeclsRecursive(@This());
std.testing.refAllDecls(@This());
}

View File

@@ -123,7 +123,11 @@ pub const Complex = extern struct {
};
}
pub fn format(self: Complex, w: *std.io.Writer) !void {
pub fn format(self: Complex, w: *std.Io.Writer) !void {
try w.print("Complex[{d:.3}, {d:.3}]", .{ self.re, self.im });
}
test "refAllDecls" {
std.testing.refAllDecls(@This());
}
};

View File

@@ -193,4 +193,8 @@ pub const Complex_x8 = struct {
.im = @mulAdd(vm.f32x8, vm.ps(t), b.im, @mulAdd(vm.f32x8, -vm.ps(t), a.im, a.im)),
};
}
test "refAllDecls" {
std.testing.refAllDecls(@This());
}
};

View File

@@ -179,7 +179,11 @@ pub const Quaternion = extern struct {
};
}
pub fn format(self: Quaternion, w: *std.io.Writer) !void {
pub fn format(self: Quaternion, w: *std.Io.Writer) !void {
try w.print("Quaternion[{d:.3}, {d:.3}, {d:.3}, {d:.3}]", .{ self.x, self.y, self.z, self.w });
}
test "refAllDecls" {
std.testing.refAllDecls(@This());
}
};

View File

@@ -278,4 +278,8 @@ pub const Quaternion_x8 = struct {
.w = @mulAdd(vm.f32x8, vm.ps(t), b.w, @mulAdd(vm.f32x8, -vm.ps(t), a.w, a.w)),
};
}
test "refAllDecls" {
std.testing.refAllDecls(@This());
}
};

View File

@@ -31,3 +31,7 @@ pub inline fn epi64(value: i64) i64x4 {
pub inline fn epu64(value: u64) u64x4 {
return @splat(value);
}
test "refAllDecls" {
std.testing.refAllDecls(@This());
}

View File

@@ -235,3 +235,7 @@ test cossin_x8 {
cossin_x8(.{ -1, -0.75, -0.5, -0.25, 0, 0.25, 0.5, 0.75 }),
);
}
test "refAllDecls" {
std.testing.refAllDecls(@This());
}

View File

@@ -167,7 +167,11 @@ pub const Vector2 = extern struct {
};
}
pub fn format(self: Vector2, w: *std.io.Writer) !void {
pub fn format(self: Vector2, w: *std.Io.Writer) !void {
try w.print("[{d}, {d}]", .{ self.x, self.y });
}
test "refAllDecls" {
std.testing.refAllDecls(@This());
}
};

View File

@@ -110,10 +110,14 @@ pub const Vector2Int = extern struct {
return self.x * other.y - self.y * other.x;
}
pub fn format(self: Vector2Int, w: *std.io.Writer) !void {
pub fn format(self: Vector2Int, w: *std.Io.Writer) !void {
try w.print("[{X:0>8}, {X:0>8}]", .{
@as(u32, @bitCast(self.x)),
@as(u32, @bitCast(self.y)),
});
}
test "refAllDecls" {
std.testing.refAllDecls(@This());
}
};

View File

@@ -171,4 +171,8 @@ pub const Vector2Int_x8 = struct {
pub inline fn cross(self: Vector2Int_x8, other: Vector2Int_x8) vm.i32x8 {
return self.x * other.y - self.y * other.x;
}
test "refAllDecls" {
std.testing.refAllDecls(@This());
}
};

View File

@@ -231,4 +231,8 @@ pub const Vector2x8 = struct {
.y = self.x * vm.ps(m.iy) + self.y * vm.ps(m.jy),
};
}
test "refAllDecls" {
std.testing.refAllDecls(@This());
}
};

View File

@@ -192,7 +192,11 @@ pub const Vector3 = extern struct {
};
}
pub fn format(self: Vector3, w: *std.io.Writer) !void {
pub fn format(self: Vector3, w: *std.Io.Writer) !void {
try w.print("[{d}, {d}, {d}]", .{ self.x, self.y, self.z });
}
test "refAllDecls" {
std.testing.refAllDecls(@This());
}
};

View File

@@ -117,11 +117,15 @@ pub const Vector3Int = extern struct {
};
}
pub fn format(self: Vector3Int, w: *std.io.Writer) !void {
pub fn format(self: Vector3Int, w: *std.Io.Writer) !void {
try w.print("[{X:0>8}, {X:0>8}, {X:0>8}]", .{
@as(u32, @bitCast(self.x)),
@as(u32, @bitCast(self.y)),
@as(u32, @bitCast(self.z)),
});
}
test "refAllDecls" {
std.testing.refAllDecls(@This());
}
};

View File

@@ -180,4 +180,8 @@ pub const Vector3Int_x8 = struct {
.z = self.x * other.y - self.y * other.x,
};
}
test "refAllDecls" {
std.testing.refAllDecls(@This());
}
};

View File

@@ -258,4 +258,8 @@ pub const Vector3x8 = struct {
.z = v.x * vm.ps(self.iz) + v.y * vm.ps(self.jz) + v.z * vm.ps(self.kz),
};
}
test "refAllDecls" {
std.testing.refAllDecls(@This());
}
};

View File

@@ -147,7 +147,11 @@ pub const Vector4 = extern struct {
};
}
pub fn format(self: Vector4, w: *std.io.Writer) !void {
pub fn format(self: Vector4, w: *std.Io.Writer) !void {
try w.print("[{d}, {d}, {d}, {d}]", .{ self.x, self.y, self.z, self.w });
}
test "refAllDecls" {
std.testing.refAllDecls(@This());
}
};

View File

@@ -112,7 +112,7 @@ pub const Vector4Int = extern struct {
return self.x * other.x + self.y * other.y + self.z * other.z + self.w * other.w;
}
pub fn format(self: Vector4Int, w: *std.io.Writer) !void {
pub fn format(self: Vector4Int, w: *std.Io.Writer) !void {
try w.print("[{X:0>8}, {X:0>8}, {X:0>8}, {X:0>8}]", .{
@as(u32, @bitCast(self.x)),
@as(u32, @bitCast(self.y)),
@@ -120,4 +120,8 @@ pub const Vector4Int = extern struct {
@as(u32, @bitCast(self.w)),
});
}
test "refAllDecls" {
std.testing.refAllDecls(@This());
}
};

View File

@@ -177,4 +177,8 @@ pub const Vector4Int_x8 = struct {
pub inline fn dot(self: Vector4Int_x8, other: Vector4Int_x8) vm.i32x8 {
return self.x * other.x + self.y * other.y + self.z * other.z + self.w * other.w;
}
test "refAllDecls" {
std.testing.refAllDecls(@This());
}
};

View File

@@ -217,4 +217,8 @@ pub const Vector4x8 = struct {
.w = self.x * vm.ps(m.iw) + self.y * vm.ps(m.jw) + self.z * vm.ps(m.kw) + self.w * vm.ps(m.tw),
};
}
test "refAllDecls" {
std.testing.refAllDecls(@This());
}
};

52
packages/web/build.zig Normal file
View File

@@ -0,0 +1,52 @@
const std = @import("std");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const module = b.addModule("web", .{
.root_source_file = b.path("src/root.zig"),
.target = target,
.optimize = optimize,
.link_libc = true,
});
module.linkSystemLibrary("ssl", .{});
module.linkSystemLibrary("crypto", .{});
const tests = b.addTest(.{
.root_module = module,
});
const exe_module = b.createModule(.{
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
.imports = &.{
.{ .name = "web", .module = module },
},
});
const exe = b.addExecutable(.{
.name = "web",
.root_module = exe_module,
});
b.installArtifact(exe);
const run_step = b.step("run", "Run the app");
const run_cmd = b.addRunArtifact(exe);
run_step.dependOn(&run_cmd.step);
run_cmd.step.dependOn(b.getInstallStep());
if (b.args) |args| {
run_cmd.addArgs(args);
}
const run_tests = b.addRunArtifact(tests);
const test_step = b.step("test", "Run tests");
test_step.dependOn(&run_tests.step);
}

View File

@@ -0,0 +1,11 @@
.{
.name = .web,
.version = "0.0.0",
.minimum_zig_version = "0.15.2",
.paths = .{
"src",
"build.zig",
"build.zig.zon",
},
.fingerprint = 0x15c938517148d390,
}

9
packages/web/cert.pem Normal file
View File

@@ -0,0 +1,9 @@
-----BEGIN CERTIFICATE-----
MIIBPDCB76ADAgECAhRuWLL9k0QOIt4+BPNlFBzRcmvREDAFBgMrZXAwFDESMBAG
A1UEAwwJbG9jYWxob3N0MB4XDTI2MDMwODIwNDczNFoXDTM2MDMwNTIwNDczNFow
FDESMBAGA1UEAwwJbG9jYWxob3N0MCowBQYDK2VwAyEAdgB1CRIUYLCPclWp2+5c
X3I0aqoY7yuhZBE9NxKKbZ+jUzBRMB0GA1UdDgQWBBQXJ3/C7HMVOjXcnqvDKdWo
PWhzsTAfBgNVHSMEGDAWgBQXJ3/C7HMVOjXcnqvDKdWoPWhzsTAPBgNVHRMBAf8E
BTADAQH/MAUGAytlcANBAFirv6F5+TamfddV1sElbfI8jfhPLxWU+z6/mxxbzooX
IKdOh/FrCrPfeUavKH9C5Vr+ztPgoTdCaFqo5mT7MAw=
-----END CERTIFICATE-----

3
packages/web/key.pem Normal file
View File

@@ -0,0 +1,3 @@
-----BEGIN PRIVATE KEY-----
MC4CAQAwBQYDK2VwBCIEIMDUsa31l2xEhX1gyB5w2WABgSbne3GHyUe0RWomq9C5
-----END PRIVATE KEY-----

View File

@@ -0,0 +1,147 @@
const std = @import("std");
const Connection = @This();
const FileDescriptor = @import("FileDescriptor.zig").FileDescriptor;
const openssl = @import("openssl.zig");
const iovec = std.posix.iovec;
const iovec_const = std.posix.iovec_const;
address: std.net.Address,
fd: FileDescriptor,
ssl: ?*openssl.Ssl,
node: std.DoublyLinkedList.Node = .{},
// TODO Consider proper usage of `send` syscall with `MSG_MORE` flag and setting
// the `TCP_CORK` option.
pub fn reinit(
self: *Connection,
address: std.net.Address,
fd: FileDescriptor,
ssl: ?*openssl.Ssl,
) void {
self.address = address;
self.fd = fd;
self.ssl = ssl;
}
pub fn deinit(self: *Connection) void {
if (self.ssl) |x| x.free();
self.fd.close();
self.* = undefined;
}
pub fn read(self: *const Connection, buf: []u8) !usize {
if (self.ssl) |ssl| {
const bytes_read = try ssl.read(buf);
return bytes_read;
} else {
const bytes_read = try self.fd.read(buf);
return bytes_read;
}
}
pub fn readAll(self: *const Connection, buf: []u8) !void {
if (self.ssl) |ssl| {
try ssl.readAll(buf);
} else {
try self.fd.readAll(buf);
}
}
pub fn readv(self: *const Connection, iov: []const iovec) !usize {
if (self.ssl) |ssl| {
var total_bytes_read: usize = 0;
for (iov) |io| {
const bytes_read = try ssl.read(io.base[0..io.len]);
total_bytes_read += bytes_read;
if (bytes_read < io.len) {
return total_bytes_read;
}
}
return total_bytes_read;
} else {
const bytes_read = try self.fd.readv(iov);
return bytes_read;
}
}
/// Might modify `iov` when `readv` syscall reads partially.
pub fn readvAll(self: *const Connection, iov: []iovec) !void {
if (self.ssl) |ssl| {
for (iov) |io| {
try ssl.readAll(io.base[0..io.len]);
}
} else {
try self.fd.readvAll(iov);
}
}
pub fn write(self: *const Connection, buf: []const u8) !usize {
if (self.ssl) |ssl| {
const bytes_written = try ssl.write(buf);
return bytes_written;
} else {
const bytes_written = try self.fd.write(buf);
return bytes_written;
}
}
pub fn writeAll(self: *const Connection, buf: []const u8) !void {
if (self.ssl) |ssl| {
try ssl.writeAll(buf);
} else {
try self.fd.writeAll(buf);
}
}
pub fn writev(self: *const Connection, iov: []const iovec_const) !usize {
if (self.ssl) |ssl| {
var total_bytes_written: usize = 0;
for (iov) |io| {
const bytes_written = try ssl.write(io.base[0..io.len]);
total_bytes_written += bytes_written;
if (bytes_written < io.len) {
return total_bytes_written;
}
}
return total_bytes_written;
} else {
const bytes_written = try self.fd.writev(iov);
return bytes_written;
}
}
/// Might modify `iov` when `writev` syscall writes partially.
pub fn writevAll(self: *const Connection, iov: []iovec_const) !void {
if (self.ssl) |ssl| {
for (iov) |io| {
try ssl.writeAll(io.base[0..io.len]);
}
} else {
try self.fd.writevAll(iov);
}
}
pub fn sendfile(self: *const Connection, fd: FileDescriptor, offset: usize, len: usize) !usize {
if (self.ssl) |ssl| {
const bytes_written = try ssl.sendfile(fd, offset, len);
return bytes_written;
} else {
const bytes_written = try self.fd.sendfile(fd, offset, len);
return bytes_written;
}
}
pub fn sendfileAll(self: *const Connection, fd: FileDescriptor, offset: usize, len: usize) !void {
if (self.ssl) |ssl| {
try ssl.sendfileAll(fd, offset, len);
} else {
try self.fd.sendfileAll(fd, offset, len);
}
}

View File

@@ -0,0 +1,238 @@
const std = @import("std");
const linux = std.os.linux;
const errno = linux.E.init;
const iovec = std.posix.iovec;
const iovec_const = std.posix.iovec_const;
pub const FileDescriptor = enum(i32) {
stdin = 0,
stdout = 1,
stderr = 2,
_,
pub fn memfd_create(name: [*:0]const u8, flags: u32) !FileDescriptor {
const rc = linux.memfd_create(name, flags);
return switch (errno(rc)) {
.SUCCESS => @enumFromInt(@as(i32, @intCast(rc))),
else => error.SystemError,
};
}
pub fn socket(domain: u32, socket_type: u32, protocol: u32) !FileDescriptor {
const rc = linux.socket(domain, socket_type, protocol);
return switch (errno(rc)) {
.SUCCESS => @enumFromInt(@as(i32, @intCast(rc))),
else => error.SystemError,
};
}
pub fn close(self: FileDescriptor) void {
_ = linux.close(@intFromEnum(self));
}
pub fn accept(self: FileDescriptor, noalias addr: ?*linux.sockaddr, noalias len: ?*linux.socklen_t) !FileDescriptor {
while (true) {
const rc = linux.accept(@intFromEnum(self), addr, len);
switch (errno(rc)) {
.SUCCESS => return @enumFromInt(@as(i32, @intCast(rc))),
.INPROGRESS, .AGAIN => continue,
.CONNABORTED => return error.ConnectionAborted,
.PERM => return error.BlockedByFirewall,
else => return error.SystemError,
}
}
}
pub fn bind(self: FileDescriptor, addr: *const linux.sockaddr, len: linux.socklen_t) !void {
const rc = linux.bind(@intFromEnum(self), addr, len);
return switch (errno(rc)) {
.SUCCESS => {},
.ACCES, .PERM => error.AccessDenied,
.ADDRINUSE => error.AddressInUse,
.ADDRNOTAVAIL => error.AddressNotAvailable,
.AFNOSUPPORT => error.AddressFamilyNotSupported,
.LOOP => error.SymLinkLoop,
.NAMETOOLONG => error.NameTooLong,
.NOENT => error.FileNotFound,
.NOTDIR => error.NotDir,
.ROFS => error.ReadOnlyFileSystem,
else => error.SystemError,
};
}
pub fn ftruncate(self: FileDescriptor, length: i64) !void {
const rc = linux.ftruncate(@intFromEnum(self), length);
return switch (errno(rc)) {
.SUCCESS => {},
else => error.SystemError,
};
}
pub fn getsockname(self: FileDescriptor, noalias addr: *linux.sockaddr, noalias len: *linux.socklen_t) !void {
const rc = linux.getsockname(@intFromEnum(self), addr, len);
return switch (errno(rc)) {
.SUCCESS => {},
else => error.SystemError,
};
}
pub fn listen(self: FileDescriptor, backlog: u32) !void {
const rc = linux.listen(@intFromEnum(self), backlog);
return switch (errno(rc)) {
.SUCCESS => {},
.ADDRINUSE => error.AddressInUse,
else => error.SystemError,
};
}
pub fn read(self: FileDescriptor, buf: []u8) !usize {
while (true) {
const rc = linux.read(@intFromEnum(self), buf.ptr, buf.len);
switch (errno(rc)) {
.SUCCESS => return rc,
.INTR => continue,
.AGAIN => return error.Timeout,
else => return error.SystemError,
}
}
}
pub fn readAll(self: FileDescriptor, buf: []u8) !void {
var total_bytes_read: usize = 0;
while (total_bytes_read < buf.len) {
const chunk = buf[total_bytes_read..];
const bytes_read = try self.read(chunk);
total_bytes_read += bytes_read;
}
}
pub fn readv(self: FileDescriptor, iov: []const iovec) !usize {
while (true) {
const rc = linux.readv(@intFromEnum(self), iov.ptr, iov.len);
switch (errno(rc)) {
.SUCCESS => return rc,
.INTR => continue,
.AGAIN, .WOULDBLOCK => error.Timeout,
else => return error.SystemError,
}
}
}
/// Might modify `iov` when `readv` syscall reads partially.
pub fn readvAll(self: FileDescriptor, iov: []iovec) !void {
var total_bytes_read: usize = 0;
var i: usize = 0;
while (i < iov.len) {
var bytes_read = try self.readv(iov[i..]);
total_bytes_read += bytes_read;
// skip whole buffers
while (i < iov.len and bytes_read >= iov[i].len) {
bytes_read -= iov[i].len;
i += 1;
}
// skip part of a buffer
if (bytes_read > 0) {
iov[i].base += bytes_read;
iov[i].len -= bytes_read;
}
}
}
pub fn setsockopt(self: FileDescriptor, level: i32, optname: u32, opt: []const u8) !void {
const rc = linux.setsockopt(@intFromEnum(self), level, optname, opt.ptr, @intCast(opt.len));
return switch (errno(rc)) {
.SUCCESS => {},
else => error.SystemError,
};
}
pub fn write(self: FileDescriptor, buf: []const u8) !usize {
while (true) {
const rc = linux.write(@intFromEnum(self), buf.ptr, buf.len);
switch (errno(rc)) {
.SUCCESS => return rc,
.INTR => continue,
else => return error.SystemError,
}
}
}
pub fn writeAll(self: FileDescriptor, buf: []const u8) !void {
var total_bytes_written: usize = 0;
while (total_bytes_written < buf.len) {
const chunk = buf[total_bytes_written..];
const bytes_written = try self.write(chunk);
total_bytes_written += bytes_written;
}
}
pub fn writev(self: FileDescriptor, iov: []const iovec_const) !usize {
while (true) {
const rc = linux.writev(@intFromEnum(self), iov.ptr, iov.len);
switch (errno(rc)) {
.SUCCESS => return rc,
.INTR => continue,
else => return error.SystemError,
}
}
}
/// Might modify `iov` when `writev` syscall writes partially.
pub fn writevAll(self: FileDescriptor, iov: []iovec_const) !void {
var total_bytes_written: usize = 0;
var i: usize = 0;
while (i < iov.len) {
var bytes_written = try self.writev(iov[i..]);
total_bytes_written += bytes_written;
// skip whole buffers
while (i < iov.len and bytes_written >= iov[i].len) {
bytes_written -= iov[i].len;
i += 1;
}
// skip part of a buffer
if (bytes_written > 0) {
iov[i].base += bytes_written;
iov[i].len -= bytes_written;
}
}
}
/// Calls `fstat` and returns total size in bytes.
pub fn size(self: FileDescriptor) !usize {
var stat = std.mem.zeroes(linux.Stat);
const rc = linux.fstat(@intFromEnum(self), &stat);
return switch (errno(rc)) {
.SUCCESS => @intCast(stat.size),
else => error.SystemError,
};
}
pub fn sendfile(self: FileDescriptor, fd: FileDescriptor, offset: usize, len: usize) !usize {
var offset_mut = offset;
while (true) {
const rc = linux.sendfile(@intFromEnum(self), @intFromEnum(fd), @ptrCast(&offset_mut), len);
switch (errno(rc)) {
.SUCCESS => return rc,
.INTR => continue,
else => return error.SystemError,
}
}
}
pub fn sendfileAll(self: FileDescriptor, fd: FileDescriptor, offset: usize, len: usize) !void {
var total_bytes_sent: usize = 0;
while (total_bytes_sent < len) {
const bytes_sent = try self.sendfile(fd, offset + total_bytes_sent, len - total_bytes_sent);
total_bytes_sent += bytes_sent;
}
}
};

41
packages/web/src/Id.zig Normal file
View File

@@ -0,0 +1,41 @@
const std = @import("std");
const UUID = @import("UUID.zig");
const decoder = &std.base64.url_safe_no_pad.Decoder;
const encoder = &std.base64.url_safe_no_pad.Encoder;
pub fn Id(comptime _tag: @Type(.enum_literal)) type {
return struct {
pub const tag = _tag;
uuid: UUID,
pub fn init(uuid: UUID) @This() {
return .{ .uuid = uuid };
}
pub fn eql(a: @This(), b: @This()) bool {
return std.mem.eql(u8, &a.bytes, &b.bytes);
}
pub fn decode(encoded: *const [22]u8) !@This() {
var bytes: [16]u8 = undefined;
decoder.decode(&bytes, encoded) catch |err| switch (err) {
error.InvalidCharacter => return error.InvalidId,
error.InvalidPadding, error.NoSpaceLeft => unreachable,
};
return .{ .bytes = bytes };
}
pub fn encode(self: @This()) [22]u8 {
var text: [22]u8 = undefined;
self.encodeInto(&text);
return text;
}
pub fn encodeInto(self: @This(), text: *[22]u8) void {
encoder.encode(text, self.bytes);
}
};
}

View File

@@ -0,0 +1,66 @@
const std = @import("std");
const Request = @This();
const http = @import("http.zig");
method: http.Method,
pathname: []const u8,
headers: *HeaderHashMap,
body: []const u8,
pub const HeaderHashMap = std.HashMapUnmanaged(
http.FieldName,
HeaderList,
http.FieldName.HashMapContext,
std.hash_map.default_max_load_percentage,
);
pub const HeaderList = struct {
list: std.SinglyLinkedList,
len: usize,
};
pub const HeaderValue = struct {
value: []const u8,
node: std.SinglyLinkedList.Node,
};
/// Gets a header field value of a given `name`. When there is no such header,
/// `null` is returned. When there is more than one header with the same name,
/// the value of the field that came later will be returned.
pub fn getHeader(self: *const Request, name: http.FieldName) ?[]const u8 {
if (self.headers.get(name)) |list| {
if (list.list.first) |head| {
const header_value: *HeaderValue = @fieldParentPtr("node", head);
return header_value.value;
}
}
}
pub fn getHeaderKnown(self: *const Request, known: http.KnownFieldName) ?[]const u8 {
return self.getHeader(.initKnonw(known));
}
pub fn getHeaders(self: *const Request, name: http.FieldName, values: [][]const u8) []const []const u8 {
var i: usize = 0;
var node: ?*std.SinglyLinkedList.Node = if (self.headers.get(name)) |list| list.list else null;
while (i < values.len) {
if (node) |n| {
const header_value: *HeaderValue = @fieldParentPtr("node", n);
values[i] = header_value.value;
i += 1;
node = n.next;
} else {
break;
}
}
return values[0..i];
}
pub fn getHeaderCount(self: *const Request, name: http.FieldName) usize {
const list = self.headers.get(name) orelse return 0;
return list.len;
}

View File

@@ -0,0 +1,16 @@
const std = @import("std");
const RequestHandler = @This();
const Request = @import("Request.zig");
const Response = @import("Response.zig");
ptr: *anyopaque,
vtable: *const VTable,
pub const VTable = struct {
handle: *const fn (self: *anyopaque, request: *Request, response: *Response) anyerror!void,
};
pub inline fn handle(self: RequestHandler, request: *Request, response: *Response) anyerror!void {
try self.vtable.handle(self.ptr, request, response);
}

View File

@@ -0,0 +1,85 @@
const std = @import("std");
const Response = @This();
const Connection = @import("Connection.zig");
const FileDescriptor = @import("FileDescriptor.zig").FileDescriptor;
const http = @import("http.zig");
const iovec = std.posix.iovec;
const iovec_const = std.posix.iovec_const;
pub const State = union(enum) {
init: void,
sent: void,
errored: anyerror,
pub fn initErrored(err: anyerror) State {
return .{ .errored = err };
}
};
connection: *Connection,
header_writer: std.Io.Writer,
body_writer: std.Io.Writer,
state: State,
pub fn init(connection: *Connection, header_write_buffer: []u8, body_write_buffer: []u8) Response {
return .{
.connection = connection,
.header_writer = .fixed(header_write_buffer),
.body_writer = .fixed(body_write_buffer),
.state = .init,
};
}
pub fn sendHeadersOnly(self: *Response) void {
std.debug.assert(self.state == .init);
const headers_slice = self.header_writer.buffered();
std.debug.assert(headers_slice.len > 0);
if (self.connection.writeAll(headers_slice)) {
self.state = .sent;
} else |err| {
self.state = .initErrored(err);
}
}
pub fn sendHeadersAndBody(self: *Response) void {
std.debug.assert(self.state == .init);
const headers_slice = self.header_writer.buffered();
const body_slice = self.body_writer.buffered();
std.debug.assert(headers_slice.len > 0);
std.debug.assert(body_slice.len > 0);
var iov = [_]iovec_const{
.{ .base = headers_slice.ptr, .len = headers_slice.len },
.{ .base = body_slice.ptr, .len = body_slice.len },
};
if (self.connection.writevAll(&iov)) {
self.state = .sent;
} else |err| {
self.state = .initErrored(err);
}
}
pub fn sendHeadersAndFile(self: *Response, fd: FileDescriptor, offset: usize, maybe_len: ?usize) void {
std.debug.assert(self.state == .init);
const headers_slice = self.header_writer.buffered();
std.debug.assert(headers_slice.len > 0);
const len = maybe_len orelse (try fd.size()) - offset;
if (self.connection.writeAll(headers_slice)) {
if (self.connection.sendfileAll(fd, offset, len)) {
self.state = .sent;
} else |err2| {
self.state = .initErrored(err2);
}
} else |err1| {
self.state = .initErrored(err1);
}
}

375
packages/web/src/Server.zig Normal file
View File

@@ -0,0 +1,375 @@
const std = @import("std");
const Server = @This();
const Connection = @import("Connection.zig");
const FileDescriptor = @import("FileDescriptor.zig").FileDescriptor;
const http = @import("http.zig");
const openssl = @import("openssl.zig");
const Request = @import("Request.zig");
const RequestHandler = @import("RequestHandler.zig");
const Worker = @import("Worker.zig");
const log = std.log.scoped(.Server);
const linux = std.os.linux;
const errno = linux.E.init;
fd: FileDescriptor,
address: std.net.Address,
ssl_ctx: ?*openssl.SslContext,
workers: []Worker,
threads: []std.Thread,
request_handler: RequestHandler,
read_timeout_us: u64,
connection_queue: std.DoublyLinkedList,
// NOTE Connection pool has no need for being doubly-linked, but the queue has
// (as it's FIFO) and we want a single intrusive Node to be able to participate
// in both lists. This is possible because a connection will never belong to
// both lists at the same time.
connection_pool: std.DoublyLinkedList,
connection_buffer: []Connection,
mutex: std.Thread.Mutex,
cond_connection_queued: std.Thread.Condition,
cond_connection_freed: std.Thread.Condition,
/// 4 kiB
const page_size = 4 * 1024;
/// 2 MiB
const huge_page_size = 2 * 1024 * 1024;
pub const Options = struct {
request_handler: RequestHandler,
address: std.net.Address = .initIp4(.{ 127, 0, 0, 1 }, 8000),
/// If not `null`, the server will use TLS with the provided OpenSSL
/// context.
ssl_ctx: ?*openssl.SslContext = null,
max_connections: u32 = 128,
/// The number of worker threads. If set to `0`, the number of worker
/// threads will be equal to the number of logical CPU cores.
worker_count: u32 = 0,
/// The maximum number of header fields the `Request` object will be able to
/// store. An HTTP request will be rejected if it has more header fields
/// than the capacity.
max_header_fields: u32 = 256,
/// The number of 2 MiB pages reserved for a single read buffer. Each worker
/// has its own read buffer. An HTTP request (headers and content combined)
/// will be rejected if it is larger than the read buffer.
read_buffer_huge_pages: u32 = 1,
/// The number of 4 kiB pages reserved for a single header write buffer.
/// Each worker has its own header write buffer. The HTTP status line, all
/// header fields and the CRLF terminator must all fit in the header write
/// buffer.
header_write_buffer_pages: u32 = 1,
/// The number of 2 MiB pages reserved for a single body write buffer. Each
/// worker has its own body write buffer. The HTTP response body must fit
/// entirely within the body write buffer. This restriction only applies to
/// bodies generated with the body writer and not to bodies sent with
/// `sendfile`.
body_write_buffer_huge_pages: u32 = 1,
/// How much time should a worker wait on an idle connection before closing
/// it. Specifically, how much time can a `read` syscall block for, before
/// the connection is forcefully closed.
read_timeout_us: u64 = 1 * std.time.us_per_s,
};
pub fn init(allocator: std.mem.Allocator, options: Options) !Server {
const worker_count = if (options.worker_count > 0) options.worker_count else try std.Thread.getCpuCount();
// Create socket fd
const fd: FileDescriptor = try .socket(
options.address.any.family,
linux.SOCK.STREAM | linux.SOCK.CLOEXEC,
if (options.address.any.family == linux.AF.UNIX) 0 else linux.IPPROTO.TCP,
);
errdefer fd.close();
const opt = std.mem.toBytes(@as(c_int, 1));
try fd.setsockopt(linux.SOL.SOCKET, linux.SO.REUSEADDR, &opt);
try fd.setsockopt(linux.SOL.SOCKET, linux.SO.REUSEPORT, &opt);
var socklen = options.address.getOsSockLen();
try fd.bind(&options.address.any, socklen);
try fd.listen(options.max_connections);
var listen_address = options.address;
try fd.getsockname(&listen_address.any, &socklen);
// Allocate arrays
const workers = try allocator.alloc(Worker, worker_count);
errdefer allocator.free(workers);
const connection_buffer = try allocator.alloc(Connection, options.max_connections);
errdefer allocator.free(connection_buffer);
const threads = try allocator.alloc(std.Thread, worker_count);
errdefer allocator.free(threads);
// Allocate and remap read buffers
const single_read_buffer_size = @as(usize, options.read_buffer_huge_pages) * huge_page_size;
const all_read_buffers_size = worker_count * single_read_buffer_size;
const double_single_read_buffer_size = 2 * single_read_buffer_size;
const double_all_read_buffers_size = 2 * all_read_buffers_size;
const read_buffer_fd: FileDescriptor = try .memfd_create("read_buffer", 0);
defer read_buffer_fd.close();
try read_buffer_fd.ftruncate(@intCast(all_read_buffers_size));
const read_buffer_ptr = try errOrPtr(linux.mmap(
null,
double_all_read_buffers_size,
linux.PROT.NONE,
linux.MAP{ .TYPE = .PRIVATE, .ANONYMOUS = true },
-1,
0,
));
errdefer _ = linux.munmap(read_buffer_ptr, double_all_read_buffers_size);
_ = linux.madvise(read_buffer_ptr, double_all_read_buffers_size, linux.MADV.HUGEPAGE);
for (0..worker_count) |i| {
const offset = i * single_read_buffer_size;
const double_offset = i * double_single_read_buffer_size;
try err(linux.mmap(
read_buffer_ptr + double_offset,
single_read_buffer_size,
linux.PROT.READ | linux.PROT.WRITE,
linux.MAP{ .TYPE = .SHARED, .FIXED = true },
@intFromEnum(read_buffer_fd),
@intCast(offset),
));
try err(linux.mmap(
read_buffer_ptr + double_offset + single_read_buffer_size,
single_read_buffer_size,
linux.PROT.READ | linux.PROT.WRITE,
linux.MAP{ .TYPE = .SHARED, .FIXED = true },
@intFromEnum(read_buffer_fd),
@intCast(offset),
));
}
// Allocate header write buffer
const single_header_write_buffer_size = @as(usize, options.header_write_buffer_pages) * page_size;
const all_header_write_buffers_size = worker_count * single_header_write_buffer_size;
const header_write_buffer_ptr = try errOrPtr(linux.mmap(
null,
all_header_write_buffers_size,
linux.PROT.READ | linux.PROT.WRITE,
linux.MAP{ .TYPE = .PRIVATE, .ANONYMOUS = true },
-1,
0,
));
errdefer _ = linux.munmap(header_write_buffer_ptr, all_header_write_buffers_size);
// Allocate body write buffer
const single_body_write_buffer_size = @as(usize, options.body_write_buffer_huge_pages) * huge_page_size;
const all_body_write_buffers_size = worker_count * single_body_write_buffer_size;
const body_write_buffer_ptr = try errOrPtr(linux.mmap(
null,
all_body_write_buffers_size,
linux.PROT.READ | linux.PROT.WRITE,
linux.MAP{ .TYPE = .PRIVATE, .ANONYMOUS = true },
-1,
0,
));
errdefer _ = linux.munmap(body_write_buffer_ptr, all_body_write_buffers_size);
_ = linux.madvise(body_write_buffer_ptr, all_body_write_buffers_size, linux.MADV.HUGEPAGE);
// Initialize workers
var workers_initialized: usize = 0;
errdefer {
for (workers[0..workers_initialized]) |*worker| {
worker.deinit(allocator);
}
}
for (workers, 0..) |*worker, i| {
const read_offset = i * double_single_read_buffer_size;
const header_write_offset = i * single_header_write_buffer_size;
const body_write_offset = i * single_body_write_buffer_size;
worker.* = try Worker.init(allocator, .{
.worker_id = i,
.max_header_fields = options.max_header_fields,
.read_buffer_ptr = read_buffer_ptr + read_offset,
.read_buffer_size = single_read_buffer_size,
.header_write_buffer = (header_write_buffer_ptr + header_write_offset)[0..single_header_write_buffer_size],
.body_write_buffer = (body_write_buffer_ptr + body_write_offset)[0..single_body_write_buffer_size],
});
workers_initialized += 1;
}
// Fill connection pool
var connection_pool: std.DoublyLinkedList = .{};
for (connection_buffer) |*c| {
connection_pool.prepend(&c.node);
}
return .{
.fd = fd,
.address = listen_address,
.ssl_ctx = options.ssl_ctx,
.workers = workers,
.threads = threads,
.request_handler = options.request_handler,
.read_timeout_us = options.read_timeout_us,
.connection_queue = .{},
.connection_pool = connection_pool,
.connection_buffer = connection_buffer,
.mutex = .{},
.cond_connection_queued = .{},
.cond_connection_freed = .{},
};
}
pub fn deinit(self: *Server, allocator: std.mem.Allocator) void {
log.debug("Deinitializing Server.", .{});
const worker_count = self.workers.len;
const single_read_buffers_size = self.workers[0].read_buffer_size;
const all_read_buffers_size = worker_count * single_read_buffers_size;
const double_all_read_buffers_size = 2 * all_read_buffers_size;
const single_header_write_buffer_size = self.workers[0].header_write_buffer.len;
const all_header_write_buffers_size = worker_count * single_header_write_buffer_size;
const single_body_write_buffer_size = self.workers[0].body_write_buffer.len;
const all_body_write_buffers_size = worker_count * single_body_write_buffer_size;
const read_buffer_ptr = self.workers[0].read_buffer_ptr;
const header_write_buffer_ptr = self.workers[0].header_write_buffer.ptr;
const body_write_buffer_ptr = self.workers[0].body_write_buffer.ptr;
for (self.workers) |*worker| {
worker.deinit(allocator);
}
_ = linux.munmap(body_write_buffer_ptr, all_body_write_buffers_size);
_ = linux.munmap(header_write_buffer_ptr, all_header_write_buffers_size);
_ = linux.munmap(read_buffer_ptr, double_all_read_buffers_size);
allocator.free(self.threads);
allocator.free(self.connection_buffer);
allocator.free(self.workers);
self.fd.close();
self.* = undefined;
}
/// This method block until the server is stopped, which is achieved by storing
/// `false` into `running`. You should use another thread or interruption
/// handler to be able to stop the server.
pub fn listen(self: *Server, running: *const std.atomic.Value(bool)) !void {
var worker_running: std.atomic.Value(bool) = .init(running.load(.acquire));
var spawned: usize = 0;
defer {
log.debug("Storing `false` into worker_running.", .{});
worker_running.store(false, .release);
log.debug("Broadcasting connection queued condition variable.", .{});
self.cond_connection_queued.broadcast();
for (self.threads[0..spawned], 0..) |*thread, i| {
log.debug("Joining the thread of worker #{d}.", .{i});
thread.join();
}
}
for (self.workers, 0..) |*worker, i| {
log.debug("Spawning thread for worker #{d}.", .{i});
self.threads[i] = try std.Thread.spawn(.{}, Worker.worker, .{ worker, self, &worker_running });
spawned += 1;
}
while (running.load(.acquire)) {
var address: std.net.Address = undefined;
var address_size: u32 = @sizeOf(std.net.Address);
log.debug("Accepting connection.", .{});
const fd = self.fd.accept(&address.any, &address_size) catch |e| {
log.err("Error while accepting connection: {}", .{e});
continue;
};
log.debug("Accepted connection from {f}", .{address});
const timeout: linux.timeval = .{
.sec = @intCast(self.read_timeout_us / std.time.us_per_s),
.usec = @intCast(self.read_timeout_us % std.time.us_per_s),
};
try fd.setsockopt(linux.SOL.SOCKET, linux.SO.RCVTIMEO, std.mem.asBytes(&timeout));
const ssl: ?*openssl.Ssl = self.maybeInitSsl(fd) catch |e| {
log.err("Error while estabilishing SSL connection: {}", .{e});
fd.close();
continue;
};
{
log.debug("Acquiring mutex.", .{});
self.mutex.lock();
log.debug("Acquired mutex.", .{});
defer {
log.debug("Unlocking mutex.", .{});
self.mutex.unlock();
}
while (true) {
if (self.connection_pool.pop()) |node| {
const connection: *Connection = @fieldParentPtr("node", node);
connection.reinit(address, fd, ssl);
log.debug("Adding connection to {f} to the connection queue.", .{connection.address});
self.connection_queue.prepend(node);
break;
}
log.debug("Waiting on connection freed condition variable.", .{});
self.cond_connection_freed.wait(&self.mutex);
log.debug("Woken up on connection freed condition variable.", .{});
}
}
log.debug("Signaling connection queued condition variable.", .{});
self.cond_connection_queued.signal();
} else {
log.debug("Loaded `false` from running, the accept loop exited.", .{});
}
}
fn maybeInitSsl(self: *const Server, fd: FileDescriptor) !?*openssl.Ssl {
if (self.ssl_ctx) |ssl_ctx| {
const ssl = try openssl.Ssl.new(ssl_ctx);
try ssl.setFd(fd);
log.debug("Accepting SSL layer.", .{});
try ssl.accept();
log.debug("Accepted SSL layer.", .{});
return ssl;
} else {
return null;
}
}
fn err(rc: usize) !void {
const e = errno(rc);
return if (e != .SUCCESS) error.SystemError else {};
}
fn errOrPtr(rc: usize) ![*]u8 {
const e = errno(rc);
return if (e != .SUCCESS) error.SystemError else @ptrFromInt(rc);
}

87
packages/web/src/UUID.zig Normal file
View File

@@ -0,0 +1,87 @@
const std = @import("std");
const UUID = @This();
bytes: [16]u8,
pub const zero: UUID = .{ .bytes = @splat(0) };
pub fn v4() UUID {
var bytes: [16]u8 = undefined;
std.crypto.random.bytes(&bytes);
bytes[6] = (bytes[6] & 0x0F) | 0x40;
bytes[8] = (bytes[8] & 0x3F) | 0x80;
return .{ .bytes = bytes };
}
pub const namespaces = struct {
/// Name string is a fully-qualified domain name
///
/// `6ba7b810-9dad-11d1-80b4-00c04fd430c8`
pub const dns: UUID = .{ .bytes = .{ 0x6B, 0xA7, 0xB8, 0x10, 0x9D, 0xAD, 0x11, 0xD1, 0x80, 0xB4, 0x00, 0xC0, 0x4F, 0xD4, 0x30, 0xC8 } };
/// Name string is a URL
///
/// `6ba7b811-9dad-11d1-80b4-00c04fd430c8`
pub const url: UUID = .{ .bytes = .{ 0x6B, 0xA7, 0xB8, 0x11, 0x9D, 0xAD, 0x11, 0xD1, 0x80, 0xB4, 0x00, 0xC0, 0x4F, 0xD4, 0x30, 0xC8 } };
/// Name string is an ISO OID
///
/// `6ba7b812-9dad-11d1-80b4-00c04fd430c8`
pub const oid: UUID = .{ .bytes = .{ 0x6B, 0xA7, 0xB8, 0x12, 0x9D, 0xAD, 0x11, 0xD1, 0x80, 0xB4, 0x00, 0xC0, 0x4F, 0xD4, 0x30, 0xC8 } };
/// Name string is an X.500 DN (in DER or a text output format)
///
/// `6ba7b814-9dad-11d1-80b4-00c04fd430c8`
pub const x500: UUID = .{ .bytes = .{ 0x6B, 0xA7, 0xB8, 0x14, 0x9D, 0xAD, 0x11, 0xD1, 0x80, 0xB4, 0x00, 0xC0, 0x4F, 0xD4, 0x30, 0xC8 } };
};
pub fn v5(namespace: UUID, name: []const u8) UUID {
const hash = blk: {
var hasher = std.crypto.hash.Sha1.init(.{});
hasher.update(namespace);
hasher.update(name);
break :blk hasher.finalResult();
};
var bytes: [16]u8 = hash[0..16].*;
bytes[6] = (bytes[6] & 0x0F) | 0x50;
bytes[8] = (bytes[8] & 0x3F) | 0x80;
return .{ .bytes = bytes };
}
var v7_lock: std.Thread.Mutex = .{};
var v7_last_timestamp: std.atomic.Value(u64) = .{ .raw = 0 };
var v7_counter: std.atomic.Value(u32) = .{ .raw = 0 };
fn getCount(timestamp: u64) u32 {
v7_lock.lock();
defer v7_lock.unlock();
if (v7_last_timestamp.swap(timestamp, .monotonic) != timestamp) {
v7_counter.store(0, .monotonic);
}
return v7_counter.fetchAdd(1, .monotonic) % 4096;
}
pub fn v7() UUID {
const timestamp: u64 = @intCast(@max(0, std.time.milliTimestamp()));
const count = getCount(timestamp);
const random = blk: {
var bytes: [8]u8 = undefined;
std.crypto.random.bytes(&bytes);
break :blk bytes;
};
var bytes: [16]u8 = undefined;
bytes[0] = @truncate(timestamp >> 40);
bytes[1] = @truncate(timestamp >> 32);
bytes[2] = @truncate(timestamp >> 24);
bytes[3] = @truncate(timestamp >> 16);
bytes[4] = @truncate(timestamp >> 8);
bytes[5] = @truncate(timestamp);
bytes[6] = (@as(u8, 7) << 4) | @as(u8, @truncate((count >> 8) & 0x0F));
bytes[7] = @truncate(count);
bytes[8] = (random[0] & 0x3F) | 0x80;
@memcpy(bytes[9..16], random[1..8]);
return .{ .bytes = bytes };
}

321
packages/web/src/Worker.zig Normal file
View File

@@ -0,0 +1,321 @@
const std = @import("std");
const Worker = @This();
const Connection = @import("Connection.zig");
const FileDescriptor = @import("FileDescriptor.zig").FileDescriptor;
const http = @import("http.zig");
const Request = @import("Request.zig");
const RequestHandler = @import("RequestHandler.zig");
const Response = @import("Response.zig");
const Server = @import("Server.zig");
const log = std.log.scoped(.Worker);
/// Integer unique for this worker. Has no functional meaning. Can be used for
/// debugging and profiling.
worker_id: usize,
read_buffer_ptr: [*]u8,
read_buffer_size: usize,
read_head: usize,
read_tail: usize,
header_write_buffer: []u8,
body_write_buffer: []u8,
header_hash_map: Request.HeaderHashMap,
header_value_buffer: []Request.HeaderValue,
pub const Options = struct {
worker_id: usize,
max_header_fields: u32,
read_buffer_ptr: [*]u8,
read_buffer_size: usize,
header_write_buffer: []u8,
body_write_buffer: []u8,
};
pub fn init(allocator: std.mem.Allocator, options: Options) !Worker {
var header_hash_map: Request.HeaderHashMap = .empty;
try header_hash_map.ensureTotalCapacity(allocator, options.max_header_fields);
errdefer header_hash_map.deinit(allocator);
const header_value_buffer = try allocator.alloc(Request.HeaderValue, options.max_header_fields);
errdefer allocator.free(header_value_buffer);
return .{
.worker_id = options.worker_id,
.read_buffer_ptr = options.read_buffer_ptr,
.read_buffer_size = options.read_buffer_size,
.read_head = 0,
.read_tail = 0,
.header_write_buffer = options.header_write_buffer,
.body_write_buffer = options.body_write_buffer,
.header_hash_map = header_hash_map,
.header_value_buffer = header_value_buffer,
};
}
pub fn deinit(self: *Worker, allocator: std.mem.Allocator) void {
log.debug("[#{d}] Deinitializing Worker.", .{self.worker_id});
allocator.free(self.header_value_buffer);
self.header_hash_map.deinit(allocator);
self.* = undefined;
}
pub fn worker(
self: *Worker,
server: *Server,
running: *const std.atomic.Value(bool),
) void {
log.debug("[#{d}] Acquiring mutex.", .{self.worker_id});
server.mutex.lock();
log.debug("[#{d}] Acquired mutex.", .{self.worker_id});
defer {
log.debug("[#{d}] Unlocking mutex.", .{self.worker_id});
server.mutex.unlock();
}
while (running.load(.acquire)) {
if (server.connection_queue.pop()) |node| {
const connection: *Connection = @fieldParentPtr("node", node);
log.debug("[#{d}] Popped connection to {f} from the connection queue.", .{ self.worker_id, connection.address });
log.debug("[#{d}] Unlocking mutex.", .{self.worker_id});
server.mutex.unlock();
defer {
log.debug("[#{d}] Acquiring mutex.", .{self.worker_id});
server.mutex.lock();
log.debug("[#{d}] Acquired mutex.", .{self.worker_id});
log.debug("[#{d}] Returning connection to connection pool.", .{self.worker_id});
server.connection_pool.append(&connection.node);
log.debug("[#{d}] Signaling connection freed condition variable.", .{self.worker_id});
server.cond_connection_freed.signal();
}
log.debug("[#{d}] Handling connection to {f}.", .{ self.worker_id, connection.address });
self.handleConnection(server.request_handler, connection, running) catch |err| {
log.err("[#{d}] Error while handling connection: {}", .{ self.worker_id, err });
};
} else {
log.debug("[#{d}] Waiting on connection queued condition variable.", .{self.worker_id});
server.cond_connection_queued.wait(&server.mutex);
log.debug("[#{d}] Woken up on connection queued condition variable.", .{self.worker_id});
}
} else {
log.debug("[#{d}] Loaded `false` from running, the worker loop exited.", .{self.worker_id});
}
}
fn handleConnection(
self: *Worker,
request_handler: RequestHandler,
connection: *Connection,
running: *const std.atomic.Value(bool),
) !void {
defer connection.deinit();
while (running.load(.acquire)) {
const res = self.handleRequest(request_handler, connection) catch |err| {
log.err("[#{d}] Error while handling request: {}", .{ self.worker_id, err });
return err;
};
if (!res) {
log.debug("[#{d}] Request handler indicated to stop handling the connection to {f}.", .{ self.worker_id, connection.address });
break;
}
} else {
log.debug("[#{d}] Loaded `false` from running, the connection handler loop exited.", .{self.worker_id});
}
}
fn handleRequest(
self: *Worker,
request_handler: RequestHandler,
connection: *Connection,
) !bool {
self.header_hash_map.clearRetainingCapacity();
var request: Request = .{
.method = undefined,
.pathname = undefined,
.headers = &self.header_hash_map,
.body = undefined,
};
var response: Response = .init(connection, self.header_write_buffer, self.body_write_buffer);
var parser: http.Parser = .init();
var next_header_index: usize = 0;
var ignore: bool = false;
var client_closed: bool = false;
var leftover_bytes = self.read_tail - self.read_head;
const max_read_tail = self.read_head + self.read_buffer_size;
while (true) {
var bytes_read: usize = undefined;
var chunk: []const u8 = undefined;
if (leftover_bytes > 0) {
bytes_read = leftover_bytes;
chunk = self.read_buffer_ptr[self.read_tail - leftover_bytes .. self.read_tail];
leftover_bytes = 0;
} else {
const read_tail = self.read_tail;
bytes_read = connection.read(self.read_buffer_ptr[read_tail..max_read_tail]) catch |err| switch (err) {
error.Timeout => {
log.debug("[#{d}] Connection to {f} timed out.", .{ self.worker_id, connection.address });
return false;
},
else => return err,
};
if (bytes_read == 0) {
log.debug("[#{d}] Read zero bytes from connection to {f}.", .{ self.worker_id, connection.address });
return false;
}
chunk = self.read_buffer_ptr[read_tail .. read_tail + bytes_read];
self.read_tail += bytes_read;
}
const res = parser.consume(chunk) catch |err| {
switch (err) {
error.MethodNotSupported => try closeWith(&response, http.status.method_not_allowed),
error.HttpVersionNotSupported => try closeWith(&response, http.status.http_version_not_supported),
error.SyntaxError => try closeWith(&response, http.status.bad_request),
}
return false;
};
const done = if (res.result) |result| std.meta.activeTag(result) == .body else false;
if (self.read_tail - self.read_head >= self.read_buffer_size and !done) {
if (parser.state == .body) {
try closeWith(&response, http.status.content_too_large);
} else {
try closeWith(&response, http.status.request_header_fields_too_large);
}
return false;
}
if (res.result) |result| {
switch (result) {
.method => |method| request.method = method,
.pathname => |pathname| request.pathname = pathname,
.header => |header| blk: {
if (header.isNamedKnown(.Connection) and std.mem.eql(u8, header.value, "close")) {
client_closed = true;
}
if (ignore) {
break :blk;
}
if (next_header_index >= self.header_value_buffer.len or self.header_hash_map.available == 0) {
// TODO Here, we could ignore, but make sure this does
// not clash with the other "request too long" checks
// (i.e. be careful not to double respond).
_ = &ignore;
try closeWith(&response, http.status.request_header_fields_too_large);
return false;
} else {
const entry = self.header_hash_map.getOrPutAssumeCapacity(header.name);
const header_value = &self.header_value_buffer[next_header_index];
header_value.* = .{ .node = .{}, .value = header.value };
next_header_index += 1;
if (!entry.found_existing) {
entry.value_ptr.* = .{
.len = 0,
.list = .{},
};
}
entry.value_ptr.list.prepend(&header_value.node);
entry.value_ptr.len += 1;
}
},
.end_of_headers => {},
.body => |body| {
request.body = body;
if (!ignore) {
request_handler.handle(&request, &response) catch |err| {
if (response.state == .init) {
response.header_writer.end = 0;
response.body_writer.end = 0;
const error_name = @errorName(err);
try response.body_writer.print("Internal Server Error\n{s}\n", .{error_name});
try response.header_writer.writeAll(http.status.internal_server_error);
try response.header_writer.writeAll("Content-Type: text/plain; charset=utf-8\r\n");
try response.header_writer.print("Content-Length: {d}\r\n", .{response.body_writer.end});
try response.header_writer.writeAll("\r\n");
response.sendHeadersAndBody();
}
};
}
if (response.state == .init) {
const no_headers = response.header_writer.end > 0;
const no_body = response.body_writer.end > 0;
if (no_headers) {
if (no_body) {
try response.header_writer.writeAll(http.status.no_content);
try response.header_writer.writeAll("\r\n");
response.sendHeadersOnly();
} else {
try response.header_writer.writeAll(http.status.ok);
try response.header_writer.writeAll("Content-Type: application/octet-stream");
try response.header_writer.print("Content-Length: {d}\r\n", .{response.body_writer.end});
try response.header_writer.writeAll("\r\n");
response.sendHeadersAndBody();
}
} else {
if (no_body) {
response.sendHeadersOnly();
} else {
response.sendHeadersAndBody();
}
}
}
leftover_bytes = bytes_read - res.consumed;
self.read_head = (self.read_tail - leftover_bytes) & ~(self.read_buffer_size - 1);
self.read_tail = self.read_head + leftover_bytes;
return !client_closed;
},
}
}
leftover_bytes = bytes_read - res.consumed;
}
}
fn closeWith(response: *Response, status_line: []const u8) !void {
// This function is meant to be called before a request handler gets to do
// anything.
std.debug.assert(response.header_writer.end == 0);
std.debug.assert(response.body_writer.end == 0);
std.debug.assert(response.state == .init);
try response.header_writer.writeAll(status_line);
try response.header_writer.writeAll("Connection: close\r\n");
try response.header_writer.writeAll("\r\n");
}

View File

@@ -0,0 +1,8 @@
const std = @import("std");
pub const FieldName = @import("http/FieldName.zig").FieldName;
pub const Header = @import("http/Header.zig");
pub const KnownFieldName = @import("http/KnownFieldName.zig").KnownFieldName;
pub const Method = @import("http/Method.zig").Method;
pub const Parser = @import("http/Parser.zig");
pub const status = @import("http/status.zig");

View File

@@ -0,0 +1,91 @@
const std = @import("std");
const KnownFieldName = @import("KnownFieldName.zig").KnownFieldName;
const Wyhash = std.hash.Wyhash;
pub const FieldName = extern struct {
data: [16]u8 align(8),
const tag_known: u8 = 0x00;
const tag_long: u8 = 0x01;
const tag_short_bias: u8 = 0x02;
pub fn init(name: []const u8) FieldName {
var data: [16]u8 align(8) = @splat(0);
if (KnownFieldName.isKnownFieldName(name)) |known| {
data[0] = tag_known;
@as(*KnownFieldName, @ptrCast(data[8..16])).* = known;
} else if (name.len <= 15) {
data[0] = @intCast(name.len + tag_short_bias);
@memcpy(data[1..][0..name.len], name);
} else {
data[0] = tag_long;
@as(*u32, @ptrCast(data[4..8])).* = @intCast(name.len);
@as(*usize, @ptrCast(data[8..16])).* = @intFromPtr(name.ptr);
}
return .{ .data = data };
}
pub fn initKnown(known: KnownFieldName) FieldName {
var data: [16]u8 align(8) = @splat(0);
data[0] = tag_known;
@as(*KnownFieldName, @ptrCast(data[8..16])).* = known;
return .{ .data = data };
}
fn getKnown(self: FieldName) KnownFieldName {
std.debug.assert(self.data[0] == tag_known);
const intval: u64 = @bitCast(self.data[8..16].*);
return @enumFromInt(intval);
}
fn getLong(self: FieldName) []const u8 {
std.debug.assert(self.data[0] == tag_long);
const len: u32 = @bitCast(self.data[4..8].*);
const intptr: usize = @bitCast(self.data[8..16].*);
const ptr: [*]const u8 = @ptrFromInt(intptr);
return ptr[0..len];
}
fn getShort(self: FieldName) []const u8 {
std.debug.assert(self.data[0] >= tag_short_bias);
const len: u8 = self.data[0] - tag_short_bias;
const str = self.data[1..][0..len];
return str;
}
pub fn hash(self: FieldName) u64 {
return switch (self.data[0]) {
tag_known => Wyhash.hash(0, self.data[8..16]),
tag_long => Wyhash.hash(1, self.getLong()),
else => Wyhash.hash(2, self.getShort()),
};
}
pub fn eql(a: FieldName, b: FieldName) bool {
const a_tag = a.data[0];
const b_tag = b.data[0];
if (a_tag != b_tag) return false;
return switch (a_tag) {
tag_known => a.getKnown() == b.getKnown(),
tag_long => std.mem.eql(u8, a.getLong(), b.getLong()),
else => std.mem.eql(u8, a.getShort(), b.getShort()),
};
}
pub const HashMapContext = struct {
pub fn hash(_: HashMapContext, key: FieldName) u64 {
return key.hash();
}
pub fn eql(_: HashMapContext, a: FieldName, b: FieldName) bool {
return FieldName.eql(a, b);
}
};
};

View File

@@ -0,0 +1,23 @@
const std = @import("std");
const Header = @This();
const FieldName = @import("FieldName.zig").FieldName;
const KnownFieldName = @import("KnownFieldName.zig").KnownFieldName;
name: FieldName,
value: []const u8,
pub fn init(name: FieldName, value: []const u8) Header {
return .{
.name = name,
.value = value,
};
}
pub fn isNamed(self: Header, name: FieldName) bool {
return FieldName.eql(self.name, name);
}
pub fn isNamedKnown(self: Header, known: KnownFieldName) bool {
return FieldName.eql(self.name, .initKnown(known));
}

View File

@@ -0,0 +1,367 @@
const std = @import("std");
pub const KnownFieldName = enum(u64) {
// --- STANDARD FIELD NAMES ------------------------------------------------
// These are all names listed under:
//
// https://www.iana.org/assignments/http-fields/http-fields.xhtml
//
// Some of them might be obsoleted or deprecated; they are included here
// nonetheless.
//
// When the list was last retrieved, its "Last Updated" date was 2026-03-06.
@"A-IM",
Accept,
@"Accept-Additions",
@"Accept-CH",
@"Accept-Charset",
@"Accept-Datetime",
@"Accept-Encoding",
@"Accept-Features",
@"Accept-Language",
@"Accept-Patch",
@"Accept-Post",
@"Accept-Query",
@"Accept-Ranges",
@"Accept-Signature",
@"Access-Control",
@"Access-Control-Allow-Credentials",
@"Access-Control-Allow-Headers",
@"Access-Control-Allow-Methods",
@"Access-Control-Allow-Origin",
@"Access-Control-Expose-Headers",
@"Access-Control-Max-Age",
@"Access-Control-Request-Headers",
@"Access-Control-Request-Method",
@"Activate-Storage-Access",
Age,
Allow,
ALPN,
@"Alt-Svc",
@"Alt-Used",
Alternates,
@"AMP-Cache-Transform",
@"Apply-To-Redirect-Ref",
@"Authentication-Control",
@"Authentication-Info",
Authorization,
@"Available-Dictionary",
@"C-Ext",
@"C-Man",
@"C-Opt",
@"C-PEP",
@"C-PEP-Info",
@"Cache-Control",
@"Cache-Group-Invalidation",
@"Cache-Groups",
@"Cache-Status",
@"Cal-Managed-ID",
@"CalDAV-Timezones",
@"Capsule-Protocol",
@"CDN-Cache-Control",
@"CDN-Loop",
@"Cert-Not-After",
@"Cert-Not-Before",
@"Clear-Site-Data",
@"Client-Cert",
@"Client-Cert-Chain",
Close,
@"CMCD-Object",
@"CMCD-Request",
@"CMCD-Session",
@"CMCD-Status",
@"CMSD-Dynamic",
@"CMSD-Static",
@"Concealed-Auth-Export",
@"Configuration-Context",
Connection,
@"Content-Base",
@"Content-Digest",
@"Content-Disposition",
@"Content-Encoding",
@"Content-ID",
@"Content-Language",
@"Content-Length",
@"Content-Location",
@"Content-MD5",
@"Content-Range",
@"Content-Script-Type",
@"Content-Security-Policy",
@"Content-Security-Policy-Report-Only",
@"Content-Style-Type",
@"Content-Type",
@"Content-Version",
Cookie,
Cookie2,
@"Cross-Origin-Embedder-Policy",
@"Cross-Origin-Embedder-Policy-Report-Only",
@"Cross-Origin-Opener-Policy",
@"Cross-Origin-Opener-Policy-Report-Only",
@"Cross-Origin-Resource-Policy",
@"CTA-Common-Access-Token",
DASL,
Date,
DAV,
@"Default-Style",
@"Delta-Base",
Deprecation,
Depth,
@"Derived-From",
Destination,
@"Detached-JWS",
@"Differential-ID",
@"Dictionary-ID",
Digest,
DPoP,
@"DPoP-Nonce",
@"Early-Data",
@"EDIINT-Features",
ETag,
Expect,
@"Expect-CT",
Expires,
Ext,
Forwarded,
From,
GetProfile,
Hobareg,
Host,
@"HTTP2-Settings",
If,
@"If-Match",
@"If-Modified-Since",
@"If-None-Match",
@"If-Range",
@"If-Schedule-Tag-Match",
@"If-Unmodified-Since",
IM,
@"Include-Referred-Token-Binding-ID",
Incremental,
Isolation,
@"Keep-Alive",
Label,
@"Last-Event-ID",
@"Last-Modified",
Link,
@"Link-Template",
Location,
@"Lock-Token",
Man,
@"Max-Forwards",
@"Memento-Datetime",
Meter,
@"Method-Check",
@"Method-Check-Expires",
@"MIME-Version",
Negotiate,
NEL,
@"OData-EntityId",
@"OData-Isolation",
@"OData-MaxVersion",
@"OData-Version",
Opt,
@"Optional-WWW-Authenticate",
@"Ordering-Type",
Origin,
@"Origin-Agent-Cluster",
OSCORE,
@"OSLC-Core-Version",
Overwrite,
P3P,
PEP,
@"PEP-Info",
@"Permissions-Policy",
@"PICS-Label",
@"Ping-From",
@"Ping-To",
Position,
Pragma,
Prefer,
@"Preference-Applied",
Priority,
ProfileObject,
Protocol,
@"Protocol-Info",
@"Protocol-Query",
@"Protocol-Request",
@"Proxy-Authenticate",
@"Proxy-Authentication-Info",
@"Proxy-Authorization",
@"Proxy-Features",
@"Proxy-Instruction",
@"Proxy-Status",
Public,
@"Public-Key-Pins",
@"Public-Key-Pins-Report-Only",
Range,
@"Redirect-Ref",
Referer,
@"Referer-Root",
@"Referrer-Policy",
Refresh,
@"Repeatability-Client-ID",
@"Repeatability-First-Sent",
@"Repeatability-Request-ID",
@"Repeatability-Result",
@"Replay-Nonce",
@"Reporting-Endpoints",
@"Repr-Digest",
@"Retry-After",
Safe,
@"Schedule-Reply",
@"Schedule-Tag",
@"Sec-Fetch-Dest",
@"Sec-Fetch-Mode",
@"Sec-Fetch-Site",
@"Sec-Fetch-Storage-Access",
@"Sec-Fetch-User",
@"Sec-GPC",
@"Sec-Purpose",
@"Sec-Token-Binding",
@"Sec-WebSocket-Accept",
@"Sec-WebSocket-Extensions",
@"Sec-WebSocket-Key",
@"Sec-WebSocket-Protocol",
@"Sec-WebSocket-Version",
@"Security-Scheme",
Server,
@"Server-Timing",
@"Set-Cookie",
@"Set-Cookie2",
@"Set-Txn",
SetProfile,
Signature,
@"Signature-Input",
SLUG,
SoapAction,
@"Status-URI",
@"Strict-Transport-Security",
Sunset,
@"Surrogate-Capability",
@"Surrogate-Control",
TCN,
TE,
Timeout,
@"Timing-Allow-Origin",
Topic,
Traceparent,
Tracestate,
Trailer,
@"Transfer-Encoding",
TTL,
Upgrade,
Urgency,
URI,
@"Use-As-Dictionary",
@"User-Agent",
@"Variant-Vary",
Vary,
Via,
@"Want-Content-Digest",
@"Want-Digest",
@"Want-Repr-Digest",
Warning,
@"WWW-Authenticate",
@"X-Content-Type-Options",
@"X-Frame-Options",
// --- NON-STANDARD FIELD NAMES --------------------------------------------
// These names include, but are not limited to:
//
// - Cloudflare HTTP headers
// https://developers.cloudflare.com/fundamentals/reference/http-headers/
// - Entries from MDN marked as "non-standard", but not "deprecated"
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Attribution-Reporting-Register-Trigger
@"Cf-Cache-Status",
@"CF-Connecting-IP",
@"CF-Connecting-IPv6",
@"CF-Connecting-O2O",
@"CF-EW-Via",
@"CF-IPCountry",
@"CF-Pseudo-IPv4",
@"Cf-Ray",
@"CF-Visitor",
@"CF-Worker",
@"Idempotency-Key",
@"True-Client-IP",
@"X-Accel-Buffering",
@"X-Accel-Charset",
@"X-Accel-Limit-Rate",
@"X-Accel-Redirect",
@"X-API-Key",
@"X-Correlation-ID",
@"X-DNS-Prefetch-Control",
@"X-Forwarded-For",
@"X-Forwarded-Host",
@"X-Forwarded-Proto",
@"X-Permitted-Cross-Domain-Policies",
@"X-Powered-By",
@"X-Request-ID",
@"X-Robots-Tag",
// --- EXPERIMENTAL FIELD NAMES --------------------------------------------
@"Sec-CH-Device-Memory",
@"Sec-CH-DPR",
@"Sec-CH-Prefers-Color-Scheme",
@"Sec-CH-Prefers-Reduced-Motion",
@"Sec-CH-Prefers-Reduced-Transparency",
@"Sec-CH-UA-Arch",
@"Sec-CH-UA-Bitness",
@"Sec-CH-UA-Form-Factors",
@"Sec-CH-UA-Full-Versi",
@"Sec-CH-UA-Full-Version-List",
@"Sec-CH-UA-Mobile",
@"Sec-CH-UA-Model",
@"Sec-CH-UA-Platform-Version",
@"Sec-CH-UA-Platform",
@"Sec-CH-UA-WoW64",
@"Sec-CH-UA",
@"Sec-CH-Viewport-Height",
@"Sec-CH-Viewport-Width",
@"Sec-CH-Width",
/// Maps **lowercased** header names to enum values.
pub const map: std.StaticStringMap(KnownFieldName) = blk: {
@setEvalBranchQuota(20000);
const fields = @typeInfo(KnownFieldName).@"enum".fields;
var kvs_list: [fields.len]struct { []const u8, KnownFieldName } = undefined;
for (fields, 0..) |field, i| {
var name_buf: [field.name.len]u8 = undefined;
_ = std.ascii.lowerString(&name_buf, field.name);
const name = name_buf;
kvs_list[i] = .{ &name, @field(KnownFieldName, field.name) };
}
break :blk .initComptime(kvs_list);
};
/// The maximum length of all known header names. Any header name longer
/// than this cannot be a known header name.
pub const max_known_field_name_len = blk: {
const fields = @typeInfo(KnownFieldName).@"enum".fields;
var max_len: usize = 0;
for (fields) |field| {
max_len = @max(max_len, field.name.len);
}
break :blk max_len;
};
pub fn isKnownFieldName(name: []const u8) ?KnownFieldName {
if (name.len > max_known_field_name_len) {
@branchHint(.unlikely);
return null;
}
var name_lowercase_buf: [max_known_field_name_len]u8 = undefined;
const name_lowercase = std.ascii.lowerString(&name_lowercase_buf, name);
return map.get(name_lowercase);
}
};

View File

@@ -0,0 +1,13 @@
const std = @import("std");
pub const Method = enum {
CONNECT,
DELETE,
GET,
HEAD,
OPTIONS,
PATCH,
POST,
PUT,
TRACE,
};

View File

@@ -0,0 +1,680 @@
//! HTTP/1.1 parser.
//!
//! This parser is *streaming*, meaning it can gracefully consume partial HTTP
//! request bytes. An instance of this parser is meant for parsing a singular
//! request. Once the request if fully completed, a new instance of the parser
//! should be initialized.
//!
//! During a single ingestion, the parser can return one of the following:
//!
//! - method of type `Method`, i.e. HTTP method (aka verb)
//! - pathname of type `[]const u8`
//! - header of type `Header`, i.e. a field name with a value
//! - end_of_headers of type `void`, i.e. a marker which informs the user of
//! this parser that there will be no more headers; this result can be used by
//! the user to make decisions about further processing of the request based
//! on the full knowledge of all the headers
//! - body of type `[]const u8`, i.e. a slice to the request body (or
//! zero-length slice if there is no request body)
//!
//! The first result returned from the parser will always be the route. Then,
//! one or more headers will follow terminated with end_of_headers marker. The
//! parser will always finish with a single body result.
//!
//! Parser methods stop processing at the first result. Therefore, if any result
//! is returned, the provided bytes might have been only partially consumed and
//! the methods must be repeatedly called until all of the bytes are consumed.
//! When the body is returned, the parser is finished and should be no longer
//! used. If the body was returned, but the bytes were not fully consumed, it
//! means that the remainder belongs to a subsequent HTTP request.
//!
//! When an error is returned from the parser, the HTTP request should be
//! considered malformed. You may choose to respond to it, but the request must
//! no longer be parsed and the connection should be closed.
//!
//! The parser is not involved in any HTTP semantics, only its syntax. It is up
//! to the user of this parser to respect all of the HTTP standards (if they
//! even choose to). For example, none of the header field valuess are verified.
//! The only exception is `Content-Length`. The parser must know the value to
//! determine the length of the request body. If the value fails to parse as a
//! decimal non-negative integer, a syntax error is returned. Note that
//! according to [RFC 9110, Section 8.6: HTTP Semantics](https://datatracker.ietf.org/doc/html/rfc9110#section-8.6),
//! `Content-Length` header field value consisting of the same decimal value
//! repeated as a comma-separated list (e.g. `Content-Length: 42, 42`) MAY be
//! accepted. This parser chooses not to accept it.
const std = @import("std");
const Parser = @This();
const FieldName = @import("FieldName.zig").FieldName;
const Header = @import("Header.zig");
const Method = @import("Method.zig").Method;
pub const Error = error{
MethodNotSupported,
HttpVersionNotSupported,
SyntaxError,
};
pub const Result = union(enum) {
method: Method,
pathname: []const u8,
header: Header,
end_of_headers: void,
body: []const u8,
pub fn initMethod(method: Method) Result {
return .{ .method = method };
}
pub fn initPathname(pathname: []const u8) Result {
return .{ .pathname = pathname };
}
pub fn initHeader(header: Header) Result {
return .{ .header = header };
}
pub fn initBody(body: []const u8) Result {
return .{ .body = body };
}
};
pub const ConsumeResult = struct {
consumed: usize,
result: ?Result,
};
pub const State = union(enum) {
init: void,
method_c: void,
method_d: void,
method_g: void,
method_h: void,
method_o: void,
method_p: void,
method_t: void,
method_co: void,
method_de: void,
method_ge: void,
method_he: void,
method_op: void,
method_pa: void,
method_po: void,
method_pu: void,
method_tr: void,
method_con: void,
method_del: void,
method_hea: void,
method_opt: void,
method_pat: void,
method_pos: void,
method_tra: void,
method_conn: void,
method_dele: void,
method_opti: void,
method_patc: void,
method_trac: void,
method_conne: void,
method_delet: void,
method_optio: void,
method_connec: void,
method_option: void,
method_complete: void,
pathname: []const u8,
pathname_complete: void,
version_h: void,
version_ht: void,
version_htt: void,
version_http: void,
@"version_http/": void,
@"version_http/1": void,
@"version_http/1.": void,
version_complete: void,
start_line_end: void,
header_name_start: void,
header_name: []const u8,
header_value: Header,
header_line_end: void,
headers_end: void,
body: []const u8,
done: void,
pub fn initPathname(pathname: []const u8) State {
return .{ .pathname = pathname };
}
pub fn initHeaderName(name: []const u8) State {
return .{ .header_name = name };
}
pub fn initHeaderValue(header: Header) State {
return .{ .header_value = header };
}
pub fn initBody(body: []const u8) State {
return .{ .body = body };
}
};
state: State,
content_length: ?usize,
pub fn init() Parser {
return .{
.state = .init,
.content_length = null,
};
}
pub fn consume(self: *Parser, chars: []const u8) Error!ConsumeResult {
var i: usize = 0;
while (i < chars.len) {
switch (self.state) {
.body => |body| {
const content_length = self.content_length.?;
const to_consume = @min(chars.len - i, content_length - body.len);
const new_body = extendSliceBy(body, to_consume);
i += to_consume;
if (new_body.len >= content_length) {
self.state = .done;
return .{
.consumed = i,
.result = .initBody(new_body),
};
} else {
self.state = .initBody(new_body);
}
},
else => {
// TODO fix
// if (chars.len - i >= vec_len) {
// const vec_res = try self.consumeVec(chars[i..][0..vec_len]);
// i += vec_res.consumed;
// if (vec_res.result) |result| {
// return .{
// .consumed = i,
// .result = result,
// };
// }
// if (vec_res.consumed > 0) {
// continue;
// }
// }
const maybe_result = try self.consumeChar(&chars[i]);
i += 1;
if (maybe_result) |result| {
return .{
.consumed = i,
.result = result,
};
}
},
}
}
std.debug.assert(i == chars.len);
return .{
.consumed = chars.len,
.result = null,
};
}
fn consumeChar(self: *Parser, char_ptr: *const u8) Error!?Result {
const char = char_ptr.*;
const char_slice: *const [1]u8 = @ptrCast(char_ptr);
const next_char_slice = @as([*]const u8, @ptrCast(char_ptr))[1..1];
switch (self.state) {
.init => switch (char) {
'C' => self.state = .method_c,
'D' => self.state = .method_d,
'G' => self.state = .method_g,
'H' => self.state = .method_h,
'O' => self.state = .method_o,
'P' => self.state = .method_p,
'T' => self.state = .method_t,
else => return error.MethodNotSupported,
},
.method_c => switch (char) {
'O' => self.state = .method_co,
else => return error.MethodNotSupported,
},
.method_d => switch (char) {
'E' => self.state = .method_de,
else => return error.MethodNotSupported,
},
.method_g => switch (char) {
'E' => self.state = .method_ge,
else => return error.MethodNotSupported,
},
.method_h => switch (char) {
'E' => self.state = .method_he,
else => return error.MethodNotSupported,
},
.method_o => switch (char) {
'P' => self.state = .method_op,
else => return error.MethodNotSupported,
},
.method_p => switch (char) {
'A' => self.state = .method_pa,
'O' => self.state = .method_po,
'U' => self.state = .method_pu,
else => return error.MethodNotSupported,
},
.method_t => switch (char) {
'R' => self.state = .method_tr,
else => return error.MethodNotSupported,
},
.method_co => switch (char) {
'N' => self.state = .method_con,
else => return error.MethodNotSupported,
},
.method_de => switch (char) {
'L' => self.state = .method_del,
else => return error.MethodNotSupported,
},
.method_ge => switch (char) {
'T' => {
self.state = .method_complete;
return .initMethod(.GET);
},
else => return error.MethodNotSupported,
},
.method_he => switch (char) {
'A' => self.state = .method_hea,
else => return error.MethodNotSupported,
},
.method_op => switch (char) {
'T' => self.state = .method_opt,
else => return error.MethodNotSupported,
},
.method_pa => switch (char) {
'T' => self.state = .method_pat,
else => return error.MethodNotSupported,
},
.method_po => switch (char) {
'S' => self.state = .method_pos,
else => return error.MethodNotSupported,
},
.method_pu => switch (char) {
'T' => {
self.state = .method_complete;
return .initMethod(.PUT);
},
else => return error.MethodNotSupported,
},
.method_tr => switch (char) {
'A' => self.state = .method_tra,
else => return error.MethodNotSupported,
},
.method_con => switch (char) {
'N' => self.state = .method_conn,
else => return error.MethodNotSupported,
},
.method_del => switch (char) {
'E' => self.state = .method_dele,
else => return error.MethodNotSupported,
},
.method_hea => switch (char) {
'D' => {
self.state = .method_complete;
return .initMethod(.HEAD);
},
else => return error.MethodNotSupported,
},
.method_opt => switch (char) {
'I' => self.state = .method_opti,
else => return error.MethodNotSupported,
},
.method_pat => switch (char) {
'C' => self.state = .method_patc,
else => return error.MethodNotSupported,
},
.method_pos => switch (char) {
'T' => {
self.state = .method_complete;
return .initMethod(.POST);
},
else => return error.MethodNotSupported,
},
.method_tra => switch (char) {
'C' => self.state = .method_trac,
else => return error.MethodNotSupported,
},
.method_conn => switch (char) {
'E' => self.state = .method_conne,
else => return error.MethodNotSupported,
},
.method_dele => switch (char) {
'T' => self.state = .method_delet,
else => return error.MethodNotSupported,
},
.method_opti => switch (char) {
'O' => self.state = .method_optio,
else => return error.MethodNotSupported,
},
.method_patc => switch (char) {
'H' => {
self.state = .method_complete;
return .initMethod(.PATCH);
},
else => return error.MethodNotSupported,
},
.method_trac => switch (char) {
'E' => {
self.state = .method_complete;
return .initMethod(.TRACE);
},
else => return error.MethodNotSupported,
},
.method_conne => switch (char) {
'C' => self.state = .method_connec,
else => return error.MethodNotSupported,
},
.method_delet => switch (char) {
'E' => {
self.state = .method_complete;
return .initMethod(.DELETE);
},
else => return error.MethodNotSupported,
},
.method_optio => switch (char) {
'N' => self.state = .method_option,
else => return error.MethodNotSupported,
},
.method_connec => switch (char) {
'T' => {
self.state = .method_complete;
return .initMethod(.CONNECT);
},
else => return error.MethodNotSupported,
},
.method_option => switch (char) {
'S' => {
self.state = .method_complete;
return .initMethod(.OPTIONS);
},
else => return error.MethodNotSupported,
},
.method_complete => switch (char) {
' ' => self.state = .initPathname(next_char_slice),
else => return error.MethodNotSupported,
},
.pathname => |pathname| switch (char) {
' ' => {
self.state = .pathname_complete;
return .initPathname(pathname);
},
else => self.state = .initPathname(extendSlice(pathname)),
},
.pathname_complete => switch (char) {
'H' => self.state = .version_h,
else => return error.HttpVersionNotSupported,
},
.version_h => switch (char) {
'T' => self.state = .version_ht,
else => return error.HttpVersionNotSupported,
},
.version_ht => switch (char) {
'T' => self.state = .version_htt,
else => return error.HttpVersionNotSupported,
},
.version_htt => switch (char) {
'P' => self.state = .version_http,
else => return error.HttpVersionNotSupported,
},
.version_http => switch (char) {
'/' => self.state = .@"version_http/",
else => return error.HttpVersionNotSupported,
},
.@"version_http/" => switch (char) {
'1' => self.state = .@"version_http/1",
else => return error.HttpVersionNotSupported,
},
.@"version_http/1" => switch (char) {
'.' => self.state = .@"version_http/1.",
else => return error.HttpVersionNotSupported,
},
.@"version_http/1." => switch (char) {
'1' => self.state = .version_complete,
else => return error.HttpVersionNotSupported,
},
.version_complete => switch (char) {
'\r' => self.state = .start_line_end,
else => return error.HttpVersionNotSupported,
},
.start_line_end => switch (char) {
'\n' => self.state = .header_name_start,
else => return error.SyntaxError,
},
.header_name_start => switch (char) {
'\r' => {
self.state = .headers_end;
return .end_of_headers;
},
else => self.state = .initHeaderName(char_slice),
},
.header_name => |name| switch (char) {
':' => self.state = .initHeaderValue(.init(.init(name), next_char_slice)),
else => self.state = .initHeaderName(extendSlice(name)),
},
.header_value => |untrimmed_header| switch (char) {
'\r' => {
self.state = .header_line_end;
const header: Header = .init(
untrimmed_header.name,
std.mem.trim(u8, untrimmed_header.value, " \t"),
);
if (header.isNamedKnown(.@"Content-Length")) {
const content_length = std.fmt.parseInt(usize, header.value, 10) catch return error.SyntaxError;
if (self.content_length) |current_content_length| {
@branchHint(.unlikely);
// Accept multiple `Content-Length` headers as long as
// they have the exact same value.
if (content_length != current_content_length) {
return error.SyntaxError;
}
} else {
self.content_length = content_length;
}
}
return .initHeader(header);
},
else => self.state = .initHeaderValue(extendHeader(untrimmed_header)),
},
.header_line_end => switch (char) {
'\n' => self.state = .header_name_start,
else => return error.SyntaxError,
},
.headers_end => switch (char) {
'\n' => {
const content_length = self.content_length orelse 0;
if (content_length == 0) {
self.state = .done;
return .initBody(&.{});
} else {
self.state = .initBody(next_char_slice);
}
},
else => return error.SyntaxError,
},
.body => |body| {
const content_length = self.content_length.?;
const new_body = extendSlice(body);
if (new_body.len >= content_length) {
self.state = .done;
return .initBody(new_body);
} else {
self.state = .initBody(new_body);
}
},
.done => unreachable,
}
return null;
}
fn extendSlice(slice: []const u8) []const u8 {
return slice.ptr[0 .. slice.len + 1];
}
fn extendSliceBy(slice: []const u8, n: usize) []const u8 {
return slice.ptr[0 .. slice.len + n];
}
fn extendHeader(header: Header) Header {
return .{
.name = header.name,
.value = extendSlice(header.value),
};
}
// --- SIMD --------------------------------------------------------------------
const Vec = @Vector(std.simd.suggestVectorLength(u8).?, u8);
const vec_len = @typeInfo(Vec).vector.len;
const Pattern = struct {
value: Vec,
mask: Vec,
len: u32,
pub fn init(comptime prefix: []const u8) Pattern {
if (prefix.len > vec_len) {
@compileError("Prefix length is too high");
}
var value: [vec_len]u8 = undefined;
var mask: [vec_len]u8 = undefined;
for (0..vec_len) |i| {
if (i < prefix.len) {
value[i] = prefix[i];
mask[i] = 0xFF;
} else {
value[i] = 0x00;
mask[i] = 0x00;
}
}
return .{
.value = value,
.mask = mask,
.len = prefix.len,
};
}
inline fn check(self: Pattern, vec: Vec) bool {
return @reduce(.And, vec & self.mask == self.value);
}
};
const patterns = struct {
pub const methods = struct {
// NOTE These patterns are arranged in a specific order, such that the
// first ones are the most common (based on vibes only).
pub const GET = Pattern.init("GET ");
pub const POST = Pattern.init("POST ");
pub const HEAD = Pattern.init("HEAD ");
pub const PUT = Pattern.init("PUT ");
pub const DELETE = Pattern.init("DELETE ");
pub const PATCH = Pattern.init("PATCH ");
pub const OPTIONS = Pattern.init("OPTIONS ");
pub const CONNECT = Pattern.init("CONNECT ");
pub const TRACE = Pattern.init("TRACE ");
};
pub const @"version_http/1.1" = Pattern.init("HTTP/1.1\r\n");
};
inline fn hasSpace(vec: Vec) bool {
const has_space = vec == @as(Vec, @splat(' '));
return @reduce(.Or, has_space);
}
inline fn hasCRLF(vec: Vec) bool {
const has_cr = vec == @as(Vec, @splat('\r'));
const has_lf = vec == @as(Vec, @splat('\n'));
return @reduce(.Or, has_cr | has_lf);
}
/// May return with `.consumed == 0`, in which case the parsing should be
/// retried with non-SIMD method.
pub fn consumeVec(self: *Parser, vec_ptr: *const [vec_len]u8) Error!ConsumeResult {
const vec: Vec = vec_ptr.*;
switch (self.state) {
.init => {
inline for (@typeInfo(patterns.methods).@"struct".decls) |decl| {
const pattern: Pattern = @field(patterns.methods, decl.name);
if (pattern.check(vec)) {
self.state = .method_complete;
return .{
.consumed = pattern.len,
.result = .initMethod(@field(Method, decl.name)),
};
}
}
return error.MethodNotSupported;
},
.pathname_state => |s| {
if (hasSpace(vec)) {
// Delegate to `consumeChar`.
return .{
.consumed = 0,
.result = null,
};
}
self.state = .pathname(s.method, s.pathname.ptr[0 .. s.pathname.len + vec_len]);
return .{
.consumed = vec_len,
.result = null,
};
},
.pathname_complete => {
if (patterns.@"version_http/1.1".check(vec)) {
self.state = .header_name_start;
return .{
.consumed = patterns.@"version_http/1.1".len,
.result = null,
};
} else {
return error.HttpVersionNotSupported;
}
},
.header_value => |s| {
if (hasCRLF(vec)) {
// Delegate to `consumeChar`.
return .{
.consumed = 0,
.result = null,
};
}
self.state = .headerValue(s.name, s.value.ptr[0 .. s.value.len + vec_len]);
return .{
.consumed = vec_len,
.result = null,
};
},
else => {
// Delegate to `consumeChar`.
return .{
.consumed = 0,
.result = null,
};
},
}
}

View File

@@ -0,0 +1,58 @@
pub const ok = "HTTP/1.1 200 OK\r\n";
pub const created = "HTTP/1.1 201 Created\r\n";
pub const accepted = "HTTP/1.1 202 Accepted\r\n";
pub const non_authoritative_information = "HTTP/1.1 203 Non-Authoritative Information\r\n";
pub const no_content = "HTTP/1.1 204 No Content\r\n";
pub const reset_content = "HTTP/1.1 205 Reset Content\r\n";
pub const partial_content = "HTTP/1.1 206 Partial Content\r\n";
pub const multi_status = "HTTP/1.1 207 Multi-Status\r\n";
pub const already_reported = "HTTP/1.1 208 Already Reported\r\n";
pub const multiple_choices = "HTTP/1.1 300 Multiple Choices\r\n";
pub const moved_permanently = "HTTP/1.1 301 Moved Permanently\r\n";
pub const found = "HTTP/1.1 302 Found\r\n";
pub const see_other = "HTTP/1.1 303 See Other\r\n";
pub const not_modified = "HTTP/1.1 304 Not Modified\r\n";
pub const temporary_redirect = "HTTP/1.1 307 Temporary Redirect\r\n";
pub const permanent_redirect = "HTTP/1.1 308 Permanent Redirect\r\n";
pub const bad_request = "HTTP/1.1 400 Bad Request\r\n";
pub const unauthorized = "HTTP/1.1 401 Unauthorized\r\n";
pub const payment_required = "HTTP/1.1 402 Payment Required\r\n";
pub const forbidden = "HTTP/1.1 403 Forbidden\r\n";
pub const not_found = "HTTP/1.1 404 Not Found\r\n";
pub const method_not_allowed = "HTTP/1.1 405 Method Not Allowed\r\n";
pub const not_acceptable = "HTTP/1.1 406 Not Acceptable\r\n";
pub const proxy_authentication_required = "HTTP/1.1 407 Proxy Authentication Required\r\n";
pub const request_timeout = "HTTP/1.1 408 Request Timeout\r\n";
pub const conflict = "HTTP/1.1 409 Conflict\r\n";
pub const gone = "HTTP/1.1 410 Gone\r\n";
pub const length_required = "HTTP/1.1 411 Length Required\r\n";
pub const precondition_failed = "HTTP/1.1 412 Precondition Failed\r\n";
pub const content_too_large = "HTTP/1.1 413 Content Too Large\r\n";
pub const uri_too_long = "HTTP/1.1 414 URI Too Long\r\n";
pub const unsupported_media_type = "HTTP/1.1 415 Unsupported Media Type\r\n";
pub const range_not_satisfiable = "HTTP/1.1 416 Range Not Satisfiable\r\n";
pub const expectation_failed = "HTTP/1.1 417 Expectation Failed\r\n";
pub const im_a_teapot = "HTTP/1.1 418 I'm a teapot\r\n";
pub const misdirected_request = "HTTP/1.1 421 Misdirected Request\r\n";
pub const unprocessable_content = "HTTP/1.1 422 Unprocessable Content\r\n";
pub const locked = "HTTP/1.1 423 Locked\r\n";
pub const failed_dependency = "HTTP/1.1 424 Failed Dependency\r\n";
pub const upgrade_required = "HTTP/1.1 426 Upgrade Required\r\n";
pub const precondition_required = "HTTP/1.1 428 Precondition Required\r\n";
pub const too_many_requests = "HTTP/1.1 429 Too Many Requests\r\n";
pub const request_header_fields_too_large = "HTTP/1.1 431 Request Header Fields Too Large\r\n";
pub const unavailable_for_legal_reasons = "HTTP/1.1 451 Unavailable For Legal Reasons\r\n";
pub const internal_server_error = "HTTP/1.1 500 Internal Server Error\r\n";
pub const not_implemented = "HTTP/1.1 501 Not Implemented\r\n";
pub const bad_gateway = "HTTP/1.1 502 Bad Gateway\r\n";
pub const service_unavailable = "HTTP/1.1 503 Service Unavailable\r\n";
pub const gateway_timeout = "HTTP/1.1 504 Gateway Timeout\r\n";
pub const http_version_not_supported = "HTTP/1.1 505 HTTP Version Not Supported\r\n";
pub const variant_also_negotiates = "HTTP/1.1 506 Variant Also Negotiates\r\n";
pub const insufficient_storage = "HTTP/1.1 507 Insufficient Storage\r\n";
pub const loop_detected = "HTTP/1.1 508 Loop Detected\r\n";
pub const not_extended = "HTTP/1.1 510 Not Extended\r\n";
pub const network_authentication_required = "HTTP/1.1 511 Network Authentication Required\r\n";

133
packages/web/src/main.zig Normal file
View File

@@ -0,0 +1,133 @@
const std = @import("std");
const web = @import("web");
const linux = std.os.linux;
const errno = linux.E.init;
const ssl = web.openssl;
const UUID = web.UUID;
var running: std.atomic.Value(bool) = .init(true);
fn interruptionHandler(sig: i32) callconv(.c) void {
var buf: [32]u8 = undefined;
const signal_name = blk: inline for (@typeInfo(linux.SIG).@"struct".decls) |decl| {
if (comptime std.mem.eql(u8, decl.name, "BLOCK") or
std.mem.eql(u8, decl.name, "UNBLOCK") or
std.mem.eql(u8, decl.name, "SETMASK")) continue;
const decl_value = @field(linux.SIG, decl.name);
if (@TypeOf(decl_value) != comptime_int) continue;
if (decl_value == sig) break :blk "SIG" ++ decl.name;
} else {
break :blk std.fmt.bufPrint(&buf, "#{d}", .{sig}) catch unreachable;
};
std.log.debug("Interrupted with signal {s}.", .{signal_name});
switch (sig) {
linux.SIG.INT, linux.SIG.TERM => {
running.store(false, .release);
},
else => {},
}
}
const Handler = struct {
fn handle(_: *anyopaque, request: *web.Request, response: *web.Response) !void {
std.log.info("{f} | {s} {s}", .{
response.connection.address,
@tagName(request.method),
request.pathname,
});
if (!std.mem.eql(u8, request.pathname, "/")) {
try response.body_writer.writeAll("Not Found\n");
try response.header_writer.writeAll(web.http.status.not_found);
try response.header_writer.writeAll("Content-Type: text/plain; charset=utf-8\r\n");
try response.header_writer.print("Content-Length: {d}\r\n", .{response.body_writer.end});
try response.header_writer.writeAll("\r\n");
response.sendHeadersAndBody();
return;
}
if (request.method != .GET) {
try response.body_writer.writeAll("Method Not Allowed\n");
try response.header_writer.writeAll(web.http.status.method_not_allowed);
try response.header_writer.writeAll("Content-Type: text/plain; charset=utf-8\r\n");
try response.header_writer.print("Content-Length: {d}\r\n", .{response.body_writer.end});
try response.header_writer.writeAll("\r\n");
response.sendHeadersAndBody();
return;
}
try response.body_writer.writeAll("{\"ok\":true}\n");
try response.header_writer.writeAll(web.http.status.ok);
try response.header_writer.writeAll("Content-Type: application/json\r\n");
try response.header_writer.print("Content-Length: {d}\r\n", .{response.body_writer.end});
try response.header_writer.writeAll("\r\n");
response.sendHeadersAndBody();
}
fn interface() web.RequestHandler {
return .{
.ptr = undefined,
.vtable = &.{
.handle = handle,
},
};
}
};
pub fn main() !void {
var gpa: std.heap.GeneralPurposeAllocator(.{
.thread_safe = true,
}) = .init;
defer _ = gpa.deinit();
const allocator = gpa.allocator();
_ = ssl.c_ssl.SSL_library_init();
_ = ssl.c_ssl.OpenSSL_add_all_algorithms();
_ = ssl.c_ssl.SSL_load_error_strings();
const ssl_ctx = try ssl.SslContext.new(.tlsServerMethod());
defer ssl_ctx.free();
_ = ssl_ctx.setMinProtoVersion(ssl.c_ssl.TLS1_3_VERSION);
_ = ssl_ctx.setOptions(ssl.c_ssl.SSL_OP_NO_SSLv3 | ssl.c_ssl.SSL_OP_NO_TLSv1 | ssl.c_ssl.SSL_OP_NO_TLSv1_1 | ssl.c_ssl.SSL_OP_NO_TLSv1_2);
try ssl_ctx.useCertificateFile("cert.pem", ssl.c_ssl.SSL_FILETYPE_PEM);
try ssl_ctx.usePrivateKeyFile("key.pem", ssl.c_ssl.SSL_FILETYPE_PEM);
try ssl_ctx.checkPrivateKey();
var server = try web.Server.init(allocator, .{
.request_handler = Handler.interface(),
.address = .initIp4(.{ 127, 0, 0, 1 }, 8000),
.ssl_ctx = ssl_ctx,
});
defer server.deinit(allocator);
const sigaction: linux.Sigaction = .{
.handler = .{ .handler = interruptionHandler },
.mask = linux.sigemptyset(),
.flags = linux.SA.RESETHAND,
};
signal(linux.SIG.INT, &sigaction);
signal(linux.SIG.TERM, &sigaction);
try server.listen(&running);
}
fn signal(sig: u8, action: *const linux.Sigaction) void {
var old_action = std.mem.zeroes(linux.Sigaction);
_ = linux.sigaction(sig, null, &old_action);
if (old_action.handler.handler == linux.SIG.IGN) return;
_ = linux.sigaction(sig, action, null);
}

View File

@@ -0,0 +1,9 @@
const std = @import("std");
pub const c_err = @import("openssl/err.zig");
pub const c_ssl = @import("openssl/ssl.zig");
pub const Ssl = @import("openssl/Ssl.zig").Ssl;
pub const SslContext = @import("openssl/SslContext.zig").SslContext;
pub const SslMethod = @import("openssl/SslMethod.zig").SslMethod;
pub const SslSession = @import("openssl/SslSession.zig").SslSession;

View File

@@ -0,0 +1,339 @@
const std = @import("std");
const c_ssl = @import("ssl.zig");
const FileDescriptor = @import("../FileDescriptor.zig").FileDescriptor;
const SslContext = @import("SslContext.zig").SslContext;
const SslMethod = @import("SslMethod.zig").SslMethod;
const SslSession = @import("SslSession.zig").SslSession;
pub const Ssl = opaque {
pub inline fn accept(self: *Ssl) !void {
const res = import.SSL_accept(self);
if (res <= 0) {
return error.OpenSslError;
}
}
pub inline fn free(self: *Ssl) void {
import.SSL_free(self);
}
pub inline fn getError(self: *const Ssl, ret_code: i32) i32 {
return import.SSL_get_error(self, ret_code);
}
pub inline fn new(ctx: *SslContext) !*Ssl {
return import.SSL_new(ctx) orelse error.OpenSslError;
}
pub inline fn read(self: *Ssl, buf: []u8) !usize {
var bytes_read: usize = undefined;
const res = import.SSL_read_ex(self, buf.ptr, buf.len, &bytes_read);
if (res <= 0) {
const err = self.getError(res);
return if (err == c_ssl.SSL_ERROR_ZERO_RETURN) 0 else error.OpenSslError;
} else {
return bytes_read;
}
}
pub fn readAll(self: *Ssl, buf: []u8) !void {
var total_bytes_read: usize = 0;
while (total_bytes_read < buf.len) {
const chunk = buf[total_bytes_read..];
const bytes_read = try self.read(chunk);
total_bytes_read += bytes_read;
}
}
pub fn sendfile(self: *Ssl, fd: FileDescriptor, offset: usize, size: usize) !usize {
const res = import.SSL_sendfile(self, fd, offset, size, 0);
return if (res <= 0) error.OpenSslError else @intCast(res);
}
pub fn sendfileAll(self: *Ssl, fd: FileDescriptor, offset: usize, size: usize) !void {
var total_bytes_sent: usize = 0;
while (total_bytes_sent < size) {
const bytes_written = try self.sendfile(fd, offset + total_bytes_sent, size - total_bytes_sent);
total_bytes_sent += bytes_written;
}
}
pub inline fn setFd(self: *Ssl, fd: FileDescriptor) !void {
const res = import.SSL_set_fd(self, @intFromEnum(fd));
if (res <= 0) {
return error.OpenSslError;
}
}
pub inline fn write(self: *Ssl, buf: []const u8) !usize {
var bytes_written: usize = undefined;
const res = import.SSL_write_ex(self, buf.ptr, buf.len, &bytes_written);
return if (res <= 0) error.OpenSslError else bytes_written;
}
pub fn writeAll(self: *Ssl, buf: []const u8) !void {
var total_bytes_written: usize = 0;
while (total_bytes_written < buf.len) {
const chunk = buf[total_bytes_written..];
const bytes_written = try self.write(chunk);
total_bytes_written += bytes_written;
}
}
};
const import = struct {
pub extern fn SSL_accept(ssl: ?*Ssl) i32;
pub extern fn SSL_add_client_CA(ssl: ?*Ssl, x: ?*c_ssl.X509) i32;
pub extern fn SSL_add_dir_cert_subjects_to_stack(stackCAs: ?*c_ssl.struct_stack_st_X509_NAME, dir: [*c]const u8) i32;
pub extern fn SSL_add_file_cert_subjects_to_stack(stackCAs: ?*c_ssl.struct_stack_st_X509_NAME, file: [*c]const u8) i32;
pub extern fn SSL_add_ssl_module() void;
pub extern fn SSL_add_store_cert_subjects_to_stack(stackCAs: ?*c_ssl.struct_stack_st_X509_NAME, uri: [*c]const u8) i32;
pub extern fn SSL_add1_host(s: ?*Ssl, hostname: [*c]const u8) i32;
pub extern fn SSL_add1_to_CA_list(ssl: ?*Ssl, x: ?*const c_ssl.X509) i32;
pub extern fn SSL_alert_desc_string_long(value: i32) [*c]const u8;
pub extern fn SSL_alert_desc_string(value: i32) [*c]const u8;
pub extern fn SSL_alert_type_string_long(value: i32) [*c]const u8;
pub extern fn SSL_alert_type_string(value: i32) [*c]const u8;
pub extern fn SSL_alloc_buffers(ssl: ?*Ssl) i32;
pub extern fn SSL_bytes_to_cipher_list(s: ?*Ssl, bytes: [*c]const u8, len: usize, isv2format: i32, sk: [*c]?*c_ssl.struct_stack_st_SSL_CIPHER, scsvs: [*c]?*c_ssl.struct_stack_st_SSL_CIPHER) i32;
pub extern fn SSL_callback_ctrl(?*Ssl, i32, ?*const fn () callconv(.c) void) i64;
pub extern fn SSL_certs_clear(s: ?*Ssl) void;
pub extern fn SSL_check_chain(s: ?*Ssl, x: ?*c_ssl.X509, pk: ?*c_ssl.EVP_PKEY, chain: ?*c_ssl.struct_stack_st_X509) i32;
pub extern fn SSL_check_private_key(ctx: ?*const Ssl) i32;
pub extern fn SSL_clear_options(s: ?*Ssl, op: u64) u64;
pub extern fn SSL_clear(s: ?*Ssl) i32;
pub extern fn SSL_client_hello_get0_ciphers(s: ?*Ssl, out: [*c][*c]const u8) usize;
pub extern fn SSL_client_hello_get0_compression_methods(s: ?*Ssl, out: [*c][*c]const u8) usize;
pub extern fn SSL_client_hello_get0_ext(s: ?*Ssl, @"type": u32, out: [*c][*c]const u8, outlen: [*c]usize) i32;
pub extern fn SSL_client_hello_get0_legacy_version(s: ?*Ssl) u32;
pub extern fn SSL_client_hello_get0_random(s: ?*Ssl, out: [*c][*c]const u8) usize;
pub extern fn SSL_client_hello_get0_session_id(s: ?*Ssl, out: [*c][*c]const u8) usize;
pub extern fn SSL_client_hello_get1_extensions_present(s: ?*Ssl, out: [*c][*c]i32, outlen: [*c]usize) i32;
pub extern fn SSL_client_hello_isv2(s: ?*Ssl) i32;
pub extern fn SSL_client_version(s: ?*const Ssl) i32;
pub extern fn SSL_config(s: ?*Ssl, name: [*c]const u8) i32;
pub extern fn SSL_connect(ssl: ?*Ssl) i32;
pub extern fn SSL_copy_session_id(to: ?*Ssl, from: ?*const Ssl) i32;
pub extern fn SSL_ct_is_enabled(s: ?*const Ssl) i32;
pub extern fn SSL_ctrl(ssl: ?*Ssl, cmd: i32, larg: i64, parg: ?*anyopaque) i64;
pub extern fn SSL_dane_clear_flags(ssl: ?*Ssl, flags: u64) u64;
pub extern fn SSL_dane_enable(s: ?*Ssl, basedomain: [*c]const u8) i32;
pub extern fn SSL_dane_set_flags(ssl: ?*Ssl, flags: u64) u64;
pub extern fn SSL_dane_tlsa_add(s: ?*Ssl, usage: u8, selector: u8, mtype: u8, data: [*c]const u8, dlen: usize) i32;
pub extern fn SSL_do_handshake(s: ?*Ssl) i32;
pub extern fn SSL_dup_CA_list(sk: ?*const c_ssl.struct_stack_st_X509_NAME) ?*c_ssl.struct_stack_st_X509_NAME;
pub extern fn SSL_dup(ssl: ?*Ssl) ?*Ssl;
pub extern fn SSL_enable_ct(s: ?*Ssl, validation_mode: i32) i32;
pub extern fn SSL_export_keying_material_early(s: ?*Ssl, out: [*c]u8, olen: usize, label: [*c]const u8, llen: usize, context: [*c]const u8, contextlen: usize) i32;
pub extern fn SSL_export_keying_material(s: ?*Ssl, out: [*c]u8, olen: usize, label: [*c]const u8, llen: usize, context: [*c]const u8, contextlen: usize, use_context: i32) i32;
pub extern fn SSL_extension_supported(ext_type: u32) i32;
pub extern fn SSL_free_buffers(ssl: ?*Ssl) i32;
pub extern fn SSL_free(ssl: ?*Ssl) void;
pub extern fn SSL_get_all_async_fds(s: ?*Ssl, fds: [*c]i32, numfds: [*c]usize) i32;
pub extern fn SSL_get_async_status(s: ?*Ssl, status: [*c]i32) i32;
pub extern fn SSL_get_certificate(ssl: ?*const Ssl) ?*c_ssl.X509;
pub extern fn SSL_get_changed_async_fds(s: ?*Ssl, addfd: [*c]i32, numaddfds: [*c]usize, delfd: [*c]i32, numdelfds: [*c]usize) i32;
pub extern fn SSL_get_cipher_list(s: ?*const Ssl, n: i32) [*c]const u8;
pub extern fn SSL_get_ciphers(s: ?*const Ssl) ?*c_ssl.struct_stack_st_SSL_CIPHER;
pub extern fn SSL_get_client_CA_list(s: ?*const Ssl) ?*c_ssl.struct_stack_st_X509_NAME;
pub extern fn SSL_get_client_ciphers(s: ?*const Ssl) ?*c_ssl.struct_stack_st_SSL_CIPHER;
pub extern fn SSL_get_client_random(ssl: ?*const Ssl, out: [*c]u8, outlen: usize) usize;
pub extern fn SSL_get_current_cipher(s: ?*const Ssl) ?*const c_ssl.SSL_CIPHER;
pub extern fn SSL_get_current_compression(s: ?*const Ssl) ?*const c_ssl.COMP_METHOD;
pub extern fn SSL_get_current_expansion(s: ?*const Ssl) ?*const c_ssl.COMP_METHOD;
pub extern fn SSL_get_default_passwd_cb_userdata(s: ?*Ssl) ?*anyopaque;
pub extern fn SSL_get_default_passwd_cb(s: ?*Ssl) ?*const c_ssl.pem_password_cb;
pub extern fn SSL_get_default_timeout(s: ?*const Ssl) i64;
pub extern fn SSL_get_early_data_status(s: ?*const Ssl) i32;
pub extern fn SSL_get_error(s: ?*const Ssl, ret_code: i32) i32;
pub extern fn SSL_get_ex_data_X509_STORE_CTX_idx() i32;
pub extern fn SSL_get_ex_data(ssl: ?*const Ssl, idx: i32) ?*anyopaque;
pub extern fn SSL_get_fd(s: ?*const Ssl) i32;
pub extern fn SSL_get_finished(s: ?*const Ssl, buf: ?*anyopaque, count: usize) usize;
pub extern fn SSL_get_info_callback(ssl: ?*const Ssl) ?*const fn (?*const Ssl, i32, i32) callconv(.c) void;
pub extern fn SSL_get_key_update_type(s: ?*const Ssl) i32;
pub extern fn SSL_get_max_early_data(s: ?*const Ssl) u32;
pub extern fn SSL_get_num_tickets(s: ?*const Ssl) usize;
pub extern fn SSL_get_options(s: ?*const Ssl) u64;
pub extern fn SSL_get_peer_cert_chain(s: ?*const Ssl) ?*c_ssl.struct_stack_st_X509;
pub extern fn SSL_get_peer_finished(s: ?*const Ssl, buf: ?*anyopaque, count: usize) usize;
pub extern fn SSL_get_peer_signature_type_nid(s: ?*const Ssl, pnid: [*c]i32) i32;
pub extern fn SSL_get_pending_cipher(s: ?*const Ssl) ?*const c_ssl.SSL_CIPHER;
pub extern fn SSL_get_privatekey(ssl: ?*const Ssl) ?*c_ssl.struct_evp_pkey_st;
pub extern fn SSL_get_psk_identity_hint(s: ?*const Ssl) [*c]const u8;
pub extern fn SSL_get_psk_identity(s: ?*const Ssl) [*c]const u8;
pub extern fn SSL_get_quiet_shutdown(ssl: ?*const Ssl) i32;
pub extern fn SSL_get_rbio(s: ?*const Ssl) ?*c_ssl.BIO;
pub extern fn SSL_get_read_ahead(s: ?*const Ssl) i32;
pub extern fn SSL_get_record_padding_callback_arg(ssl: ?*const Ssl) ?*anyopaque;
pub extern fn SSL_get_recv_max_early_data(s: ?*const Ssl) u32;
pub extern fn SSL_get_rfd(s: ?*const Ssl) i32;
pub extern fn SSL_get_security_callback(s: ?*const Ssl) ?*const fn (?*const Ssl, ?*const SslContext, i32, i32, i32, ?*anyopaque, ?*anyopaque) callconv(.c) i32;
pub extern fn SSL_get_security_level(s: ?*const Ssl) i32;
pub extern fn SSL_get_selected_srtp_profile(s: ?*Ssl) [*c]c_ssl.SRTP_PROTECTION_PROFILE;
pub extern fn SSL_get_server_random(ssl: ?*const Ssl, out: [*c]u8, outlen: usize) usize;
pub extern fn SSL_get_servername_type(s: ?*const Ssl) i32;
pub extern fn SSL_get_servername(s: ?*const Ssl, @"type": i32) [*c]const u8;
pub extern fn SSL_get_session(ssl: ?*const Ssl) ?*SslSession;
pub extern fn SSL_get_shared_ciphers(s: ?*const Ssl, buf: [*c]u8, size: i32) [*c]u8;
pub extern fn SSL_get_shared_sigalgs(s: ?*Ssl, idx: i32, psign: [*c]i32, phash: [*c]i32, psignandhash: [*c]i32, rsig: [*c]u8, rhash: [*c]u8) i32;
pub extern fn SSL_get_shutdown(ssl: ?*const Ssl) i32;
pub extern fn SSL_get_sigalgs(s: ?*Ssl, idx: i32, psign: [*c]i32, phash: [*c]i32, psignandhash: [*c]i32, rsig: [*c]u8, rhash: [*c]u8) i32;
pub extern fn SSL_get_signature_type_nid(s: ?*const Ssl, pnid: [*c]i32) i32;
pub extern fn SSL_get_srp_g(s: ?*Ssl) ?*c_ssl.BIGNUM;
pub extern fn SSL_get_srp_N(s: ?*Ssl) ?*c_ssl.BIGNUM;
pub extern fn SSL_get_srp_userinfo(s: ?*Ssl) [*c]u8;
pub extern fn SSL_get_srp_username(s: ?*Ssl) [*c]u8;
pub extern fn SSL_get_srtp_profiles(ssl: ?*Ssl) ?*c_ssl.struct_stack_st_SRTP_PROTECTION_PROFILE;
pub extern fn SSL_get_SSL_CTX(ssl: ?*const Ssl) ?*SslContext;
pub extern fn SSL_get_ssl_method(s: ?*const Ssl) ?*const SslMethod;
pub extern fn SSL_get_state(ssl: ?*const Ssl) c_ssl.OSSL_HANDSHAKE_STATE;
pub extern fn SSL_get_verify_callback(s: ?*const Ssl) c_ssl.SSL_verify_cb;
pub extern fn SSL_get_verify_depth(s: ?*const Ssl) i32;
pub extern fn SSL_get_verify_mode(s: ?*const Ssl) i32;
pub extern fn SSL_get_verify_result(ssl: ?*const Ssl) i64;
pub extern fn SSL_get_version(s: ?*const Ssl) [*c]const u8;
pub extern fn SSL_get_wbio(s: ?*const Ssl) ?*c_ssl.BIO;
pub extern fn SSL_get_wfd(s: ?*const Ssl) i32;
pub extern fn SSL_get0_alpn_selected(ssl: ?*const Ssl, data: [*c][*c]const u8, len: [*c]u32) void;
pub extern fn SSL_get0_CA_list(s: ?*const Ssl) ?*const c_ssl.struct_stack_st_X509_NAME;
pub extern fn SSL_get0_dane_authority(s: ?*Ssl, mcert: [*c]?*c_ssl.X509, mspki: [*c]?*c_ssl.EVP_PKEY) i32;
pub extern fn SSL_get0_dane_tlsa(s: ?*Ssl, usage: [*c]u8, selector: [*c]u8, mtype: [*c]u8, data: [*c][*c]const u8, dlen: [*c]usize) i32;
pub extern fn SSL_get0_dane(ssl: ?*Ssl) ?*c_ssl.SSL_DANE;
pub extern fn SSL_get0_next_proto_negotiated(s: ?*const Ssl, data: [*c][*c]const u8, len: [*c]u32) void;
pub extern fn SSL_get0_param(ssl: ?*Ssl) ?*c_ssl.X509_VERIFY_PARAM;
pub extern fn SSL_get0_peer_CA_list(s: ?*const Ssl) ?*const c_ssl.struct_stack_st_X509_NAME;
pub extern fn SSL_get0_peer_certificate(s: ?*const Ssl) ?*c_ssl.X509;
pub extern fn SSL_get0_peer_scts(s: ?*Ssl) ?*const c_ssl.struct_stack_st_SCT;
pub extern fn SSL_get0_peername(s: ?*Ssl) [*c]const u8;
pub extern fn SSL_get0_security_ex_data(s: ?*const Ssl) ?*anyopaque;
pub extern fn SSL_get0_verified_chain(s: ?*const Ssl) ?*c_ssl.struct_stack_st_X509;
pub extern fn SSL_get1_peer_certificate(s: ?*const Ssl) ?*c_ssl.X509;
pub extern fn SSL_get1_session(ssl: ?*Ssl) ?*SslSession;
pub extern fn SSL_get1_supported_ciphers(s: ?*Ssl) ?*c_ssl.struct_stack_st_SSL_CIPHER;
pub extern fn SSL_group_to_name(s: ?*Ssl, id: i32) [*c]const u8;
pub extern fn SSL_has_matching_session_id(s: ?*const Ssl, id: [*c]const u8, id_len: u32) i32;
pub extern fn SSL_has_pending(s: ?*const Ssl) i32;
pub extern fn SSL_in_before(s: ?*const Ssl) i32;
pub extern fn SSL_in_init(s: ?*const Ssl) i32;
pub extern fn SSL_is_dtls(s: ?*const Ssl) i32;
pub extern fn SSL_is_init_finished(s: ?*const Ssl) i32;
pub extern fn SSL_is_server(s: ?*const Ssl) i32;
pub extern fn SSL_key_update(s: ?*Ssl, updatetype: i32) i32;
pub extern fn SSL_load_client_CA_file_ex(file: [*c]const u8, libctx: ?*c_ssl.OSSL_LIB_CTX, propq: [*c]const u8) ?*c_ssl.struct_stack_st_X509_NAME;
pub extern fn SSL_load_client_CA_file(file: [*c]const u8) ?*c_ssl.struct_stack_st_X509_NAME;
pub extern fn SSL_new_session_ticket(s: ?*Ssl) i32;
pub extern fn SSL_new(ctx: ?*SslContext) ?*Ssl;
pub extern fn SSL_peek_ex(ssl: ?*Ssl, buf: ?*anyopaque, num: usize, readbytes: ?*usize) i32;
pub extern fn SSL_peek(ssl: ?*Ssl, buf: ?*anyopaque, num: i32) i32;
pub extern fn SSL_pending(s: ?*const Ssl) i32;
pub extern fn SSL_read_early_data(s: ?*Ssl, buf: ?*anyopaque, num: usize, readbytes: ?*usize) i32;
pub extern fn SSL_read_ex(ssl: ?*Ssl, buf: ?*anyopaque, num: usize, readbytes: ?*usize) i32;
pub extern fn SSL_read(ssl: ?*Ssl, buf: ?*anyopaque, num: i32) i32;
pub extern fn SSL_renegotiate_abbreviated(s: ?*Ssl) i32;
pub extern fn SSL_renegotiate_pending(s: ?*const Ssl) i32;
pub extern fn SSL_renegotiate(s: ?*Ssl) i32;
pub extern fn SSL_rstate_string_long(s: ?*const Ssl) [*c]const u8;
pub extern fn SSL_rstate_string(s: ?*const Ssl) [*c]const u8;
pub extern fn SSL_select_next_proto(out: [*c][*c]u8, outlen: [*c]u8, in: [*c]const u8, inlen: u32, client: [*c]const u8, client_len: u32) i32;
pub extern fn SSL_sendfile(s: ?*Ssl, fd: i32, offset: c_ssl.off_t, size: usize, flags: i32) isize;
pub extern fn SSL_session_reused(s: ?*const Ssl) i32;
pub extern fn SSL_set_accept_state(s: ?*Ssl) void;
pub extern fn SSL_set_allow_early_data_cb(s: ?*Ssl, cb: c_ssl.SSL_allow_early_data_cb_fn, arg: ?*anyopaque) void;
pub extern fn SSL_set_alpn_protos(ssl: ?*Ssl, protos: [*c]const u8, protos_len: u32) i32;
pub extern fn SSL_set_async_callback_arg(s: ?*Ssl, arg: ?*anyopaque) i32;
pub extern fn SSL_set_async_callback(s: ?*Ssl, callback: c_ssl.SSL_async_callback_fn) i32;
pub extern fn SSL_set_bio(s: ?*Ssl, rbio: ?*c_ssl.BIO, wbio: ?*c_ssl.BIO) void;
pub extern fn SSL_set_block_padding(ssl: ?*Ssl, block_size: usize) i32;
pub extern fn SSL_set_cert_cb(s: ?*Ssl, cb: ?*const fn (?*Ssl, ?*anyopaque) callconv(.c) i32, arg: ?*anyopaque) void;
pub extern fn SSL_set_cipher_list(s: ?*Ssl, str: [*c]const u8) i32;
pub extern fn SSL_set_ciphersuites(s: ?*Ssl, str: [*c]const u8) i32;
pub extern fn SSL_set_client_CA_list(s: ?*Ssl, name_list: ?*c_ssl.struct_stack_st_X509_NAME) void;
pub extern fn SSL_set_connect_state(s: ?*Ssl) void;
pub extern fn SSL_set_ct_validation_callback(s: ?*Ssl, callback: c_ssl.ssl_ct_validation_cb, arg: ?*anyopaque) i32;
pub extern fn SSL_set_debug(s: ?*Ssl, debug: i32) void;
pub extern fn SSL_set_default_passwd_cb_userdata(s: ?*Ssl, u: ?*anyopaque) void;
pub extern fn SSL_set_default_passwd_cb(s: ?*Ssl, cb: ?*const c_ssl.pem_password_cb) void;
pub extern fn SSL_set_default_read_buffer_len(s: ?*Ssl, len: usize) void;
pub extern fn SSL_set_ex_data(ssl: ?*Ssl, idx: i32, data: ?*anyopaque) i32;
pub extern fn SSL_set_fd(s: ?*Ssl, fd: i32) i32;
pub extern fn SSL_set_generate_session_id(s: ?*Ssl, cb: c_ssl.GEN_SESSION_CB) i32;
pub extern fn SSL_set_hostflags(s: ?*Ssl, flags: u32) void;
pub extern fn SSL_set_info_callback(ssl: ?*Ssl, cb: ?*const fn (?*const Ssl, i32, i32) callconv(.c) void) void;
pub extern fn SSL_set_max_early_data(s: ?*Ssl, max_early_data: u32) i32;
pub extern fn SSL_set_msg_callback(ssl: ?*Ssl, cb: ?*const fn (i32, i32, i32, ?*const anyopaque, usize, ?*Ssl, ?*anyopaque) callconv(.c) void) void;
pub extern fn SSL_set_not_resumable_session_callback(ssl: ?*Ssl, cb: ?*const fn (?*Ssl, i32) callconv(.c) i32) void;
pub extern fn SSL_set_num_tickets(s: ?*Ssl, num_tickets: usize) i32;
pub extern fn SSL_set_options(s: ?*Ssl, op: u64) u64;
pub extern fn SSL_set_post_handshake_auth(s: ?*Ssl, val: i32) void;
pub extern fn SSL_set_psk_client_callback(ssl: ?*Ssl, cb: c_ssl.SSL_psk_client_cb_func) void;
pub extern fn SSL_set_psk_find_session_callback(s: ?*Ssl, cb: c_ssl.SSL_psk_find_session_cb_func) void;
pub extern fn SSL_set_psk_server_callback(ssl: ?*Ssl, cb: c_ssl.SSL_psk_server_cb_func) void;
pub extern fn SSL_set_psk_use_session_callback(s: ?*Ssl, cb: c_ssl.SSL_psk_use_session_cb_func) void;
pub extern fn SSL_set_purpose(ssl: ?*Ssl, purpose: i32) i32;
pub extern fn SSL_set_quiet_shutdown(ssl: ?*Ssl, mode: i32) void;
pub extern fn SSL_set_read_ahead(s: ?*Ssl, yes: i32) void;
pub extern fn SSL_set_record_padding_callback_arg(ssl: ?*Ssl, arg: ?*anyopaque) void;
pub extern fn SSL_set_record_padding_callback(ssl: ?*Ssl, cb: ?*const fn (?*Ssl, i32, usize, ?*anyopaque) callconv(.c) usize) i32;
pub extern fn SSL_set_recv_max_early_data(s: ?*Ssl, recv_max_early_data: u32) i32;
pub extern fn SSL_set_rfd(s: ?*Ssl, fd: i32) i32;
pub extern fn SSL_set_security_callback(s: ?*Ssl, cb: ?*const fn (?*const Ssl, ?*const SslContext, i32, i32, i32, ?*anyopaque, ?*anyopaque) callconv(.c) i32) void;
pub extern fn SSL_set_security_level(s: ?*Ssl, level: i32) void;
pub extern fn SSL_set_session_id_context(ssl: ?*Ssl, sid_ctx: [*c]const u8, sid_ctx_len: u32) i32;
pub extern fn SSL_set_session_secret_cb(s: ?*Ssl, session_secret_cb: c_ssl.tls_session_secret_cb_fn, arg: ?*anyopaque) i32;
pub extern fn SSL_set_session_ticket_ext_cb(s: ?*Ssl, cb: c_ssl.tls_session_ticket_ext_cb_fn, arg: ?*anyopaque) i32;
pub extern fn SSL_set_session_ticket_ext(s: ?*Ssl, ext_data: ?*anyopaque, ext_len: i32) i32;
pub extern fn SSL_set_session(to: ?*Ssl, session: ?*SslSession) i32;
pub extern fn SSL_set_shutdown(ssl: ?*Ssl, mode: i32) void;
pub extern fn SSL_set_srp_server_param_pw(s: ?*Ssl, user: [*c]const u8, pass: [*c]const u8, grp: [*c]const u8) i32;
pub extern fn SSL_set_srp_server_param(s: ?*Ssl, N: ?*const c_ssl.BIGNUM, g: ?*const c_ssl.BIGNUM, sa: ?*c_ssl.BIGNUM, v: ?*c_ssl.BIGNUM, info: [*c]u8) i32;
pub extern fn SSL_set_SSL_CTX(ssl: ?*Ssl, ctx: ?*SslContext) ?*SslContext;
pub extern fn SSL_set_ssl_method(s: ?*Ssl, method: ?*const SslMethod) i32;
pub extern fn SSL_set_tlsext_max_fragment_length(ssl: ?*Ssl, mode: u8) i32;
pub extern fn SSL_set_tlsext_use_srtp(ssl: ?*Ssl, profiles: [*c]const u8) i32;
pub extern fn SSL_set_tmp_dh_callback(ssl: ?*Ssl, dh: ?*const fn (?*Ssl, i32, i32) callconv(.c) ?*c_ssl.DH) void;
pub extern fn SSL_set_trust(ssl: ?*Ssl, trust: i32) i32;
pub extern fn SSL_set_verify_depth(s: ?*Ssl, depth: i32) void;
pub extern fn SSL_set_verify_result(ssl: ?*Ssl, v: i64) void;
pub extern fn SSL_set_verify(s: ?*Ssl, mode: i32, callback: c_ssl.SSL_verify_cb) void;
pub extern fn SSL_set_wfd(s: ?*Ssl, fd: i32) i32;
pub extern fn SSL_set0_CA_list(s: ?*Ssl, name_list: ?*c_ssl.struct_stack_st_X509_NAME) void;
pub extern fn SSL_set0_rbio(s: ?*Ssl, rbio: ?*c_ssl.BIO) void;
pub extern fn SSL_set0_security_ex_data(s: ?*Ssl, ex: ?*anyopaque) void;
pub extern fn SSL_set0_tmp_dh_pkey(s: ?*Ssl, dhpkey: ?*c_ssl.EVP_PKEY) i32;
pub extern fn SSL_set0_wbio(s: ?*Ssl, wbio: ?*c_ssl.BIO) void;
pub extern fn SSL_set1_host(s: ?*Ssl, hostname: [*c]const u8) i32;
pub extern fn SSL_set1_param(ssl: ?*Ssl, vpm: ?*c_ssl.X509_VERIFY_PARAM) i32;
pub extern fn SSL_shutdown(s: ?*Ssl) i32;
pub extern fn SSL_srp_server_param_with_username(s: ?*Ssl, ad: [*c]i32) i32;
pub extern fn SSL_state_string_long(s: ?*const Ssl) [*c]const u8;
pub extern fn SSL_state_string(s: ?*const Ssl) [*c]const u8;
pub extern fn SSL_stateless(s: ?*Ssl) i32;
pub extern fn SSL_test_functions() ?*const c_ssl.struct_openssl_ssl_test_functions;
pub extern fn SSL_trace(write_p: i32, version: i32, content_type: i32, buf: ?*const anyopaque, len: usize, ssl: ?*Ssl, arg: ?*anyopaque) void;
pub extern fn SSL_up_ref(s: ?*Ssl) i32;
pub extern fn SSL_use_cert_and_key(ssl: ?*Ssl, x509: ?*c_ssl.X509, privatekey: ?*c_ssl.EVP_PKEY, chain: ?*c_ssl.struct_stack_st_X509, override: i32) i32;
pub extern fn SSL_use_certificate_ASN1(ssl: ?*Ssl, d: [*c]const u8, len: i32) i32;
pub extern fn SSL_use_certificate_chain_file(ssl: ?*Ssl, file: [*c]const u8) i32;
pub extern fn SSL_use_certificate_file(ssl: ?*Ssl, file: [*c]const u8, @"type": i32) i32;
pub extern fn SSL_use_certificate(ssl: ?*Ssl, x: ?*c_ssl.X509) i32;
pub extern fn SSL_use_PrivateKey_ASN1(pk: i32, ssl: ?*Ssl, d: [*c]const u8, len: i64) i32;
pub extern fn SSL_use_PrivateKey_file(ssl: ?*Ssl, file: [*c]const u8, @"type": i32) i32;
pub extern fn SSL_use_PrivateKey(ssl: ?*Ssl, pkey: ?*c_ssl.EVP_PKEY) i32;
pub extern fn SSL_use_psk_identity_hint(s: ?*Ssl, identity_hint: [*c]const u8) i32;
pub extern fn SSL_use_RSAPrivateKey_ASN1(ssl: ?*Ssl, d: [*c]const u8, len: i64) i32;
pub extern fn SSL_use_RSAPrivateKey_file(ssl: ?*Ssl, file: [*c]const u8, @"type": i32) i32;
pub extern fn SSL_use_RSAPrivateKey(ssl: ?*Ssl, rsa: ?*c_ssl.RSA) i32;
pub extern fn SSL_verify_client_post_handshake(s: ?*Ssl) i32;
pub extern fn SSL_version(ssl: ?*const Ssl) i32;
pub extern fn SSL_waiting_for_async(s: ?*Ssl) i32;
pub extern fn SSL_want(s: ?*const Ssl) i32;
pub extern fn SSL_write_early_data(s: ?*Ssl, buf: ?*const anyopaque, num: usize, written: ?*usize) i32;
pub extern fn SSL_write_ex(s: ?*Ssl, buf: ?*const anyopaque, num: usize, written: ?*usize) i32;
pub extern fn SSL_write(ssl: ?*Ssl, buf: ?*const anyopaque, num: i32) i32;
};

View File

@@ -0,0 +1,657 @@
const std = @import("std");
const c_ssl = @import("ssl.zig");
const Ssl = @import("Ssl.zig").Ssl;
const SslMethod = @import("SslMethod.zig").SslMethod;
const SslSession = @import("SslSession.zig").SslSession;
pub const SslContext = opaque {
// --- MACROS --------------------------------------------------------------
pub inline fn setMode(self: *SslContext, op: anytype) i64 {
return self.ctrl(c_ssl.c_ssl.SSL_CTRL_MODE, op, null);
}
pub inline fn clearMode(self: *SslContext, op: anytype) i64 {
return self.ctrl(c_ssl.SSL_CTRL_CLEAR_MODE, op, null);
}
pub inline fn getMode(self: *SslContext) i64 {
return self.ctrl(c_ssl.SSL_CTRL_MODE, 0, null);
}
pub inline fn setCertFlags(self: *SslContext, op: anytype) i64 {
return self.ctrl(c_ssl.SSL_CTRL_CERT_FLAGS, op, null);
}
pub inline fn clearCertFlags(self: *SslContext, op: anytype) i64 {
return self.ctrl(c_ssl.SSL_CTRL_CLEAR_CERT_FLAGS, op, null);
}
pub inline fn setMsgCallbackArg(self: *SslContext, arg: anytype) i64 {
return self.ctrl(c_ssl.SSL_CTRL_SET_MSG_CALLBACK_ARG, 0, arg);
}
pub inline fn sessNumber(self: *SslContext) i64 {
return self.ctrl(c_ssl.SSL_CTRL_SESS_NUMBER, 0, null);
}
pub inline fn sessConnect(self: *SslContext) i64 {
return self.ctrl(c_ssl.SSL_CTRL_SESS_CONNECT, 0, null);
}
pub inline fn sessConnectGood(self: *SslContext) i64 {
return self.ctrl(c_ssl.SSL_CTRL_SESS_CONNECT_GOOD, 0, null);
}
pub inline fn sessConnectRenegotiate(self: *SslContext) i64 {
return self.ctrl(c_ssl.SSL_CTRL_SESS_CONNECT_RENEGOTIATE, 0, null);
}
pub inline fn sessAccept(self: *SslContext) i64 {
return self.ctrl(c_ssl.SSL_CTRL_SESS_ACCEPT, 0, null);
}
pub inline fn sessAcceptRenegotiate(self: *SslContext) i64 {
return self.ctrl(c_ssl.SSL_CTRL_SESS_ACCEPT_RENEGOTIATE, 0, null);
}
pub inline fn sessAcceptGood(self: *SslContext) i64 {
return self.ctrl(c_ssl.SSL_CTRL_SESS_ACCEPT_GOOD, 0, null);
}
pub inline fn sessHits(self: *SslContext) i64 {
return self.ctrl(c_ssl.SSL_CTRL_SESS_HIT, 0, null);
}
pub inline fn sessCbHits(self: *SslContext) i64 {
return self.ctrl(c_ssl.SSL_CTRL_SESS_CB_HIT, 0, null);
}
pub inline fn sessMisses(self: *SslContext) i64 {
return self.ctrl(c_ssl.SSL_CTRL_SESS_MISSES, 0, null);
}
pub inline fn sessTimeouts(self: *SslContext) i64 {
return self.ctrl(c_ssl.SSL_CTRL_SESS_TIMEOUTS, 0, null);
}
pub inline fn sessCacheFull(self: *SslContext) i64 {
return self.ctrl(c_ssl.SSL_CTRL_SESS_CACHE_FULL, 0, null);
}
pub inline fn setTlsextServernameArg(self: *SslContext, arg: anytype) i64 {
return self.ctrl(c_ssl.SSL_CTRL_SET_TLSEXT_SERVERNAME_ARG, 0, arg);
}
pub inline fn getTlsextTicketKeys(self: *SslContext, keys: anytype, keylen: anytype) i64 {
return self.ctrl(c_ssl.SSL_CTRL_GET_TLSEXT_TICKET_KEYS, keylen, keys);
}
pub inline fn setTlsextTicketKeys(self: *SslContext, keys: anytype, keylen: anytype) i64 {
return self.ctrl(c_ssl.SSL_CTRL_SET_TLSEXT_TICKET_KEYS, keylen, keys);
}
pub inline fn getTlsextStatusCb(self: *SslContext, cb: anytype) i64 {
return self.ctrl(c_ssl.SSL_CTRL_GET_TLSEXT_STATUS_REQ_CB, 0, @import("std").zig.c_translation.cast(?*anyopaque, cb));
}
pub inline fn getTlsextStatusArg(self: *SslContext, arg: anytype) i64 {
return self.ctrl(c_ssl.SSL_CTRL_GET_TLSEXT_STATUS_REQ_CB_ARG, 0, arg);
}
pub inline fn setTlsextStatusArg(self: *SslContext, arg: anytype) i64 {
return self.ctrl(c_ssl.SSL_CTRL_SET_TLSEXT_STATUS_REQ_CB_ARG, 0, arg);
}
pub inline fn setTlsextStatusType(self: *SslContext, @"type": anytype) i64 {
return self.ctrl(c_ssl.SSL_CTRL_SET_TLSEXT_STATUS_REQ_TYPE, @"type", null);
}
pub inline fn getTlsextStatusType(self: *SslContext) i64 {
return self.ctrl(c_ssl.SSL_CTRL_GET_TLSEXT_STATUS_REQ_TYPE, 0, null);
}
pub inline fn getAppData(self: *const SslContext) ?*anyopaque {
return self.getExData(0);
}
pub inline fn setAppData(self: *SslContext, data: ?*anyopaque) i32 {
return self.setExData(0, data);
}
pub inline fn setTmpDh(self: *SslContext, dh: anytype) i64 {
return self.ctrl(c_ssl.SSL_CTRL_SET_TMP_DH, 0, @import("std").zig.c_translation.cast([*c]u8, dh));
}
pub inline fn setDhAuto(self: *SslContext, onoff: anytype) i64 {
return self.ctrl(c_ssl.SSL_CTRL_SET_DH_AUTO, onoff, null);
}
pub inline fn setTmpEcdh(self: *SslContext, ecdh: anytype) i64 {
return self.ctrl(c_ssl.SSL_CTRL_SET_TMP_ECDH, 0, @import("std").zig.c_translation.cast([*c]u8, ecdh));
}
pub inline fn addExtraChainCert(self: *SslContext, x509: anytype) i64 {
return self.ctrl(c_ssl.SSL_CTRL_EXTRA_CHAIN_CERT, 0, @import("std").zig.c_translation.cast([*c]u8, x509));
}
pub inline fn getExtraChainCerts(self: *SslContext, px509: anytype) i64 {
return self.ctrl(c_ssl.SSL_CTRL_GET_EXTRA_CHAIN_CERTS, 0, px509);
}
pub inline fn getExtraChainCertsOnly(self: *SslContext, px509: anytype) i64 {
return self.ctrl(c_ssl.SSL_CTRL_GET_EXTRA_CHAIN_CERTS, @as(c_int, 1), px509);
}
pub inline fn clearExtraChainCerts(self: *SslContext) i64 {
return self.ctrl(c_ssl.SSL_CTRL_CLEAR_EXTRA_CHAIN_CERTS, 0, null);
}
pub inline fn set0Chain(self: *SslContext, sk: anytype) i64 {
return self.ctrl(c_ssl.SSL_CTRL_CHAIN, 0, @import("std").zig.c_translation.cast([*c]u8, sk));
}
pub inline fn set1Chain(self: *SslContext, sk: anytype) i64 {
return self.ctrl(c_ssl.SSL_CTRL_CHAIN, @as(c_int, 1), @import("std").zig.c_translation.cast([*c]u8, sk));
}
pub inline fn add0ChainCert(self: *SslContext, x509: anytype) i64 {
return self.ctrl(c_ssl.SSL_CTRL_CHAIN_CERT, 0, @import("std").zig.c_translation.cast([*c]u8, x509));
}
pub inline fn add1ChainCert(self: *SslContext, x509: anytype) i64 {
return self.ctrl(c_ssl.SSL_CTRL_CHAIN_CERT, @as(c_int, 1), @import("std").zig.c_translation.cast([*c]u8, x509));
}
pub inline fn get0ChainCerts(self: *SslContext, px509: anytype) i64 {
return self.ctrl(c_ssl.SSL_CTRL_GET_CHAIN_CERTS, 0, px509);
}
pub inline fn clearChainCerts(self: *SslContext) i64 {
return self.set0Chain(null);
}
pub inline fn buildCertChain(self: *SslContext, flags: anytype) i64 {
return self.ctrl(c_ssl.SSL_CTRL_BUILD_CERT_CHAIN, flags, null);
}
pub inline fn selectCurrentCert(self: *SslContext, x509: anytype) i64 {
return self.ctrl(c_ssl.SSL_CTRL_SELECT_CURRENT_CERT, 0, @import("std").zig.c_translation.cast([*c]u8, x509));
}
pub inline fn setCurrentCert(self: *SslContext, op: anytype) i64 {
return self.ctrl(c_ssl.SSL_CTRL_SET_CURRENT_CERT, op, null);
}
pub inline fn set0VerifyCertStore(self: *SslContext, st: anytype) i64 {
return self.ctrl(c_ssl.SSL_CTRL_SET_VERIFY_CERT_STORE, 0, @import("std").zig.c_translation.cast([*c]u8, st));
}
pub inline fn set1VerifyCertStore(self: *SslContext, st: anytype) i64 {
return self.ctrl(c_ssl.SSL_CTRL_SET_VERIFY_CERT_STORE, @as(c_int, 1), @import("std").zig.c_translation.cast([*c]u8, st));
}
pub inline fn get0VerifyCertStore(self: *SslContext, st: anytype) i64 {
return self.ctrl(c_ssl.SSL_CTRL_GET_VERIFY_CERT_STORE, 0, @import("std").zig.c_translation.cast([*c]u8, st));
}
pub inline fn set0ChainCertStore(self: *SslContext, st: anytype) i64 {
return self.ctrl(c_ssl.SSL_CTRL_SET_CHAIN_CERT_STORE, 0, @import("std").zig.c_translation.cast([*c]u8, st));
}
pub inline fn set1ChainCertStore(self: *SslContext, st: anytype) i64 {
return self.ctrl(c_ssl.SSL_CTRL_SET_CHAIN_CERT_STORE, @as(c_int, 1), @import("std").zig.c_translation.cast([*c]u8, st));
}
pub inline fn get0ChainCertStore(self: *SslContext, st: anytype) i64 {
return self.ctrl(c_ssl.SSL_CTRL_GET_CHAIN_CERT_STORE, 0, @import("std").zig.c_translation.cast([*c]u8, st));
}
pub inline fn set1Groups(self: *SslContext, glist: anytype, glistlen: anytype) i64 {
return self.ctrl(c_ssl.SSL_CTRL_SET_GROUPS, glistlen, @import("std").zig.c_translation.cast([*c]c_int, glist));
}
pub inline fn set1GroupsList(self: *SslContext, s: anytype) i64 {
return self.ctrl(c_ssl.SSL_CTRL_SET_GROUPS_LIST, 0, @import("std").zig.c_translation.cast([*c]u8, s));
}
pub inline fn set1Sigalgs(self: *SslContext, slist: anytype, slistlen: anytype) i64 {
return self.ctrl(c_ssl.SSL_CTRL_SET_SIGALGS, slistlen, @import("std").zig.c_translation.cast([*c]c_int, slist));
}
pub inline fn set1SigalgsList(self: *SslContext, s: anytype) i64 {
return self.ctrl(c_ssl.SSL_CTRL_SET_SIGALGS_LIST, 0, @import("std").zig.c_translation.cast([*c]u8, s));
}
pub inline fn set1ClientSigalgs(self: *SslContext, slist: anytype, slistlen: anytype) i64 {
return self.ctrl(c_ssl.SSL_CTRL_SET_CLIENT_SIGALGS, slistlen, @import("std").zig.c_translation.cast([*c]c_int, slist));
}
pub inline fn set1ClientSigalgsList(self: *SslContext, s: anytype) i64 {
return self.ctrl(c_ssl.SSL_CTRL_SET_CLIENT_SIGALGS_LIST, 0, @import("std").zig.c_translation.cast([*c]u8, s));
}
pub inline fn set1ClientCertificateTypes(self: *SslContext, clist: anytype, clistlen: anytype) i64 {
return self.ctrl(c_ssl.SSL_CTRL_SET_CLIENT_CERT_TYPES, clistlen, @import("std").zig.c_translation.cast([*c]u8, clist));
}
pub inline fn setMinProtoVersion(self: *SslContext, version: anytype) i64 {
return self.ctrl(c_ssl.SSL_CTRL_SET_MIN_PROTO_VERSION, version, null);
}
pub inline fn setMaxProtoVersion(self: *SslContext, version: anytype) i64 {
return self.ctrl(c_ssl.SSL_CTRL_SET_MAX_PROTO_VERSION, version, null);
}
pub inline fn getMinProtoVersion(self: *SslContext) i64 {
return self.ctrl(c_ssl.SSL_CTRL_GET_MIN_PROTO_VERSION, 0, null);
}
pub inline fn getMaxProtoVersion(self: *SslContext) i64 {
return self.ctrl(c_ssl.SSL_CTRL_GET_MAX_PROTO_VERSION, 0, null);
}
pub inline fn getExNewIndex(
argl: i64,
argp: ?*anyopaque,
new_func: ?*const c_ssl.CRYPTO_EX_new,
dup_func: ?*const c_ssl.CRYPTO_EX_dup,
free_func: ?*const c_ssl.CRYPTO_EX_free,
) i32 {
return c_ssl.CRYPTO_get_ex_new_index(c_ssl.CRYPTO_EX_INDEX_SSL_CTX, argl, argp, new_func, dup_func, free_func);
}
pub inline fn sessSetCacheSize(self: *SslContext, t: anytype) i64 {
return self.ctrl(c_ssl.SSL_CTRL_SET_SESS_CACHE_SIZE, t, null);
}
pub inline fn sessGetCacheSize(self: *SslContext) i64 {
return self.ctrl(c_ssl.SSL_CTRL_GET_SESS_CACHE_SIZE, 0, null);
}
pub inline fn setSessionCacheMode(self: *SslContext, m: anytype) i64 {
return self.ctrl(c_ssl.SSL_CTRL_SET_SESS_CACHE_MODE, m, null);
}
pub inline fn getSessionCacheMode(self: *SslContext) i64 {
return self.ctrl(c_ssl.SSL_CTRL_GET_SESS_CACHE_MODE, 0, null);
}
pub inline fn getDefaultReadAhead(self: *SslContext) i64 {
return self.getReadAhead();
}
pub inline fn setDefaultReadAhead(self: *SslContext, m: anytype) i64 {
return self.setReadAhead(m);
}
pub inline fn getReadAhead(self: *SslContext) i64 {
return self.ctrl(c_ssl.SSL_CTRL_GET_READ_AHEAD, 0, null);
}
pub inline fn setReadAhead(self: *SslContext, m: anytype) i64 {
return self.ctrl(c_ssl.SSL_CTRL_SET_READ_AHEAD, m, null);
}
pub inline fn getMaxCertList(self: *SslContext) i64 {
return self.ctrl(c_ssl.SSL_CTRL_GET_MAX_CERT_LIST, 0, null);
}
pub inline fn setMaxCertList(self: *SslContext, m: anytype) i64 {
return self.ctrl(c_ssl.SSL_CTRL_SET_MAX_CERT_LIST, m, null);
}
pub inline fn setMaxSendFragment(self: *SslContext, m: anytype) i64 {
return self.ctrl(c_ssl.SSL_CTRL_SET_MAX_SEND_FRAGMENT, m, null);
}
pub inline fn setSplitSendFragment(self: *SslContext, m: anytype) i64 {
return self.ctrl(c_ssl.SSL_CTRL_SET_SPLIT_SEND_FRAGMENT, m, null);
}
pub inline fn setMaxPipelines(self: *SslContext, m: anytype) i64 {
return self.ctrl(c_ssl.SSL_CTRL_SET_MAX_PIPELINES, m, null);
}
// --- METHODS -------------------------------------------------------------
pub inline fn checkPrivateKey(self: *const SslContext) !void {
if (import.SSL_CTX_check_private_key(self) == 0) {
return error.InvalidPrivateKey;
}
}
pub inline fn ctrl(self: *SslContext, cmd: i32, larg: i64, parg: ?*anyopaque) i64 {
return import.SSL_CTX_ctrl(self, cmd, larg, parg);
}
pub inline fn free(self: *SslContext) void {
import.SSL_CTX_free(self);
}
pub inline fn getExData(self: *const SslContext, index: i32) ?*anyopaque {
return import.SSL_CTX_get_ex_data(self, index);
}
pub inline fn new(method: ?*const SslMethod) !*SslContext {
return import.SSL_CTX_new(method) orelse error.OpenSslError;
}
pub inline fn setExData(self: *SslContext, index: i32, data: ?*anyopaque) i32 {
return import.SSL_CTX_set_ex_data(self, index, data);
}
pub inline fn setOptions(self: *SslContext, op: u64) u64 {
return import.SSL_CTX_set_options(self, op);
}
pub inline fn useCertificateFile(self: *SslContext, file: [*:0]const u8, @"type": i32) !void {
const res = import.SSL_CTX_use_certificate_file(self, file, @"type");
if (res <= 0) {
return error.OpenSslError;
}
}
pub inline fn usePrivateKeyFile(self: *SslContext, file: [*:0]const u8, @"type": i32) !void {
const res = import.SSL_CTX_use_PrivateKey_file(self, file, @"type");
if (res <= 0) {
return error.OpenSslError;
}
}
};
const import = struct {
pub extern fn SSL_CTX_add_client_CA(ctx: *SslContext, x: ?*c_ssl.X509) i32;
pub extern fn SSL_CTX_add_client_custom_ext(
ctx: *SslContext,
ext_type: u32,
add_cb: c_ssl.custom_ext_add_cb,
free_cb: c_ssl.custom_ext_free_cb,
add_arg: ?*anyopaque,
parse_cb: c_ssl.custom_ext_parse_cb,
parse_arg: ?*anyopaque,
) i32;
pub extern fn SSL_CTX_add_custom_ext(
ctx: *SslContext,
ext_type: u32,
context: u32,
add_cb: c_ssl.SSL_custom_ext_add_cb_ex,
free_cb: c_ssl.SSL_custom_ext_free_cb_ex,
add_arg: ?*anyopaque,
parse_cb: c_ssl.SSL_custom_ext_parse_cb_ex,
parse_arg: ?*anyopaque,
) i32;
pub extern fn SSL_CTX_add_server_custom_ext(
ctx: *SslContext,
ext_type: u32,
add_cb: c_ssl.custom_ext_add_cb,
free_cb: c_ssl.custom_ext_free_cb,
add_arg: ?*anyopaque,
parse_cb: c_ssl.custom_ext_parse_cb,
parse_arg: ?*anyopaque,
) i32;
pub extern fn SSL_CTX_add_session(ctx: *SslContext, session: ?*c_ssl.Session) i32;
pub extern fn SSL_CTX_add1_to_CA_list(ctx: *SslContext, x: ?*const c_ssl.X509) i32;
pub extern fn SSL_CTX_callback_ctrl(*SslContext, i32, ?*const fn () callconv(.c) void) i64;
pub extern fn SSL_CTX_check_private_key(ctx: *const SslContext) i32;
pub extern fn SSL_CTX_clear_options(ctx: *SslContext, op: u64) u64;
pub extern fn SSL_CTX_config(ctx: *SslContext, name: [*c]const u8) i32;
pub extern fn SSL_CTX_ct_is_enabled(ctx: *const SslContext) i32;
pub extern fn SSL_CTX_ctrl(ctx: *SslContext, cmd: i32, larg: i64, parg: ?*anyopaque) i64;
pub extern fn SSL_CTX_dane_clear_flags(ctx: *SslContext, flags: i64) i64;
pub extern fn SSL_CTX_dane_enable(ctx: *SslContext) i32;
pub extern fn SSL_CTX_dane_mtype_set(ctx: *SslContext, md: ?*const c_ssl.EVP_MD, mtype: u8, ord: u8) i32;
pub extern fn SSL_CTX_dane_set_flags(ctx: *SslContext, flags: i64) i64;
pub extern fn SSL_CTX_enable_ct(ctx: *SslContext, validation_mode: i32) i32;
pub extern fn SSL_CTX_flush_sessions(ctx: *SslContext, tm: i64) void;
pub extern fn SSL_CTX_free(*SslContext) void;
pub extern fn SSL_CTX_get_cert_store(*const SslContext) ?*c_ssl.X509_STORE;
pub extern fn SSL_CTX_get_ciphers(ctx: *const SslContext) ?*c_ssl.struct_stack_st_SSL_CIPHER;
pub extern fn SSL_CTX_get_client_CA_list(s: *const SslContext) ?*c_ssl.struct_stack_st_X509_NAME;
pub extern fn SSL_CTX_get_client_cert_cb(ctx: *SslContext) ?*const fn (?*Ssl, [*c]?*c_ssl.X509, [*c]?*c_ssl.EVP_PKEY) callconv(.c) i32;
pub extern fn SSL_CTX_get_default_passwd_cb_userdata(ctx: *SslContext) ?*anyopaque;
pub extern fn SSL_CTX_get_default_passwd_cb(ctx: *SslContext) ?*const c_ssl.pem_password_cb;
pub extern fn SSL_CTX_get_ex_data(ssl: *const SslContext, idx: i32) ?*anyopaque;
pub extern fn SSL_CTX_get_info_callback(ctx: *SslContext) ?*const fn (?*const Ssl, i32, i32) callconv(.c) void;
pub extern fn SSL_CTX_get_keylog_callback(ctx: *const SslContext) c_ssl.SSL_CTX_keylog_cb_func;
pub extern fn SSL_CTX_get_max_early_data(ctx: *const SslContext) u32;
pub extern fn SSL_CTX_get_num_tickets(ctx: *const SslContext) usize;
pub extern fn SSL_CTX_get_options(ctx: *const SslContext) u64;
pub extern fn SSL_CTX_get_quiet_shutdown(ctx: *const SslContext) i32;
pub extern fn SSL_CTX_get_record_padding_callback_arg(ctx: *const SslContext) ?*anyopaque;
pub extern fn SSL_CTX_get_recv_max_early_data(ctx: *const SslContext) u32;
pub extern fn SSL_CTX_get_security_callback(ctx: *const SslContext) ?*const fn (
?*const Ssl,
*const SslContext,
i32,
i32,
i32,
?*anyopaque,
?*anyopaque,
) callconv(.c) i32;
pub extern fn SSL_CTX_get_security_level(ctx: *const SslContext) i32;
pub extern fn SSL_CTX_get_ssl_method(ctx: *const SslContext) ?*const SslMethod;
pub extern fn SSL_CTX_get_timeout(ctx: *const SslContext) i64;
pub extern fn SSL_CTX_get_verify_callback(ctx: *const SslContext) c_ssl.SSL_verify_cb;
pub extern fn SSL_CTX_get_verify_depth(ctx: *const SslContext) i32;
pub extern fn SSL_CTX_get_verify_mode(ctx: *const SslContext) i32;
pub extern fn SSL_CTX_get0_CA_list(ctx: *const SslContext) ?*const c_ssl.struct_stack_st_X509_NAME;
pub extern fn SSL_CTX_get0_certificate(ctx: *const SslContext) ?*c_ssl.X509;
pub extern fn SSL_CTX_get0_ctlog_store(ctx: *const SslContext) ?*const c_ssl.CTLOG_STORE;
pub extern fn SSL_CTX_get0_param(ctx: *SslContext) ?*c_ssl.X509_VERIFY_PARAM;
pub extern fn SSL_CTX_get0_privatekey(ctx: *const SslContext) ?*c_ssl.EVP_PKEY;
pub extern fn SSL_CTX_get0_security_ex_data(ctx: *const SslContext) ?*anyopaque;
pub extern fn SSL_CTX_has_client_custom_ext(ctx: *const SslContext, ext_type: u32) i32;
pub extern fn SSL_CTX_load_verify_dir(ctx: *SslContext, CApath: [*c]const u8) i32;
pub extern fn SSL_CTX_load_verify_file(ctx: *SslContext, CAfile: [*c]const u8) i32;
pub extern fn SSL_CTX_load_verify_locations(ctx: *SslContext, CAfile: [*c]const u8, CApath: [*c]const u8) i32;
pub extern fn SSL_CTX_load_verify_store(ctx: *SslContext, CAstore: [*c]const u8) i32;
pub extern fn SSL_CTX_new_ex(libctx: ?*c_ssl.OSSL_LIB_CTX, propq: [*c]const u8, meth: ?*const SslMethod) ?*SslContext;
pub extern fn SSL_CTX_new(meth: ?*const SslMethod) ?*SslContext;
pub extern fn SSL_CTX_remove_session(ctx: *SslContext, session: ?*SslSession) i32;
pub extern fn SSL_CTX_sess_get_get_cb(ctx: *SslContext) ?*const fn (
?*c_ssl.struct_ssl_st,
[*c]const u8,
i32,
[*c]i32,
) callconv(.c) ?*SslSession;
pub extern fn SSL_CTX_sess_get_new_cb(ctx: *SslContext) ?*const fn (
?*c_ssl.struct_ssl_st,
?*SslSession,
) callconv(.c) i32;
pub extern fn SSL_CTX_sess_get_remove_cb(ctx: *SslContext) ?*const fn (
?*c_ssl.struct_ssl_ctx_st,
?*SslSession,
) callconv(.c) void;
pub extern fn SSL_CTX_sess_set_get_cb(
ctx: *SslContext,
get_session_cb: ?*const fn (
?*c_ssl.struct_ssl_st,
[*c]const u8,
i32,
[*c]i32,
) callconv(.c) ?*SslSession,
) void;
pub extern fn SSL_CTX_sess_set_new_cb(
ctx: *SslContext,
new_session_cb: ?*const fn (
?*c_ssl.struct_ssl_st,
?*SslSession,
) callconv(.c) i32,
) void;
pub extern fn SSL_CTX_sess_set_remove_cb(
ctx: *SslContext,
remove_session_cb: ?*const fn (
?*c_ssl.struct_ssl_ctx_st,
?*SslSession,
) callconv(.c) void,
) void;
pub extern fn SSL_CTX_sessions(ctx: *SslContext) ?*c_ssl.struct_lhash_st_SSL_SESSION;
pub extern fn SSL_CTX_set_allow_early_data_cb(ctx: *SslContext, cb: c_ssl.SSL_allow_early_data_cb_fn, arg: ?*anyopaque) void;
pub extern fn SSL_CTX_set_alpn_protos(ctx: *SslContext, protos: [*c]const u8, protos_len: u32) i32;
pub extern fn SSL_CTX_set_alpn_select_cb(ctx: *SslContext, cb: c_ssl.SSL_CTX_alpn_select_cb_func, arg: ?*anyopaque) void;
pub extern fn SSL_CTX_set_async_callback_arg(ctx: *SslContext, arg: ?*anyopaque) i32;
pub extern fn SSL_CTX_set_async_callback(ctx: *SslContext, callback: c_ssl.SSL_async_callback_fn) i32;
pub extern fn SSL_CTX_set_block_padding(ctx: *SslContext, block_size: usize) i32;
pub extern fn SSL_CTX_set_cert_cb(c: *SslContext, cb: ?*const fn (?*Ssl, ?*anyopaque) callconv(.c) i32, arg: ?*anyopaque) void;
pub extern fn SSL_CTX_set_cert_store(*SslContext, ?*c_ssl.X509_STORE) void;
pub extern fn SSL_CTX_set_cert_verify_callback(
ctx: *SslContext,
cb: ?*const fn (
?*c_ssl.X509_STORE_CTX,
?*anyopaque,
) callconv(.c) i32,
arg: ?*anyopaque,
) void;
pub extern fn SSL_CTX_set_cipher_list(*SslContext, str: [*c]const u8) i32;
pub extern fn SSL_CTX_set_ciphersuites(ctx: *SslContext, str: [*c]const u8) i32;
pub extern fn SSL_CTX_set_client_CA_list(ctx: *SslContext, name_list: ?*c_ssl.struct_stack_st_X509_NAME) void;
pub extern fn SSL_CTX_set_client_cert_cb(
ctx: *SslContext,
client_cert_cb: ?*const fn (
?*Ssl,
[*c]?*c_ssl.X509,
[*c]?*c_ssl.EVP_PKEY,
) callconv(.c) i32,
) void;
pub extern fn SSL_CTX_set_client_cert_engine(ctx: *SslContext, e: ?*c_ssl.ENGINE) i32;
pub extern fn SSL_CTX_set_client_hello_cb(c: *SslContext, cb: c_ssl.SSL_client_hello_cb_fn, arg: ?*anyopaque) void;
pub extern fn SSL_CTX_set_cookie_generate_cb(ctx: *SslContext, app_gen_cookie_cb: ?*const fn (?*Ssl, [*c]u8, [*c]u32) callconv(.c) i32) void;
pub extern fn SSL_CTX_set_cookie_verify_cb(ctx: *SslContext, app_verify_cookie_cb: ?*const fn (?*Ssl, [*c]const u8, u32) callconv(.c) i32) void;
pub extern fn SSL_CTX_set_ct_validation_callback(ctx: *SslContext, callback: c_ssl.ssl_ct_validation_cb, arg: ?*anyopaque) i32;
pub extern fn SSL_CTX_set_ctlog_list_file(ctx: *SslContext, path: [*c]const u8) i32;
pub extern fn SSL_CTX_set_default_ctlog_list_file(ctx: *SslContext) i32;
pub extern fn SSL_CTX_set_default_passwd_cb_userdata(ctx: *SslContext, u: ?*anyopaque) void;
pub extern fn SSL_CTX_set_default_passwd_cb(ctx: *SslContext, cb: ?*const c_ssl.pem_password_cb) void;
pub extern fn SSL_CTX_set_default_read_buffer_len(ctx: *SslContext, len: usize) void;
pub extern fn SSL_CTX_set_default_verify_dir(ctx: *SslContext) i32;
pub extern fn SSL_CTX_set_default_verify_file(ctx: *SslContext) i32;
pub extern fn SSL_CTX_set_default_verify_paths(ctx: *SslContext) i32;
pub extern fn SSL_CTX_set_default_verify_store(ctx: *SslContext) i32;
pub extern fn SSL_CTX_set_ex_data(ssl: *SslContext, idx: i32, data: ?*anyopaque) i32;
pub extern fn SSL_CTX_set_generate_session_id(ctx: *SslContext, cb: c_ssl.GEN_SESSION_CB) i32;
pub extern fn SSL_CTX_set_info_callback(ctx: *SslContext, cb: ?*const fn (?*const Ssl, i32, i32) callconv(.c) void) void;
pub extern fn SSL_CTX_set_keylog_callback(ctx: *SslContext, cb: c_ssl.SSL_CTX_keylog_cb_func) void;
pub extern fn SSL_CTX_set_max_early_data(ctx: *SslContext, max_early_data: u32) i32;
pub extern fn SSL_CTX_set_msg_callback(
ctx: *SslContext,
cb: ?*const fn (
i32,
i32,
i32,
?*const anyopaque,
usize,
?*Ssl,
?*anyopaque,
) callconv(.c) void,
) void;
pub extern fn SSL_CTX_set_next_proto_select_cb(s: *SslContext, cb: c_ssl.SSL_CTX_npn_select_cb_func, arg: ?*anyopaque) void;
pub extern fn SSL_CTX_set_next_protos_advertised_cb(s: *SslContext, cb: c_ssl.SSL_CTX_npn_advertised_cb_func, arg: ?*anyopaque) void;
pub extern fn SSL_CTX_set_not_resumable_session_callback(ctx: *SslContext, cb: ?*const fn (?*Ssl, i32) callconv(.c) i32) void;
pub extern fn SSL_CTX_set_num_tickets(ctx: *SslContext, num_tickets: usize) i32;
pub extern fn SSL_CTX_set_options(ctx: *SslContext, op: u64) u64;
pub extern fn SSL_CTX_set_post_handshake_auth(ctx: *SslContext, val: i32) void;
pub extern fn SSL_CTX_set_psk_client_callback(ctx: *SslContext, cb: c_ssl.SSL_psk_client_cb_func) void;
pub extern fn SSL_CTX_set_psk_find_session_callback(ctx: *SslContext, cb: c_ssl.SSL_psk_find_session_cb_func) void;
pub extern fn SSL_CTX_set_psk_server_callback(ctx: *SslContext, cb: c_ssl.SSL_psk_server_cb_func) void;
pub extern fn SSL_CTX_set_psk_use_session_callback(ctx: *SslContext, cb: c_ssl.SSL_psk_use_session_cb_func) void;
pub extern fn SSL_CTX_set_purpose(ctx: *SslContext, purpose: i32) i32;
pub extern fn SSL_CTX_set_quiet_shutdown(ctx: *SslContext, mode: i32) void;
pub extern fn SSL_CTX_set_record_padding_callback_arg(ctx: *SslContext, arg: ?*anyopaque) void;
pub extern fn SSL_CTX_set_record_padding_callback(ctx: *SslContext, cb: ?*const fn (?*Ssl, i32, usize, ?*anyopaque) callconv(.c) usize) void;
pub extern fn SSL_CTX_set_recv_max_early_data(ctx: *SslContext, recv_max_early_data: u32) i32;
pub extern fn SSL_CTX_set_security_callback(
ctx: *SslContext,
cb: ?*const fn (
?*const Ssl,
*const SslContext,
i32,
i32,
i32,
?*anyopaque,
?*anyopaque,
) callconv(.c) i32,
) void;
pub extern fn SSL_CTX_set_security_level(ctx: *SslContext, level: i32) void;
pub extern fn SSL_CTX_set_session_id_context(ctx: *SslContext, sid_ctx: [*c]const u8, sid_ctx_len: u32) i32;
pub extern fn SSL_CTX_set_session_ticket_cb(
ctx: *SslContext,
gen_cb: c_ssl.SSL_CTX_generate_session_ticket_fn,
dec_cb: c_ssl.SSL_CTX_decrypt_session_ticket_fn,
arg: ?*anyopaque,
) i32;
pub extern fn SSL_CTX_set_srp_cb_arg(ctx: *SslContext, arg: ?*anyopaque) i32;
pub extern fn SSL_CTX_set_srp_client_pwd_callback(ctx: *SslContext, cb: ?*const fn (?*Ssl, ?*anyopaque) callconv(.c) [*c]u8) i32;
pub extern fn SSL_CTX_set_srp_password(ctx: *SslContext, password: [*c]u8) i32;
pub extern fn SSL_CTX_set_srp_strength(ctx: *SslContext, strength: i32) i32;
pub extern fn SSL_CTX_set_srp_username_callback(ctx: *SslContext, cb: ?*const fn (?*Ssl, [*c]i32, ?*anyopaque) callconv(.c) i32) i32;
pub extern fn SSL_CTX_set_srp_username(ctx: *SslContext, name: [*c]u8) i32;
pub extern fn SSL_CTX_set_srp_verify_param_callback(ctx: *SslContext, cb: ?*const fn (?*Ssl, ?*anyopaque) callconv(.c) i32) i32;
pub extern fn SSL_CTX_set_ssl_version(ctx: *SslContext, meth: ?*const SslMethod) i32;
pub extern fn SSL_CTX_set_stateless_cookie_generate_cb(
ctx: *SslContext,
gen_stateless_cookie_cb: ?*const fn (
?*Ssl,
[*c]u8,
[*c]usize,
) callconv(.c) i32,
) void;
pub extern fn SSL_CTX_set_stateless_cookie_verify_cb(
ctx: *SslContext,
verify_stateless_cookie_cb: ?*const fn (
?*Ssl,
[*c]const u8,
usize,
) callconv(.c) i32,
) void;
pub extern fn SSL_CTX_set_timeout(ctx: *SslContext, t: i64) i64;
pub extern fn SSL_CTX_set_tlsext_max_fragment_length(ctx: *SslContext, mode: u8) i32;
pub extern fn SSL_CTX_set_tlsext_ticket_key_evp_cb(
ctx: *SslContext,
fp: ?*const fn (
?*Ssl,
[*c]u8,
[*c]u8,
?*c_ssl.EVP_CIPHER_CTX,
?*c_ssl.EVP_MAC_CTX,
i32,
) callconv(.c) i32,
) i32;
pub extern fn SSL_CTX_set_tlsext_use_srtp(ctx: *SslContext, profiles: [*c]const u8) i32;
pub extern fn SSL_CTX_set_tmp_dh_callback(ctx: *SslContext, dh: ?*const fn (?*Ssl, i32, i32) callconv(.c) ?*c_ssl.DH) void;
pub extern fn SSL_CTX_set_trust(ctx: *SslContext, trust: i32) i32;
pub extern fn SSL_CTX_set_verify_depth(ctx: *SslContext, depth: i32) void;
pub extern fn SSL_CTX_set_verify(ctx: *SslContext, mode: i32, callback: c_ssl.SSL_verify_cb) void;
pub extern fn SSL_CTX_set0_CA_list(ctx: *SslContext, name_list: ?*c_ssl.struct_stack_st_X509_NAME) void;
pub extern fn SSL_CTX_set0_ctlog_store(ctx: *SslContext, logs: ?*c_ssl.CTLOG_STORE) void;
pub extern fn SSL_CTX_set0_security_ex_data(ctx: *SslContext, ex: ?*anyopaque) void;
pub extern fn SSL_CTX_set0_tmp_dh_pkey(ctx: *SslContext, dhpkey: ?*c_ssl.EVP_PKEY) i32;
pub extern fn SSL_CTX_set1_cert_store(*SslContext, ?*c_ssl.X509_STORE) void;
pub extern fn SSL_CTX_set1_param(ctx: *SslContext, vpm: ?*c_ssl.X509_VERIFY_PARAM) i32;
pub extern fn SSL_CTX_SRP_CTX_free(ctx: *SslContext) i32;
pub extern fn SSL_CTX_SRP_CTX_init(ctx: *SslContext) i32;
pub extern fn SSL_CTX_up_ref(ctx: *SslContext) i32;
pub extern fn SSL_CTX_use_cert_and_key(
ctx: *SslContext,
x509: ?*c_ssl.X509,
privatekey: ?*c_ssl.EVP_PKEY,
chain: ?*c_ssl.struct_stack_st_X509,
override: i32,
) i32;
pub extern fn SSL_CTX_use_certificate_ASN1(ctx: *SslContext, len: i32, d: [*c]const u8) i32;
pub extern fn SSL_CTX_use_certificate_chain_file(ctx: *SslContext, file: [*c]const u8) i32;
pub extern fn SSL_CTX_use_certificate_file(ctx: *SslContext, file: [*c]const u8, @"type": i32) i32;
pub extern fn SSL_CTX_use_certificate(ctx: *SslContext, x: ?*c_ssl.X509) i32;
pub extern fn SSL_CTX_use_PrivateKey_ASN1(pk: i32, ctx: *SslContext, d: [*c]const u8, len: i64) i32;
pub extern fn SSL_CTX_use_PrivateKey_file(ctx: *SslContext, file: [*c]const u8, @"type": i32) i32;
pub extern fn SSL_CTX_use_PrivateKey(ctx: *SslContext, pkey: ?*c_ssl.EVP_PKEY) i32;
pub extern fn SSL_CTX_use_psk_identity_hint(ctx: *SslContext, identity_hint: [*c]const u8) i32;
pub extern fn SSL_CTX_use_RSAPrivateKey_ASN1(ctx: *SslContext, d: [*c]const u8, len: i64) i32;
pub extern fn SSL_CTX_use_RSAPrivateKey_file(ctx: *SslContext, file: [*c]const u8, @"type": i32) i32;
pub extern fn SSL_CTX_use_RSAPrivateKey(ctx: *SslContext, rsa: ?*c_ssl.RSA) i32;
pub extern fn SSL_CTX_use_serverinfo_ex(ctx: *SslContext, version: u32, serverinfo: [*c]const u8, serverinfo_length: usize) i32;
pub extern fn SSL_CTX_use_serverinfo_file(ctx: *SslContext, file: [*c]const u8) i32;
pub extern fn SSL_CTX_use_serverinfo(ctx: *SslContext, serverinfo: [*c]const u8, serverinfo_length: usize) i32;
};

View File

@@ -0,0 +1,111 @@
const std = @import("std");
pub const SslMethod = opaque {
pub inline fn dtlsClientMethod() ?*const SslMethod {
return import.DTLS_client_method();
}
pub inline fn dtlsMethod() ?*const SslMethod {
return import.DTLS_method();
}
pub inline fn dtlsServerMethod() ?*const SslMethod {
return import.DTLS_server_method();
}
pub inline fn dtlsV1_2ClientMethod() ?*const SslMethod {
return import.DTLSv1_2_client_method();
}
pub inline fn dtlsV1_2Method() ?*const SslMethod {
return import.DTLSv1_2_method();
}
pub inline fn dtlsV1_2ServerMethod() ?*const SslMethod {
return import.DTLSv1_2_server_method();
}
pub inline fn dtlsV1ClientMethod() ?*const SslMethod {
return import.DTLSv1_client_method();
}
pub inline fn dtlsV1Method() ?*const SslMethod {
return import.DTLSv1_method();
}
pub inline fn dtlsV1ServerMethod() ?*const SslMethod {
return import.DTLSv1_server_method();
}
pub inline fn tlsClientMethod() ?*const SslMethod {
return import.TLS_client_method();
}
pub inline fn tlsMethod() ?*const SslMethod {
return import.TLS_method();
}
pub inline fn tlsServerMethod() ?*const SslMethod {
return import.TLS_server_method();
}
pub inline fn tlsV1_1ClientMethod() ?*const SslMethod {
return import.TLSv1_1_client_method();
}
pub inline fn tlsV1_1Method() ?*const SslMethod {
return import.TLSv1_1_method();
}
pub inline fn tlsV1_1ServerMethod() ?*const SslMethod {
return import.TLSv1_1_server_method();
}
pub inline fn tlsV1_2ClientMethod() ?*const SslMethod {
return import.TLSv1_2_client_method();
}
pub inline fn tlsV1_2Method() ?*const SslMethod {
return import.TLSv1_2_method();
}
pub inline fn tlsV1_2ServerMethod() ?*const SslMethod {
return import.TLSv1_2_server_method();
}
pub inline fn tlsV1ClientMethod() ?*const SslMethod {
return import.TLSv1_client_method();
}
pub inline fn tlsV1Method() ?*const SslMethod {
return import.TLSv1_method();
}
pub inline fn tlsV1ServerMethod() ?*const SslMethod {
return import.TLSv1_server_method();
}
};
const import = struct {
pub extern fn DTLS_client_method() ?*const SslMethod;
pub extern fn DTLS_method() ?*const SslMethod;
pub extern fn DTLS_server_method() ?*const SslMethod;
pub extern fn DTLSv1_2_client_method() ?*const SslMethod;
pub extern fn DTLSv1_2_method() ?*const SslMethod;
pub extern fn DTLSv1_2_server_method() ?*const SslMethod;
pub extern fn DTLSv1_client_method() ?*const SslMethod;
pub extern fn DTLSv1_method() ?*const SslMethod;
pub extern fn DTLSv1_server_method() ?*const SslMethod;
pub extern fn TLS_client_method() ?*const SslMethod;
pub extern fn TLS_method() ?*const SslMethod;
pub extern fn TLS_server_method() ?*const SslMethod;
pub extern fn TLSv1_1_client_method() ?*const SslMethod;
pub extern fn TLSv1_1_method() ?*const SslMethod;
pub extern fn TLSv1_1_server_method() ?*const SslMethod;
pub extern fn TLSv1_2_client_method() ?*const SslMethod;
pub extern fn TLSv1_2_method() ?*const SslMethod;
pub extern fn TLSv1_2_server_method() ?*const SslMethod;
pub extern fn TLSv1_client_method() ?*const SslMethod;
pub extern fn TLSv1_method() ?*const SslMethod;
pub extern fn TLSv1_server_method() ?*const SslMethod;
};

View File

@@ -0,0 +1,3 @@
const std = @import("std");
pub const SslSession = opaque {};

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

18
packages/web/src/root.zig Normal file
View File

@@ -0,0 +1,18 @@
const std = @import("std");
comptime {
// Only Linux supported
std.debug.assert(@import("builtin").os.tag == .linux);
}
pub const Connection = @import("Connection.zig");
pub const FileDescriptor = @import("FileDescriptor.zig").FileDescriptor;
pub const http = @import("http.zig");
pub const Id = @import("Id.zig").Id;
pub const openssl = @import("openssl.zig");
pub const Request = @import("Request.zig");
pub const RequestHandler = @import("RequestHandler.zig");
pub const Response = @import("Response.zig");
pub const Server = @import("Server.zig");
pub const UUID = @import("UUID.zig");
pub const Worker = @import("Worker.zig");