diff --git a/src/BufferManager.zig b/src/BufferManager.zig new file mode 100644 index 0000000..e73207c --- /dev/null +++ b/src/BufferManager.zig @@ -0,0 +1,37 @@ +const std = @import("std"); +const BufferInfo = @import("./qtshit/types/UserType.zig").BufferInfo; + +pub const BufferManager = struct { + allocator: *std.mem.Allocator, + buffers: std.ArrayList(BufferInfo), + + pub fn addBufferInfo(s: *BufferManager, bufferInfo: BufferInfo) !void { + try s.buffers.append(BufferInfo{ + .ID = bufferInfo.ID, + .NetworkID = bufferInfo.NetworkID, + .Type = bufferInfo.Type, + .Name = try s.allocator.dupe(u8, bufferInfo.Name), + }); + } + pub fn getFirstByName(s: *BufferManager, name: []const u8) !?BufferInfo { + for (s.buffers.items) |item| { + if (std.mem.eql(u8, item.Name, name)) { + return item; + } + } + return null; + } + pub fn deinit(s: *BufferManager, ) void { + for (s.buffers.items) |item| { + s.allocator.free(item.Name); + } + s.buffers.deinit(); + } +}; + +pub fn initBufferManager(allocator: *std.mem.Allocator) BufferManager { + return BufferManager{ + .allocator = allocator, + .buffers = std.ArrayList(BufferInfo).init(allocator), + }; +} diff --git a/src/client.zig b/src/client.zig index 1acc0f0..6be14ad 100644 --- a/src/client.zig +++ b/src/client.zig @@ -1,4 +1,7 @@ const std = @import("std"); + +const BufferManager = @import("./BufferManager.zig"); + const read = @import("./qtshit/read.zig"); const write = @import("./qtshit/write.zig"); const range = @import("./qtshit/utils/RangeIter.zig").range; @@ -6,6 +9,8 @@ const QVariantType = @import("./qtshit/types/QVariant.zig").QVariant; const prettyPrintQVariant = @import("./qtshit/utils/prettyPrintQVariant.zig").prettyPrintQVariant; const freeQVariant = @import("./qtshit/utils/free/freeQVariant.zig").freeQVariant; const QVariantMapToQVariantList = @import("./qtshit/utils/QVariantMapToQVariantList.zig").QVariantMapToQVariantList; +const UserType = @import("./qtshit/types/UserType.zig"); + const tls = @import("./deps/iguanaTLS/src/main.zig"); fn dumpDebug(name: []const u8, list: std.ArrayList(u8)) !void { @@ -23,6 +28,7 @@ fn dumpDebug(name: []const u8, list: std.ArrayList(u8)) !void { pub const Client = struct { allocator: *std.mem.Allocator, stream: *std.net.Stream, + bufferManager: BufferManager.BufferManager, pub var tlsAllowed = !true; pub var tlsConnected = !true; @@ -30,6 +36,11 @@ pub const Client = struct { pub const TLSStream = tls.Client(std.net.Stream.Reader, std.net.Stream.Writer, tls.ciphersuites.all, false); pub var tlsClient: TLSStream = undefined; + pub fn deinit(s: *Client) void { + s.bufferManager.deinit(); + } + + pub fn initTLS(s: *Client) !void { if (!tlsConnected and tlsAllowed) { var randBuf: [32]u8 = undefined; @@ -84,6 +95,7 @@ pub const Client = struct { pub fn readFrame(s: *Client) !QVariantType { try s.initTLS(); var data: std.ArrayList(u8) = undefined; + defer data.deinit(); if (tlsConnected) { var reader = tlsClient.reader(); @@ -112,18 +124,22 @@ pub const Client = struct { } pub fn quassel_init_packet(s: *Client) !void { var data = std.ArrayList(u8).init(s.allocator); + defer data.deinit(); var map = std.StringHashMap(QVariantType).init(s.allocator); + defer map.deinit(); + try map.put("MsgType", .{ .String = "ClientInit" }); try map.put("UseCompression", .{ .UInt = 0 }); try map.put("UseSsl", .{ .UInt = 1 }); - try map.put("ProtocolVersion", .{ .UInt = 10 }); try map.put("ClientVersion", .{ .String = "0.1 (quasselclient)" }); try map.put("ClientDate", .{ .String = "Wed, 02 Jun 2021 17:30:30 +0100" }); try map.put("Features", .{ .Int = 0x00008000 }); + var featureList = std.ArrayList([]const u8).init(s.allocator); defer featureList.deinit(); + try featureList.append("LongTime"); try featureList.append("LongMessageID"); try featureList.append("SenderPrefixes"); @@ -137,16 +153,18 @@ pub const Client = struct { try s.writeFrame(data); - var varient = try s.readFrame(); - prettyPrintQVariant(varient, 0); - tlsAllowed = varient.QVariantMap.get("SupportSsl").?.Byte == 1; - freeQVariant(varient, s.allocator); + var variant = try s.readFrame(); + defer freeQVariant(variant, s.allocator); + tlsAllowed = variant.QVariantMap.get("SupportSsl").?.Byte == 1; } pub fn quassel_login(s: *Client, username: []const u8, password: []const u8) !void { var data = std.ArrayList(u8).init(s.allocator); + defer data.deinit(); var map = std.StringHashMap(QVariantType).init(s.allocator); + defer map.deinit(); + try map.put("MsgType", .{ .String = "ClientLogin" }); try map.put("User", .{ .String = username }); try map.put("Password", .{ .String = password }); @@ -156,19 +174,68 @@ pub const Client = struct { }); try s.writeFrame(data); + + var loginResponse = try s.readFrame(); + defer freeQVariant(loginResponse, s.allocator); + + var loginResponseMap = loginResponse.QVariantMap; + if (loginResponseMap.get("MsgType")) |msgType| { + if (std.mem.eql(u8, msgType.String, "ClientLoginReject")) { + return error.InvalidLogin; + } + } + } + + fn handle_session_init_packet(s: *Client, sessionState: std.StringHashMap(QVariantType)) !void { + for (sessionState.get("BufferInfos").?.QVariantList) |qvar| { + try s.bufferManager.addBufferInfo(qvar.UserType.BufferInfo); + } } pub fn read_quassel_packet(s: *Client) !void { - std.debug.print("\n\nQuassel Packet: \n", .{}); - var varient = try s.readFrame(); - prettyPrintQVariant(varient, 0); - freeQVariant(varient, s.allocator); + var variant = try s.readFrame(); + defer freeQVariant(variant, s.allocator); + + switch (variant) { + .QVariantMap => |map| { + if (map.get("MsgType")) |msgType| { + if (std.mem.eql(u8, msgType.String, "SessionInit")) { + var sessionState = map.get("SessionState").?.QVariantMap; + try s.handle_session_init_packet(sessionState); + } + } + }, + else => { + std.debug.print("\n\n Unknown: Quassel Packet: \n", .{}); + prettyPrintQVariant(variant, 0); + }, + } } + + pub fn send_message(s: *Client, bufferInfo: UserType.BufferInfo, message: []const u8) !void { + var data = std.ArrayList(u8).init(s.allocator); + defer data.deinit(); + + var listItems = std.ArrayList(QVariantType).init(s.allocator); + defer listItems.deinit(); + + try listItems.append(.{ .Int = 2 }); + try listItems.append(.{ .QByteArray = "2sendInput(BufferInfo,QString)" }); + try listItems.append(.{ .UserType = .{ .BufferInfo = bufferInfo } }); + try listItems.append(.{ .String = message }); + + try write.writeQVariant(data.writer(), s.allocator, .{ + .QVariantList = listItems.items, + }); + + try s.writeFrame(data); + } }; pub fn initClient(allocator: *std.mem.Allocator, stream: *std.net.Stream) Client { return Client{ .allocator = allocator, .stream = stream, + .bufferManager = BufferManager.initBufferManager(allocator), }; } diff --git a/src/main.zig b/src/main.zig index 0db049d..67a517a 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1,42 +1,63 @@ const std = @import("std"); -const read = @import("./qtshit/read.zig"); +const DebugAllocator = @import("./debug_allocator.zig"); const write = @import("./qtshit/write.zig"); - const initClient = @import("./client.zig").initClient; -pub fn main() !void { - const allocator = std.heap.page_allocator; - +pub fn realMain(allocator: *std.mem.Allocator) !void { var argIter = std.process.args(); - _ = try argIter.next(allocator).?; + + allocator.free(try argIter.next(allocator).?); + var host = try argIter.next(allocator).?; + defer allocator.free(host); var port = try argIter.next(allocator).?; + defer allocator.free(port); var portInt = try std.fmt.parseInt(u16, port, 10); var username = try argIter.next(allocator).?; + defer allocator.free(username); var password = try argIter.next(allocator).?; + defer allocator.free(password); std.debug.print("host={s} port={d}\n", .{ host, portInt }); - while (true) { - var sock = try std.net.tcpConnectToHost(allocator, host, portInt); - var client = initClient(allocator, &sock); - try client.handshake(); - try client.quassel_init_packet(); - try client.quassel_login(username, password); + var sock = try std.net.tcpConnectToHost(allocator, host, portInt); - while (true) { - client.read_quassel_packet() catch |err| { - if (err == error.DecodeError) { - std.debug.print("Decode Error.\n", .{}); - } else if (err == error.EndOfStream) { - std.debug.print("EOS.\n", .{}); - std.time.sleep(1000 * std.time.ns_per_ms); - continue; - } else { - return err; - } - }; - } + var client = initClient(allocator, &sock); + defer client.deinit(); + + try client.handshake(); + try client.quassel_init_packet(); + try client.quassel_login(username, password); + + _ = try client.read_quassel_packet(); + + var bufferInfo = try client.bufferManager.getFirstByName("z_is_stimky"); + try client.send_message(bufferInfo.?, "uwu, owo, uwu"); + + while (true) { + client.read_quassel_packet() catch |err| { + if (err == error.DecodeError) { + std.debug.print("Decode Error.\n", .{}); + } else if (err == error.EndOfStream) { + std.debug.print("EOS.\n", .{}); + std.time.sleep(1000 * std.time.ns_per_ms); + return; + } else { + return err; + } + }; + break; } } + +pub fn main() !void { + var gpalloc = std.heap.GeneralPurposeAllocator(.{ + .stack_trace_frames = 20, + }){}; + defer std.debug.assert(!gpalloc.deinit()); + + const alloc = &gpalloc.allocator; + + try realMain(alloc); +} diff --git a/src/qtshit/read/usertypes/readUserType.zig b/src/qtshit/read/usertypes/readUserType.zig index 97569c5..8c4278b 100644 --- a/src/qtshit/read/usertypes/readUserType.zig +++ b/src/qtshit/read/usertypes/readUserType.zig @@ -12,8 +12,9 @@ const readMessage = @import("./readMessage.zig").readMessage; pub fn readUserType(reader: anytype, allocator: *std.mem.Allocator) !UserType { var userTypeName = try readQByteArray(reader, allocator); + defer allocator.free(userTypeName); + userTypeName = userTypeName[0 .. userTypeName.len - 1]; - //std.debug.print("read: readUserType name={s} \n", .{userTypeName}); if (std.mem.eql(u8, userTypeName, "BufferId")) { return UserType{ diff --git a/src/qtshit/types/QVariant.zig b/src/qtshit/types/QVariant.zig index 15b70b4..dcae427 100644 --- a/src/qtshit/types/QVariant.zig +++ b/src/qtshit/types/QVariant.zig @@ -7,7 +7,7 @@ pub const QVariant = union(enum) { Short: u16, Byte: u8, String: []const u8, - QByteArray: []u8, + QByteArray: []const u8, QStringList: [][]const u8, QVariantList: []QVariant, QVariantMap: std.StringHashMap(QVariant), diff --git a/src/qtshit/types/UserType.zig b/src/qtshit/types/UserType.zig index 1b72b35..4657913 100644 --- a/src/qtshit/types/UserType.zig +++ b/src/qtshit/types/UserType.zig @@ -20,7 +20,7 @@ pub const BufferInfo = struct { ID: i32, NetworkID: i32, Type: u16, - Name: []u8, + Name: []const u8, }; pub const Message = struct { diff --git a/src/qtshit/utils/free/freeQByteArray.zig b/src/qtshit/utils/free/freeQByteArray.zig index b596878..2638a62 100644 --- a/src/qtshit/utils/free/freeQByteArray.zig +++ b/src/qtshit/utils/free/freeQByteArray.zig @@ -1,5 +1,5 @@ const std = @import("std"); -pub fn freeQByteArray(qbytearray: []u8, allocator: *std.mem.Allocator) void { +pub fn freeQByteArray(qbytearray: []const u8, allocator: *std.mem.Allocator) void { allocator.free(qbytearray); } diff --git a/src/qtshit/utils/prettyPrintQVariant.zig b/src/qtshit/utils/prettyPrintQVariant.zig index dbfaed2..077f845 100644 --- a/src/qtshit/utils/prettyPrintQVariant.zig +++ b/src/qtshit/utils/prettyPrintQVariant.zig @@ -43,22 +43,27 @@ pub fn prettyPrintUserType(usertype: UserType, indentLevel: u64) void { }, .IrcUser => |value| { std.debug.print("IrcUser:\n", .{}); + print_indent_level(indentLevel + 1); prettyPrintQVariantMap(value, indentLevel + 1); }, .IrcChannel => |value| { std.debug.print("IrcChannel:\n", .{}); + print_indent_level(indentLevel + 1); prettyPrintQVariantMap(value, indentLevel + 1); }, .Identity => |value| { std.debug.print("Identity:\n", .{}); + print_indent_level(indentLevel + 1); prettyPrintQVariantMap(value, indentLevel + 1); }, .NetworkInfo => |value| { std.debug.print("NetworkInfo:\n", .{}); + print_indent_level(indentLevel + 1); prettyPrintQVariantMap(value, indentLevel + 1); }, .NetworkServer => |value| { std.debug.print("NetworkServer:\n", .{}); + print_indent_level(indentLevel + 1); prettyPrintQVariantMap(value, indentLevel + 1); }, .BufferInfo => |value| { @@ -106,7 +111,6 @@ pub fn prettyPrintUserType(usertype: UserType, indentLevel: u64) void { } pub fn prettyPrintQVariantMap(qvariantmap: std.StringHashMap(QVariant), indentLevel: u64) void { - print_indent_level(indentLevel); std.debug.print("QVariantMap:\n", .{}); var qMapIter = qvariantmap.iterator(); while (qMapIter.next()) |v| { diff --git a/src/qtshit/utils/unicode.zig b/src/qtshit/utils/unicode.zig index 4811343..0c8e100 100644 --- a/src/qtshit/utils/unicode.zig +++ b/src/qtshit/utils/unicode.zig @@ -26,7 +26,7 @@ pub fn utf16BEToUtf8(allocator: *std.mem.Allocator, utf16: []u16) ![]u8 { // Big Endian to Little Endian var utf16LE = byteSwapArray(u16, &utf16BE); - var utf8 = try std.unicode.utf16leToUtf8AllocZ(allocator, utf16LE); + var utf8 = try std.unicode.utf16leToUtf8Alloc(allocator, utf16LE); return utf8; } diff --git a/src/qtshit/write/usertypes/writeBufferInfo.zig b/src/qtshit/write/usertypes/writeBufferInfo.zig new file mode 100644 index 0000000..98a6dd5 --- /dev/null +++ b/src/qtshit/write/usertypes/writeBufferInfo.zig @@ -0,0 +1,19 @@ +const std = @import("std"); +const BufferInfo = @import("../../types/UserType.zig").BufferInfo; +const writeInt = @import("../writeInt.zig").writeInt; +const writeShort = @import("../writeShort.zig").writeShort; +const writeByte = @import("../writeByte.zig").writeByte; +const writeQByteArray = @import("../writeQByteArray.zig").writeQByteArray; + +pub fn writeBufferInfo(writer: anytype, bufferInfo: BufferInfo) !void { + try writeQByteArray(writer, "BufferInfo\x00"); + try writeInt(writer, bufferInfo.ID); + try writeInt(writer, bufferInfo.NetworkID); + try writeShort(writer, bufferInfo.Type); + // 4 undocumented bytes. + try writeByte(writer, 0x00); + try writeByte(writer, 0x00); + try writeByte(writer, 0x00); + try writeByte(writer, 0x00); + try writeQByteArray(writer, bufferInfo.Name); +} \ No newline at end of file diff --git a/src/qtshit/write/usertypes/writeUserType.zig b/src/qtshit/write/usertypes/writeUserType.zig new file mode 100644 index 0000000..90e53d8 --- /dev/null +++ b/src/qtshit/write/usertypes/writeUserType.zig @@ -0,0 +1,14 @@ +const std = @import("std"); +const UserType = @import("../../types/UserType.zig").UserType; +const writeBufferInfo = @import("./writeBufferInfo.zig").writeBufferInfo; + +pub fn writeUserType(writer: anytype, allocator: *std.mem.Allocator, usertype: UserType) !void { + switch (usertype) { + .BufferInfo => |value| { + try writeBufferInfo(writer, value); + }, + else => { + @panic("Unsupported!"); + }, + } +} \ No newline at end of file diff --git a/src/qtshit/write/writeQVariant.zig b/src/qtshit/write/writeQVariant.zig index 020b729..a40d1c6 100644 --- a/src/qtshit/write/writeQVariant.zig +++ b/src/qtshit/write/writeQVariant.zig @@ -13,6 +13,8 @@ const writeQVariantList = @import("./writeQVariantList.zig").writeQVariantList; const writeQVariantHeader = @import("./writeQVariantHeader.zig").writeQVariantHeader; const writeQVariantMap = @import("./writeQVariantMap.zig").writeQVariantMap; const writeQStringList = @import("./writeQStringList.zig").writeQStringList; +const writeUserType = @import("./usertypes/writeUserType.zig").writeUserType; + pub fn writeQVariant(writer: anytype, allocator: *std.mem.Allocator, variant: QVariantType) (@TypeOf(writer).Error || std.os.WriteError || error{OutOfMemory} || error{InvalidUtf8})!void { try writeQVariantHeader(writer, try QVariantTypeID(variant)); @@ -47,8 +49,8 @@ pub fn writeQVariant(writer: anytype, allocator: *std.mem.Allocator, variant: QV .QDateTime => { @panic("Can't write QDateTime"); }, - .UserType => { - @panic("Can't write UserTypes"); + .UserType => |out| { + try writeUserType(writer, allocator, out); }, //else => { // @panic("Unsupported!");