2 * Copyright (c) 2010-2023 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.ConcurrentHashMap;
18 import java.util.concurrent.Executors;
19 import java.util.concurrent.ScheduledExecutorService;
21 import org.eclipse.jdt.annotation.NonNullByDefault;
22 import org.eclipse.jdt.annotation.Nullable;
23 import org.openhab.binding.knx.internal.client.KNXClient;
24 import org.openhab.binding.knx.internal.client.StatusUpdateCallback;
25 import org.openhab.core.common.ThreadPoolManager;
26 import org.openhab.core.thing.Bridge;
27 import org.openhab.core.thing.ChannelUID;
28 import org.openhab.core.thing.ThingStatus;
29 import org.openhab.core.thing.ThingStatusDetail;
30 import org.openhab.core.thing.binding.BaseBridgeHandler;
31 import org.openhab.core.types.Command;
33 import tuwien.auto.calimero.IndividualAddress;
34 import tuwien.auto.calimero.knxnetip.SecureConnection;
35 import tuwien.auto.calimero.mgmt.Destination;
36 import tuwien.auto.calimero.secure.KnxSecureException;
39 * The {@link KNXBridgeBaseThingHandler} is responsible for handling commands, which are
40 * sent to one of the channels.
42 * @author Simon Kaufmann - Initial contribution and API
43 * @author Holger Friedrich - KNX Secure configuration
46 public abstract class KNXBridgeBaseThingHandler extends BaseBridgeHandler implements StatusUpdateCallback {
48 public static class SecureTunnelConfig {
49 public SecureTunnelConfig() {
51 userKey = new byte[0];
56 public byte[] userKey;
60 public static class SecureRoutingConfig {
61 public SecureRoutingConfig() {
62 backboneGroupKey = new byte[0];
63 latencyToleranceMs = 0;
66 public byte[] backboneGroupKey;
67 public long latencyToleranceMs = 0;
71 * Helper class to carry information which can be used by the
72 * command line extension (openHAB console).
74 public record CommandExtensionData(Map<String, Long> unknownGA) {
77 protected ConcurrentHashMap<IndividualAddress, Destination> destinations = new ConcurrentHashMap<>();
78 private final ScheduledExecutorService knxScheduler = ThreadPoolManager.getScheduledPool("knx");
79 private final ScheduledExecutorService backgroundScheduler = Executors.newSingleThreadScheduledExecutor();
80 protected SecureRoutingConfig secureRouting;
81 protected SecureTunnelConfig secureTunnel;
82 private CommandExtensionData commandExtensionData;
84 public KNXBridgeBaseThingHandler(Bridge bridge) {
86 secureRouting = new SecureRoutingConfig();
87 secureTunnel = new SecureTunnelConfig();
88 commandExtensionData = new CommandExtensionData(new TreeMap<>());
91 protected abstract KNXClient getClient();
93 public CommandExtensionData getCommandExtensionData() {
94 return commandExtensionData;
98 * Initialize KNX secure if configured (full interface)
100 * @param cRouterBackboneGroupKey shared key for secure router mode.
101 * @param cTunnelDevAuth device password for IP interface in tunnel mode.
102 * @param cTunnelUser user id for tunnel mode. Must be an integer >0.
103 * @param cTunnelPassword user password for tunnel mode.
106 protected boolean initializeSecurity(String cRouterBackboneGroupKey, String cTunnelDevAuth, String cTunnelUser,
107 String cTunnelPassword) throws KnxSecureException {
108 secureRouting = new SecureRoutingConfig();
109 secureTunnel = new SecureTunnelConfig();
111 boolean securityInitialized = false;
113 // step 1: secure routing, backbone group key manually specified in OH config
114 if (!cRouterBackboneGroupKey.isBlank()) {
115 // provided in config
116 String key = cRouterBackboneGroupKey.trim().replaceFirst("^0x", "").trim().replace(" ", "");
117 if (!key.isEmpty()) {
118 // helper may throw KnxSecureException
119 secureRouting.backboneGroupKey = secHelperParseBackboneKey(key);
120 securityInitialized = true;
124 // step 2: check if valid tunnel parameters are specified in config
125 if (!cTunnelDevAuth.isBlank()) {
126 secureTunnel.devKey = SecureConnection.hashDeviceAuthenticationPassword(cTunnelDevAuth.toCharArray());
127 securityInitialized = true;
129 if (!cTunnelPassword.isBlank()) {
130 secureTunnel.userKey = SecureConnection.hashUserPassword(cTunnelPassword.toCharArray());
131 securityInitialized = true;
133 if (!cTunnelUser.isBlank()) {
134 String user = cTunnelUser.trim();
136 secureTunnel.user = Integer.decode(user);
137 } catch (NumberFormatException e) {
138 throw new KnxSecureException("tunnelUser must be a number >0");
140 if (secureTunnel.user <= 0) {
141 throw new KnxSecureException("tunnelUser must be a number >0");
143 securityInitialized = true;
146 // step 5: router: load latencyTolerance
148 // this parameter is currently not exposed in config, it may later be set by using the keyring
149 secureRouting.latencyToleranceMs = 2000;
151 return securityInitialized;
155 * converts hex string (32 characters) to byte[16]
157 * @param hexstring 32 characters hex
158 * @return key in byte array format
160 public static byte[] secHelperParseBackboneKey(String hexstring) throws KnxSecureException {
161 if (hexstring.length() != 32) {
162 throw new KnxSecureException("backbone key must be 32 characters (16 byte hex notation)");
165 byte[] parsed = new byte[16];
167 for (byte i = 0; i < 16; i++) {
168 parsed[i] = (byte) Integer.parseInt(hexstring.substring(2 * i, 2 * i + 2), 16);
170 } catch (NumberFormatException e) {
171 throw new KnxSecureException("backbone key configured, cannot parse hex string, illegal character", e);
177 public void handleCommand(ChannelUID channelUID, Command command) {
178 // Nothing to do here
181 public ScheduledExecutorService getScheduler() {
185 public ScheduledExecutorService getBackgroundScheduler() {
186 return backgroundScheduler;
190 public void updateStatus(ThingStatus status) {
191 super.updateStatus(status);
195 public void updateStatus(ThingStatus status, ThingStatusDetail statusDetail, @Nullable String description) {
196 super.updateStatus(status, statusDetail, description);