const std = @import("std"); const usb = @import("usb.zig"); const Allocator = std.mem.Allocator; const t = std.testing; const Io = std.Io; const ArrayList = std.ArrayList; const Transport = usb.Transport; const Session = @This(); io: Io, gpa: Allocator, ep_size: u16, sequence: u16, transport: Transport, // Bulk-frame message ids (RI4ToolsController ICD4CommsUsb): // requests carry the command id, every response carries RESULT. const msg_result: u32 = 0x0000000D; const job_side_channel: u32 = 0; const read_timeout_ms: u32 = 5000; const max_response: usize = 4096; pub fn init(io: Io, allocator: Allocator, path: []const u8) !Session { return .{ .io = io, .gpa = allocator, .sequence = 0, .ep_size = 0, .transport = try .open(path, allocator, io), }; } pub fn deinit(self: *Session) void { self.transport.close(); self.* = undefined; } /// Send one bulk-frame request and return the response's data section. /// /// Request: [msg_id u32][job u32][total u32][plen u32][payload] /// Response: [RESULT u32][job u32][total u32][plen u32][status u32] /// [dlen u32][data...] pub fn request(self: *Session, msg_id: u32, payload: []const u8) ![]u8 { var frame: ArrayList(u8) = .empty; defer frame.deinit(self.gpa); var word: [4]u8 = undefined; std.mem.writeInt(u32, &word, msg_id, .little); try frame.appendSlice(self.gpa, &word); std.mem.writeInt(u32, &word, job_side_channel, .little); try frame.appendSlice(self.gpa, &word); std.mem.writeInt(u32, &word, @intCast(Transport.header_size + payload.len), .little); try frame.appendSlice(self.gpa, &word); std.mem.writeInt(u32, &word, @intCast(payload.len), .little); try frame.appendSlice(self.gpa, &word); try frame.appendSlice(self.gpa, payload); try self.transport.write(frame.items, read_timeout_ms); var raw: [max_response]u8 = undefined; const n = try self.transport.read(&raw, read_timeout_ms); // PTG-style ops answer with the bare op payload ([op][code][len u16] // [data][chk][status]); other message classes use the RESULT // envelope. Distinguish by the first word. const first = if (n >= 4) std.mem.readInt(u32, raw[0..4], .little) else 0; if (first != msg_result) { if (n == 0) return error.ToolProtocol; return self.gpa.dupe(u8, raw[0..n]); } if (n < Transport.header_size + 8) return error.ToolProtocol; const total = std.mem.readInt(u32, raw[8..12], .little); if (total == Transport.header_size) { // Bare ack: status 0, no data. return self.gpa.alloc(u8, 0); } if (n < Transport.header_size + 8) return error.ToolProtocol; const status = std.mem.readInt(u32, raw[Transport.header_size..][0..4], .little); const dlen = std.mem.readInt(u32, raw[Transport.header_size + 4 ..][0..4], .little); if (dlen > n - Transport.header_size - 8) return error.ToolProtocol; if (status != 0) return error.ToolFailure; return self.gpa.dupe(u8, raw[Transport.header_size + 8 ..][0..dlen]); } const protocol_ok: u8 = 0x80; const protocol_list: u8 = 0x81; const protocol_data: u8 = 0x84; const protocol_failed: u8 = 0xA0; const Peeled = struct { ok: bool, data: []u8, pub fn deinit(self: Peeled, gpa: Allocator) void { gpa.free(self.data); } }; fn peelResponse(gpa: Allocator, payload: []const u8, trailing_status: bool) !Peeled { switch (payload[0]) { protocol_ok => return .{ .ok = true, .data = try gpa.alloc(u8, 0) }, protocol_list => return .{ .ok = true, .data = try gpa.dupe(u8, payload[1..]) }, protocol_data => { if (trailing_status and payload[payload.len - 1] == 0) return .{ .ok = true, .data = try gpa.dupe(u8, payload[1 .. payload.len - 1]) }; const code = try gpa.alloc(u8, 1); code[0] = payload[payload.len - 1]; return .{ .ok = false, .data = code }; }, else => { if (payload.len < 2) return error.ToolProtocol; const code = try gpa.alloc(u8, 1); code[0] = payload[1]; return .{ .ok = false, .data = code }; }, } } test "peelResponse" { { const p = [_]u8{Session.protocol_ok}; var r = try Session.peelResponse(t.allocator, &p, true); defer r.deinit(t.allocator); try t.expect(r.ok); try t.expectEqual(@as(usize, 0), r.data.len); } { const p = [_]u8{ Session.protocol_data, 0xAB, 0xCD, 0x00 }; var r = try Session.peelResponse(t.allocator, &p, true); defer r.deinit(t.allocator); try t.expect(r.ok); try t.expectEqualSlices(u8, &.{ 0xAB, 0xCD }, r.data); } { const p = [_]u8{ Session.protocol_data, 0xAB, 0xCD, 0x21 }; var r = try Session.peelResponse(t.allocator, &p, true); defer r.deinit(t.allocator); try t.expect(!r.ok); try t.expectEqualSlices(u8, &.{0x21}, r.data); } { const p = [_]u8{ Session.protocol_failed, 0x30 }; var r = try Session.peelResponse(t.allocator, &p, true); defer r.deinit(t.allocator); try t.expect(!r.ok); try t.expectEqualSlices(u8, &.{0x30}, r.data); } }