M1: doc-level free list, sessions, and a spec runner that no longer overstates #1
141
src/commands.zig
141
src/commands.zig
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user