Skip to content

Commit 10b3f0a

Browse files
[knx] Improve handling of unknown encrypted frames (openhab#17721)
* Show encrypted frames without a matching key using console command "openhab:knx list-unknown-ga"; sort output numerically by GA. * Add trace logging to show decryption error due to missing key. Previously, those frames were silently dropped unless logging for Calimero was explicitly enabled as well. Signed-off-by: Holger Friedrich <[email protected]>
1 parent 9a02c7f commit 10b3f0a

File tree

3 files changed

+46
-8
lines changed

3 files changed

+46
-8
lines changed

bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/client/AbstractKNXClient.java

+43-6
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,16 @@
3939
import org.slf4j.LoggerFactory;
4040

4141
import tuwien.auto.calimero.CloseEvent;
42+
import tuwien.auto.calimero.DataUnitBuilder;
4243
import tuwien.auto.calimero.DetachEvent;
4344
import tuwien.auto.calimero.FrameEvent;
4445
import tuwien.auto.calimero.GroupAddress;
4546
import tuwien.auto.calimero.IndividualAddress;
47+
import tuwien.auto.calimero.KNXAddress;
4648
import tuwien.auto.calimero.KNXException;
4749
import tuwien.auto.calimero.KNXIllegalArgumentException;
50+
import tuwien.auto.calimero.cemi.CEMILData;
51+
import tuwien.auto.calimero.cemi.CemiTData;
4852
import tuwien.auto.calimero.datapoint.CommandDP;
4953
import tuwien.auto.calimero.datapoint.Datapoint;
5054
import tuwien.auto.calimero.device.ProcessCommunicationResponder;
@@ -60,6 +64,7 @@
6064
import tuwien.auto.calimero.process.ProcessEvent;
6165
import tuwien.auto.calimero.process.ProcessListener;
6266
import tuwien.auto.calimero.secure.KnxSecureException;
67+
import tuwien.auto.calimero.secure.SecureApplicationLayer;
6368
import tuwien.auto.calimero.secure.Security;
6469

6570
/**
@@ -348,12 +353,13 @@ private void processEvent(String task, ProcessEvent event, ListenerNotification
348353
if (!isHandled) {
349354
logger.trace("Address '{}' is not configured in openHAB", destination);
350355
final String type = switch (event.getServiceCode()) {
351-
case 0x80 -> " GROUP_WRITE(";
352-
case 0x40 -> " GROUP_RESPONSE(";
353-
case 0x00 -> " GROUP_READ(";
354-
default -> " ?(";
356+
case 0x80 -> "GROUP_WRITE";
357+
case 0x40 -> "GROUP_RESPONSE";
358+
case 0x00 -> "GROUP_READ";
359+
default -> "?";
355360
};
356-
final String key = destination.toString() + type + event.getASDU().length + ")";
361+
final String key = String.format("%2d/%1d/%3d %s(%02d)", destination.getMainGroup(),
362+
destination.getMiddleGroup(), destination.getSubGroup8(), type, event.getASDU().length);
357363
commandExtensionData.unknownGA().compute(key, (k, v) -> v == null ? 1 : v + 1);
358364
}
359365
}
@@ -429,7 +435,38 @@ public void linkClosed(@Nullable CloseEvent closeEvent) {
429435

430436
@Override
431437
public void indication(@Nullable FrameEvent e) {
432-
// no-op
438+
// NetworkLinkListener indication. This implementation is triggered whenever a frame is received.
439+
// It is not necessary for OH, as we process incoming group writes via different triggers.
440+
// However, this indication also covers encrypted data secure frames, which would typically
441+
// be dropped silently by the Calimero library (a log message is only visible when log level for Calimero
442+
// is set manually).
443+
444+
// Implementation searches for incoming data secure frames which cannot be decoded due to missing key
445+
if (e != null) {
446+
final var cemi = e.getFrame();
447+
if (!(cemi instanceof CemiTData)) {
448+
final CEMILData f = (CEMILData) cemi;
449+
final int ctrl = f.getPayload()[0] & 0xfc;
450+
if (ctrl == 0) {
451+
final KNXAddress dst = f.getDestination();
452+
if (dst instanceof GroupAddress ga) {
453+
if (dst.getRawAddress() != 0) {
454+
final byte[] payload = f.getPayload();
455+
final int service = DataUnitBuilder.getAPDUService(payload);
456+
if (service == SecureApplicationLayer.SecureService) {
457+
if (!openhabSecurity.groupKeys().containsKey(dst)) {
458+
logger.trace("Address '{}' cannot be decrypted, group key missing", dst);
459+
final String key = String.format(
460+
"%2d/%1d/%3d secure: missing group key, cannot decrypt", ga.getMainGroup(),
461+
ga.getMiddleGroup(), ga.getSubGroup8());
462+
commandExtensionData.unknownGA().compute(key, (k, v) -> v == null ? 1 : v + 1);
463+
}
464+
}
465+
}
466+
}
467+
}
468+
}
469+
}
433470
}
434471

435472
@Override

bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/console/KNXCommandExtension.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ public void execute(String[] args, Console console) {
5656
console.println("KNX bridge \"" + bridgeHandler.getThing().getLabel()
5757
+ "\": group address, type, number of bytes, and number of occurrence since last reload of binding:");
5858
for (Entry<String, Long> entry : bridgeHandler.getCommandExtensionData().unknownGA().entrySet()) {
59-
console.println(entry.getKey() + " " + entry.getValue());
59+
console.println(entry.getKey() + " " + entry.getValue());
6060
}
6161
}
6262
return;

bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/handler/KNXBridgeBaseThingHandler.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import java.util.NoSuchElementException;
2222
import java.util.Optional;
2323
import java.util.Set;
24+
import java.util.SortedMap;
2425
import java.util.TreeMap;
2526
import java.util.concurrent.Executors;
2627
import java.util.concurrent.ScheduledExecutorService;
@@ -85,7 +86,7 @@ public SecureRoutingConfig() {
8586
* Helper class to carry information which can be used by the
8687
* command line extension (openHAB console).
8788
*/
88-
public record CommandExtensionData(Map<String, Long> unknownGA) {
89+
public record CommandExtensionData(SortedMap<String, Long> unknownGA) {
8990
}
9091

9192
private final ScheduledExecutorService knxScheduler = ThreadPoolManager.getScheduledPool("knx");

0 commit comments

Comments
 (0)