]> git.basschouten.com Git - openhab-addons.git/commitdiff
[snmp] SNMP v3 fixes and improvements (#16803)
authorJ-N-K <github@klug.nrw>
Wed, 5 Jun 2024 21:30:33 +0000 (23:30 +0200)
committerGitHub <noreply@github.com>
Wed, 5 Jun 2024 21:30:33 +0000 (23:30 +0200)
* [snmp] SNMP v3 fixes and improvements

(cherry picked from commit a32af97d785f301ad2253801353af53a9941d6f9)

Signed-off-by: Jan N. Klug <github@klug.nrw>
bundles/org.openhab.binding.snmp/README.md
bundles/org.openhab.binding.snmp/pom.xml
bundles/org.openhab.binding.snmp/src/main/java/org/openhab/binding/snmp/internal/SnmpService.java
bundles/org.openhab.binding.snmp/src/main/java/org/openhab/binding/snmp/internal/SnmpServiceImpl.java
bundles/org.openhab.binding.snmp/src/main/java/org/openhab/binding/snmp/internal/SnmpTargetHandler.java
bundles/org.openhab.binding.snmp/src/main/java/org/openhab/binding/snmp/internal/config/SnmpTargetConfiguration.java
bundles/org.openhab.binding.snmp/src/main/java/org/openhab/binding/snmp/internal/types/SnmpPrivProtocol.java
bundles/org.openhab.binding.snmp/src/main/resources/OH-INF/i18n/snmp.properties
bundles/org.openhab.binding.snmp/src/main/resources/OH-INF/thing/thing-types.xml
bundles/org.openhab.binding.snmp/src/test/java/org/openhab/binding/snmp/internal/AbstractSnmpTargetHandlerTest.java

index e2cd61acb4a699d73912c3ad57a786e6879c0e6b..725d27f945909881d1a8739b25e222e6fa0f121d 100644 (file)
@@ -77,13 +77,8 @@ The default is `v1`.
 
 ### `target3`
 
-The `target3` thing has additional mandatory parameters: `engineId` and `user`.
-
-The `engineId` must be given in hexadecimal notation (case-insensitive) without separators (e.g. `80000009035c710dbcd9e6`).
-The allowed length is 11 to 32 bytes (22 to 64 hex characters).
-If you encounter problems, please check if your agent prefixes the set engine id (e.g. Mikrotik uses `80003a8c04` and appends the set value to that).
-
-The `user` parameter is named "securityName" or "userName" in most agents.
+The `target3` thing has an additional mandatory parameter: `user`.
+This value of this parameter is named "securityName" or "userName" in most agents.
 
 Optional configuration parameters are: `securityModel`, `authProtocol`, `authPassphrase`, `privProtocol` and `privPassphrase`.
 
@@ -99,7 +94,7 @@ If authentication encryption is required, at least `authPassphrase` needs to be
 Other possible values for `authProtocol` are `SHA`, `HMAC128SHA224`, `HMAC192SHA256`, `HMAC256SHA384` and `HMAC384SHA512`.
 
 If encryption of transmitted data (privacy encryption) is required, at least `privPassphrase` needs to be set, while `privProtocol` defaults to `DES`.
-Other possible values for `privProtocol` are `AES128`, `AES192` and `AES256`.
+Other possible values for `privProtocol` are `DES3`, `AES128`, `AES192` and `AES256`.
 
 ## Channels
 
index a53a670b5dbdae898c2f59871f35ff9a92aaa6ce..89182919dbed905436da86f8ca8538f6ba5c6bd7 100644 (file)
     <dependency>
       <groupId>org.snmp4j</groupId>
       <artifactId>snmp4j</artifactId>
-      <version>2.8.6</version>
+      <version>3.8.2</version>
+      <scope>compile</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.snmp4j</groupId>
+      <artifactId>snmp4j-unix-transport</artifactId>
+      <version>1.1.0</version>
       <scope>compile</scope>
     </dependency>
   </dependencies>
index 58b42791004c291fbb5efca88647193fc4361f59..80f51eacc700f3669c91f17192550ef8ff8c0287 100644 (file)
@@ -16,12 +16,13 @@ import java.io.IOException;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
-import org.openhab.binding.snmp.internal.types.SnmpAuthProtocol;
-import org.openhab.binding.snmp.internal.types.SnmpPrivProtocol;
 import org.snmp4j.CommandResponder;
 import org.snmp4j.PDU;
 import org.snmp4j.Target;
 import org.snmp4j.event.ResponseListener;
+import org.snmp4j.security.UsmUser;
+import org.snmp4j.smi.Address;
+import org.snmp4j.smi.OctetString;
 
 /**
  * The {@link SnmpService} is responsible for SNMP communication
@@ -33,12 +34,53 @@ import org.snmp4j.event.ResponseListener;
 @NonNullByDefault
 public interface SnmpService {
 
+    /**
+     * Add a listener for received PDUs to the service
+     *
+     * @param listener the listener
+     */
     void addCommandResponder(CommandResponder listener);
 
+    /**
+     * Remove a listener for received PDUs from the service
+     *
+     * @param listener the listener
+     */
     void removeCommandResponder(CommandResponder listener);
 
-    void send(PDU pdu, Target target, @Nullable Object userHandle, ResponseListener listener) throws IOException;
+    /**
+     * Send a PDU to the given target
+     *
+     * @param pdu the PDU
+     * @param target the target
+     * @param userHandle an optional user-handle to identify the request
+     * @param listener the listener for the response (always called, even in case of timeout)
+     * @throws IOException when an error occurs
+     */
+    void send(PDU pdu, Target<?> target, @Nullable Object userHandle, ResponseListener listener) throws IOException;
 
-    void addUser(String userName, SnmpAuthProtocol snmpAuthProtocol, @Nullable String authPassphrase,
-            SnmpPrivProtocol snmpPrivProtocol, @Nullable String privPassphrase, byte[] engineId);
+    /**
+     * Add a user to the service for a given engine id (v3 only)
+     *
+     * @param user the {@link UsmUser} that should be added
+     * @param engineId the engine id
+     */
+    void addUser(UsmUser user, OctetString engineId);
+
+    /**
+     * Remove a user from the service and clear the context engine id for this address (v3 only)
+     *
+     * @param address the remote address
+     * @param user the user
+     * @param engineId the engine id
+     */
+    void removeUser(Address address, UsmUser user, OctetString engineId);
+
+    /**
+     * Get the engine id of a remote system for a given address (v3 only)
+     *
+     * @param address the address of the remote system
+     * @return the engine id or {@code null} when engine id could not be determined
+     */
+    byte @Nullable [] getEngineId(Address address);
 }
index 86e223feb38f9f314e8692b7b2b130712b6d3b47..5182e7093afaf082e2649252522cb3aa2687baaa 100644 (file)
@@ -22,8 +22,6 @@ import java.util.Set;
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
 import org.openhab.binding.snmp.internal.config.SnmpServiceConfiguration;
-import org.openhab.binding.snmp.internal.types.SnmpAuthProtocol;
-import org.openhab.binding.snmp.internal.types.SnmpPrivProtocol;
 import org.openhab.core.config.core.Configuration;
 import org.osgi.service.component.annotations.Activate;
 import org.osgi.service.component.annotations.Component;
@@ -37,11 +35,22 @@ import org.snmp4j.Snmp;
 import org.snmp4j.Target;
 import org.snmp4j.event.ResponseListener;
 import org.snmp4j.mp.MPv3;
+import org.snmp4j.security.AuthHMAC128SHA224;
+import org.snmp4j.security.AuthHMAC192SHA256;
+import org.snmp4j.security.AuthHMAC256SHA384;
+import org.snmp4j.security.AuthHMAC384SHA512;
+import org.snmp4j.security.AuthMD5;
+import org.snmp4j.security.AuthSHA;
 import org.snmp4j.security.Priv3DES;
+import org.snmp4j.security.PrivAES128;
+import org.snmp4j.security.PrivAES192;
+import org.snmp4j.security.PrivAES256;
+import org.snmp4j.security.PrivDES;
 import org.snmp4j.security.SecurityModels;
 import org.snmp4j.security.SecurityProtocols;
 import org.snmp4j.security.USM;
 import org.snmp4j.security.UsmUser;
+import org.snmp4j.smi.Address;
 import org.snmp4j.smi.OctetString;
 import org.snmp4j.smi.UdpAddress;
 import org.snmp4j.transport.DefaultUdpTransportMapping;
@@ -58,7 +67,6 @@ import org.snmp4j.transport.DefaultUdpTransportMapping;
 public class SnmpServiceImpl implements SnmpService {
     private final Logger logger = LoggerFactory.getLogger(SnmpServiceImpl.class);
 
-    private @NonNullByDefault({}) SnmpServiceConfiguration config;
     private @Nullable Snmp snmp;
     private @Nullable DefaultUdpTransportMapping transport;
 
@@ -67,9 +75,7 @@ public class SnmpServiceImpl implements SnmpService {
 
     @Activate
     public SnmpServiceImpl(Map<String, Object> config) {
-        SecurityProtocols.getInstance().addDefaultProtocols();
-        SecurityProtocols.getInstance().addPrivacyProtocol(new Priv3DES());
-
+        addProtocols();
         OctetString localEngineId = new OctetString(MPv3.createLocalEngineID());
         USM usm = new USM(SecurityProtocols.getInstance(), localEngineId, 0);
         SecurityModels.getInstance().addSecurityModel(usm);
@@ -79,34 +85,33 @@ public class SnmpServiceImpl implements SnmpService {
 
     @Modified
     protected void modified(Map<String, Object> config) {
-        this.config = new Configuration(config).as(SnmpServiceConfiguration.class);
+        SnmpServiceConfiguration snmpCfg = new Configuration(config).as(SnmpServiceConfiguration.class);
         try {
             shutdownSnmp();
 
             final DefaultUdpTransportMapping transport;
 
-            if (this.config.port > 0) {
-                transport = new DefaultUdpTransportMapping(new UdpAddress(this.config.port), true);
+            if (snmpCfg.port > 0) {
+                transport = new DefaultUdpTransportMapping(new UdpAddress(snmpCfg.port), true);
             } else {
                 transport = new DefaultUdpTransportMapping();
             }
 
-            SecurityProtocols.getInstance().addDefaultProtocols();
-            SecurityProtocols.getInstance().addPrivacyProtocol(new Priv3DES());
+            addProtocols();
 
             final Snmp snmp = new Snmp(transport);
             listeners.forEach(snmp::addCommandResponder);
             snmp.listen();
 
             // re-add user entries
-            userEntries.forEach(u -> addUser(snmp, u));
+            userEntries.forEach(u -> snmp.getUSM().addUser(u.user, u.engineId));
 
             this.snmp = snmp;
             this.transport = transport;
 
             logger.debug("initialized SNMP at {}", transport.getAddress());
         } catch (IOException e) {
-            logger.warn("could not open SNMP instance on port {}: {}", this.config.port, e.getMessage());
+            logger.warn("could not open SNMP instance on port {}: {}", snmpCfg.port, e.getMessage());
         }
     }
 
@@ -120,6 +125,21 @@ public class SnmpServiceImpl implements SnmpService {
         }
     }
 
+    private void addProtocols() {
+        SecurityProtocols secProtocols = SecurityProtocols.getInstance();
+        secProtocols.addAuthenticationProtocol(new AuthMD5());
+        secProtocols.addAuthenticationProtocol(new AuthSHA());
+        secProtocols.addAuthenticationProtocol(new AuthHMAC128SHA224());
+        secProtocols.addAuthenticationProtocol(new AuthHMAC192SHA256());
+        secProtocols.addAuthenticationProtocol(new AuthHMAC256SHA384());
+        secProtocols.addAuthenticationProtocol(new AuthHMAC384SHA512());
+        secProtocols.addPrivacyProtocol(new PrivDES());
+        secProtocols.addPrivacyProtocol(new Priv3DES());
+        secProtocols.addPrivacyProtocol(new PrivAES128());
+        secProtocols.addPrivacyProtocol(new PrivAES192());
+        secProtocols.addPrivacyProtocol(new PrivAES256());
+    }
+
     private void shutdownSnmp() throws IOException {
         DefaultUdpTransportMapping transport = this.transport;
         if (transport != null) {
@@ -152,7 +172,7 @@ public class SnmpServiceImpl implements SnmpService {
     }
 
     @Override
-    public void send(PDU pdu, Target target, @Nullable Object userHandle, ResponseListener listener)
+    public void send(PDU pdu, Target<?> target, @Nullable Object userHandle, ResponseListener listener)
             throws IOException {
         Snmp snmp = this.snmp;
         if (snmp != null) {
@@ -164,35 +184,40 @@ public class SnmpServiceImpl implements SnmpService {
     }
 
     @Override
-    public void addUser(String userName, SnmpAuthProtocol snmpAuthProtocol, @Nullable String authPassphrase,
-            SnmpPrivProtocol snmpPrivProtocol, @Nullable String privPassphrase, byte[] engineId) {
-        UsmUser usmUser = new UsmUser(new OctetString(userName),
-                authPassphrase != null ? snmpAuthProtocol.getOid() : null,
-                authPassphrase != null ? new OctetString(authPassphrase) : null,
-                privPassphrase != null ? snmpPrivProtocol.getOid() : null,
-                privPassphrase != null ? new OctetString(privPassphrase) : null);
-        OctetString securityNameOctets = new OctetString(userName);
-
-        UserEntry userEntry = new UserEntry(securityNameOctets, new OctetString(engineId), usmUser);
+    public void addUser(UsmUser user, OctetString engineId) {
+        UserEntry userEntry = new UserEntry(user, engineId);
         userEntries.add(userEntry);
 
         Snmp snmp = this.snmp;
         if (snmp != null) {
-            addUser(snmp, userEntry);
+            snmp.getUSM().addUser(user, engineId);
         }
     }
 
-    private static void addUser(Snmp snmp, UserEntry userEntry) {
-        snmp.getUSM().addUser(userEntry.securityName, userEntry.engineId, userEntry.user);
+    @Override
+    public void removeUser(Address address, UsmUser user, OctetString engineId) {
+        Snmp snmp = this.snmp;
+        if (snmp != null) {
+            snmp.getUSM().removeAllUsers(user.getSecurityName(), engineId);
+            snmp.removeCachedContextEngineId(address);
+        }
+        userEntries.removeIf(e -> e.engineId.equals(engineId) && e.user.equals(user));
+    }
+
+    @Override
+    public byte @Nullable [] getEngineId(Address address) {
+        Snmp snmp = this.snmp;
+        if (snmp != null) {
+            return snmp.discoverAuthoritativeEngineID(address, 15000);
+        }
+        return null;
     }
 
     private static class UserEntry {
-        public OctetString securityName;
         public OctetString engineId;
         public UsmUser user;
 
-        public UserEntry(OctetString securityName, OctetString engineId, UsmUser user) {
-            this.securityName = securityName;
+        public UserEntry(UsmUser user, OctetString engineId) {
             this.engineId = engineId;
             this.user = user;
         }
index 5b3c8bd72c3d061f4988b7fde0978a0a04d1e887..7890e50112a7b2f1cb09b23ce87ef14d1ffdfd5c 100644 (file)
@@ -17,7 +17,6 @@ import static org.openhab.binding.snmp.internal.SnmpBindingConstants.*;
 import java.io.IOException;
 import java.net.InetAddress;
 import java.net.UnknownHostException;
-import java.util.Collections;
 import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.ScheduledFuture;
@@ -53,7 +52,6 @@ import org.openhab.core.types.RefreshType;
 import org.openhab.core.types.State;
 import org.openhab.core.types.UnDefType;
 import org.openhab.core.types.util.UnitUtils;
-import org.openhab.core.util.HexUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.snmp4j.AbstractTarget;
@@ -68,6 +66,8 @@ import org.snmp4j.UserTarget;
 import org.snmp4j.event.ResponseEvent;
 import org.snmp4j.event.ResponseListener;
 import org.snmp4j.mp.SnmpConstants;
+import org.snmp4j.security.UsmUser;
+import org.snmp4j.smi.Address;
 import org.snmp4j.smi.Counter64;
 import org.snmp4j.smi.Integer32;
 import org.snmp4j.smi.IpAddress;
@@ -87,8 +87,8 @@ import org.snmp4j.smi.VariableBinding;
  */
 @NonNullByDefault
 public class SnmpTargetHandler extends BaseThingHandler implements ResponseListener, CommandResponder {
-    private static final Pattern HEXSTRING_VALIDITY = Pattern.compile("([a-f0-9]{2}[ :-]?)+");
-    private static final Pattern HEXSTRING_EXTRACTOR = Pattern.compile("[^a-f0-9]");
+    private static final Pattern HEX_STRING_VALIDITY = Pattern.compile("([A-Fa-f0-9]{2}[ :-]?)+");
+    private static final Pattern HEX_STRING_EXTRACTOR = Pattern.compile("[^A-Fa-f0-9]");
 
     private final Logger logger = LoggerFactory.getLogger(SnmpTargetHandler.class);
 
@@ -97,13 +97,17 @@ public class SnmpTargetHandler extends BaseThingHandler implements ResponseListe
     private @Nullable ScheduledFuture<?> refresh;
     private int timeoutCounter = 0;
 
-    private @NonNullByDefault({}) AbstractTarget target;
+    private @NonNullByDefault({}) AbstractTarget<UdpAddress> target;
     private @NonNullByDefault({}) String targetAddressString;
 
     private @NonNullByDefault({}) Set<SnmpInternalChannelConfiguration> readChannelSet;
     private @NonNullByDefault({}) Set<SnmpInternalChannelConfiguration> writeChannelSet;
     private @NonNullByDefault({}) Set<SnmpInternalChannelConfiguration> trapChannelSet;
 
+    // SNMP v3
+    private @Nullable UsmUser usmUser;
+    private @Nullable OctetString engineId;
+
     public SnmpTargetHandler(Thing thing, SnmpService snmpService) {
         super(thing);
         this.snmpService = snmpService;
@@ -139,7 +143,7 @@ public class SnmpTargetHandler extends BaseThingHandler implements ResponseListe
                     }
                 } else {
                     Command rawValue = command;
-                    if (command instanceof QuantityType quantityCommand) {
+                    if (command instanceof QuantityType<?> quantityCommand) {
                         Unit<?> channelUnit = channel.unit;
                         if (channelUnit == null) {
                             rawValue = new DecimalType(quantityCommand.toBigDecimal());
@@ -180,7 +184,7 @@ public class SnmpTargetHandler extends BaseThingHandler implements ResponseListe
         try {
             if (config.protocol.toInteger() == SnmpConstants.version1
                     || config.protocol.toInteger() == SnmpConstants.version2c) {
-                CommunityTarget target = new CommunityTarget();
+                CommunityTarget<UdpAddress> target = new CommunityTarget<>();
                 target.setCommunity(new OctetString(config.community));
                 this.target = target;
             } else if (config.protocol.toInteger() == SnmpConstants.version3) {
@@ -189,31 +193,31 @@ public class SnmpTargetHandler extends BaseThingHandler implements ResponseListe
                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "user not set");
                     return;
                 }
-                String engineIdHexString = config.engineId;
-                if (engineIdHexString == null) {
-                    updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "engineId not set");
-                    return;
-                }
                 String authPassphrase = config.authPassphrase;
-                if ((config.securityModel == SnmpSecurityModel.AUTH_PRIV
-                        || config.securityModel == SnmpSecurityModel.AUTH_NO_PRIV)
-                        && (authPassphrase == null || authPassphrase.isEmpty())) {
+                if (config.securityModel != SnmpSecurityModel.NO_AUTH_NO_PRIV
+                        && (authPassphrase == null || authPassphrase.isBlank())) {
                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
                             "Authentication passphrase not configured");
                     return;
                 }
                 String privPassphrase = config.privPassphrase;
                 if (config.securityModel == SnmpSecurityModel.AUTH_PRIV
-                        && (privPassphrase == null || privPassphrase.isEmpty())) {
+                        && (privPassphrase == null || privPassphrase.isBlank())) {
                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
                             "Privacy passphrase not configured");
                     return;
                 }
-                byte[] engineId = HexUtils.hexToBytes(engineIdHexString);
-                snmpService.addUser(userName, config.authProtocol, authPassphrase, config.privProtocol, privPassphrase,
-                        engineId);
-                UserTarget target = new UserTarget();
-                target.setAuthoritativeEngineID(engineId);
+                usmUser = new UsmUser(new OctetString(userName),
+                        config.securityModel != SnmpSecurityModel.NO_AUTH_NO_PRIV ? config.authProtocol.getOid() : null,
+                        config.securityModel != SnmpSecurityModel.NO_AUTH_NO_PRIV && authPassphrase != null
+                                ? new OctetString(authPassphrase)
+                                : null,
+                        config.securityModel == SnmpSecurityModel.AUTH_PRIV ? config.privProtocol.getOid() : null,
+                        config.securityModel == SnmpSecurityModel.AUTH_PRIV && privPassphrase != null
+                                ? new OctetString(privPassphrase)
+                                : null);
+
+                UserTarget<UdpAddress> target = new UserTarget<>();
                 target.setSecurityName(new OctetString(config.user));
                 target.setSecurityLevel(config.securityModel.getSecurityLevel());
                 this.target = target;
@@ -248,6 +252,16 @@ public class SnmpTargetHandler extends BaseThingHandler implements ResponseListe
             r.cancel(true);
         }
         snmpService.removeCommandResponder(this);
+
+        UsmUser user = usmUser;
+        OctetString engineId = this.engineId;
+        Address address = target.getAddress();
+        if (user != null && engineId != null && address != null) {
+            snmpService.removeUser(address, user, engineId);
+        }
+
+        usmUser = null;
+        this.engineId = null;
     }
 
     @Override
@@ -260,7 +274,7 @@ public class SnmpTargetHandler extends BaseThingHandler implements ResponseListe
             // Always cancel async request when response has been received
             // otherwise a memory leak is created! Not canceling a request
             // immediately can be useful when sending a request to a broadcast
-            // address (Comment is taken from the snmp4j API doc).
+            // address (Comment is taken from the SNMP4J API doc).
             ((Snmp) event.getSource()).cancel(event.getRequest(), this);
         }
 
@@ -350,7 +364,7 @@ public class SnmpTargetHandler extends BaseThingHandler implements ResponseListe
             if (configUnit != null) {
                 unit = UnitUtils.parseUnit(configUnit);
                 if (unit == null) {
-                    logger.warn("Failed to parse unit from '{}'for channel '{}'", unit, channel.getUID());
+                    logger.warn("Failed to parse unit from '{}'for channel '{}'", configUnit, channel.getUID());
                 }
             }
         } else if (CHANNEL_TYPE_UID_STRING.equals(channel.getChannelTypeUID())) {
@@ -394,8 +408,9 @@ public class SnmpTargetHandler extends BaseThingHandler implements ResponseListe
     }
 
     private void generateChannelConfigs() {
-        Set<SnmpInternalChannelConfiguration> channelConfigs = Collections.unmodifiableSet(thing.getChannels().stream()
-                .map(this::getChannelConfigFromChannel).filter(Objects::nonNull).collect(Collectors.toSet()));
+        Set<SnmpInternalChannelConfiguration> channelConfigs = thing.getChannels().stream()
+                .map(this::getChannelConfigFromChannel).filter(Objects::nonNull).map(Objects::requireNonNull)
+                .collect(Collectors.toUnmodifiableSet());
         this.readChannelSet = channelConfigs.stream()
                 .filter(c -> c.mode == SnmpChannelMode.READ || c.mode == SnmpChannelMode.READ_WRITE)
                 .collect(Collectors.toSet());
@@ -520,9 +535,9 @@ public class SnmpTargetHandler extends BaseThingHandler implements ResponseListe
             case HEXSTRING -> {
                 if (command instanceof StringType stringCommand) {
                     String commandString = stringCommand.toString().toLowerCase();
-                    Matcher commandMatcher = HEXSTRING_VALIDITY.matcher(commandString);
+                    Matcher commandMatcher = HEX_STRING_VALIDITY.matcher(commandString);
                     if (commandMatcher.matches()) {
-                        commandString = HEXSTRING_EXTRACTOR.matcher(commandString).replaceAll("");
+                        commandString = HEX_STRING_EXTRACTOR.matcher(commandString).replaceAll("");
                         return OctetString.fromHexStringPairs(commandString);
                     }
                 }
@@ -541,7 +556,25 @@ public class SnmpTargetHandler extends BaseThingHandler implements ResponseListe
     private boolean renewTargetAddress() {
         try {
             target.setAddress(new UdpAddress(InetAddress.getByName(config.hostname), config.port));
-            targetAddressString = ((UdpAddress) target.getAddress()).getInetAddress().getHostAddress();
+            targetAddressString = target.getAddress().getInetAddress().getHostAddress();
+            logger.trace("Determined {} as address for {} (thing {})", target.getAddress(), config.hostname,
+                    this.thing.getUID());
+            UsmUser user = usmUser;
+            if (user != null && target instanceof UserTarget<UdpAddress> userTarget) {
+                byte[] engineIdBytes = snmpService.getEngineId(target.getAddress());
+                if (engineIdBytes != null) {
+                    OctetString engineIdOctets = new OctetString(engineIdBytes);
+                    this.engineId = engineIdOctets;
+                    logger.trace("Determined {} as engineId for {}", engineIdOctets, target.getAddress());
+                    snmpService.addUser(user, engineIdOctets);
+                    userTarget.setAuthoritativeEngineID(engineIdBytes);
+                } else {
+                    target.setAddress(null);
+                    updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
+                            "Cannot determine engineId");
+                    return false;
+                }
+            }
             return true;
         } catch (UnknownHostException e) {
             target.setAddress(null);
index 218a48e6e8cc50a37959c5ae2e4c4d4eca574a9a..8f3a0068ac042c68718518cca14f4bc54dda547e 100644 (file)
@@ -41,7 +41,6 @@ public class SnmpTargetConfiguration {
     // v3 only
     public SnmpSecurityModel securityModel = SnmpSecurityModel.NO_AUTH_NO_PRIV;
     public @Nullable String user;
-    public @Nullable String engineId;
     public SnmpAuthProtocol authProtocol = SnmpAuthProtocol.MD5;
     public @Nullable String authPassphrase;
     public SnmpPrivProtocol privProtocol = SnmpPrivProtocol.DES;
index 91148048818b0718587b0e438a478e810141792b..9efee83a8de8f3f06c7bd4a6cc317d0ed4e960fa 100644 (file)
@@ -13,6 +13,7 @@
 package org.openhab.binding.snmp.internal.types;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.snmp4j.security.Priv3DES;
 import org.snmp4j.security.PrivAES128;
 import org.snmp4j.security.PrivAES192;
 import org.snmp4j.security.PrivAES256;
@@ -29,7 +30,8 @@ public enum SnmpPrivProtocol {
     AES128(PrivAES128.ID),
     AES192(PrivAES192.ID),
     AES256(PrivAES256.ID),
-    DES(PrivDES.ID);
+    DES(PrivDES.ID),
+    DES3(Priv3DES.ID);
 
     private final OID oid;
 
index ee531d1c7690de94b75d9e277dcddb5e0afee47f..322261c92e565f9711781cded01e3d5248a5dbc2 100644 (file)
@@ -47,6 +47,7 @@ thing-type.config.snmp.target3.privProtocol.option.AES128 = AES128
 thing-type.config.snmp.target3.privProtocol.option.AES192 = AES192
 thing-type.config.snmp.target3.privProtocol.option.AES256 = AES256
 thing-type.config.snmp.target3.privProtocol.option.DES = DES
+thing-type.config.snmp.target3.privProtocol.option.DES3 = 3DES
 thing-type.config.snmp.target3.refresh.label = Refresh Time
 thing-type.config.snmp.target3.refresh.description = Refresh time in s (default 60s)
 thing-type.config.snmp.target3.retries.label = Retries
@@ -85,8 +86,8 @@ channel-type.config.snmp.number.mode.option.READ_WRITE = Read/Write
 channel-type.config.snmp.number.mode.option.TRAP = Trap
 channel-type.config.snmp.number.oid.label = OID
 channel-type.config.snmp.number.oid.description = OID in dotted format (eg. .1.3.6.1.4.1.6574.3.1.1.3.0)
-channel-type.config.snmp.number.unit.label = Unit Of Measurement
-channel-type.config.snmp.number.unit.description = Unit of measurement (optional). The unit is used for representing the value in the GUI as well as for converting incoming values (like from '°F' to '°C'). Examples: "°C", "°F"
+channel-type.config.snmp.number.unit.label = Unit
+channel-type.config.snmp.number.unit.description = The unit of this value.
 channel-type.config.snmp.string.datatype.label = Datatype
 channel-type.config.snmp.string.datatype.description = Content data type
 channel-type.config.snmp.string.datatype.option.STRING = String
index 782d70d6fe0659600cde478b0ffcd80d818e7e9b..82ffc2d8985060fd17e3712e4ad3c89da6825d87 100644 (file)
                                <description>Hostname or IP address of target host</description>
                                <context>network-address</context>
                        </parameter>
-                       <parameter name="engineId" type="text" required="true">
-                               <label>Engine ID</label>
-                               <description>The authorization engine ID of this target in hexadecimal notation (22-64 characters)</description>
-                       </parameter>
                        <parameter name="user" type="text" required="true">
                                <label>Username</label>
                        </parameter>
                                        <option value="AES192">AES192</option>
                                        <option value="AES256">AES256</option>
                                        <option value="DES">DES</option>
+                                       <option value="DES3">3DES</option>
                                </options>
                                <limitToOptions>true</limitToOptions>
                                <default>DES</default>
index 2ad98869c541ac6183e9d6a064ec900f9703b26c..570f3faba575f4f4b872cf0660368feaac8730b7 100644 (file)
@@ -21,7 +21,6 @@ import java.io.IOException;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Vector;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
@@ -156,9 +155,8 @@ public abstract class AbstractSnmpTargetHandlerTest extends JavaTest {
         if (refresh) {
             ArgumentCaptor<PDU> pduCaptor = ArgumentCaptor.forClass(PDU.class);
             verify(snmpService, timeout(500).atLeast(1)).send(pduCaptor.capture(), any(), eq(null), eq(thingHandler));
-            Vector<? extends VariableBinding> variables = pduCaptor.getValue().getVariableBindings();
-            assertTrue(variables.stream().filter(v -> v.getOid().toDottedString().equals(TEST_OID)).findFirst()
-                    .isPresent());
+            List<? extends VariableBinding> variables = pduCaptor.getValue().getVariableBindings();
+            assertTrue(variables.stream().anyMatch(v -> v.getOid().toDottedString().equals(TEST_OID)));
         } else {
             verify(snmpService, never()).send(any(), any(), eq(null), eq(thingHandler));
         }