client/lualibs/commands.lua
--[[
Handle Proxmark Communication Commands
--]]

local _commands = require('pm3_cmd')
local util = require('utils')
local TIMEOUT = 2000

local _reverse_lookup,k,v = {}
    for k, v in pairs(_commands) do
        _reverse_lookup[v] =  k
    end
    _commands.tostring = function(command)
    if(type(command) == 'number') then
        return ("%s (%d)"):format(_reverse_lookup[command]or "ERROR UNDEFINED!", command)
    end
    return ("Error, numeric argument expected, got : %s"):format(tostring(command))
end

Command = {

    new = function(self, o)

        local o = o or {}   -- create object if user does not provide one
        setmetatable(o, self) -- DIY inheritance a'la javascript
        self.__index = self

        o.cmd = o.cmd or _commands.CMD_UNKNOWN
        o.arg1 = o.arg1 or 0
        o.arg2 = o.arg2 or 0
        o.arg3 = o.arg3 or 0
        local data = o.data or "0"

        if (type(data) == 'string') then
            -- We need to check if it is correct length, otherwise pad it
            local len = #data
            if (len < 1024) then
                --Should be 1024 hex characters to represent 512 bytes of data
                data = data .. string.rep("0",1024 - len )
            end
            if (len > 1024) then
                -- OOps, a bit too much data here
                print( ( "WARNING: data size too large, was %s chars, will be truncated "):format(len) )
                --
                data = data:sub(1, 1024)
            end
        else
            print(("WARNING; data was NOT a (hex-) string, but was %s"):format(type(data)))
        end
        o.data = data
        return o
    end,
    newMIX = function(self, o)

        local o = o or {}   -- create object if user does not provide one
        setmetatable(o, self) -- DIY inheritance a'la javascript
        self.__index = self

        o.cmd = o.cmd or _commands.CMD_UNKNOWN
        o.arg1 = o.arg1 or 0
        o.arg2 = o.arg2 or 0
        o.arg3 = o.arg3 or 0
        local data = o.data or ''

        if (type(data) == 'string') then
            if (#data > 1024) then
                -- OOps, a bit too much data here
                print( ( "WARNING: data size too large, was %s chars, will be truncated "):format( #data) )
                --
                data = data:sub(1, 1024)
            end
        end
        o.data = data
        return o
    end,
    newNG = function(self, o)

        local o = o or {}   -- create object if user does not provide one
        setmetatable(o, self) -- DIY inheritance a'la javascript
        self.__index = self

        o.cmd = o.cmd or _commands.CMD_UNKNOWN
        local data = o.data or ''

        if (type(data) == 'string') then
            if (#data > 1024) then
                -- OOps, a bit too much data here
                print( ( "WARNING: data size too large, was %s chars, will be truncated "):format( #data) )
                --
                data = data:sub(1, 1024)
            end
        end
        o.data = data
        return o
    end,
    parse = function(packet)
            local count, cmd, arg1, arg2, arg3, data = bin.unpack('LLLL', packet)
            local length = #packet - count + 1
            count, data = bin.unpack('H'..length, packet, count)
            return Command:new{cmd = cmd, arg1 = arg1, arg2 = arg2, arg3 = arg3, data = data}
    end
}
-- commented out,  not used.
function Command:__tostring()
    local output = ("%s\r\nargs : (%s, %s, %s)\r\ndata:\r\n%s\r\n"):format(
        _commands.tostring(self.cmd),
        tostring(self.arg1),
        tostring(self.arg2),
        tostring(self.arg3),
        tostring(self.data))
    return output
end

function Command:getBytes()
    --If a hex-string has been used
    local data  = self.data
    local cmd = self.cmd
    local arg1, arg2, arg3 = self.arg1, self.arg2, self.arg3
    return bin.pack("LLLLH",cmd, arg1, arg2, arg3, data);
end

--- Sends a packet to the device
-- @param command - the usb packet to send
-- @param ignoreresponse - if set to true, we don't read the device answer packet
--     which is usually recipe for fail. If not sent, the host will wait 2s for a
--     response of type CMD_ACK or like NG use the CMD as ack..
-- @return packet,nil if successful
--         nil, errormessage if unsuccessful
function Command:sendMIX( ignore_response, timeout, use_cmd_ack)
    if timeout == nil then timeout = TIMEOUT end
    local data = self.data
    local cmd = self.cmd
    local arg1, arg2, arg3 = self.arg1, self.arg2, self.arg3

    local err, msg = core.SendCommandMIX(cmd, arg1, arg2, arg3, data)
    if err == nil then return err, msg end
    if ignore_response then return true, nil end

    local ack = _commands.CMD_ACK
    if use_cmd_ack then
        ack = cmd
    end

    local response, msg = core.WaitForResponseTimeout(ack, timeout)
    if response == nil then
        return nil, 'Error, waiting for response timed out :: '..msg
    end
    -- lets digest
    data = nil
    cmd = nil
    arg1 = nil
    arg2 = nil
    arg3 = nil
    local count, length, magic, status, crc, ng

--         S    S       I      s       S    L     L     L
    count, cmd, length, magic, status, crc, arg1, arg2, arg3 = bin.unpack('SSIsSLLL', response)
    count, data, ng = bin.unpack('H'..length..'C', response, count)

    local packed = bin.pack("LLLLH", cmd, arg1, arg2, arg3, data)
    return packed, nil;
end
function Command:sendNG( ignore_response, timeout )
    if timeout == nil then timeout = TIMEOUT end
    local data = self.data
    local cmd = self.cmd
    local err, msg = core.SendCommandNG(cmd, data)
    if err == nil then return nil, msg end

    if ignore_response then return true, nil end
    local response, msg = core.WaitForResponseTimeout(cmd, timeout)
    if response == nil then
        return nil, 'Error, waiting for response timed out :: '..msg
    end

    data = nil
    cmd = nil
    local count, length, magic, status, crc, arg0, arg1, arg2

    count, cmd, length, magic, status, crc, arg0, arg1, arg2 = bin.unpack('SSIsSLLL', response)
    count, data, ng = bin.unpack('H'..length..'C', response, count)

--[[  uncomment if you want to debug
    print('NG package received')
    print('CMD    ::', tostring(cmd))
    print('Length ::', tostring(length))
    print('Magic  ::', string.format("0x%08X", magic), util.ConvertHexToAscii(string.format("0x%08X", magic)))
    print('Status ::', tostring(status))
    print('crc    ::', string.format("0x%02X", crc))
    print('Args   ::', ("(%s, %s, %s)\r\n"):format(
                    tostring(arg0),
                    tostring(arg1),
                    tostring(arg2)))
    print('NG     ::', ng)
    print('Data   ::', data)
--]]
    return { Cmd = cmd,
            Length = length,
            Magic = magic,
            Status = status,
            Crc = crc,
            Oldarg0 = arg0,
            Oldarg1 = arg1,
            Oldarg2 = arg2,
            Data = data,
            Ng = ng
    }
end

return _commands
