Home IO Control
ESPHome add-on for IO-Homecontrol devices
Loading...
Searching...
No Matches
pairing_responder.cpp
Go to the documentation of this file.
1/// @file pairing_responder.cpp
2/// @brief Pure decision logic for the device-role key-extraction responder.
3/// @ingroup hioc_hub
4
5#include "pairing_responder.h"
6
7#include "proto_commands.h"
8
9#include <cstring>
10
11namespace esphome {
12namespace home_io_control {
14
16 switch (state) {
18 return "disarmed";
20 return "armed_idle";
22 return "sent_discover_resp";
24 return "sent_confirm_ack";
26 return "sent_challenge";
28 return "extracted";
30 return "sent_address_resp";
31 }
32 return "disarmed";
33}
34
35bool on_discover_request(ResponderContext &ctx, const uint8_t hub_node_id[NODE_ID_SIZE]) {
38 return true;
39 }
40 // EXTRACTED/SENT_ADDRESS_RESP are a *completed* attempt, not an in-flight one — unlike
41 // SENT_CONFIRM_ACK above, a fresh 0x28 arriving here cannot be the same hub's redundant
42 // mid-exchange rebroadcast (the only documented real-hub case for staying silent; see the
43 // doxygen), so it is treated like ARMED_IDLE: start a new attempt. But ONLY for the hub this
44 // responder actually extracted a key from — 0x28 is a broadcast handled before the throwaway-ID
45 // dst filter, so accepting it unconditionally here would let any unrelated hub's ordinary 0x28
46 // traffic knock a live post-extraction address-verification round with the real hub back to
47 // SENT_DISCOVER_RESP. A different hub's 0x28 is silently ignored instead.
49 if (memcmp(hub_node_id, ctx.hub_node_id, NODE_ID_SIZE) != 0)
50 return false;
52 return true;
53 }
54 return false;
55}
56
63
64bool on_key_init(ResponderContext &ctx, const uint8_t challenge[HMAC_SIZE], const uint8_t hub_node_id[NODE_ID_SIZE]) {
65 // Both pre-key-exchange states are accepted: a hub that insists on our 0x2D arrives here from
66 // SENT_CONFIRM_ACK, one that gives up waiting for it arrives straight from SENT_DISCOVER_RESP.
68 memcpy(ctx.challenge, challenge, HMAC_SIZE);
69 memcpy(ctx.hub_node_id, hub_node_id, NODE_ID_SIZE);
71 return true;
72 }
73 // Retry: the hub already got past this phase once — keep the previously-stored challenge and
74 // hub node ID, just resend the same 0x3C (see doxygen for why regenerating would be wrong).
76}
77
78bool on_key_transfer(ResponderContext &ctx, const uint8_t transfer_payload[AES_KEY_SIZE]) {
80 return false;
81 if (!recover_system_key_from_transfer(transfer_payload, ctx.challenge, ctx.recovered_key))
82 return false;
84 return true;
85}
86
89 return false;
91 return true;
92}
93
95
96} // namespace pairing_responder
97} // namespace home_io_control
98} // namespace esphome
bool on_key_transfer(ResponderContext &ctx, const uint8_t transfer_payload[AES_KEY_SIZE])
Decide how to react to an inbound CMD_KEY_TRANSFER (0x32) while armed.
const char * responder_stage_name(ResponderState state)
Get a short, log/telemetry-friendly name for a responder state.
bool on_discover_confirm(ResponderContext &ctx)
Decide how to react to an inbound CMD_DISCOVER_CONFIRM (0x2C) addressed to our throwaway ID.
bool on_address_challenge(const ResponderContext &ctx)
Decide how to react to an inbound CMD_CHALLENGE_REQ (0x3C) — issued by the hub this time,...
bool on_key_init(ResponderContext &ctx, const uint8_t challenge[HMAC_SIZE], const uint8_t hub_node_id[NODE_ID_SIZE])
Decide how to react to an inbound CMD_KEY_INIT (0x31) addressed to our throwaway ID.
bool on_address_req(ResponderContext &ctx)
Decide how to react to an inbound CMD_ADDRESS_REQ (0x36) addressed to our throwaway ID.
ResponderState
State machine for the device-role key-extraction responder.
@ ARMED_IDLE
Armed, listening for a discovery request (0x28).
@ SENT_DISCOVER_RESP
Replied to discovery (0x29); waiting for discovery-confirm (0x2C) or key-init (0x31).
@ SENT_CONFIRM_ACK
Acknowledged discovery-confirm (0x2D); waiting for key-init (0x31).
@ DISARMED
Not armed; 0x28/0x2C/0x31/0x32 traffic is ignored.
@ SENT_ADDRESS_RESP
Answered a hub's CMD_ADDRESS_REQ (0x36) with our CMD_ADDRESS_RESP (0x37); waiting for the hub's own C...
@ SENT_CHALLENGE
Replied to key-init with our challenge (0x3C); waiting for key-transfer (0x32).
bool on_discover_request(ResponderContext &ctx, const uint8_t hub_node_id[NODE_ID_SIZE])
Decide how to react to an inbound CMD_DISCOVER_REQ (0x28) while armed.
static constexpr uint8_t NODE_ID_SIZE
Device/node addresses are 3 bytes (e.g., "123ABC").
Definition proto_sizes.h:20
bool recover_system_key_from_transfer(const uint8_t transfer_payload[AES_KEY_SIZE], const uint8_t challenge[HMAC_SIZE], uint8_t out_key[AES_KEY_SIZE])
Recover the system key from a CMD_KEY_TRANSFER payload.
static constexpr uint8_t HMAC_SIZE
Authentication HMAC is 6 bytes (truncated AES output).
Definition proto_sizes.h:22
static constexpr uint8_t AES_KEY_SIZE
AES-128 key size.
Definition proto_sizes.h:23
Pure decision logic for the device-role "Accept Foreign Pairing" (system-key extraction) responder.
Command builders for the IO‑Homecontrol protocol.
uint8_t hub_node_id[NODE_ID_SIZE]
Foreign hub's real node ID, captured from the 0x31's src.
uint8_t challenge[HMAC_SIZE]
Our challenge, generated on the first 0x31 of an attempt.
uint8_t recovered_key[AES_KEY_SIZE]
Recovered system key; valid once state == EXTRACTED.