Skip to content

Commit

Permalink
fix(service): URI-encode device id for method invocation (fix #289)
Browse files Browse the repository at this point in the history
  • Loading branch information
Pierre Cauchois committed May 16, 2018
1 parent 7ff8a64 commit 86a69f4
Show file tree
Hide file tree
Showing 3 changed files with 34 additions and 9 deletions.
18 changes: 10 additions & 8 deletions service/devdoc/device_method_requirements.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,28 +34,28 @@ rebootMethod.invokeOn('deviceId', function(err, result, response) {
### DeviceMethod(params, restApiClient) [Constructor]
The `DeviceMethod` constructor initializes a new instance of a `DeviceMethod` object.

**SRS_NODE_IOTHUB_DEVICE_METHOD_16_004: [** The `DeviceMethod` constructor shall throw a `ReferenceError` if `params.methodName` is `null`, `undefined` or an empty string. **]**
**SRS_NODE_IOTHUB_DEVICE_METHOD_16_004: [** The `DeviceMethod` constructor shall throw a `ReferenceError` if `params.methodName` is `null`, `undefined` or an empty string. **]**

**SRS_NODE_IOTHUB_DEVICE_METHOD_16_005: [** The `DeviceMethod` constructor shall throw a `TypeError` if `params.methodName` is not a `string`. **]**
**SRS_NODE_IOTHUB_DEVICE_METHOD_16_005: [** The `DeviceMethod` constructor shall throw a `TypeError` if `params.methodName` is not a `string`. **]**

**SRS_NODE_IOTHUB_DEVICE_METHOD_16_013: [** The `DeviceMethod` constructor shall set the `DeviceMethod.params` property to the `params` argument value. **]**

**SRS_NODE_IOTHUB_DEVICE_METHOD_16_006: [** The `DeviceMethod` constructor shall set the `DeviceMethod.params.responseTimeoutInSeconds` property value to the `params.responseTimeoutInSeconds` argument value or to the default (`30`) if the `responseTimeoutInSeconds` value is falsy. **]**
**SRS_NODE_IOTHUB_DEVICE_METHOD_16_006: [** The `DeviceMethod` constructor shall set the `DeviceMethod.params.responseTimeoutInSeconds` property value to the `params.responseTimeoutInSeconds` argument value or to the default (`30`) if the `responseTimeoutInSeconds` value is falsy. **]**

**SRS_NODE_IOTHUB_DEVICE_METHOD_16_016: [** The `DeviceMethod` constructor shall set the `DeviceMethod.params.connectTimeoutInSeconds` property value to the `params.connectTimeoutInSeconds` argument value or to the default (`0`) if the `connectTimeoutInSeconds` value is falsy. **]**

**SRS_NODE_IOTHUB_DEVICE_METHOD_16_015: [** The `DeviceMethod` constructor shall set the `DeviceMethod.params.payload` property value to the `params.payload` argument value or to the default (`null`) if the `payload` argument is `null` or `undefined`. **]**
**SRS_NODE_IOTHUB_DEVICE_METHOD_16_015: [** The `DeviceMethod` constructor shall set the `DeviceMethod.params.payload` property value to the `params.payload` argument value or to the default (`null`) if the `payload` argument is `null` or `undefined`. **]**

### invokeOn(deviceId, done)
The `invokeOn` uses the IoT Hub service to run a method on a device and returns the result. The payload will be sent to the device and act as method arguments. The `timeoutInSeconds` value
is expressed in seconds and indicates how much time a method should wait for a result before timing out, once the connection between the service and the device has been established.

**SRS_NODE_IOTHUB_DEVICE_METHOD_16_008: [** The `invokeOn` method shall throw a `ReferenceError` if `deviceId` is `null`, `undefined` or an empty string. **]**
**SRS_NODE_IOTHUB_DEVICE_METHOD_16_008: [** The `invokeOn` method shall throw a `ReferenceError` if `deviceId` is `null`, `undefined` or an empty string. **]**

**SRS_NODE_IOTHUB_DEVICE_METHOD_16_011: [** The `invokeOn` method shall construct an HTTP request using information supplied by the caller, as follows:
```
POST /twins/<deviceId>/methods?api-version=<version> HTTP/1.1
Authorization: <config.sharedAccessSignature>
Authorization: <config.sharedAccessSignature>
Content-Type: application/json; charset=utf-8
Request-Id: <guid>
{
Expand All @@ -65,8 +65,10 @@ Request-Id: <guid>
"payload": <DeviceMethod.params.payload>
}
```
**]**
**]**

**SRS_NODE_IOTHUB_DEVICE_METHOD_16_009: [** The `invokeOn` method shall invoke the `done` callback with a standard javascript `Error` object if the method execution failed. **]**
**SRS_NODE_IOTHUB_DEVICE_METHOD_16_017: [** The `invokeOn` method shall uri-encode the device id. **]**

**SRS_NODE_IOTHUB_DEVICE_METHOD_16_009: [** The `invokeOn` method shall invoke the `done` callback with a standard javascript `Error` object if the method execution failed. **]**

**SRS_NODE_IOTHUB_DEVICE_METHOD_16_010: [** The `invokeOn` method shall invoke the `done` callback with a `null` first argument, a result second argument and a transport-specific response third argument if the method execution succeeded. **]**
3 changes: 2 additions & 1 deletion service/src/device_method.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@ export class DeviceMethod {
/*Codes_SRS_NODE_IOTHUB_DEVICE_METHOD_16_008: [The `invokeOn` method shall throw a `ReferenceError` if `deviceId` is `null`, `undefined` or an empty string.]*/
if (deviceId === null || deviceId === undefined || deviceId === '') throw new ReferenceError('deviceId cannot be \'' + deviceId + '\'');

const path = '/twins/' + deviceId + '/methods' + endpoint.versionQueryString();
/*Codes_SRS_NODE_IOTHUB_DEVICE_METHOD_16_017: [The `invokeOn` method shall uri-encode the device id.]*/
const path = '/twins/' + encodeURIComponent(deviceId) + '/methods' + endpoint.versionQueryString();
const headers = {
'Content-Type': 'application/json; charset=utf-8'
};
Expand Down
22 changes: 22 additions & 0 deletions service/test/_device_method_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,28 @@ describe('DeviceMethod', function() {
method.invokeOn(fakeDeviceId, testCallback);
});

/*Tests_SRS_NODE_IOTHUB_DEVICE_METHOD_16_017: [The `invokeOn` method shall uri-encode the device id.]*/
it('URI-encodes the device id', function(testCallback) {
var fakeMethodParams = {
methodName: 'method',
payload: { foo: 'bar' },
responseTimeoutInSeconds: 42
};

var fakeDeviceId = 'device#';
var uriEncodedDeviceId = encodeURIComponent(fakeDeviceId);

var fakeRestClient = {
executeApiCall: function(method, path, headers, body, timeout, callback) {
assert.equal(path, '/twins/' + uriEncodedDeviceId + '/methods' + endpoint.versionQueryString());
callback();
}
};

var method = new DeviceMethod(fakeMethodParams, fakeRestClient);
method.invokeOn(fakeDeviceId, testCallback);
});

[-1, 0, '', {}, { foo: 'bar' }, 'one line', new Buffer([0xDE, 0xAD, 0xBE, 0xEF])].forEach(function(goodPayload) {
it('builds a correct request when the payload is ' + goodPayload.toString(), function(testCallback) {
var fakeMethodParams = {
Expand Down

0 comments on commit 86a69f4

Please sign in to comment.