M1: doc-level free list, sessions, and a spec runner that no longer overstates #1

Merged
dev merged 37 commits from m1-cursors into main 2026-08-09 16:15:34 +00:00
Showing only changes of commit f5c827e581 - Show all commits

View File

@@ -559,10 +559,108 @@ fn reject_bad_session_fields(msg: *wire.Message, reply: *wire.Reply, cmd: []cons
return reject_txn_fields(msg, reply, cmd);
}
fn cmd_end_sessions(_: *Context, _: *wire.Message, reply: *wire.Reply) !void {
/// Still a no-op -- there is nothing to end -- but no longer a blind `ok`.
///
/// A driver sends this on close for every session it handed out, so it is the
/// one session command that arrives in normal operation. Validating an array
/// we then discard looks like ceremony; it is not. `ok: 1` to a malformed
/// `endSessions` is the same class of answer as `ok: 1` to a transactional
/// write: the client is told the server understood, and it did not. mongod's
/// field path is `endSessions.endSessionsFromClient`, an IDL artefact -- the
/// command's own field is `endSessions` and the parsed argument has a
/// different name -- and it is reproduced rather than tidied, because a name
/// that differs from the real server's is worse than an odd one.
fn cmd_end_sessions(_: *Context, msg: *wire.Message, reply: *wire.Reply) !void {
const arena = reply.arena_alloc();
const sessions = switch (msg.body.get("endSessions").?) {
.array => |a| a,
else => |v| {
const text = try std.fmt.allocPrint(
arena,
"BSON field 'endSessions.endSessions' is the wrong type '{s}', expected type 'array'",
.{v.type_name()},
);
return reply.put_error(@intFromEnum(ErrorCode.type_mismatch), "TypeMismatch", text);
},
};
for (sessions, 0..) |entry, i| {
if (try reject_bad_session_entry(entry, i, reply)) return;
}
try reply.put_ok();
}
/// One element of `endSessions`. Answers whether an error reply was written.
fn reject_bad_session_entry(entry: bson.Value, i: usize, reply: *wire.Reply) !bool {
const arena = reply.arena_alloc();
const prefix = "BSON field 'endSessions.endSessionsFromClient";
const doc = switch (entry) {
.doc => |d| d,
else => {
const text = try std.fmt.allocPrint(
arena,
"{s}.{d}' is the wrong type '{s}', expected type 'object'",
.{ prefix, i, entry.type_name() },
);
try reply.put_error(@intFromEnum(ErrorCode.type_mismatch), "TypeMismatch", text);
return true;
},
};
var seen_id = false;
for (doc) |pair| {
if (std.mem.eql(u8, pair.key, "uid")) continue;
if (!std.mem.eql(u8, pair.key, "id")) {
const text = try std.fmt.allocPrint(
arena,
"{s}.{s}' is an unknown field.",
.{ prefix, pair.key },
);
try reply.put_error(@intFromEnum(ErrorCode.idl_unknown_field), "IDLUnknownField", text);
return true;
}
seen_id = true;
const bin = switch (pair.value) {
.binary => |b| b,
else => {
const text = try std.fmt.allocPrint(
arena,
"{s}.id' is the wrong type '{s}', expected type 'binData'",
.{ prefix, pair.value.type_name() },
);
try reply.put_error(@intFromEnum(ErrorCode.type_mismatch), "TypeMismatch", text);
return true;
},
};
if (bin.subtype != 4) {
const text = try std.fmt.allocPrint(
arena,
"{s}.id' is the wrong binData type '{s}', expected type 'UUID'",
.{ prefix, subtype_name(bin.subtype) },
);
try reply.put_error(@intFromEnum(ErrorCode.type_mismatch), "TypeMismatch", text);
return true;
}
if (bin.data.len != 16) {
try reply.put_error(
@intFromEnum(ErrorCode.invalid_uuid),
"InvalidUUID",
"uuid must be a 16-byte binary field with UUID (4) subtype",
);
return true;
}
}
if (!seen_id) {
try reply.put_error(
@intFromEnum(ErrorCode.idl_failed_to_parse),
"IDLFailedToParse",
"BSON field 'endSessions.endSessionsFromClient.id' is missing but a required field",
);
return true;
}
return false;
}
fn cmd_connection_status(_: *Context, _: *wire.Message, reply: *wire.Reply) !void {
const auth_info = try reply.arena_alloc().alloc(bson.Pair, 2);
auth_info[0] = .{ .key = "authenticatedUsers", .value = .{ .array = &.{} } };
@@ -3194,6 +3292,47 @@ test "a transactional write is refused rather than applied" {
try testing.expectEqual(@as(i64, 0), try doc_count(&ctx, "txn"));
}
test "endSessions judges the array it is handed" {
// Still a no-op, and that is not what is being tested. The command arrives
// in normal operation -- a driver sends it on close for every session it
// handed out -- so a blind `ok` here tells a client the server understood
// something it never looked at. Codes measured against mongod 8.3.7.
var threaded: std.Io.Threaded = .init_single_threaded;
defer threaded.deinit();
const io = threaded.io();
var tdb = try TestDb.init(io);
defer tdb.deinit();
var ctx = tdb.ctx(io);
const uuid = [_]u8{0x6B} ** 16;
const good = bson.Value{ .binary = .{ .subtype = 4, .data = &uuid } };
const one = bson.Value{ .doc = &.{.{ .key = "id", .value = good }} };
try testing.expectEqual(@as(?i32, null), try run_for_code(&ctx, "endSessions", .{ .array = &.{} }, &.{}));
try testing.expectEqual(@as(?i32, null), try run_for_code(&ctx, "endSessions", .{ .array = &.{one} }, &.{}));
// `uid` rides along once authentication is on, exactly as it does in lsid.
try testing.expectEqual(@as(?i32, null), try run_for_code(&ctx, "endSessions", .{ .array = &.{.{ .doc = &.{
.{ .key = "id", .value = good },
.{ .key = "uid", .value = .{ .binary = .{ .subtype = 0, .data = &[_]u8{0} ** 32 } } },
} }} }, &.{}));
try testing.expectEqual(@as(i32, 14), (try run_for_code(&ctx, "endSessions", .{ .string = "nope" }, &.{})).?);
try testing.expectEqual(@as(i32, 14), (try run_for_code(&ctx, "endSessions", .{ .array = &.{.{ .int32 = 5 }} }, &.{})).?);
try testing.expectEqual(@as(i32, 14), (try run_for_code(&ctx, "endSessions", .{ .array = &.{.{ .doc = &.{
.{ .key = "id", .value = .{ .string = "nope" } },
} }} }, &.{})).?);
try testing.expectEqual(@as(i32, 207), (try run_for_code(&ctx, "endSessions", .{ .array = &.{.{ .doc = &.{
.{ .key = "id", .value = .{ .binary = .{ .subtype = 4, .data = uuid[0..15] } } },
} }} }, &.{})).?);
try testing.expectEqual(@as(i32, 40414), (try run_for_code(&ctx, "endSessions", .{ .array = &.{.{ .doc = &.{} }} }, &.{})).?);
try testing.expectEqual(@as(i32, 40415), (try run_for_code(&ctx, "endSessions", .{ .array = &.{.{ .doc = &.{
.{ .key = "id", .value = good },
.{ .key = "bogus", .value = .{ .int32 = 1 } },
} }} }, &.{})).?);
// A bad entry after a good one is still a bad entry.
try testing.expectEqual(@as(i32, 14), (try run_for_code(&ctx, "endSessions", .{ .array = &.{ one, .{ .int32 = 5 } } }, &.{})).?);
}
test "an unknown command is reported before its session id is judged" {
// Measured: mongod answers CommandNotFound to `{nosuchcmd: 1, lsid: 5}`,
// so the lookup comes first and this is where the check belongs.