Something went wrong. Try again.
Completely reimplimented in zig (eventually)
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233const std = @import("std");const Session = @import("Session.zig");
const Allocator = std.mem.Allocator;const ArrayList = std.ArrayList;const t = std.testing;
const PtgSession = @This();
gpa: Allocator,session: *Session,
pub const Op = enum { list, active, set, reset_stats, remove,};
const msg_ptg_op: u32 = 0x204;
const op_init: u8 = 1;const op_uninit: u8 = 2;const op_reinit: u8 = 3;const op_browse_sdcard: u8 = 4;const op_load_image: u8 = 5;const op_program: u8 = 6;const op_get_pk_tool_info: u8 = 7;const op_get_active_image: u8 = 8;const op_get_image_stats: u8 = 9;const op_set_image_stats: u8 = 10;const op_delete_image_dir: u8 = 11;
const root_path = "0:\x00";
const max_payload: usize = 100;const status_ok: u8 = 0x80;
pub fn init(gpa: Allocator, session: *Session) PtgSession { return .{ .gpa = gpa, .session = session };}
pub fn parseOpResponse(gpa: Allocator, op: u8, retval: []const u8) ![]u8 { if (retval.len == 0) return error.ToolProtocol; if (retval[0] != op) return error.ToolProtocol; if (retval[retval.len - 1] != status_ok) return error.ToolFailure; if (retval.len < 6) return gpa.alloc(u8, 0); return gpa.dupe(u8, retval[4 .. retval.len - 2]);}
fn exchange(self: *PtgSession, msg_type: u32, payload: []const u8) ![]u8 { return self.session.request(msg_type, payload);}
const OpResult = struct { data: []u8,
pub fn deinit(self: OpResult, gpa: Allocator) void { gpa.free(self.data); }};
fn ptgOp(self: *PtgSession, op: u8, tail: []const u8) !OpResult { if (tail.len > max_payload - 1) return error.NameTooLong;
// The tool expects the op buffer zero-padded to max_payload bytes // (MPLAB X sends ByteBuffer.allocate(100).array() verbatim). var payload: [max_payload]u8 = @splat(0); payload[0] = op; @memcpy(payload[1 .. 1 + tail.len], tail);
const raw = try self.exchange(msg_ptg_op, &payload); defer self.gpa.free(raw);
const data = try parseOpResponse(self.gpa, op, raw); return .{ .data = data };}
fn nameTail(self: *PtgSession, tail: *ArrayList(u8), name: []const u8) !void { var buf: [2]u8 = undefined; std.mem.writeInt(u16, &buf, @intCast(name.len), .little); try tail.appendSlice(self.gpa, &buf); try tail.appendSlice(self.gpa, name);}
const ImageEntry = struct { name: []u8, total: u32, pass: u32,
pub fn deinit(self: ImageEntry, gpa: Allocator) void { gpa.free(self.name); }};
pub const truncated_marker = "<TRUNCATED>";
pub fn listImages(self: *PtgSession, allocator: Allocator) ![]ImageEntry { const listing = try self.browse(); defer self.gpa.free(listing);
var entries: ArrayList(ImageEntry) = .empty; errdefer { for (entries.items) |entry| entry.deinit(allocator); entries.deinit(allocator); }
var iter = std.mem.splitScalar(u8, listing, '\n'); while (iter.next()) |part| { const trimmed = std.mem.trim(u8, part, " \r\t\x00"); if (trimmed.len == 0) continue; if (std.mem.find(u8, trimmed, ".ptg") == null) { if (std.mem.find(u8, trimmed, truncated_marker) != null) { try entries.append(allocator, .{ .name = try allocator.dupe(u8, truncated_marker), .total = std.math.maxInt(u32), .pass = std.math.maxInt(u32), }); } continue; } const entry = try self.imageEntryFromName(allocator, trimmed); errdefer entry.deinit(allocator); try entries.append(allocator, entry); } return entries.toOwnedSlice(allocator);}
fn imageEntryFromName(self: *PtgSession, allocator: Allocator, name: []const u8) !ImageEntry { const stats = try self.getStats(name); defer self.gpa.free(stats.name_data); return .{ .name = try allocator.dupe(u8, name), .total = stats.total, .pass = stats.pass, };}
const Stats = struct { total: u32, pass: u32, name_data: []u8,};
fn browse(self: *PtgSession) ![]u8 { var tail: ArrayList(u8) = .empty; defer tail.deinit(self.gpa); try self.nameTail(&tail, root_path); var result = try self.ptgOp(op_browse_sdcard, tail.items); defer result.deinit(self.gpa); return self.gpa.dupe(u8, result.data);}
pub fn getStats(self: *PtgSession, name: []const u8) !Stats { var tail: ArrayList(u8) = .empty; defer tail.deinit(self.gpa); try self.nameTail(&tail, name); var result = try self.ptgOp(op_get_image_stats, tail.items); errdefer result.deinit(self.gpa); if (result.data.len < 8) return error.ToolProtocol; return .{ .total = std.mem.readInt(u32, result.data[0..4], .little), .pass = std.mem.readInt(u32, result.data[4..8], .little), .name_data = result.data, };}
pub fn getActiveImage(self: *PtgSession, allocator: Allocator) ![]u8 { var result = try self.ptgOp(op_get_active_image, ""); defer result.deinit(self.gpa); // The tool replies either "0:/<name>.ptg" (set at transfer time) or // bare "<name>.ptg" (set via LOAD_IMAG). Match MPLAB X, which falls // back to the whole string when no slash is present. const path = if (std.mem.findScalar(u8, result.data, '/')) |slash| result.data[slash + 1 ..] else result.data; const trimmed = std.mem.trim(u8, path, "\x00 \r\t\n"); if (std.mem.find(u8, trimmed, ".ptg") == null) return error.NoActiveImage; return allocator.dupe(u8, trimmed);}
pub fn setActiveImage(self: *PtgSession, name: []const u8) !void { var tail: ArrayList(u8) = .empty; defer tail.deinit(self.gpa); try self.nameTail(&tail, name); var result = try self.ptgOp(op_load_image, tail.items); result.deinit(self.gpa);}
pub fn resetStats(self: *PtgSession, name: []const u8) !void { var tail: ArrayList(u8) = .empty; defer tail.deinit(self.gpa); var buf: [2]u8 = undefined; std.mem.writeInt(u16, &buf, @intCast(name.len + 8), .little); try tail.appendSlice(self.gpa, &buf); try tail.appendNTimes(self.gpa, 0, 8); try tail.appendSlice(self.gpa, name); var result = try self.ptgOp(op_set_image_stats, tail.items); result.deinit(self.gpa);}
pub fn deleteImage(self: *PtgSession, name: []const u8) !void { var tail: ArrayList(u8) = .empty; defer tail.deinit(self.gpa); try self.nameTail(&tail, name); var result = try self.ptgOp(op_delete_image_dir, tail.items); result.deinit(self.gpa);}
test "parseOpResponse long form" { const retval = [_]u8{ 9, 0, 0, 0, 0x05, 0, 0, 0, 0x03, 0, 0, 0, 0x80 }; const data = try PtgSession.parseOpResponse(t.allocator, 9, &retval); defer t.allocator.free(data); try t.expectEqual(8, data.len); try t.expectEqual(5, std.mem.readInt(u32, data[0..4], .little)); try t.expectEqual(3, std.mem.readInt(u32, data[4..8], .little));}
test "parseOpResponse short form" { const ok_short = [_]u8{ 5, 0x80 }; const data = try PtgSession.parseOpResponse(t.allocator, 5, &ok_short); defer t.allocator.free(data); try t.expectEqual(0, data.len);
const bad_op = [_]u8{ 6, 0x80 }; try t.expectError(error.ToolProtocol, PtgSession.parseOpResponse(t.allocator, 5, &bad_op));
const bad_status = [_]u8{ 5, 0x21 }; try t.expectError(error.ToolFailure, PtgSession.parseOpResponse(t.allocator, 5, &bad_status));}