diff --git a/deps/src/main/java/com/microsoft/azure/sdk/iot/deps/transport/amqp/AmqpMessage.java b/deps/src/main/java/com/microsoft/azure/sdk/iot/deps/transport/amqp/AmqpMessage.java index 534fdb28f1..27ffe8f4ff 100644 --- a/deps/src/main/java/com/microsoft/azure/sdk/iot/deps/transport/amqp/AmqpMessage.java +++ b/deps/src/main/java/com/microsoft/azure/sdk/iot/deps/transport/amqp/AmqpMessage.java @@ -72,6 +72,23 @@ public void setApplicationProperty(Map userProperties) this.messageImpl.setApplicationProperties(applicationProperties); } + /** + * Set the application property for the message + * @return Map of properties + */ + public Map getApplicationProperty() + { + ApplicationProperties appProperty = this.messageImpl.getApplicationProperties(); + if (appProperty == null) + { + return null; + } + else + { + return appProperty.getValue(); + } + } + /** * Sets the data value * @param data the {@code byte[]} to be decoded diff --git a/deps/src/main/java/com/microsoft/azure/sdk/iot/deps/transport/http/HttpResponse.java b/deps/src/main/java/com/microsoft/azure/sdk/iot/deps/transport/http/HttpResponse.java index 14bebbaf5d..46818f905f 100644 --- a/deps/src/main/java/com/microsoft/azure/sdk/iot/deps/transport/http/HttpResponse.java +++ b/deps/src/main/java/com/microsoft/azure/sdk/iot/deps/transport/http/HttpResponse.java @@ -103,6 +103,12 @@ public String getHeaderField(String field) return values; } + public boolean isFieldAvailable(String field) + { + String canonicalizedField = canonicalizeFieldName(field); + return this.headerFields.containsKey(canonicalizedField); + } + /** * Getter for the header fields. * diff --git a/iot-e2e-tests/common/src/main/java/com/microsoft/azure/sdk/iot/common/helpers/X509Cert.java b/iot-e2e-tests/common/src/main/java/com/microsoft/azure/sdk/iot/common/helpers/X509Cert.java deleted file mode 100644 index 311da46c1d..0000000000 --- a/iot-e2e-tests/common/src/main/java/com/microsoft/azure/sdk/iot/common/helpers/X509Cert.java +++ /dev/null @@ -1,216 +0,0 @@ -/* - * - * Copyright (c) Microsoft. All rights reserved. - * Licensed under the MIT license. See LICENSE file in the project root for full license information. - * - */ - -package com.microsoft.azure.sdk.iot.common.helpers; - -import com.microsoft.azure.sdk.iot.deps.util.Base64; -import sun.security.tools.keytool.CertAndKeyGen; -import sun.security.x509.*; - -import javax.xml.bind.DatatypeConverter; -import java.io.IOException; -import java.security.*; -import java.security.cert.CertificateEncodingException; -import java.security.cert.CertificateException; -import java.security.cert.X509Certificate; -import java.util.ArrayList; -import java.util.Collection; - -// Generates RSA Certs and private Keys -public class X509Cert -{ - private static final String BEGIN_KEY = "-----BEGIN PRIVATE KEY-----\n"; - private static final String END_KEY = "\n-----END PRIVATE KEY-----\n"; - private static final String BEGIN_CERT = "-----BEGIN CERTIFICATE-----\n"; - private static final String END_CERT = "\n-----END CERTIFICATE-----\n"; - private static final String DN_ROOT = "CN=%s, L=Redmond, C=US"; - private static final String DN_INTERMEDIATE = "CN=intermediate_%s, L=Redmond, C=US"; - private static final String DN_LEAF = "CN=%s, L=Redmond, C=US"; - private static final long ONE_DAY = 1*24*60*60; - - private class CertKeyPair - { - PrivateKey key; - X509Certificate certificate; - - CertKeyPair(PrivateKey key, X509Certificate certificate) - { - this.key = key; - this.certificate = certificate; - } - } - private CertKeyPair root; - private ArrayList intermediates; - private CertKeyPair leaf; - private Collection intermediatesPem; - private boolean useDice; - - - public X509Cert(int intermediatesCount, boolean useDice, String cNLeaf, String cNRoot) throws NoSuchAlgorithmException - { - intermediatesPem = new ArrayList<>(intermediatesCount); - intermediates = new ArrayList<>(intermediatesCount); - this.useDice = useDice; - - if (!useDice) - { - try - { - if (cNRoot == null) - { - cNRoot = "root"; - } - this.root = createCertAndKey(String.format(DN_ROOT, cNRoot), ONE_DAY); - this.root.certificate = createSignedCertificate(this.root.certificate, this.root.certificate, this.root.key, false); - for (int i = 0; i < intermediatesCount; i++) - { - this.intermediates.add(createCertAndKey(String.format(DN_INTERMEDIATE, i), ONE_DAY)); - if (i == 0) - { - this.intermediates.get(i).certificate = createSignedCertificate(this.intermediates.get(i).certificate, this.root.certificate, this.root.key, false); - } - else - { - this.intermediates.get(i).certificate = createSignedCertificate(this.intermediates.get(i).certificate, - this.intermediates.get(i - 1).certificate, - this.intermediates.get(i - 1).key, false); - } - } - if (cNLeaf == null) - { - cNLeaf = "leaf"; - } - - this.leaf = createCertAndKey(String.format(DN_LEAF, cNLeaf), ONE_DAY); - if (intermediatesCount > 0) - { - this.leaf.certificate = createSignedCertificate(this.leaf.certificate, this.intermediates.get(intermediatesCount - 1).certificate, - this.intermediates.get(intermediatesCount - 1).key, true); - } - else - { - this.leaf.certificate = createSignedCertificate(this.leaf.certificate, this.root.certificate, - this.root.key, true); - } - } - catch (Exception e) - { - e.printStackTrace(); - } - } - else - { - // use DICE to generate certs when it supports - throw new UnsupportedOperationException("Dice client is not yet supported"); - } - } - - /** - * - * @param DN eg "CN=Test, L=Redmond, C=GB" - * @param validity 24 * 60 * 60 is 1 Day - * @return A private key and X509 certificate - * @throws NoSuchAlgorithmException - * @throws NoSuchProviderException - * @throws InvalidKeyException - * @throws IOException - * @throws CertificateException - * @throws SignatureException - */ - private CertKeyPair createCertAndKey(String DN, long validity) throws - NoSuchAlgorithmException, - NoSuchProviderException, - InvalidKeyException, IOException, - CertificateException, - SignatureException - { - //Generate ROOT certificate - CertAndKeyGen keyGen = new CertAndKeyGen("RSA", "SHA1WithRSA", null); - keyGen.generate(1024); - - PrivateKey key = keyGen.getPrivateKey(); - - X509Certificate x509Certificate = keyGen.getSelfCertificate(new X500Name(DN), validity); - return new CertKeyPair(key, x509Certificate); - } - - private static X509Certificate createSignedCertificate(X509Certificate certificate, X509Certificate issuerCertificate, - PrivateKey issuerPrivateKey, boolean isLeaf) - throws CertificateException, IOException, NoSuchProviderException, - NoSuchAlgorithmException, InvalidKeyException, SignatureException - { - - Principal issuer = issuerCertificate.getSubjectDN(); - String issuerSigAlg = issuerCertificate.getSigAlgName(); - - byte[] inCertBytes = certificate.getTBSCertificate(); - X509CertInfo info = new X509CertInfo(inCertBytes); - info.set(X509CertInfo.ISSUER, issuer); - - if (!isLeaf) - { - CertificateExtensions exts = new CertificateExtensions(); - BasicConstraintsExtension bce = new BasicConstraintsExtension(true, -1); - exts.set(BasicConstraintsExtension.NAME, new BasicConstraintsExtension(false, bce.getExtensionValue())); - info.set(X509CertInfo.EXTENSIONS, exts); - } - - X509CertImpl outCert = new X509CertImpl(info); - outCert.sign(issuerPrivateKey, issuerSigAlg); - - return outCert; - } - - public String getPrivateKeyLeafPem() - { - StringBuilder pem = new StringBuilder(); - pem.append(BEGIN_KEY); - pem.append(new String (Base64.encodeBase64Local(this.leaf.key.getEncoded()))); - pem.append(END_KEY); - return pem.toString(); - } - - public String getPublicCertLeafPem() throws IOException, GeneralSecurityException - { - StringBuilder pem = new StringBuilder(); - pem.append(BEGIN_CERT); - pem.append(new String (Base64.encodeBase64Local(this.leaf.certificate.getEncoded()))); - pem.append(END_CERT); - return pem.toString(); - } - - public String getPublicCertRootPem() throws IOException, GeneralSecurityException - { - StringBuilder pem = new StringBuilder(); - pem.append(BEGIN_CERT); - pem.append(new String (Base64.encodeBase64Local(this.root.certificate.getEncoded()))); - pem.append(END_CERT); - return pem.toString(); - } - - public Collection getIntermediatesPem() throws CertificateEncodingException - { - for (CertKeyPair c : this.intermediates) - { - StringBuilder pem = new StringBuilder(); - pem.append(BEGIN_CERT); - pem.append(new String(Base64.encodeBase64Local(c.certificate.getEncoded()))); - pem.append(END_CERT); - intermediatesPem.add(pem.toString()); - } - return intermediatesPem; - } - - public String getThumbPrintLeaf() throws NoSuchAlgorithmException, CertificateEncodingException - { - MessageDigest md = MessageDigest.getInstance("SHA-1"); - byte[] der = this.leaf.certificate.getEncoded(); - md.update(der); - byte[] digest = md.digest(); - return DatatypeConverter.printHexBinary(digest); - } -} diff --git a/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/contract/ProvisioningDeviceClientContract.java b/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/contract/ProvisioningDeviceClientContract.java index 0f5f08c44f..b14f3236f1 100644 --- a/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/contract/ProvisioningDeviceClientContract.java +++ b/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/contract/ProvisioningDeviceClientContract.java @@ -17,6 +17,24 @@ public abstract class ProvisioningDeviceClientContract { + private int retryValue = DEFAULT_RETRY_AFTER_VALUE; + protected static final String RETRY_AFTER = "retry-after"; + private static final Integer DEFAULT_RETRY_AFTER_VALUE = 2; + private static final Integer MAX_PROV_GET_THROTTLE_TIME = 5; + + protected void setRetrieveRetryAfterValue(String protocolRetryValue) + { + if (protocolRetryValue != null && !protocolRetryValue.isEmpty()) + { + retryValue = Integer.parseInt(protocolRetryValue); + // ensure the value is between the tolerances + if (retryValue < DEFAULT_RETRY_AFTER_VALUE || retryValue > MAX_PROV_GET_THROTTLE_TIME) + { + retryValue = DEFAULT_RETRY_AFTER_VALUE; + } + } + } + /** * Static method to create contracts with the service over the specified protocol * @param provisioningDeviceClientConfig Config used for provisioning @@ -58,4 +76,13 @@ public static ProvisioningDeviceClientContract createProvisioningContract(Provis public abstract void authenticateWithProvisioningService(RequestData requestData, ResponseCallback responseCallback, Object dpsAuthorizationCallbackContext) throws ProvisioningDeviceClientException; public abstract void getRegistrationStatus(RequestData requestData, ResponseCallback responseCallback, Object dpsAuthorizationCallbackContext) throws ProvisioningDeviceClientException; public abstract void close() throws ProvisioningDeviceConnectionException; + + /** + * Method to get the DPS retry after value + * @return integer value of the number of milliseconds to wait to call dps service + */ + public int getRetryValue() + { + return this.retryValue*1000; + } } diff --git a/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/contract/amqp/ContractAPIAmqp.java b/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/contract/amqp/ContractAPIAmqp.java index 454f97bb72..39d2ab41a2 100644 --- a/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/contract/amqp/ContractAPIAmqp.java +++ b/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/contract/amqp/ContractAPIAmqp.java @@ -18,6 +18,7 @@ import javax.net.ssl.SSLContext; import java.io.IOException; +import java.util.Map; public class ContractAPIAmqp extends ProvisioningDeviceClientContract { @@ -27,6 +28,18 @@ public class ContractAPIAmqp extends ProvisioningDeviceClientContract private String idScope; private SaslHandler amqpSaslHandler; + private void processRetryAfterValue(Map appProperties) + { + if (appProperties != null) + { + if (appProperties.containsKey(RETRY_AFTER)) + { + Object retryAfterValue = appProperties.get(RETRY_AFTER); + setRetrieveRetryAfterValue(retryAfterValue.toString()); + } + } + } + /** * This constructor creates an instance of DpsAPIAmqps class and initializes member variables * @param provisioningDeviceClientConfig Config used for provisioning Cannot be {@code null}. @@ -173,6 +186,8 @@ public synchronized void authenticateWithProvisioningService(RequestData request // SRS_ContractAPIAmqp_07_005: [This method shall send an AMQP message with the property of iotdps-register.] this.provisioningAmqpOperations.sendRegisterMessage(responseCallback, callbackContext, payload); + + processRetryAfterValue(this.provisioningAmqpOperations.getAmqpMessageProperties()); } /** diff --git a/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/contract/amqp/ProvisioningAmqpOperations.java b/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/contract/amqp/ProvisioningAmqpOperations.java index 6702c47c90..820a9435df 100644 --- a/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/contract/amqp/ProvisioningAmqpOperations.java +++ b/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/contract/amqp/ProvisioningAmqpOperations.java @@ -35,6 +35,7 @@ public class ProvisioningAmqpOperations extends AmqpDeviceOperations implements private final Queue receivedMessages = new LinkedBlockingQueue<>(); private final ObjectLock receiveLock = new ObjectLock(); + private Map messageAppProperties; private String idScope; private String hostName; @@ -94,6 +95,9 @@ private void retrieveAmqpMessage(ResponseCallback responseCallback, Object callb if (this.receivedMessages.size() > 0) { AmqpMessage message = this.receivedMessages.remove(); + + // Need to keep property around to get the retry-after value + this.messageAppProperties = message.getApplicationProperty(); byte[] msgData = message.getAmqpBody(); if (msgData != null) { @@ -286,6 +290,15 @@ public void sendRegisterMessage(ResponseCallback responseCallback, Object callba } } + /** + * Returns the message properties of the current message + * @return Map of application properties + */ + public Map getAmqpMessageProperties() + { + return this.messageAppProperties; + } + /** * connectionEstablished Unused */ diff --git a/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/contract/http/ContractAPIHttp.java b/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/contract/http/ContractAPIHttp.java index 5d639c5a35..0c6108091a 100644 --- a/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/contract/http/ContractAPIHttp.java +++ b/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/contract/http/ContractAPIHttp.java @@ -141,6 +141,15 @@ private HttpResponse sendRequest(HttpRequest request) throws ProvisioningDeviceH return response; } + private void processRetryAfterValue(HttpResponse httpResponse) + { + if (httpResponse.isFieldAvailable(RETRY_AFTER)) + { + // Set the retry after value + setRetrieveRetryAfterValue(httpResponse.getHeaderField(RETRY_AFTER)); + } + } + /** * Requests hub to provide a device key to begin authentication over HTTP (Only for TPM) * @param responseCallback A non {@code null} value for the callback @@ -280,6 +289,10 @@ public synchronized void authenticateWithProvisioningService(RequestData request //SRS_ContractAPIHttp_25_015: [This method shall send http request and verify the status by calling 'ProvisioningDeviceClientExceptionManager.verifyHttpResponse'.] //SRS_ContractAPIHttp_25_017: [If service return any other status other than <300 then this method shall throw ProvisioningDeviceHubException.] HttpResponse httpResponse = this.sendRequest(httpRequest); + + // Set the retry after value + processRetryAfterValue(httpResponse); + //SRS_ContractAPIHttp_25_016: [If service return a status as < 300 then this method shall trigger the callback to the user with the response message.] responseCallback.run(new ResponseData(httpResponse.getBody(), ContractState.DPS_REGISTRATION_RECEIVED, 0), dpsAuthorizationCallbackContext); } @@ -338,6 +351,10 @@ public synchronized void getRegistrationStatus(RequestData requestData, Response //SRS_ContractAPIHttp_25_022: [This method shall send http request and verify the status by calling 'ProvisioningDeviceClientExceptionManager.verifyHttpResponse'.] //SRS_ContractAPIHttp_25_024: [If service return any other status other than < 300 then this method shall throw ProvisioningDeviceHubException.] HttpResponse httpResponse = this.sendRequest(httpRequest); + + // Set the retry after value from the service + processRetryAfterValue(httpResponse); + //SRS_ContractAPIHttp_25_023: [If service return a status as < 300 then this method shall trigger the callback to the user with the response message.] responseCallback.run(new ResponseData(httpResponse.getBody(),ContractState.DPS_REGISTRATION_RECEIVED, 0), dpsAuthorizationCallbackContext); } diff --git a/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/contract/mqtt/ContractAPIMqtt.java b/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/contract/mqtt/ContractAPIMqtt.java index 5694862f37..ed007d55ad 100644 --- a/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/contract/mqtt/ContractAPIMqtt.java +++ b/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/contract/mqtt/ContractAPIMqtt.java @@ -101,6 +101,37 @@ private void executeProvisioningMessage(String topic, byte[] body, ResponseCallb } + private void processRetryAfterValue(String mqttTopic) + { + if (mqttTopic != null && !mqttTopic.isEmpty()) + { + // Split the string + for (String topicPart: mqttTopic.split("&")) + { + int retryPosition = topicPart.indexOf(RETRY_AFTER); + // if retry-after is in here we need parse out the value + if (retryPosition > -1) + { + String targetRetryAfter; + // Make sure there's no data after the retry after + int topicSeparator = topicPart.indexOf(";"); + if (topicSeparator > -1) + { + // substring the value adding 1 for the = and only go to the ; + targetRetryAfter = topicPart.substring(RETRY_AFTER.length()+1, topicSeparator); + } + else + { + // substring the value adding 1 for the = and only go to the ; + targetRetryAfter = topicPart.substring(RETRY_AFTER.length()+1); + } + setRetrieveRetryAfterValue(targetRetryAfter); + break; + } + } + } + } + /** * Indicates need to open MQTT connection * @param requestData Data used for the connection initialization @@ -314,6 +345,8 @@ public synchronized void requestNonceForTPM(RequestData requestData, ResponseCal @Override public void messageReceived(MqttMessage message) { + processRetryAfterValue(message.getTopic()); + // SRS_ProvisioningAmqpOperations_07_013: [This method shall add the message to a message queue.] this.receivedMessages.add(message); synchronized (this.receiveLock) diff --git a/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/task/ProvisioningTask.java b/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/task/ProvisioningTask.java index bdbfc8bffa..af216ebddd 100644 --- a/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/task/ProvisioningTask.java +++ b/provisioning/provisioning-device-client/src/main/java/com/microsoft/azure/sdk/iot/provisioning/device/internal/task/ProvisioningTask.java @@ -139,8 +139,7 @@ private RegistrationOperationStatusParser invokeRegister() throws InterruptedExc private RegistrationOperationStatusParser invokeStatus(String operationId) throws TimeoutException, InterruptedException, ExecutionException, ProvisioningDeviceClientException { - // To-Do : Add appropriate wait time retrieved from Service - Thread.sleep(DEFAULT_DELAY_BETWEEN_STATUS_CHECKS); + Thread.sleep(provisioningDeviceClientContract.getRetryValue()); StatusTask statusTask = new StatusTask(securityProvider, provisioningDeviceClientContract, operationId, this.authorization); FutureTask futureStatusTask = new FutureTask(statusTask); diff --git a/provisioning/provisioning-device-client/src/test/java/tests/unit/com/microsoft/azure/sdk/iot/provisioning/device/internal/contract/http/ContractAPIHttpTest.java b/provisioning/provisioning-device-client/src/test/java/tests/unit/com/microsoft/azure/sdk/iot/provisioning/device/internal/contract/http/ContractAPIHttpTest.java index 34dfcb9bd2..09785baab1 100644 --- a/provisioning/provisioning-device-client/src/test/java/tests/unit/com/microsoft/azure/sdk/iot/provisioning/device/internal/contract/http/ContractAPIHttpTest.java +++ b/provisioning/provisioning-device-client/src/test/java/tests/unit/com/microsoft/azure/sdk/iot/provisioning/device/internal/contract/http/ContractAPIHttpTest.java @@ -774,6 +774,65 @@ public void authenticateWithDPSWithAuthSucceeds() throws IOException, Provisioni }; } + @Test + public void authenticateWithDPSWithAuthAndRetryAfterSucceeds() throws IOException, ProvisioningDeviceClientException + { + //arrange + final byte[] expectedPayload = "testByte".getBytes(); + ContractAPIHttp contractAPIHttp = createContractClass(); + prepareRequestExpectations(); + new NonStrictExpectations() + { + { + mockedRequestData.getRegistrationId(); + result = TEST_REGISTRATION_ID; + mockedRequestData.getEndorsementKey(); + result = TEST_EK; + mockedRequestData.getStorageRootKey(); + result = TEST_SRK; + mockedRequestData.getSslContext(); + result = mockedSslContext; + mockedRequestData.getSasToken(); + result = TEST_SAS_TOKEN; + mockedHttpRequest.send(); + result = mockedHttpResponse; + + mockedHttpResponse.isFieldAvailable("retry-after"); + result = true; + mockedHttpResponse.getHeaderField("retry-after"); + result = "3"; + + mockedHttpResponse.getStatus(); + result = 400; + new DeviceRegistrationParser(anyString, anyString, anyString, anyString); + result = mockedDeviceRegistrationParser; + mockedDeviceRegistrationParser.toJson(); + result = "some json"; + } + }; + + //act + contractAPIHttp.authenticateWithProvisioningService(mockedRequestData, mockedResponseCallback, null); + + //assert + prepareRequestVerifications(HttpMethod.PUT, 1); + + new Verifications() + { + { + new UrlPathBuilder(TEST_HOST_NAME, TEST_SCOPE_ID, ProvisioningDeviceClientTransportProtocol.HTTPS); + times = 1; + mockedUrlPathBuilder.generateRegisterUrl(TEST_REGISTRATION_ID); + times = 1; + mockedHttpRequest.setSSLContext(mockedSslContext); + times = 1; + mockedResponseCallback.run((ResponseData) any, null); + times = 1; + + } + }; + } + //SRS_ContractAPIHttp_25_011: [If either registrationId, sslcontext or restResponseCallback is null or if registrationId is empty then this method shall throw ProvisioningDeviceClientException.] @Test (expected = ProvisioningDeviceClientException.class) public void authenticateWithDPSThrowsOnNullRegistrationId() throws ProvisioningDeviceClientException diff --git a/provisioning/provisioning-device-client/src/test/java/tests/unit/com/microsoft/azure/sdk/iot/provisioning/device/internal/contract/mqtt/ContractAPIMqttTest.java b/provisioning/provisioning-device-client/src/test/java/tests/unit/com/microsoft/azure/sdk/iot/provisioning/device/internal/contract/mqtt/ContractAPIMqttTest.java index 1d482cf58a..0df431b0fc 100644 --- a/provisioning/provisioning-device-client/src/test/java/tests/unit/com/microsoft/azure/sdk/iot/provisioning/device/internal/contract/mqtt/ContractAPIMqttTest.java +++ b/provisioning/provisioning-device-client/src/test/java/tests/unit/com/microsoft/azure/sdk/iot/provisioning/device/internal/contract/mqtt/ContractAPIMqttTest.java @@ -34,6 +34,8 @@ import java.util.HashMap; import java.util.Map; +import static org.junit.Assert.assertEquals; + /* * Unit tests for ContractAPIMqtt * Code coverage : 80% methods, 85% lines @@ -86,6 +88,9 @@ public class ContractAPIMqttTest @Mocked DeviceRegistrationParser mockedDeviceRegistrationParser; + @Mocked + Integer mockedInteger; + @Mocked byte[] mockedByteArray = new byte[10]; @@ -459,6 +464,33 @@ public void authenticateWithProvisioningServiceWithX509Succeeds() throws Provisi }; } + @Test + public void authenticateWithProvisioningServiceWithRetryAfterSucceeds() throws ProvisioningDeviceClientException, IOException, InterruptedException + { + //arrange + ContractAPIMqtt contractAPIMqtt = createContractClass(); + + new Expectations() + { + { + mockedMqttMessage.getTopic(); + result = "mqtt/topic/value&retry-after=5;more-values=value"; + } + }; + openContractAPI(contractAPIMqtt); + + //act + contractAPIMqtt.messageReceived(mockedMqttMessage); + + //assert + new Verifications() + { + { + assertEquals(5000, contractAPIMqtt.getRetryValue()); + } + }; + } + @Test public void authenticateWithProvisioningServiceWithPayloadSucceeds() throws ProvisioningDeviceClientException, IOException, InterruptedException {