Skip to content

Commit ed58efb

Browse files
committed
Support keypair generation for Aliro
Signed-off-by: Hunsup Jung <[email protected]>
1 parent aa630fa commit ed58efb

File tree

2 files changed

+166
-68
lines changed

2 files changed

+166
-68
lines changed

drivers/SmartThings/matter-lock/src/lock_utils.lua

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,9 @@ local lock_utils = {
5050
ENDPOINT_KEY_INDEX = "endpointKeyIndex",
5151
ENDPOINT_KEY_TYPE = "endpointKeyType",
5252
DEVICE_KEY_ID = "deviceKeyId",
53-
COMMAND_REQUEST_ID = "commandRequestId"
53+
COMMAND_REQUEST_ID = "commandRequestId",
54+
MODULAR_PROFILE_UPDATED = "modularProfileUpdated",
55+
ALIRO_READER_CONFIG_UPDATED = "aliroReaderConfigUpdated"
5456
}
5557
local capabilities = require "st.capabilities"
5658
local json = require "st.json"

drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua

Lines changed: 163 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ local clusters = require "st.matter.clusters"
1818
local im = require "st.matter.interaction_model"
1919
local utils = require "st.utils"
2020
local lock_utils = require "lock_utils"
21+
local security = require "st.security"
2122

2223
local version = require "version"
2324
if version.api < 10 then
@@ -26,14 +27,18 @@ end
2627

2728
local DoorLock = clusters.DoorLock
2829
local PowerSource = clusters.PowerSource
30+
local DoorLockFeatureMapAttr = {ID = 0xFFFC, cluster = 0x0101}
2931

3032
local INITIAL_CREDENTIAL_INDEX = 1
3133
local ALL_INDEX = 0xFFFE
3234
local MIN_EPOCH_S = 0
3335
local MAX_EPOCH_S = 0xffffffff
3436
local THIRTY_YEARS_S = 946684800 -- 1970-01-01T00:00:00 ~ 2000-01-01T00:00:00
35-
36-
local MODULAR_PROFILE_UPDATED = "__MODULAR_PROFILE_UPDATED"
37+
local PRIV_KEY_START = 15
38+
local PRIV_KEY_END = 78
39+
local PUB_KEY_START = 115
40+
local PUB_KEY_END = 242
41+
local PUB_KEY_PREFIX = "04"
3742

3843
local RESPONSE_STATUS_MAP = {
3944
[DoorLock.types.DlStatus.SUCCESS] = "success",
@@ -194,6 +199,9 @@ local function device_init(driver, device)
194199
end
195200
end
196201
end
202+
if device.manufacturer_info.vendor_id == 0x135D then
203+
device:add_subscribed_attribute(DoorLockFeatureMapAttr)
204+
end
197205
device:subscribe()
198206
end
199207

@@ -207,10 +215,118 @@ local function device_added(driver, device)
207215
end
208216
end
209217

218+
-- This function check busy_state and if busy_state is false, set it to true(current time)
219+
local function is_busy_state_set(device)
220+
local c_time = os.time()
221+
local busy_state = device:get_field(lock_utils.BUSY_STATE) or false
222+
if busy_state == false or c_time - busy_state > 10 then
223+
device:set_field(lock_utils.BUSY_STATE, c_time, {persist = true})
224+
return false
225+
else
226+
return true
227+
end
228+
end
229+
230+
local function hex_string_to_octet_string(hex_string)
231+
if hex_string == nil then
232+
return nil
233+
end
234+
local octet_string = ""
235+
for i = 1, #hex_string, 2 do
236+
local hex = hex_string:sub(i, i + 1)
237+
octet_string = octet_string .. string.char(tonumber(hex, 16))
238+
end
239+
return octet_string
240+
end
241+
242+
local function create_group_id_resolving_key()
243+
math.randomseed(os.time())
244+
local result = string.format("%02x", math.random(0, 255))
245+
for i = 1, 15 do
246+
result = result .. string.format("%02x", math.random(0, 255))
247+
end
248+
return result
249+
end
250+
251+
local function generate_keypair(device)
252+
local request_opts = {
253+
key_algorithm = {
254+
type = "ec",
255+
curve = "prime256v1"
256+
},
257+
signature_algorithm = "sha256",
258+
return_formats = {
259+
pem = true,
260+
der = true
261+
},
262+
subject = {
263+
common_name = "reader config"
264+
},
265+
validity_days = 36500,
266+
x509_extensions = {
267+
key_usage = {
268+
critical = true,
269+
digital_signature = true
270+
},
271+
certificate_policies = {
272+
critical = true,
273+
policy_2030_5_self_signed_client = true
274+
}
275+
}
276+
}
277+
local status, result = security.generate_self_signed_cert(request_opts)
278+
local privKey = string.sub(utils.bytes_to_hex_string(status.key_der), PRIV_KEY_START, PRIV_KEY_END)
279+
local pubKey = PUB_KEY_PREFIX .. string.sub(utils.bytes_to_hex_string(status.key_der), PUB_KEY_START, PUB_KEY_END)
280+
281+
return privKey, pubKey
282+
end
283+
284+
local function set_reader_config(device)
285+
local cmdName = "setReaderConfig"
286+
local groupId = create_group_id_resolving_key()
287+
local groupResolvingKey = nil
288+
local aliro_ble_uwb_eps = device:get_endpoints(DoorLock.ID, {feature_bitmap = DoorLock.types.Feature.ALIROBLEUWB})
289+
if #aliro_ble_uwb_eps > 0 then
290+
groupResolvingKey = create_group_id_resolving_key()
291+
end
292+
local privKey, pubKey = generate_keypair(device)
293+
294+
-- Check busy state
295+
if is_busy_state_set(device) then
296+
local command_result_info = {
297+
commandName = cmdName,
298+
statusCode = "busy"
299+
}
300+
device:emit_event(capabilities.lockAliro.commandResult(
301+
command_result_info, {state_change = true, visibility = {displayed = false}}
302+
))
303+
return
304+
end
305+
306+
-- Save values to field
307+
device:set_field(lock_utils.COMMAND_NAME, cmdName, {persist = true})
308+
device:set_field(lock_utils.VERIFICATION_KEY, pubKey, {persist = true})
309+
device:set_field(lock_utils.GROUP_ID, groupId, {persist = true})
310+
device:set_field(lock_utils.GROUP_RESOLVING_KEY, groupResolvingKey, {persist = true})
311+
312+
-- Send command
313+
local ep = find_default_endpoint(device, clusters.DoorLock.ID)
314+
device:send(
315+
DoorLock.server.commands.SetAliroReaderConfig(
316+
device, ep,
317+
hex_string_to_octet_string(privKey),
318+
hex_string_to_octet_string(pubKey),
319+
hex_string_to_octet_string(groupId),
320+
hex_string_to_octet_string(groupResolvingKey)
321+
)
322+
)
323+
end
324+
210325
local function match_profile_modular(driver, device)
211326
local enabled_optional_component_capability_pairs = {}
212327
local main_component_capabilities = {}
213328
local modular_profile_name = "lock-modular"
329+
local is_support_aliro = false
214330
for _, device_ep in pairs(device.endpoints) do
215331
for _, ep_cluster in pairs(device_ep.clusters) do
216332
if ep_cluster.cluster_id == DoorLock.ID then
@@ -237,6 +353,7 @@ local function match_profile_modular(driver, device)
237353
end
238354
if clus_has_feature(DoorLock.types.Feature.ALIRO_PROVISIONING) then
239355
table.insert(main_component_capabilities, capabilities.lockAliro.ID)
356+
is_support_aliro = true
240357
end
241358
break
242359
end
@@ -252,7 +369,12 @@ local function match_profile_modular(driver, device)
252369

253370
table.insert(enabled_optional_component_capability_pairs, {"main", main_component_capabilities})
254371
device:try_update_metadata({profile = modular_profile_name, optional_component_capabilities = enabled_optional_component_capability_pairs})
255-
device:set_field(MODULAR_PROFILE_UPDATED, true)
372+
device:set_field(lock_utils.MODULAR_PROFILE_UPDATED, true)
373+
374+
local reader_config_updated = device:get_field(lock_utils.ALIRO_READER_CONFIG_UPDATED) or nil
375+
if is_support_aliro == true and reader_config_updated ~= true then
376+
set_reader_config(device)
377+
end
256378
end
257379

258380
local function match_profile_switch(driver, device)
@@ -291,10 +413,10 @@ local function match_profile_switch(driver, device)
291413
end
292414

293415
local function info_changed(driver, device, event, args)
294-
if device.profile.id == args.old_st_store.profile.id and not device:get_field(MODULAR_PROFILE_UPDATED) then
416+
if device.profile.id == args.old_st_store.profile.id and not device:get_field(lock_utils.MODULAR_PROFILE_UPDATED) then
295417
return
296418
end
297-
device:set_field(MODULAR_PROFILE_UPDATED, nil)
419+
device:set_field(lock_utils.MODULAR_PROFILE_UPDATED, nil)
298420
for cap_id, attributes in pairs(subscribed_attributes) do
299421
if device:supports_capability_by_id(cap_id) then
300422
for _, attr in ipairs(attributes) do
@@ -341,18 +463,6 @@ local function driver_switched(driver, device)
341463
match_profile(driver, device)
342464
end
343465

344-
-- This function check busy_state and if busy_state is false, set it to true(current time)
345-
local function is_busy_state_set(device)
346-
local c_time = os.time()
347-
local busy_state = device:get_field(lock_utils.BUSY_STATE) or false
348-
if busy_state == false or c_time - busy_state > 10 then
349-
device:set_field(lock_utils.BUSY_STATE, c_time, {persist = true})
350-
return false
351-
else
352-
return true
353-
end
354-
end
355-
356466
-- Matter Handler
357467
----------------
358468
-- Lock State --
@@ -526,21 +636,6 @@ local function max_year_schedule_of_user_handler(driver, device, ib, response)
526636
device:emit_event(capabilities.lockSchedules.yearDaySchedulesPerUser(ib.data.value, {visibility = {displayed = false}}))
527637
end
528638

529-
----------------
530-
-- Aliro Util --
531-
----------------
532-
local function hex_string_to_octet_string(hex_string)
533-
if hex_string == nil then
534-
return nil
535-
end
536-
local octet_string = ""
537-
for i = 1, #hex_string, 2 do
538-
local hex = hex_string:sub(i, i + 1)
539-
octet_string = octet_string .. string.char(tonumber(hex, 16))
540-
end
541-
return octet_string
542-
end
543-
544639
-----------------------------------
545640
-- Aliro Reader Verification Key --
546641
-----------------------------------
@@ -633,6 +728,23 @@ local function max_aliro_endpoint_key_handler(driver, device, ib, response)
633728
end
634729
end
635730

731+
---------------------------------------------
732+
-- Feature Map of Door Lock --
733+
---------------------------------------------
734+
local function door_lock_feature_map_handler(driver, device, ib, response)
735+
for _, device_ep in pairs(device.endpoints) do
736+
for _, ep_cluster in pairs(device_ep.clusters) do
737+
if ep_cluster.cluster_id == DoorLock.ID then
738+
if ep_cluster.feature_map == ib.data.value then
739+
return
740+
end
741+
ep_cluster.feature_map = ib.data.value
742+
end
743+
end
744+
end
745+
match_profile(driver, device)
746+
end
747+
636748
---------------------------------
637749
-- Power Source Attribute List --
638750
---------------------------------
@@ -2542,6 +2654,20 @@ local function handle_set_reader_config(driver, device, command)
25422654
return
25432655
end
25442656

2657+
local reader_config_updated = device:get_field(lock_utils.ALIRO_READER_CONFIG_UPDATED) or nil
2658+
if reader_config_updated == true then
2659+
-- Update commandResult
2660+
local command_result_info = {
2661+
commandName = cmdName,
2662+
statusCode = "success"
2663+
}
2664+
device:emit_event(capabilities.lockAliro.commandResult(
2665+
command_result_info, {state_change = true, visibility = {displayed = false}}
2666+
))
2667+
device:set_field(lock_utils.BUSY_STATE, false, {persist = true})
2668+
return
2669+
end
2670+
25452671
-- Save values to field
25462672
device:set_field(lock_utils.COMMAND_NAME, cmdName, {persist = true})
25472673
device:set_field(lock_utils.VERIFICATION_KEY, verificationKey, {persist = true})
@@ -2564,43 +2690,12 @@ end
25642690
local function set_aliro_reader_config_handler(driver, device, ib, response)
25652691
-- Get result
25662692
local cmdName = device:get_field(lock_utils.COMMAND_NAME)
2567-
local verificationKey = device:get_field(lock_utils.VERIFICATION_KEY)
2568-
local groupId = device:get_field(lock_utils.GROUP_ID)
2569-
local groupResolvingKey = device:get_field(lock_utils.GROUP_RESOLVING_KEY)
2570-
2571-
local status = "success"
2572-
if ib.status == DoorLock.types.DlStatus.FAILURE then
2573-
status = "failure"
2693+
local status = "failure"
2694+
if ib.status == DoorLock.types.DlStatus.SUCCESS then
2695+
status = "success"
2696+
device:set_field(lock_utils.ALIRO_READER_CONFIG_UPDATED, true)
25742697
elseif ib.status == DoorLock.types.DlStatus.INVALID_FIELD then
25752698
status = "invalidCommand"
2576-
elseif ib.status == DoorLock.types.DlStatus.SUCCESS then
2577-
if verificationKey ~= nil then
2578-
device:emit_event(capabilities.lockAliro.readerVerificationKey(
2579-
verificationKey,
2580-
{
2581-
state_change = true,
2582-
visibility = {displayed = false}
2583-
}
2584-
))
2585-
end
2586-
if groupId ~= nil then
2587-
device:emit_event(capabilities.lockAliro.readerGroupIdentifier(
2588-
groupId,
2589-
{
2590-
state_change = true,
2591-
visibility = {displayed = false}
2592-
}
2593-
))
2594-
end
2595-
if groupResolvingKey ~= nil then
2596-
device:emit_event(capabilities.lockAliro.groupResolvingKey(
2597-
groupResolvingKey,
2598-
{
2599-
state_change = true,
2600-
visibility = {displayed = false}
2601-
}
2602-
))
2603-
end
26042699
end
26052700

26062701
-- Update commandResult
@@ -2896,6 +2991,7 @@ local new_matter_lock_handler = {
28962991
[DoorLock.attributes.AliroBLEAdvertisingVersion.ID] = aliro_ble_advertising_version_handler,
28972992
[DoorLock.attributes.NumberOfAliroCredentialIssuerKeysSupported.ID] = max_aliro_credential_issuer_key_handler,
28982993
[DoorLock.attributes.NumberOfAliroEndpointKeysSupported.ID] = max_aliro_endpoint_key_handler,
2994+
[DoorLockFeatureMapAttr.ID] = door_lock_feature_map_handler,
28992995
},
29002996
[PowerSource.ID] = {
29012997
[PowerSource.attributes.AttributeList.ID] = handle_power_source_attribute_list,

0 commit comments

Comments
 (0)