redis.zig 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. const std = @import("std");
  2. const engine_mod = @import("engine.zig");
  3. const max_resp_frame = engine_mod.pkvdb_max_frame + 1024 * 1024 + 1024;
  4. pub const CommandType = enum { set, get, del, unknown };
  5. pub const Command = struct {
  6. command_type: CommandType,
  7. key: []const u8,
  8. value: []const u8 = "",
  9. };
  10. pub const ParseResult = struct {
  11. command: Command,
  12. consumed: usize,
  13. };
  14. pub const Response = union(enum) {
  15. simple: []const u8,
  16. integer: i64,
  17. null_bulk,
  18. bulk: engine_mod.Value,
  19. failure: []const u8,
  20. pub fn deinit(self: *Response, allocator: std.mem.Allocator) void {
  21. if (self.* == .bulk) allocator.free(self.bulk.bytes);
  22. self.* = undefined;
  23. }
  24. };
  25. fn parseUnsigned(bytes: []const u8) !usize {
  26. if (bytes.len == 0) return error.InvalidFrame;
  27. var value: usize = 0;
  28. for (bytes) |byte| {
  29. if (byte < '0' or byte > '9') return error.InvalidFrame;
  30. value = try std.math.add(usize, try std.math.mul(usize, value, 10), byte - '0');
  31. }
  32. return value;
  33. }
  34. fn lineEnd(bytes: []const u8, start: usize) !usize {
  35. const end = std.mem.indexOfPos(u8, bytes, start, "\r\n") orelse return error.Incomplete;
  36. return end;
  37. }
  38. fn bulk(bytes: []const u8, position: *usize) ![]const u8 {
  39. if (position.* >= bytes.len) return error.Incomplete;
  40. if (bytes[position.*] != '$') return error.InvalidFrame;
  41. const end = try lineEnd(bytes, position.* + 1);
  42. const length = try parseUnsigned(bytes[position.* + 1 .. end]);
  43. if (length > engine_mod.pkvdb_max_frame) return error.FrameTooLarge;
  44. const start = try std.math.add(usize, end, 2);
  45. const data_end = try std.math.add(usize, start, length);
  46. const final = try std.math.add(usize, data_end, 2);
  47. if (final > max_resp_frame) return error.FrameTooLarge;
  48. if (final > bytes.len) return error.Incomplete;
  49. if (!std.mem.eql(u8, bytes[data_end..final], "\r\n")) return error.InvalidFrame;
  50. position.* = final;
  51. return bytes[start..data_end];
  52. }
  53. pub fn parse(bytes: []const u8) !ParseResult {
  54. if (bytes.len == 0) return error.Incomplete;
  55. if (bytes[0] != '*') return error.InvalidFrame;
  56. const end = try lineEnd(bytes, 1);
  57. const count = try parseUnsigned(bytes[1..end]);
  58. if (count == 0 or count > 16) return error.InvalidFrame;
  59. var position = end + 2;
  60. var fields: [16][]const u8 = undefined;
  61. for (0..count) |index| fields[index] = try bulk(bytes, &position);
  62. const command_type: CommandType = if (std.ascii.eqlIgnoreCase(fields[0], "SET")) .set else if (std.ascii.eqlIgnoreCase(fields[0], "GET")) .get else if (std.ascii.eqlIgnoreCase(fields[0], "DEL")) .del else .unknown;
  63. switch (command_type) {
  64. .set => if (count != 3) return error.InvalidFrame,
  65. .get, .del => if (count != 2) return error.InvalidFrame,
  66. .unknown => {},
  67. }
  68. return .{ .command = .{ .command_type = command_type, .key = if (count > 1) fields[1] else "", .value = if (count > 2) fields[2] else "" }, .consumed = position };
  69. }
  70. pub fn execute(engine: *engine_mod.Engine, allocator: std.mem.Allocator, command: Command) !Response {
  71. return switch (command.command_type) {
  72. .set => if (engine.put(command.key, command.value)) |_| Response{ .simple = "OK" } else |_| Response{ .failure = "ERR write failed" },
  73. .get => if (try engine.get(allocator, command.key)) |value| Response{ .bulk = value } else Response.null_bulk,
  74. .del => Response{ .integer = if (try engine.delete(command.key)) 1 else 0 },
  75. .unknown => Response{ .failure = "ERR unknown command" },
  76. };
  77. }
  78. pub fn encodePrefix(response: Response, output: []u8) ![]const u8 {
  79. return switch (response) {
  80. .simple => |value| std.fmt.bufPrint(output, "+{s}\r\n", .{value}),
  81. .failure => |value| std.fmt.bufPrint(output, "-{s}\r\n", .{value}),
  82. .integer => |value| std.fmt.bufPrint(output, ":{d}\r\n", .{value}),
  83. .null_bulk => std.fmt.bufPrint(output, "$-1\r\n", .{}),
  84. .bulk => |value| std.fmt.bufPrint(output, "${d}\r\n", .{value.bytes.len}),
  85. };
  86. }
  87. test "RESP binary parsing and pipelining" {
  88. const first = "*3\r\n$3\r\nSET\r\n$3\r\na\x00b\r\n$4\r\nx\r\ny\r\n";
  89. const second = "*2\r\n$3\r\nGET\r\n$3\r\na\x00b\r\n";
  90. const bytes = first ++ second;
  91. const parsed = try parse(bytes);
  92. try std.testing.expectEqual(first.len, parsed.consumed);
  93. try std.testing.expectEqualSlices(u8, "a\x00b", parsed.command.key);
  94. const next = try parse(bytes[parsed.consumed..]);
  95. try std.testing.expectEqual(CommandType.get, next.command.command_type);
  96. }
  97. test "RESP engine compatibility" {
  98. var tmp = std.testing.tmpDir(.{});
  99. defer tmp.cleanup();
  100. var path_buffer: [std.fs.max_path_bytes]u8 = undefined;
  101. const directory = try tmp.dir.realpath(".", &path_buffer);
  102. const path = try std.fmt.allocPrint(std.testing.allocator, "{s}/resp.pkvdb", .{directory});
  103. defer std.testing.allocator.free(path);
  104. var engine = try engine_mod.Engine.open(std.testing.allocator, path);
  105. defer engine.close();
  106. var response = try execute(&engine, std.testing.allocator, .{ .command_type = .set, .key = "k", .value = "v" });
  107. response.deinit(std.testing.allocator);
  108. response = try execute(&engine, std.testing.allocator, .{ .command_type = .get, .key = "k" });
  109. defer response.deinit(std.testing.allocator);
  110. try std.testing.expectEqualStrings("v", response.bulk.bytes);
  111. }