Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion template/src/lib/utils.lua
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,9 @@ end
--- @param allowEmptyValues? boolean Allows empty strings as parameter values (optional). Defaults to false.
--- @param logCommand? boolean If false, prevents logging of the command's content (optional). Defaults to true.
function SendToDevice(deviceId, strCommand, tParams, allowEmptyValues, logCommand)
if logCommand ~= nil then
allowEmptyValues = allowEmptyValues == true
end
return C4Call("SendToDevice", deviceId, strCommand, tParams, allowEmptyValues, logCommand)
end

Expand All @@ -173,6 +176,9 @@ end
--- @param strMessage? string Overrides message type ("COMMAND" or "NOTIFY") (optional). Defaults to "COMMAND".
--- @param allowEmptyValues? boolean Allows empty values in message parameters (optional).
function SendToProxy(idBinding, strCommand, tParams, strMessage, allowEmptyValues)
if allowEmptyValues ~= nil then
strMessage = strMessage or "COMMAND"
end
return C4Call("SendToProxy", idBinding, strCommand, tParams, strMessage, allowEmptyValues)
end

Expand All @@ -184,7 +190,7 @@ end
--- @param allowEmptyValues? boolean Allows empty strings as parameter values (optional). Defaults to false.
--- @param logCommand? boolean If true, lets C4 log the command's content (optional). Defaults to false.
function SendSecretToDevice(deviceId, strCommand, tParams, allowEmptyValues, logCommand)
return C4CallHiding("SendToDevice", 3, deviceId, strCommand, tParams, allowEmptyValues, logCommand == true)
return C4CallHiding("SendToDevice", 3, deviceId, strCommand, tParams, allowEmptyValues == true, logCommand == true)
end

--- SendToProxy for a message whose parameters hold a secret, such as a user code: its trace names the binding
Expand All @@ -195,6 +201,9 @@ end
--- @param strMessage? string Overrides message type ("COMMAND" or "NOTIFY") (optional). Defaults to "COMMAND".
--- @param allowEmptyValues? boolean Allows empty values in message parameters (optional).
function SendSecretToProxy(idBinding, strCommand, tParams, strMessage, allowEmptyValues)
if allowEmptyValues ~= nil then
strMessage = strMessage or "COMMAND"
end
return C4CallHiding("SendToProxy", 3, idBinding, strCommand, tParams, strMessage, allowEmptyValues)
end

Expand Down
28 changes: 26 additions & 2 deletions template/test/c4_shim.lua
Original file line number Diff line number Diff line change
Expand Up @@ -187,8 +187,32 @@ end
function C4:GetDriverFileName()
return "example.c4z"
end
function C4:SendToDevice() end
function C4:SendToProxy() end
-- The controller checks the type of each argument it is handed, by place, so a nil handed as the 4th or 5th
-- argument is refused even when nothing follows it. The messages are the controller's (4.3.0, 2026-11-10).
function C4:SendToDevice(...)
local n, _, _, tParams, allowEmptyValues, logCommand = select("#", ...), ...
if n < 3 then
error("Error: Not Enough Parameters Passed (expected 3)", 0)
elseif type(tParams) ~= "table" then
error("tParams should be a table", 0)
elseif n >= 4 and type(allowEmptyValues) ~= "boolean" then
error("Error: 4th parameter must be a boolean value", 0)
elseif n >= 5 and type(logCommand) ~= "boolean" then
error("Error: 5th parameter must be a boolean value", 0)
end
end
function C4:SendToProxy(...)
local n, _, _, tParams, strMessage, allowEmptyValues = select("#", ...), ...
if n < 3 then
error("Error: Not Enough Parameters Passed (expected 3)", 0)
elseif type(tParams) ~= "table" and type(tParams) ~= "string" then
error("Error: 3rd parameter must be a string (strValue) or table (tParams)", 0)
elseif n >= 4 and type(strMessage) ~= "string" then
error("strCallType should be a string", 0)
elseif n >= 5 and type(allowEmptyValues) ~= "boolean" then
error("Error: 5th parameter must be a boolean value", 0)
end
end

---------------------------------------------------------------------------
-- Network bindings
Expand Down
66 changes: 66 additions & 0 deletions template/test/test_c4_shim.lua
Original file line number Diff line number Diff line change
Expand Up @@ -1373,6 +1373,72 @@ ShimSetDeviceHidden(20, true)
T.eq("a hidden device is not listed", listed({ DeviceIds = "20" }), {})
ShimResetDevices()

--------------------------------------------------------------------------------
T.section("C4:SendToDevice and C4:SendToProxy")
--------------------------------------------------------------------------------
do
--- The message a call is refused with, or nil if it is accepted.
local function refusal(fn)
local ok, err = pcall(fn)
return not ok and err or nil
end

local DEVICE_FOURTH = "Error: 4th parameter must be a boolean value"
local FIFTH = "Error: 5th parameter must be a boolean value"
local CALL_TYPE = "strCallType should be a string"
local TOO_FEW = "Error: Not Enough Parameters Passed (expected 3)"

-- Each case: what it is, how many arguments follow the command, those arguments, the refusal or nil.
local deviceCalls = {
{ "a table of parameters", 1, { {} }, nil },
{ "both flags", 3, { {}, false, false }, nil },
{ "both flags set", 3, { {}, true, true }, nil },
{ "a nil 4th before a 5th", 3, { {}, nil, false }, DEVICE_FOURTH },
{ "a nil 4th passed on", 2, { {}, nil }, DEVICE_FOURTH },
{ "a string 4th", 3, { {}, "x", false }, DEVICE_FOURTH },
{ "a nil 5th passed on", 3, { {}, false, nil }, FIFTH },
{ "two nils passed on", 3, { {}, nil, nil }, DEVICE_FOURTH },
{ "no parameters", 2, { nil, false }, "tParams should be a table" },
{ "a string of parameters", 1, { "<x/>" }, "tParams should be a table" },
{ "too few arguments", 0, {}, TOO_FEW },
}
for _, case in ipairs(deviceCalls) do
local label, count, args, want = unpack(case, 1, 4)
T.eq(
"SendToDevice with " .. label,
refusal(function()
C4:SendToDevice(999999, "PROBE", unpack(args, 1, count))
end),
want
)
end

local proxyCalls = {
{ "a message type and a flag", 3, { {}, "COMMAND", true }, nil },
{ "a notify and a flag", 3, { {}, "NOTIFY", false }, nil },
{ "a string of parameters", 2, { "<x/>", "NOTIFY" }, nil },
{ "a nil message type before a flag", 3, { {}, nil, true }, CALL_TYPE },
{ "a flag in the message type's place", 2, { {}, true }, CALL_TYPE },
{ "a nil message type before a false flag", 3, { {}, nil, false }, CALL_TYPE },
{ "a nil message type passed on", 2, { {}, nil }, CALL_TYPE },
{ "a nil 5th passed on", 3, { {}, "COMMAND", nil }, FIFTH },
{ "a string 5th", 3, { {}, "COMMAND", "x" }, FIFTH },
{ "no parameters", 2, { nil, "NOTIFY" }, "Error: 3rd parameter must be a string (strValue) or table (tParams)" },
{ "no parameters passed on", 1, { nil }, "Error: 3rd parameter must be a string (strValue) or table (tParams)" },
{ "too few arguments", 0, {}, TOO_FEW },
}
for _, case in ipairs(proxyCalls) do
local label, count, args, want = unpack(case, 1, 4)
T.eq(
"SendToProxy with " .. label,
refusal(function()
C4:SendToProxy(9999, "PROBE", unpack(args, 1, count))
end),
want
)
end
end

--------------------------------------------------------------------------------
T.section("Lua 5.1 library")
--------------------------------------------------------------------------------
Expand Down
52 changes: 50 additions & 2 deletions template/test/test_secret_sends_trace.lua
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,14 @@ local CODE = "FAKE-CODE-0000"

-- What reached C4, as each call handed it over.
local sent = {}
local shimSendToProxy, shimSendToDevice = C4.SendToProxy, C4.SendToDevice
function C4:SendToProxy(...)
sent[#sent + 1] = { "SendToProxy", select("#", ...), ... }
return shimSendToProxy(self, ...)
end
function C4:SendToDevice(...)
sent[#sent + 1] = { "SendToDevice", select("#", ...), ... }
return shimSendToDevice(self, ...)
end

--- Everything lib.logging prints at trace level while fn runs.
Expand Down Expand Up @@ -57,13 +60,20 @@ sent = {}
output = traced(function()
SendSecretToDevice(42, "ARM", { UserCode = CODE })
end)
T.contains("its device and command", output, "C4:SendToDevice(42, ARM, ***REDACTED***, nil, false)")
T.contains("its device and command", output, "C4:SendToDevice(42, ARM, ***REDACTED***, false, false)")
T.excludes("and never the code", output, CODE)
T.eq("C4 is handed the parameters whole", sent[1][5].UserCode, CODE)
T.eq("and told not to log the command's content", { sent[1][2], sent[1][7] }, { 5, false })
T.eq(
"and told not to log the command's content, with empty values refused",
{ sent[1][2], sent[1][6], sent[1][7] },
{ 5, false, false }
)
sent = {}
SendSecretToDevice(42, "ARM", { UserCode = CODE }, nil, true)
T.eq("unless the caller asks it to", sent[1][7], true)
sent = {}
SendSecretToDevice(42, "ARM", { UserCode = "" }, true)
T.eq("and empty values allowed when asked for", sent[1][6], true)

T.section("the plain wrappers still trace every argument")
output = traced(function()
Expand All @@ -72,4 +82,42 @@ end)
T.excludes("nothing is redacted", output, "***REDACTED***")
T.contains("the call is traced", output, "C4:SendToProxy(5001, LOCK_STATUS_CHANGED, ")

T.section("an argument left out before one that is given reaches C4 as its default")
sent = {}
local calls = {
{
"SendSecretToDevice, with no flags",
function()
SendSecretToDevice(42, "ARM", { UserCode = CODE })
end,
},
{
"SendToDevice, told only not to log",
function()
SendToDevice(42, "REFRESH", {}, nil, false)
end,
},
{
"SendSecretToProxy, told only to allow empty values",
function()
SendSecretToProxy(5001, "USER_ADDED", { PASSCODE = "" }, nil, true)
end,
},
{
"SendToProxy, told only to allow empty values",
function()
SendToProxy(5001, "LOCK_STATUS_CHANGED", { LOCK_STATUS = "" }, nil, true)
end,
},
}
for _, call in ipairs(calls) do
T.truthy("C4 accepts " .. call[1], pcall(call[2]))
end
T.eq("a device command's empty values refused", { sent[1][6], sent[2][6] }, { false, false })
T.eq("a proxy message sent as a command", { sent[3][6], sent[4][6] }, { "COMMAND", "COMMAND" })
sent = {}
SendToDevice(42, "REFRESH", {})
SendToProxy(5001, "LOCK_STATUS_CHANGED", {})
T.eq("and nothing is added when nothing follows", { sent[1][2], sent[2][2] }, { 3, 3 })

T.finish()
Loading