milestone 19: hygiene sweep - dead ecs surface, single-source constants, tls classification, frontend state hazards, docker smoke network fix
This commit is contained in:
@@ -55,10 +55,19 @@ pub const max_rotated_path_bytes = std.Io.Dir.max_path_bytes + 4;
|
||||
pub const Stats = struct {
|
||||
lines_written: u64 = 0,
|
||||
lines_deduped: u64 = 0,
|
||||
/// Messages that did not fit `max_message_bytes` and carry the
|
||||
/// `truncation_marker` in place of their last bytes. Counted where the
|
||||
/// truncation happens, so a truncated message the dedup window then
|
||||
/// swallows counts here and under `lines_deduped`.
|
||||
lines_truncated: u64 = 0,
|
||||
rotations: u64 = 0,
|
||||
sink_errors: u64 = 0,
|
||||
};
|
||||
|
||||
/// What a truncated message ends in, the same three bytes `safe_url.zig` uses
|
||||
/// so both truncations read alike to an operator.
|
||||
pub const truncation_marker = "...";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Level
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -287,8 +296,18 @@ pub fn logFn(
|
||||
|
||||
var message_buf: [max_message_bytes]u8 = undefined;
|
||||
var mw: std.Io.Writer = .fixed(&message_buf);
|
||||
mw.print(format, args) catch {};
|
||||
const message = mw.buffered();
|
||||
// A message longer than the buffer is truncated rather than dropped, but a
|
||||
// silently cut line reads as a complete one: the marker says the sink cut
|
||||
// it, and the counter says how often that happens.
|
||||
const message = if (mw.print(format, args)) mw.buffered() else |_| blk: {
|
||||
state.stats.lines_truncated += 1;
|
||||
// `@min` rather than a plain subtraction: a `print` that fails on its
|
||||
// first chunk leaves fewer bytes buffered than the marker is long, and
|
||||
// the marker still has to fit.
|
||||
const kept = @min(mw.buffered().len, message_buf.len - truncation_marker.len);
|
||||
@memcpy(message_buf[kept..][0..truncation_marker.len], truncation_marker);
|
||||
break :blk message_buf[0 .. kept + truncation_marker.len];
|
||||
};
|
||||
|
||||
if (comptime isDedupScope(scope) and enabled(message_level, .warn)) {
|
||||
comptime std.debug.assert(@tagName(scope).len <= max_scope_name_bytes);
|
||||
@@ -1032,3 +1051,51 @@ test "rotation covers max_files - 1 generations" {
|
||||
try testing.expectEqualStrings("nxdns.log.1", names[0]);
|
||||
try testing.expectEqualStrings("nxdns.log.4", names[3]);
|
||||
}
|
||||
|
||||
test "a message over the buffer is marked and counted" {
|
||||
var threaded: std.Io.Threaded = .init(testing.allocator, .{});
|
||||
defer threaded.deinit();
|
||||
|
||||
var tmp = testing.tmpDir(.{});
|
||||
defer tmp.cleanup();
|
||||
var path_buf: [128]u8 = undefined;
|
||||
const p = try std.fmt.bufPrint(&path_buf, ".zig-cache/tmp/{s}/nxdns.log", .{tmp.sub_path});
|
||||
|
||||
var stderr_buf: [64]u8 = undefined;
|
||||
_ = std.debug.lockStderr(&stderr_buf);
|
||||
const saved = state;
|
||||
defer {
|
||||
closeFileLocked();
|
||||
state = saved;
|
||||
std.debug.unlockStderr();
|
||||
}
|
||||
|
||||
state.stats = .{};
|
||||
state.io = threaded.io();
|
||||
state.threshold = .info;
|
||||
state.output = .file;
|
||||
state.path_len = p.len;
|
||||
@memcpy(state.path_buf[0..p.len], p);
|
||||
state.max_files = 0;
|
||||
state.max_bytes = 0;
|
||||
state.file = null;
|
||||
state.file_pos = 0;
|
||||
state.rotate_pending = false;
|
||||
state.installed = true;
|
||||
|
||||
const oversized: [max_message_bytes + 100]u8 = @splat('a');
|
||||
logFn(.info, .default, "{s}", .{&oversized});
|
||||
|
||||
try testing.expectEqual(@as(u64, 1), state.stats.lines_truncated);
|
||||
try testing.expectEqual(@as(u64, 1), state.stats.lines_written);
|
||||
try testing.expectEqual(@as(u64, 0), state.stats.sink_errors);
|
||||
|
||||
closeFileLocked();
|
||||
var read_buf: [max_message_bytes * 2]u8 = undefined;
|
||||
const written = try std.Io.Dir.cwd().readFile(state.io, p, &read_buf);
|
||||
try testing.expect(std.mem.endsWith(u8, written, truncation_marker ++ "\n"));
|
||||
// The marker replaces the last bytes of the message rather than extending
|
||||
// it, so the record still fits what the buffer held.
|
||||
const colon = std.mem.indexOf(u8, written, ": ").?;
|
||||
try testing.expectEqual(max_message_bytes, written[colon + 2 ..].len - 1);
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#include <mbedtls/ctr_drbg.h>
|
||||
#include <mbedtls/entropy.h>
|
||||
#include <mbedtls/net_sockets.h>
|
||||
#include <mbedtls/pk.h>
|
||||
#include <mbedtls/ssl.h>
|
||||
#include <mbedtls/x509_crt.h>
|
||||
@@ -33,3 +34,19 @@ size_t nx_max_context_alignment(void)
|
||||
if (_Alignof(mbedtls_ctr_drbg_context) > max) max = _Alignof(mbedtls_ctr_drbg_context);
|
||||
return max;
|
||||
}
|
||||
|
||||
/* The configuration enums and error codes tls_server.zig transcribes from the
|
||||
* pinned headers. A version bump that renumbers one of these would otherwise
|
||||
* change behaviour silently -- a moved close_notify inverts the truncation
|
||||
* check -- so the Zig side compares every value against the getter here. */
|
||||
int nx_const_ssl_transport_stream(void) { return MBEDTLS_SSL_TRANSPORT_STREAM; }
|
||||
int nx_const_ssl_is_server(void) { return MBEDTLS_SSL_IS_SERVER; }
|
||||
int nx_const_ssl_verify_none(void) { return MBEDTLS_SSL_VERIFY_NONE; }
|
||||
int nx_const_ssl_preset_default(void) { return MBEDTLS_SSL_PRESET_DEFAULT; }
|
||||
|
||||
int nx_const_err_ssl_conn_eof(void) { return MBEDTLS_ERR_SSL_CONN_EOF; }
|
||||
int nx_const_err_ssl_peer_close_notify(void) { return MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY; }
|
||||
int nx_const_err_ssl_want_read(void) { return MBEDTLS_ERR_SSL_WANT_READ; }
|
||||
int nx_const_err_ssl_want_write(void) { return MBEDTLS_ERR_SSL_WANT_WRITE; }
|
||||
int nx_const_err_net_recv_failed(void) { return MBEDTLS_ERR_NET_RECV_FAILED; }
|
||||
int nx_const_err_net_send_failed(void) { return MBEDTLS_ERR_NET_SEND_FAILED; }
|
||||
|
||||
@@ -76,6 +76,7 @@ pub const ServerContext = struct {
|
||||
alpn: ?[*:null]const ?[*:0]const u8,
|
||||
) InitError!ServerContext {
|
||||
assert(nx_max_context_alignment() <= context_alignment);
|
||||
assert(transcriptionsAgree());
|
||||
|
||||
// TLS 1.3 is on in the stock config; its key schedule runs through PSA.
|
||||
const psa_status = psa_crypto_init();
|
||||
@@ -671,6 +672,35 @@ extern fn nx_max_context_alignment() usize;
|
||||
/// The public key of the first certificate in `crt`.
|
||||
extern fn nx_x509_crt_pk(crt: *X509Crt) *PkContext;
|
||||
|
||||
extern fn nx_const_ssl_transport_stream() c_int;
|
||||
extern fn nx_const_ssl_is_server() c_int;
|
||||
extern fn nx_const_ssl_verify_none() c_int;
|
||||
extern fn nx_const_ssl_preset_default() c_int;
|
||||
extern fn nx_const_err_ssl_conn_eof() c_int;
|
||||
extern fn nx_const_err_ssl_peer_close_notify() c_int;
|
||||
extern fn nx_const_err_ssl_want_read() c_int;
|
||||
extern fn nx_const_err_ssl_want_write() c_int;
|
||||
extern fn nx_const_err_net_recv_failed() c_int;
|
||||
extern fn nx_const_err_net_send_failed() c_int;
|
||||
|
||||
/// Every transcribed value beside the header macro it came from. The sizes and
|
||||
/// alignments have always been read from the shim rather than transcribed;
|
||||
/// these ten were the exception, and a renumbering after a version bump would
|
||||
/// change behaviour with nothing to say so — a moved `close_notify` inverts the
|
||||
/// truncation detection in `read` and `close`.
|
||||
fn transcriptionsAgree() bool {
|
||||
return ssl_transport_stream == nx_const_ssl_transport_stream() and
|
||||
ssl_is_server == nx_const_ssl_is_server() and
|
||||
ssl_verify_none == nx_const_ssl_verify_none() and
|
||||
ssl_preset_default == nx_const_ssl_preset_default() and
|
||||
err_conn_eof == nx_const_err_ssl_conn_eof() and
|
||||
err_peer_close_notify == nx_const_err_ssl_peer_close_notify() and
|
||||
err_want_read == nx_const_err_ssl_want_read() and
|
||||
err_want_write == nx_const_err_ssl_want_write() and
|
||||
err_net_recv_failed == nx_const_err_net_recv_failed() and
|
||||
err_net_send_failed == nx_const_err_net_send_failed();
|
||||
}
|
||||
|
||||
// -- tests -----------------------------------------------------------------
|
||||
|
||||
test "ServerContext.init accepts the fixture cert and key" {
|
||||
@@ -736,6 +766,23 @@ test "the shim agrees with the alignment contexts are allocated at" {
|
||||
try std.testing.expect(nx_sizeof_ctr_drbg_context() > 0);
|
||||
}
|
||||
|
||||
test "the shim agrees with every transcribed mbedtls constant" {
|
||||
try std.testing.expect(transcriptionsAgree());
|
||||
|
||||
// Value by value, so a failure names the one that drifted instead of
|
||||
// reporting that something did.
|
||||
try std.testing.expectEqual(ssl_transport_stream, nx_const_ssl_transport_stream());
|
||||
try std.testing.expectEqual(ssl_is_server, nx_const_ssl_is_server());
|
||||
try std.testing.expectEqual(ssl_verify_none, nx_const_ssl_verify_none());
|
||||
try std.testing.expectEqual(ssl_preset_default, nx_const_ssl_preset_default());
|
||||
try std.testing.expectEqual(err_conn_eof, nx_const_err_ssl_conn_eof());
|
||||
try std.testing.expectEqual(err_peer_close_notify, nx_const_err_ssl_peer_close_notify());
|
||||
try std.testing.expectEqual(err_want_read, nx_const_err_ssl_want_read());
|
||||
try std.testing.expectEqual(err_want_write, nx_const_err_ssl_want_write());
|
||||
try std.testing.expectEqual(err_net_recv_failed, nx_const_err_net_recv_failed());
|
||||
try std.testing.expectEqual(err_net_send_failed, nx_const_err_net_send_failed());
|
||||
}
|
||||
|
||||
test "readErrorFor keeps a canceled read out of the TLS failure bucket" {
|
||||
try std.testing.expectEqual(ServerStream.ReadError.Canceled, readErrorFor(error.Canceled));
|
||||
try std.testing.expectEqual(ServerStream.ReadError.TlsFailed, readErrorFor(error.ConnectionResetByPeer));
|
||||
|
||||
Reference in New Issue
Block a user