Skip to content

Commit

Permalink
Merge pull request #22 from AlCalzone/blockwise
Browse files Browse the repository at this point in the history
Support receiving block-wise responses
  • Loading branch information
AlCalzone authored Dec 24, 2017
2 parents 290a099 + b041be3 commit a7f92e5
Show file tree
Hide file tree
Showing 13 changed files with 852 additions and 150 deletions.
24 changes: 21 additions & 3 deletions build/CoapClient.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ export interface RequestOptions {
confirmable?: boolean;
/** Whether this message will be retransmitted on loss */
retransmit?: boolean;
/** The preferred block size of partial responses */
preferredBlockSize?: number;
}
export interface CoapResponse {
code: MessageCode;
Expand Down Expand Up @@ -40,12 +42,18 @@ export declare class CoapClient {
private static pendingRequestsByUrl;
/** Queue of the messages waiting to be sent */
private static sendQueue;
/** Number of message we expect an answer for */
private static concurrency;
/** Default values for request options */
private static defaultRequestOptions;
/**
* Sets the security params to be used for the given hostname
*/
static setSecurityParams(hostname: string, params: SecurityParameters): void;
/**
* Sets the default options for requests
* @param defaults The default options to use for requests when no options are given
*/
static setDefaultRequestOptions(defaults: RequestOptions): void;
private static getRequestOptions(options?);
/**
* Closes and forgets about connections, useful if DTLS session is reset on remote end
* @param originOrHostname - Origin (protocol://hostname:port) or Hostname to reset,
Expand All @@ -60,6 +68,11 @@ export declare class CoapClient {
* @param options - Various options to control the request.
*/
static request(url: string | nodeUrl.Url, method: RequestMethod, payload?: Buffer, options?: RequestOptions): Promise<CoapResponse>;
/**
* Creates a RetransmissionInfo to use for retransmission of lost packets
* @param messageId The message id of the corresponding request
*/
private static createRetransmissionInfo(messageId);
/**
* Pings a CoAP endpoint to check if it is alive
* @param target - The target to be pinged. Must be a string, NodeJS.Url or Origin and has to contain the protocol, host and port.
Expand All @@ -73,6 +86,11 @@ export declare class CoapClient {
private static retransmit(msgID);
private static getRetransmissionInterval();
private static stopRetransmission(request);
/**
* When the server responds with block-wise responses, this requests the next block.
* @param request The original request which resulted in a block-wise response
*/
private static requestNextBlock(request);
/**
* Observes a CoAP resource
* @param url - The URL to be requested. Must start with coap:// or coaps://
Expand Down Expand Up @@ -102,7 +120,7 @@ export declare class CoapClient {
* @param message The message to send
* @param highPriority Whether the message should be prioritized
*/
private static send(connection, message, highPriority?);
private static send(connection, message, priority?);
private static workOffSendQueue();
/**
* Does the actual sending of a message and starts concurrency/retransmission handling
Expand Down
226 changes: 160 additions & 66 deletions build/CoapClient.js

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions build/Message.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export declare const MessageCodes: Readonly<{
valid: MessageCode;
changed: MessageCode;
content: MessageCode;
continue: MessageCode;
};
clientError: {
__major: number;
Expand All @@ -46,6 +47,7 @@ export declare const MessageCodes: Readonly<{
notFound: MessageCode;
methodNotAllowed: MessageCode;
notAcceptable: MessageCode;
requestEntityIncomplete: MessageCode;
preconditionFailed: MessageCode;
requestEntityTooLarge: MessageCode;
unsupportedContentFormat: MessageCode;
Expand Down Expand Up @@ -81,4 +83,8 @@ export declare class Message {
* serializes this message into a buffer
*/
serialize(): Buffer;
/**
* Checks if this message is part of a blockwise transfer
*/
isPartialMessage(): boolean;
}
15 changes: 15 additions & 0 deletions build/Message.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ exports.MessageCodes = Object.freeze({
valid: new MessageCode(2, 3),
changed: new MessageCode(2, 4),
content: new MessageCode(2, 5),
continue: new MessageCode(2, 31),
},
clientError: {
__major: 4,
Expand All @@ -59,6 +60,7 @@ exports.MessageCodes = Object.freeze({
notFound: new MessageCode(4, 4),
methodNotAllowed: new MessageCode(4, 5),
notAcceptable: new MessageCode(4, 6),
requestEntityIncomplete: new MessageCode(4, 8),
preconditionFailed: new MessageCode(4, 12),
requestEntityTooLarge: new MessageCode(4, 13),
unsupportedContentFormat: new MessageCode(4, 15),
Expand Down Expand Up @@ -165,6 +167,19 @@ class Message {
}
return ret;
}
/**
* Checks if this message is part of a blockwise transfer
*/
isPartialMessage() {
// start with the response option, since that's more likely
const block2option = Option_1.findOption(this.options, "Block2");
if (this.code.isResponse() && block2option != null)
return true;
const block1option = Option_1.findOption(this.options, "Block1");
if (this.code.isRequest() && block1option != null)
return true;
return false;
}
}
exports.Message = Message;
/*
Expand Down
73 changes: 62 additions & 11 deletions build/Option.d.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
/// <reference types="node" />
import { ContentFormats } from "./ContentFormats";
/**
* All defined option names
*/
export declare type OptionName = "Observe" | "Uri-Port" | "Content-Format" | "Max-Age" | "Accept" | "Block2" | "Block1" | "Size2" | "Size1" | "If-Match" | "ETag" | "If-None-Match" | "Uri-Host" | "Location-Path" | "Uri-Path" | "Uri-Query" | "Location-Query" | "Proxy-Uri" | "Proxy-Scheme";
/**
* Abstract base class for all message options. Provides methods to parse and serialize.
*/
export declare abstract class Option {
readonly code: number;
readonly name: string;
readonly name: OptionName;
rawValue: Buffer;
constructor(code: number, name: string, rawValue: Buffer);
constructor(code: number, name: OptionName, rawValue: Buffer);
readonly noCacheKey: boolean;
readonly unsafe: boolean;
readonly critical: boolean;
Expand All @@ -30,36 +34,69 @@ export declare abstract class Option {
* Specialized Message option for numeric contents
*/
export declare class NumericOption extends Option {
readonly name: string;
readonly name: OptionName;
readonly repeatable: boolean;
readonly maxLength: number;
constructor(code: number, name: string, repeatable: boolean, maxLength: number, rawValue: Buffer);
constructor(code: number, name: OptionName, repeatable: boolean, maxLength: number, rawValue: Buffer);
value: number;
static create(code: number, name: string, repeatable: boolean, maxLength: number, rawValue: Buffer): NumericOption;
static create(code: number, name: OptionName, repeatable: boolean, maxLength: number, rawValue: Buffer): NumericOption;
toString(): string;
}
/**
* Specialized Message optionis for blockwise transfer
*/
export declare class BlockOption extends NumericOption {
static create(code: number, name: OptionName, repeatable: boolean, maxLength: number, rawValue: Buffer): BlockOption;
/**
* The size exponent of this block in the range 0..6
* The actual block size is calculated by 2**(4 + exp)
*/
sizeExponent: number;
/**
* The size of this block in bytes
*/
readonly blockSize: number;
/**
* Indicates if there are more blocks following after this one.
*/
isLastBlock: boolean;
/**
* The sequence number of this block.
* When present in a request message, this determines the number of the block being requested
* When present in a response message, this indicates the number of the provided block
*/
blockNumber: number;
/**
* Returns the position of the first byte of this block in the complete message
*/
readonly byteOffset: number;
toString(): string;
}
/**
* Specialized Message options for binary (and empty) content.
*/
export declare class BinaryOption extends Option {
readonly name: string;
readonly name: OptionName;
readonly repeatable: boolean;
readonly minLength: number;
readonly maxLength: number;
constructor(code: number, name: string, repeatable: boolean, minLength: number, maxLength: number, rawValue: Buffer);
constructor(code: number, name: OptionName, repeatable: boolean, minLength: number, maxLength: number, rawValue: Buffer);
value: Buffer;
static create(code: number, name: string, repeatable: boolean, minLength: number, maxLength: number, rawValue: Buffer): BinaryOption;
static create(code: number, name: OptionName, repeatable: boolean, minLength: number, maxLength: number, rawValue: Buffer): BinaryOption;
toString(): string;
}
/**
* Specialized Message options for string content.
*/
export declare class StringOption extends Option {
readonly name: string;
readonly name: OptionName;
readonly repeatable: boolean;
readonly minLength: number;
readonly maxLength: number;
constructor(code: number, name: string, repeatable: boolean, minLength: number, maxLength: number, rawValue: Buffer);
constructor(code: number, name: OptionName, repeatable: boolean, minLength: number, maxLength: number, rawValue: Buffer);
value: string;
static create(code: number, name: string, repeatable: boolean, minLength: number, maxLength: number, rawValue: Buffer): StringOption;
static create(code: number, name: OptionName, repeatable: boolean, minLength: number, maxLength: number, rawValue: Buffer): StringOption;
toString(): string;
}
export declare const Options: Readonly<{
UriHost: (hostname: string) => Option;
Expand All @@ -68,4 +105,18 @@ export declare const Options: Readonly<{
LocationPath: (pathname: string) => Option;
ContentFormat: (format: ContentFormats) => Option;
Observe: (observe: boolean) => Option;
Block1: (num: number, isLast: boolean, size: number) => Option;
Block2: (num: number, isLast: boolean, size: number) => Option;
}>;
/**
* Searches for a single option in an array of options
* @param opts The options array to search for the option
* @param name The name of the option to search for
*/
export declare function findOption(opts: Option[], name: OptionName): Option;
/**
* Searches for a repeatable option in an array of options
* @param opts The options array to search for the option
* @param name The name of the option to search for
*/
export declare function findOptions(opts: Option[], name: OptionName): Option[];
112 changes: 111 additions & 1 deletion build/Option.js
Original file line number Diff line number Diff line change
Expand Up @@ -175,8 +175,78 @@ class NumericOption extends Option {
static create(code, name, repeatable, maxLength, rawValue) {
return new NumericOption(code, name, repeatable, maxLength, rawValue);
}
toString() {
return `${this.name} (${this.code}): ${this.value}`;
}
}
exports.NumericOption = NumericOption;
/**
* Specialized Message optionis for blockwise transfer
*/
class BlockOption extends NumericOption {
static create(code, name, repeatable, maxLength, rawValue) {
return new BlockOption(code, name, repeatable, maxLength, rawValue);
}
/**
* The size exponent of this block in the range 0..6
* The actual block size is calculated by 2**(4 + exp)
*/
get sizeExponent() {
return this.value & 0b111;
}
set sizeExponent(value) {
if (value < 0 || value > 6) {
throw new Error("the size exponent must be in the range of 0..6");
}
// overwrite the last 3 bits
this.value = (this.value & ~0b111) | value;
}
/**
* The size of this block in bytes
*/
get blockSize() {
return 1 << (this.sizeExponent + 4);
}
/**
* Indicates if there are more blocks following after this one.
*/
get isLastBlock() {
const moreBlocks = (this.value & 0b1000) === 0b1000;
return !moreBlocks;
}
set isLastBlock(value) {
const moreBlocks = !value;
// overwrite the 4th bit
this.value = (this.value & ~0b1000) | (moreBlocks ? 0b1000 : 0);
}
/**
* The sequence number of this block.
* When present in a request message, this determines the number of the block being requested
* When present in a response message, this indicates the number of the provided block
*/
get blockNumber() {
return this.value >>> 4;
}
set blockNumber(value) {
// TODO: check if we need to update the value length
this.value = (value << 4) | (this.value & 0b1111);
}
/**
* Returns the position of the first byte of this block in the complete message
*/
get byteOffset() {
// from the spec:
// Implementation note: As an implementation convenience, "(val & ~0xF)
// << (val & 7)", i.e., the option value with the last 4 bits masked
// out, shifted to the left by the value of SZX, gives the byte
// position of the first byte of the block being transferred.
return (this.value & ~0b1111) << (this.value & 0b111);
}
toString() {
return `${this.name} (${this.code}): ${this.blockNumber}/${this.isLastBlock ? 0 : 1}/${this.blockSize}`;
}
}
exports.BlockOption = BlockOption;
/**
* Specialized Message options for binary (and empty) content.
*/
Expand Down Expand Up @@ -206,6 +276,9 @@ class BinaryOption extends Option {
static create(code, name, repeatable, minLength, maxLength, rawValue) {
return new BinaryOption(code, name, repeatable, minLength, maxLength, rawValue);
}
toString() {
return `${this.name} (${this.code}): 0x${this.rawValue.toString("hex")}`;
}
}
exports.BinaryOption = BinaryOption;
/**
Expand Down Expand Up @@ -237,6 +310,9 @@ class StringOption extends Option {
static create(code, name, repeatable, minLength, maxLength, rawValue) {
return new StringOption(code, name, repeatable, minLength, maxLength, rawValue);
}
toString() {
return `${this.name} (${this.code}): "${this.value}"`;
}
}
exports.StringOption = StringOption;
/**
Expand All @@ -254,6 +330,9 @@ defineOptionConstructor(NumericOption, 7, "Uri-Port", false, 2);
defineOptionConstructor(NumericOption, 12, "Content-Format", false, 2);
defineOptionConstructor(NumericOption, 14, "Max-Age", false, 4);
defineOptionConstructor(NumericOption, 17, "Accept", false, 2);
defineOptionConstructor(BlockOption, 23, "Block2", false, 3);
defineOptionConstructor(BlockOption, 27, "Block1", false, 3);
defineOptionConstructor(NumericOption, 28, "Size2", false, 4);
defineOptionConstructor(NumericOption, 60, "Size1", false, 4);
defineOptionConstructor(BinaryOption, 1, "If-Match", true, 0, 8);
defineOptionConstructor(BinaryOption, 4, "ETag", true, 1, 8);
Expand All @@ -265,13 +344,44 @@ defineOptionConstructor(StringOption, 15, "Uri-Query", true, 0, 255);
defineOptionConstructor(StringOption, 20, "Location-Query", true, 0, 255);
defineOptionConstructor(StringOption, 35, "Proxy-Uri", true, 1, 1034);
defineOptionConstructor(StringOption, 39, "Proxy-Scheme", true, 1, 255);
// tslint:disable:no-string-literal
// tslint:disable-next-line:variable-name
exports.Options = Object.freeze({
UriHost: (hostname) => optionConstructors["Uri-Host"](Buffer.from(hostname)),
UriPort: (port) => optionConstructors["Uri-Port"](numberToBuffer(port)),
UriPath: (pathname) => optionConstructors["Uri-Path"](Buffer.from(pathname)),
LocationPath: (pathname) => optionConstructors["Location-Path"](Buffer.from(pathname)),
ContentFormat: (format) => optionConstructors["Content-Format"](numberToBuffer(format)),
// tslint:disable-next-line:no-string-literal
Observe: (observe) => optionConstructors["Observe"](Buffer.from([observe ? 0 : 1])),
Block1: (num, isLast, size) => {
// Warning: we're not checking for a valid size here, do that in advance!
const sizeExp = Math.log2(size) - 4;
const value = (num << 4) | (isLast ? 0 : 0b1000) | (sizeExp & 0b111);
return optionConstructors["Block1"](numberToBuffer(value));
},
Block2: (num, isLast, size) => {
// Warning: we're not checking for a valid size here, do that in advance!
const sizeExp = Math.log2(size) - 4;
const value = (num << 4) | (isLast ? 0 : 0b1000) | (sizeExp & 0b111);
return optionConstructors["Block2"](numberToBuffer(value));
},
});
// tslint:enable:no-string-literal
/**
* Searches for a single option in an array of options
* @param opts The options array to search for the option
* @param name The name of the option to search for
*/
function findOption(opts, name) {
return opts.find(o => o.name === name);
}
exports.findOption = findOption;
/**
* Searches for a repeatable option in an array of options
* @param opts The options array to search for the option
* @param name The name of the option to search for
*/
function findOptions(opts, name) {
return opts.filter(o => o.name === name);
}
exports.findOptions = findOptions;
2 changes: 2 additions & 0 deletions build/lib/LogMessage.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import { Message } from "../Message";
export declare function logMessage(msg: Message, includePayload?: boolean): void;
Loading

0 comments on commit a7f92e5

Please sign in to comment.