feature. See also
. The project being documented here (as the example) is the Zig library itself.
Client.Connection
pub const Connection = struct
File
Code
pub const Connection = struct {
client: *Client,
stream_writer: Io.net.Stream.Writer,
stream_reader: Io.net.Stream.Reader,
pool_node: std.DoublyLinkedList.Node,
port: u16,
host_len: u8,
proxied: bool,
closing: bool,
protocol: Protocol,
const Plain = struct {
connection: Connection,
fn create(
client: *Client,
remote_host: HostName,
port: u16,
stream: Io.net.Stream,
) error{OutOfMemory}!*Plain {
const io = client.io;
const gpa = client.allocator;
const alloc_len = allocLen(client, remote_host.bytes.len);
const base = try gpa.alignedAlloc(u8, .of(Plain), alloc_len);
errdefer gpa.free(base);
const host_buffer = base[@sizeOf(Plain)..][0..remote_host.bytes.len];
const socket_read_buffer = host_buffer.ptr[host_buffer.len..][0..client.read_buffer_size];
const socket_write_buffer = socket_read_buffer.ptr[socket_read_buffer.len..][0..client.write_buffer_size];
assert(base.ptr + alloc_len == socket_write_buffer.ptr + socket_write_buffer.len);
@memcpy(host_buffer, remote_host.bytes);
const plain: *Plain = @ptrCast(base);
plain.* = .{
.connection = .{
.client = client,
.stream_writer = stream.writer(io, socket_write_buffer),
.stream_reader = stream.reader(io, socket_read_buffer),
.pool_node = .{},
.port = port,
.host_len = @intCast(remote_host.bytes.len),
.proxied = false,
.closing = false,
.protocol = .plain,
},
};
return plain;
}
fn destroy(plain: *Plain) void {
const c = &plain.connection;
const gpa = c.client.allocator;
const base: [*]align(@alignOf(Plain)) u8 = @ptrCast(plain);
gpa.free(base[0..allocLen(c.client, c.host_len)]);
}
fn allocLen(client: *Client, host_len: usize) usize {
return @sizeOf(Plain) + host_len + client.read_buffer_size + client.write_buffer_size;
}
fn host(plain: *Plain) HostName {
const base: [*]u8 = @ptrCast(plain);
return .{ .bytes = base[@sizeOf(Plain)..][0..plain.connection.host_len] };
}
};
const Tls = struct {
client: std.crypto.tls.Client,
connection: Connection,
fn create(
client: *Client,
remote_host: HostName,
port: u16,
stream: Io.net.Stream,
) !*Tls {
const io = client.io;
const gpa = client.allocator;
const alloc_len = allocLen(client, remote_host.bytes.len);
const base = try gpa.alignedAlloc(u8, .of(Tls), alloc_len);
errdefer gpa.free(base);
const host_buffer = base[@sizeOf(Tls)..][0..remote_host.bytes.len];
// size, and the HTTP body reader wants enough buffer for the
// entire HTTP header. This means we need a combined upper bound.
const tls_read_buffer_len = client.tls_buffer_size + client.read_buffer_size;
const tls_read_buffer = host_buffer.ptr[host_buffer.len..][0..tls_read_buffer_len];
const tls_write_buffer = tls_read_buffer.ptr[tls_read_buffer.len..][0..client.tls_buffer_size];
const socket_write_buffer = tls_write_buffer.ptr[tls_write_buffer.len..][0..client.write_buffer_size];
const socket_read_buffer = socket_write_buffer.ptr[socket_write_buffer.len..][0..client.tls_buffer_size];
assert(base.ptr + alloc_len == socket_read_buffer.ptr + socket_read_buffer.len);
@memcpy(host_buffer, remote_host.bytes);
const tls: *Tls = @ptrCast(base);
var random_buffer: [std.crypto.tls.Client.Options.entropy_len]u8 = undefined;
io.random(&random_buffer);
tls.* = .{
.connection = .{
.client = client,
.stream_writer = stream.writer(io, tls_write_buffer),
.stream_reader = stream.reader(io, socket_read_buffer),
.pool_node = .{},
.port = port,
.host_len = @intCast(remote_host.bytes.len),
.proxied = false,
.closing = false,
.protocol = .tls,
},
.client = std.crypto.tls.Client.init(
&tls.connection.stream_reader.interface,
&tls.connection.stream_writer.interface,
.{
.host = .{ .explicit = remote_host.bytes },
.ca = .{ .bundle = .{
.gpa = client.allocator,
.io = client.io,
.lock = &client.ca_bundle_lock,
.bundle = &client.ca_bundle,
} },
.ssl_key_log = client.ssl_key_log,
.read_buffer = tls_read_buffer,
.write_buffer = socket_write_buffer,
.entropy = &random_buffer,
.realtime_now = client.now.?,
// the content length which is used to detect truncation attacks.
.allow_truncation_attacks = true,
},
) catch |err| switch (err) {
error.WriteFailed => return tls.connection.stream_writer.err.?,
error.ReadFailed => return tls.connection.stream_reader.err.?,
else => |e| return e,
},
};
return tls;
}
fn destroy(tls: *Tls) void {
const c = &tls.connection;
const gpa = c.client.allocator;
const base: [*]align(@alignOf(Tls)) u8 = @ptrCast(tls);
gpa.free(base[0..allocLen(c.client, c.host_len)]);
}
fn allocLen(client: *Client, host_len: usize) usize {
const tls_read_buffer_len = client.tls_buffer_size + client.read_buffer_size;
return @sizeOf(Tls) + host_len + tls_read_buffer_len + client.tls_buffer_size +
client.write_buffer_size + client.tls_buffer_size;
}
fn host(tls: *Tls) HostName {
const base: [*]u8 = @ptrCast(tls);
return .{ .bytes = base[@sizeOf(Tls)..][0..tls.connection.host_len] };
}
};
pub const ReadError = std.crypto.tls.Client.ReadError || Io.net.Stream.Reader.Error;
pub fn getReadError(c: *const Connection) ?ReadError {
return switch (c.protocol) {
.tls => {
if (disable_tls) unreachable;
const tls: *const Tls = @alignCast(@fieldParentPtr("connection", c));
return tls.client.read_err orelse c.stream_reader.err.?;
},
.plain => {
return c.stream_reader.err.?;
},
};
}
fn getStream(c: *Connection) Io.net.Stream {
return c.stream_reader.stream;
}
pub fn host(c: *Connection) HostName {
return switch (c.protocol) {
.tls => {
if (disable_tls) unreachable;
const tls: *Tls = @alignCast(@fieldParentPtr("connection", c));
return tls.host();
},
.plain => {
const plain: *Plain = @alignCast(@fieldParentPtr("connection", c));
return plain.host();
},
};
}
pub fn destroy(c: *Connection, io: Io) void {
c.stream_reader.stream.close(io);
switch (c.protocol) {
.tls => {
if (disable_tls) unreachable;
const tls: *Tls = @alignCast(@fieldParentPtr("connection", c));
tls.destroy();
},
.plain => {
const plain: *Plain = @alignCast(@fieldParentPtr("connection", c));
plain.destroy();
},
}
}
pub fn writer(c: *Connection) *Writer {
return switch (c.protocol) {
.tls => {
if (disable_tls) unreachable;
const tls: *Tls = @alignCast(@fieldParentPtr("connection", c));
return &tls.client.writer;
},
.plain => &c.stream_writer.interface,
};
}
pub fn reader(c: *Connection) *Reader {
return switch (c.protocol) {
.tls => {
if (disable_tls) unreachable;
const tls: *Tls = @alignCast(@fieldParentPtr("connection", c));
return &tls.client.reader;
},
.plain => &c.stream_reader.interface,
};
}
pub fn flush(c: *Connection) Writer.Error!void {
if (c.protocol == .tls) {
if (disable_tls) unreachable;
const tls: *Tls = @alignCast(@fieldParentPtr("connection", c));
try tls.client.writer.flush();
}
try c.stream_writer.interface.flush();
}
pub fn end(c: *Connection) Writer.Error!void {
if (c.protocol == .tls) {
if (disable_tls) unreachable;
const tls: *Tls = @alignCast(@fieldParentPtr("connection", c));
try tls.client.end();
}
try c.stream_writer.interface.flush();
}
}