]> git.basschouten.com Git - openhab-addons.git/blob
eda5d51e32ab9089592edc76d122c0e4e5415368
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.knx.internal.handler;
14
15 import java.util.Map;
16 import java.util.TreeMap;
17 import java.util.concurrent.ConcurrentHashMap;
18 import java.util.concurrent.Executors;
19 import java.util.concurrent.ScheduledExecutorService;
20
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;
32
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;
37
38 /**
39  * The {@link KNXBridgeBaseThingHandler} is responsible for handling commands, which are
40  * sent to one of the channels.
41  *
42  * @author Simon Kaufmann - Initial contribution and API
43  * @author Holger Friedrich - KNX Secure configuration
44  */
45 @NonNullByDefault
46 public abstract class KNXBridgeBaseThingHandler extends BaseBridgeHandler implements StatusUpdateCallback {
47
48     public static class SecureTunnelConfig {
49         public SecureTunnelConfig() {
50             devKey = new byte[0];
51             userKey = new byte[0];
52             user = 0;
53         }
54
55         public byte[] devKey;
56         public byte[] userKey;
57         public int user = 0;
58     }
59
60     public static class SecureRoutingConfig {
61         public SecureRoutingConfig() {
62             backboneGroupKey = new byte[0];
63             latencyToleranceMs = 0;
64         }
65
66         public byte[] backboneGroupKey;
67         public long latencyToleranceMs = 0;
68     }
69
70     /**
71      * Helper class to carry information which can be used by the
72      * command line extension (openHAB console).
73      */
74     public record CommandExtensionData(Map<String, Long> unknownGA) {
75     }
76
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;
83
84     public KNXBridgeBaseThingHandler(Bridge bridge) {
85         super(bridge);
86         secureRouting = new SecureRoutingConfig();
87         secureTunnel = new SecureTunnelConfig();
88         commandExtensionData = new CommandExtensionData(new TreeMap<>());
89     }
90
91     protected abstract KNXClient getClient();
92
93     public CommandExtensionData getCommandExtensionData() {
94         return commandExtensionData;
95     }
96
97     /***
98      * Initialize KNX secure if configured (full interface)
99      *
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.
104      * @return
105      */
106     protected boolean initializeSecurity(String cRouterBackboneGroupKey, String cTunnelDevAuth, String cTunnelUser,
107             String cTunnelPassword) throws KnxSecureException {
108         secureRouting = new SecureRoutingConfig();
109         secureTunnel = new SecureTunnelConfig();
110
111         boolean securityInitialized = false;
112
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;
121             }
122         }
123
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;
128         }
129         if (!cTunnelPassword.isBlank()) {
130             secureTunnel.userKey = SecureConnection.hashUserPassword(cTunnelPassword.toCharArray());
131             securityInitialized = true;
132         }
133         if (!cTunnelUser.isBlank()) {
134             String user = cTunnelUser.trim();
135             try {
136                 secureTunnel.user = Integer.decode(user);
137             } catch (NumberFormatException e) {
138                 throw new KnxSecureException("tunnelUser must be a number >0");
139             }
140             if (secureTunnel.user <= 0) {
141                 throw new KnxSecureException("tunnelUser must be a number >0");
142             }
143             securityInitialized = true;
144         }
145
146         // step 5: router: load latencyTolerance
147         // default to 2000ms
148         // this parameter is currently not exposed in config, it may later be set by using the keyring
149         secureRouting.latencyToleranceMs = 2000;
150
151         return securityInitialized;
152     }
153
154     /***
155      * converts hex string (32 characters) to byte[16]
156      *
157      * @param hexstring 32 characters hex
158      * @return key in byte array format
159      */
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)");
163         }
164
165         byte[] parsed = new byte[16];
166         try {
167             for (byte i = 0; i < 16; i++) {
168                 parsed[i] = (byte) Integer.parseInt(hexstring.substring(2 * i, 2 * i + 2), 16);
169             }
170         } catch (NumberFormatException e) {
171             throw new KnxSecureException("backbone key configured, cannot parse hex string, illegal character", e);
172         }
173         return parsed;
174     }
175
176     @Override
177     public void handleCommand(ChannelUID channelUID, Command command) {
178         // Nothing to do here
179     }
180
181     public ScheduledExecutorService getScheduler() {
182         return knxScheduler;
183     }
184
185     public ScheduledExecutorService getBackgroundScheduler() {
186         return backgroundScheduler;
187     }
188
189     @Override
190     public void updateStatus(ThingStatus status) {
191         super.updateStatus(status);
192     }
193
194     @Override
195     public void updateStatus(ThingStatus status, ThingStatusDetail statusDetail, @Nullable String description) {
196         super.updateStatus(status, statusDetail, description);
197     }
198 }