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;
15 import java.util.concurrent.ConcurrentHashMap;
16 import java.util.concurrent.Executors;
17 import java.util.concurrent.ScheduledExecutorService;
19 import org.eclipse.jdt.annotation.NonNullByDefault;
20 import org.eclipse.jdt.annotation.Nullable;
21 import org.openhab.binding.knx.internal.client.KNXClient;
22 import org.openhab.binding.knx.internal.client.StatusUpdateCallback;
23 import org.openhab.core.common.ThreadPoolManager;
24 import org.openhab.core.thing.Bridge;
25 import org.openhab.core.thing.ChannelUID;
26 import org.openhab.core.thing.ThingStatus;
27 import org.openhab.core.thing.ThingStatusDetail;
28 import org.openhab.core.thing.binding.BaseBridgeHandler;
29 import org.openhab.core.types.Command;
31 import tuwien.auto.calimero.IndividualAddress;
32 import tuwien.auto.calimero.knxnetip.SecureConnection;
33 import tuwien.auto.calimero.mgmt.Destination;
34 import tuwien.auto.calimero.secure.KnxSecureException;
37 * The {@link KNXBridgeBaseThingHandler} is responsible for handling commands, which are
38 * sent to one of the channels.
40 * @author Simon Kaufmann - Initial contribution and API
41 * @author Holger Friedrich - KNX Secure configuration
44 public abstract class KNXBridgeBaseThingHandler extends BaseBridgeHandler implements StatusUpdateCallback {
46 public static class SecureTunnelConfig {
47 public SecureTunnelConfig() {
49 userKey = new byte[0];
54 public byte[] userKey;
58 public static class SecureRoutingConfig {
59 public SecureRoutingConfig() {
60 backboneGroupKey = new byte[0];
61 latencyToleranceMs = 0;
64 public byte[] backboneGroupKey;
65 public long latencyToleranceMs = 0;
68 protected ConcurrentHashMap<IndividualAddress, Destination> destinations = new ConcurrentHashMap<>();
69 private final ScheduledExecutorService knxScheduler = ThreadPoolManager.getScheduledPool("knx");
70 private final ScheduledExecutorService backgroundScheduler = Executors.newSingleThreadScheduledExecutor();
71 protected SecureRoutingConfig secureRouting;
72 protected SecureTunnelConfig secureTunnel;
74 public KNXBridgeBaseThingHandler(Bridge bridge) {
76 secureRouting = new SecureRoutingConfig();
77 secureTunnel = new SecureTunnelConfig();
80 protected abstract KNXClient getClient();
83 * Initialize KNX secure if configured (full interface)
85 * @param cRouterBackboneGroupKey shared key for secure router mode.
86 * @param cTunnelDevAuth device password for IP interface in tunnel mode.
87 * @param cTunnelUser user id for tunnel mode. Must be an integer >0.
88 * @param cTunnelPassword user password for tunnel mode.
91 protected boolean initializeSecurity(String cRouterBackboneGroupKey, String cTunnelDevAuth, String cTunnelUser,
92 String cTunnelPassword) throws KnxSecureException {
93 secureRouting = new SecureRoutingConfig();
94 secureTunnel = new SecureTunnelConfig();
96 boolean securityInitialized = false;
98 // step 1: secure routing, backbone group key manually specified in OH config
99 if (!cRouterBackboneGroupKey.isBlank()) {
100 // provided in config
101 String key = cRouterBackboneGroupKey.trim().replaceFirst("^0x", "").trim().replace(" ", "");
102 if (!key.isEmpty()) {
103 // helper may throw KnxSecureException
104 secureRouting.backboneGroupKey = secHelperParseBackboneKey(key);
105 securityInitialized = true;
109 // step 2: check if valid tunnel parameters are specified in config
110 if (!cTunnelDevAuth.isBlank()) {
111 secureTunnel.devKey = SecureConnection.hashDeviceAuthenticationPassword(cTunnelDevAuth.toCharArray());
112 securityInitialized = true;
114 if (!cTunnelPassword.isBlank()) {
115 secureTunnel.userKey = SecureConnection.hashUserPassword(cTunnelPassword.toCharArray());
116 securityInitialized = true;
118 if (!cTunnelUser.isBlank()) {
119 String user = cTunnelUser.trim();
121 secureTunnel.user = Integer.decode(user);
122 } catch (NumberFormatException e) {
123 throw new KnxSecureException("tunnelUser must be a number >0");
125 if (secureTunnel.user <= 0) {
126 throw new KnxSecureException("tunnelUser must be a number >0");
128 securityInitialized = true;
131 // step 5: router: load latencyTolerance
133 // this parameter is currently not exposed in config, it may later be set by using the keyring
134 secureRouting.latencyToleranceMs = 2000;
136 return securityInitialized;
140 * converts hex string (32 characters) to byte[16]
142 * @param hexstring 32 characters hex
143 * @return key in byte array format
145 public static byte[] secHelperParseBackboneKey(String hexstring) throws KnxSecureException {
146 if (hexstring.length() != 32) {
147 throw new KnxSecureException("backbone key must be 32 characters (16 byte hex notation)");
150 byte[] parsed = new byte[16];
152 for (byte i = 0; i < 16; i++) {
153 parsed[i] = (byte) Integer.parseInt(hexstring.substring(2 * i, 2 * i + 2), 16);
155 } catch (NumberFormatException e) {
156 throw new KnxSecureException("backbone key configured, cannot parse hex string, illegal character", e);
162 public void handleCommand(ChannelUID channelUID, Command command) {
163 // Nothing to do here
166 public ScheduledExecutorService getScheduler() {
170 public ScheduledExecutorService getBackgroundScheduler() {
171 return backgroundScheduler;
175 public void updateStatus(ThingStatus status) {
176 super.updateStatus(status);
180 public void updateStatus(ThingStatus status, ThingStatusDetail statusDetail, @Nullable String description) {
181 super.updateStatus(status, statusDetail, description);