2 * Copyright (c) 2010-2024 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.knx.internal.handler;
16 import java.util.TreeMap;
17 import java.util.concurrent.Executors;
18 import java.util.concurrent.ScheduledExecutorService;
20 import org.eclipse.jdt.annotation.NonNullByDefault;
21 import org.eclipse.jdt.annotation.Nullable;
22 import org.openhab.binding.knx.internal.client.KNXClient;
23 import org.openhab.binding.knx.internal.client.StatusUpdateCallback;
24 import org.openhab.core.common.ThreadPoolManager;
25 import org.openhab.core.thing.Bridge;
26 import org.openhab.core.thing.ChannelUID;
27 import org.openhab.core.thing.ThingStatus;
28 import org.openhab.core.thing.ThingStatusDetail;
29 import org.openhab.core.thing.binding.BaseBridgeHandler;
30 import org.openhab.core.types.Command;
32 import tuwien.auto.calimero.knxnetip.SecureConnection;
33 import tuwien.auto.calimero.secure.KnxSecureException;
36 * The {@link KNXBridgeBaseThingHandler} is responsible for handling commands, which are
37 * sent to one of the channels.
39 * @author Simon Kaufmann - Initial contribution and API
40 * @author Holger Friedrich - KNX Secure configuration
43 public abstract class KNXBridgeBaseThingHandler extends BaseBridgeHandler implements StatusUpdateCallback {
45 public static class SecureTunnelConfig {
46 public SecureTunnelConfig() {
48 userKey = new byte[0];
53 public byte[] userKey;
57 public static class SecureRoutingConfig {
58 public SecureRoutingConfig() {
59 backboneGroupKey = new byte[0];
60 latencyToleranceMs = 0;
63 public byte[] backboneGroupKey;
64 public long latencyToleranceMs = 0;
68 * Helper class to carry information which can be used by the
69 * command line extension (openHAB console).
71 public record CommandExtensionData(Map<String, Long> unknownGA) {
74 private final ScheduledExecutorService knxScheduler = ThreadPoolManager.getScheduledPool("knx");
75 private final ScheduledExecutorService backgroundScheduler = Executors.newSingleThreadScheduledExecutor();
76 protected SecureRoutingConfig secureRouting;
77 protected SecureTunnelConfig secureTunnel;
78 private CommandExtensionData commandExtensionData;
80 public KNXBridgeBaseThingHandler(Bridge bridge) {
82 secureRouting = new SecureRoutingConfig();
83 secureTunnel = new SecureTunnelConfig();
84 commandExtensionData = new CommandExtensionData(new TreeMap<>());
87 protected abstract KNXClient getClient();
89 public CommandExtensionData getCommandExtensionData() {
90 return commandExtensionData;
94 * Initialize KNX secure if configured (full interface)
96 * @param cRouterBackboneGroupKey shared key for secure router mode.
97 * @param cTunnelDevAuth device password for IP interface in tunnel mode.
98 * @param cTunnelUser user id for tunnel mode. Must be an integer >0.
99 * @param cTunnelPassword user password for tunnel mode.
102 protected boolean initializeSecurity(String cRouterBackboneGroupKey, String cTunnelDevAuth, String cTunnelUser,
103 String cTunnelPassword) throws KnxSecureException {
104 secureRouting = new SecureRoutingConfig();
105 secureTunnel = new SecureTunnelConfig();
107 boolean securityInitialized = false;
109 // step 1: secure routing, backbone group key manually specified in OH config
110 if (!cRouterBackboneGroupKey.isBlank()) {
111 // provided in config
112 String key = cRouterBackboneGroupKey.trim().replaceFirst("^0x", "").trim().replace(" ", "");
113 if (!key.isEmpty()) {
114 // helper may throw KnxSecureException
115 secureRouting.backboneGroupKey = secHelperParseBackboneKey(key);
116 securityInitialized = true;
120 // step 2: check if valid tunnel parameters are specified in config
121 if (!cTunnelDevAuth.isBlank()) {
122 secureTunnel.devKey = SecureConnection.hashDeviceAuthenticationPassword(cTunnelDevAuth.toCharArray());
123 securityInitialized = true;
125 if (!cTunnelPassword.isBlank()) {
126 secureTunnel.userKey = SecureConnection.hashUserPassword(cTunnelPassword.toCharArray());
127 securityInitialized = true;
129 if (!cTunnelUser.isBlank()) {
130 String user = cTunnelUser.trim();
132 secureTunnel.user = Integer.decode(user);
133 } catch (NumberFormatException e) {
134 throw new KnxSecureException("tunnelUser must be a number >0");
136 if (secureTunnel.user <= 0) {
137 throw new KnxSecureException("tunnelUser must be a number >0");
139 securityInitialized = true;
142 // step 5: router: load latencyTolerance
144 // this parameter is currently not exposed in config, it may later be set by using the keyring
145 secureRouting.latencyToleranceMs = 2000;
147 return securityInitialized;
151 * converts hex string (32 characters) to byte[16]
153 * @param hexstring 32 characters hex
154 * @return key in byte array format
156 public static byte[] secHelperParseBackboneKey(String hexstring) throws KnxSecureException {
157 if (hexstring.length() != 32) {
158 throw new KnxSecureException("backbone key must be 32 characters (16 byte hex notation)");
161 byte[] parsed = new byte[16];
163 for (byte i = 0; i < 16; i++) {
164 parsed[i] = (byte) Integer.parseInt(hexstring.substring(2 * i, 2 * i + 2), 16);
166 } catch (NumberFormatException e) {
167 throw new KnxSecureException("backbone key configured, cannot parse hex string, illegal character", e);
173 public void handleCommand(ChannelUID channelUID, Command command) {
174 // Nothing to do here
177 public ScheduledExecutorService getScheduler() {
181 public ScheduledExecutorService getBackgroundScheduler() {
182 return backgroundScheduler;
186 public void updateStatus(ThingStatus status) {
187 super.updateStatus(status);
191 public void updateStatus(ThingStatus status, ThingStatusDetail statusDetail, @Nullable String description) {
192 super.updateStatus(status, statusDetail, description);