Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: auto-remove failed SmartStart nodes when bootstrapping times out #6483

Merged
merged 1 commit into from
Oct 31, 2023
Merged
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
46 changes: 33 additions & 13 deletions packages/zwave-js/src/lib/controller/Controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2769,6 +2769,17 @@ supported CCs: ${
return SecurityBootstrapFailure.UserCanceled;
};

const abortTimeout = async () => {
this.driver.controllerLog.logNode(node.id, {
message:
`Security S2 bootstrapping failed: a secure inclusion timer has elapsed`,
level: "warn",
});

await abort();
return SecurityBootstrapFailure.Timeout;
};

// Ask the node for its desired security classes and key exchange params
const kexParams = await api
.withOptions({ reportTimeoutMs: inclusionTimeouts.TA1 })
Expand Down Expand Up @@ -2876,7 +2887,8 @@ supported CCs: ${
cc instanceof Security2CCPublicKeyReport
|| cc instanceof Security2CCKEXFail,
inclusionTimeouts.TA2,
);
).catch(() => "timeout" as const);
if (pubKeyResponse === "timeout") return abortTimeout();
if (
pubKeyResponse instanceof Security2CCKEXFail
|| pubKeyResponse.includingNode
Expand Down Expand Up @@ -2990,25 +3002,26 @@ supported CCs: ${
return abortUser();
}

const keySetEcho = await Promise.race([
const kexSetEcho = await Promise.race([
this.driver.waitForCommand<
Security2CCKEXSet | Security2CCKEXFail
>(
(cc) =>
cc instanceof Security2CCKEXSet
|| cc instanceof Security2CCKEXFail,
tai2RemainingMs,
),
).catch(() => "timeout" as const),
this.cancelBootstrapS2Promise,
]);
if (typeof keySetEcho === "number") {
if (kexSetEcho === "timeout") return abortTimeout();
if (typeof kexSetEcho === "number") {
// The bootstrapping process was canceled - this is most likely because the PIN was incorrect
// and the node's commands cannot be decoded
await abort(keySetEcho);
await abort(kexSetEcho);
return SecurityBootstrapFailure.S2IncorrectPIN;
}
// Validate that the received command contains the correct list of keys
if (keySetEcho instanceof Security2CCKEXFail) {
if (kexSetEcho instanceof Security2CCKEXFail) {
this.driver.controllerLog.logNode(node.id, {
message:
`The joining node canceled the Security S2 bootstrapping.`,
Expand All @@ -3017,7 +3030,7 @@ supported CCs: ${
});
await abort();
return SecurityBootstrapFailure.NodeCanceled;
} else if (!keySetEcho.echo) {
} else if (!kexSetEcho.echo) {
this.driver.controllerLog.logNode(node.id, {
message:
`Security S2 bootstrapping failed: KEXSet received without echo flag`,
Expand All @@ -3027,7 +3040,7 @@ supported CCs: ${
await abort(KEXFailType.WrongSecurityLevel);
return SecurityBootstrapFailure.NodeCanceled;
} else if (
!keySetEcho.isEncapsulatedWith(
!kexSetEcho.isEncapsulatedWith(
CommandClasses["Security 2"],
Security2Command.MessageEncapsulation,
)
Expand All @@ -3041,8 +3054,8 @@ supported CCs: ${
await abort(KEXFailType.WrongSecurityLevel);
return SecurityBootstrapFailure.S2WrongSecurityLevel;
} else if (
keySetEcho.grantedKeys.length !== grantedKeys.length
|| !keySetEcho.grantedKeys.every((k) => grantedKeys.includes(k))
kexSetEcho.grantedKeys.length !== grantedKeys.length
|| !kexSetEcho.grantedKeys.every((k) => grantedKeys.includes(k))
) {
this.driver.controllerLog.logNode(node.id, {
message:
Expand Down Expand Up @@ -3070,7 +3083,8 @@ supported CCs: ${
cc instanceof Security2CCNetworkKeyGet
|| cc instanceof Security2CCKEXFail,
inclusionTimeouts.TA3,
);
).catch(() => "timeout" as const);
if (keyRequest === "timeout") return abortTimeout();
if (keyRequest instanceof Security2CCKEXFail) {
this.driver.controllerLog.logNode(node.id, {
message:
Expand Down Expand Up @@ -3127,7 +3141,8 @@ supported CCs: ${
cc instanceof Security2CCNetworkKeyVerify
|| cc instanceof Security2CCKEXFail,
inclusionTimeouts.TA4,
);
).catch(() => "timeout" as const);
if (verify === "timeout") return abortTimeout();
if (verify instanceof Security2CCKEXFail) {
this.driver.controllerLog.logNode(node.id, {
message:
Expand Down Expand Up @@ -3169,7 +3184,8 @@ supported CCs: ${
>(
(cc) => cc instanceof Security2CCTransferEnd,
inclusionTimeouts.TA5,
);
).catch(() => "timeout" as const);
if (transferEnd === "timeout") return abortTimeout();
if (!transferEnd.keyRequestComplete) {
// S2 bootstrapping failed
this.driver.controllerLog.logNode(node.id, {
Expand Down Expand Up @@ -3209,10 +3225,12 @@ supported CCs: ${
} catch (e) {
let errorMessage =
`Security S2 bootstrapping failed, the node was not granted any S2 security class`;
let result = SecurityBootstrapFailure.Unknown;
if (!isZWaveError(e)) {
errorMessage += `: ${e as any}`;
} else if (e.code === ZWaveErrorCodes.Controller_MessageExpired) {
errorMessage += ": a secure inclusion timer has elapsed.";
result = SecurityBootstrapFailure.Timeout;
} else if (
e.code !== ZWaveErrorCodes.Controller_MessageDropped
&& e.code !== ZWaveErrorCodes.Controller_NodeTimeout
Expand All @@ -3223,6 +3241,8 @@ supported CCs: ${
// Remember that the node was NOT granted any S2 security classes
unGrantSecurityClasses();
node.removeCC(CommandClasses["Security 2"]);

return result;
} finally {
// Whatever happens, no further communication needs the temporary key
deleteTempKey();
Expand Down