Compare commits
19 Commits
85f4957661
...
dc839e098c
| Author | SHA1 | Date | |
|---|---|---|---|
| dc839e098c | |||
| 380145a986 | |||
| 0cce9d9bce | |||
| 572f4be896 | |||
| 8d45a93e6e | |||
| 09266e678f | |||
| cbf0d6a9da | |||
| 712e214f61 | |||
| fe4a585b6b | |||
| 9a4932e629 | |||
| 6315589fa1 | |||
| 1f07cc38ba | |||
| e09a00a4ba | |||
| 738ba5bd37 | |||
| 66d49ea8d5 | |||
| f02ece22fa | |||
| 2e97aef842 | |||
| 9e7955495f | |||
| ef15201f21 |
2
.gitattributes
vendored
2
.gitattributes
vendored
@@ -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
1
.gitignore
vendored
@@ -1,2 +1,3 @@
|
||||
.zig-cache
|
||||
zig-out
|
||||
zig-pkg
|
||||
|
||||
8
.vscode/settings.json
vendored
Normal file
8
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"files.exclude": {
|
||||
"**/.zig-cache": true,
|
||||
},
|
||||
"files.associations": {
|
||||
"**/*.{c,h}": "c",
|
||||
},
|
||||
}
|
||||
88
README.md
Normal file
88
README.md
Normal 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.
|
||||
@@ -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",
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -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) };
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
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
BIN
packages/media/png-specification.pdf
(Stored with Git LFS)
Normal file
Binary file not shown.
25
packages/media/src/Format.zig
Normal file
25
packages/media/src/Format.zig
Normal 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());
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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
151
packages/media/src/jpeg.zig
Normal 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());
|
||||
}
|
||||
41
packages/media/src/jxl.zig
Normal file
41
packages/media/src/jxl.zig
Normal 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
645
packages/media/src/png.zig
Normal 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 image’s 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());
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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 },
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -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
259
packages/myid/src/data.zig
Normal 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",
|
||||
});
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
pub const database_path = "db.sqlite3";
|
||||
pub const socket_path = "myid.sock";
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
6
packages/myid/src/id.zig
Normal 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);
|
||||
@@ -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");
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
};
|
||||
|
||||
@@ -252,4 +252,8 @@ pub const Matrix3x2 = extern struct {
|
||||
.ty = -inv_det * (self.tx * iy + self.ty * jy),
|
||||
};
|
||||
}
|
||||
|
||||
test "refAllDecls" {
|
||||
std.testing.refAllDecls(@This());
|
||||
}
|
||||
};
|
||||
|
||||
@@ -406,4 +406,8 @@ pub const Matrix3x2x8 = struct {
|
||||
.ty = -inv_det * (self.tx * iy + self.ty * jy),
|
||||
};
|
||||
}
|
||||
|
||||
test "refAllDecls" {
|
||||
std.testing.refAllDecls(@This());
|
||||
}
|
||||
};
|
||||
|
||||
@@ -507,4 +507,8 @@ pub const Matrix4x4 = extern struct {
|
||||
// zig fmt: on
|
||||
};
|
||||
}
|
||||
|
||||
test "refAllDecls" {
|
||||
std.testing.refAllDecls(@This());
|
||||
}
|
||||
};
|
||||
|
||||
@@ -763,4 +763,8 @@ pub const Matrix4x4x8 = extern struct {
|
||||
// zig fmt: on
|
||||
};
|
||||
}
|
||||
|
||||
test "refAllDecls" {
|
||||
std.testing.refAllDecls(@This());
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
};
|
||||
|
||||
@@ -180,4 +180,8 @@ pub const Vector3Int_x8 = struct {
|
||||
.z = self.x * other.y - self.y * other.x,
|
||||
};
|
||||
}
|
||||
|
||||
test "refAllDecls" {
|
||||
std.testing.refAllDecls(@This());
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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
52
packages/web/build.zig
Normal 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);
|
||||
}
|
||||
11
packages/web/build.zig.zon
Normal file
11
packages/web/build.zig.zon
Normal 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
9
packages/web/cert.pem
Normal 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
3
packages/web/key.pem
Normal file
@@ -0,0 +1,3 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MC4CAQAwBQYDK2VwBCIEIMDUsa31l2xEhX1gyB5w2WABgSbne3GHyUe0RWomq9C5
|
||||
-----END PRIVATE KEY-----
|
||||
147
packages/web/src/Connection.zig
Normal file
147
packages/web/src/Connection.zig
Normal 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);
|
||||
}
|
||||
}
|
||||
238
packages/web/src/FileDescriptor.zig
Normal file
238
packages/web/src/FileDescriptor.zig
Normal 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
41
packages/web/src/Id.zig
Normal 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);
|
||||
}
|
||||
};
|
||||
}
|
||||
66
packages/web/src/Request.zig
Normal file
66
packages/web/src/Request.zig
Normal 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;
|
||||
}
|
||||
16
packages/web/src/RequestHandler.zig
Normal file
16
packages/web/src/RequestHandler.zig
Normal 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);
|
||||
}
|
||||
85
packages/web/src/Response.zig
Normal file
85
packages/web/src/Response.zig
Normal 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
375
packages/web/src/Server.zig
Normal 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
87
packages/web/src/UUID.zig
Normal 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
321
packages/web/src/Worker.zig
Normal 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");
|
||||
}
|
||||
8
packages/web/src/http.zig
Normal file
8
packages/web/src/http.zig
Normal 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");
|
||||
91
packages/web/src/http/FieldName.zig
Normal file
91
packages/web/src/http/FieldName.zig
Normal 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);
|
||||
}
|
||||
};
|
||||
};
|
||||
23
packages/web/src/http/Header.zig
Normal file
23
packages/web/src/http/Header.zig
Normal 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));
|
||||
}
|
||||
367
packages/web/src/http/KnownFieldName.zig
Normal file
367
packages/web/src/http/KnownFieldName.zig
Normal 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);
|
||||
}
|
||||
};
|
||||
13
packages/web/src/http/Method.zig
Normal file
13
packages/web/src/http/Method.zig
Normal file
@@ -0,0 +1,13 @@
|
||||
const std = @import("std");
|
||||
|
||||
pub const Method = enum {
|
||||
CONNECT,
|
||||
DELETE,
|
||||
GET,
|
||||
HEAD,
|
||||
OPTIONS,
|
||||
PATCH,
|
||||
POST,
|
||||
PUT,
|
||||
TRACE,
|
||||
};
|
||||
680
packages/web/src/http/Parser.zig
Normal file
680
packages/web/src/http/Parser.zig
Normal 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,
|
||||
};
|
||||
},
|
||||
}
|
||||
}
|
||||
58
packages/web/src/http/status.zig
Normal file
58
packages/web/src/http/status.zig
Normal 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
133
packages/web/src/main.zig
Normal 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);
|
||||
}
|
||||
9
packages/web/src/openssl.zig
Normal file
9
packages/web/src/openssl.zig
Normal 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;
|
||||
339
packages/web/src/openssl/Ssl.zig
Normal file
339
packages/web/src/openssl/Ssl.zig
Normal 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;
|
||||
};
|
||||
657
packages/web/src/openssl/SslContext.zig
Normal file
657
packages/web/src/openssl/SslContext.zig
Normal 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;
|
||||
};
|
||||
111
packages/web/src/openssl/SslMethod.zig
Normal file
111
packages/web/src/openssl/SslMethod.zig
Normal 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;
|
||||
};
|
||||
3
packages/web/src/openssl/SslSession.zig
Normal file
3
packages/web/src/openssl/SslSession.zig
Normal file
@@ -0,0 +1,3 @@
|
||||
const std = @import("std");
|
||||
|
||||
pub const SslSession = opaque {};
|
||||
7309
packages/web/src/openssl/err.zig
Normal file
7309
packages/web/src/openssl/err.zig
Normal file
File diff suppressed because it is too large
Load Diff
30571
packages/web/src/openssl/ssl.zig
Normal file
30571
packages/web/src/openssl/ssl.zig
Normal file
File diff suppressed because it is too large
Load Diff
18
packages/web/src/root.zig
Normal file
18
packages/web/src/root.zig
Normal 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");
|
||||
Reference in New Issue
Block a user