/********************************************************************************
 * Copyright (c) 2018 Contributors to the Eclipse Foundation
 *
 * See the NOTICE file(s) distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0, or the W3C Software Notice and
 * Document License (2015-05-13) which is available at
 * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document.
 *
 * SPDX-License-Identifier: EPL-2.0 OR W3C-20150513
 ********************************************************************************/ /**
 * CoAPS client based on node-coap-client by AlCalzone
 */ import * as TD from "@node-wot/td-tools" ; import { Subscription } from "rxjs/Subscription" ; import { ProtocolClient , Content , ProtocolHelpers } from "@node-wot/core" ; import { CoapForm , CoapMethodName , isValidCoapMethod , isSupportedCoapMethod } from "./coap" ; import { CoapClient as coaps , CoapResponse , RequestMethod , SecurityParameters } from "node-coap-client" ; import { Readable } from "stream" ; declare interface pskSecurityParameters { [ identity ] : string ; } export default class CoapsClient implements ProtocolClient { private authorization : SecurityParameters ; public toString ( ) { return "[CoapsClient]" ; } public readResource ( form ) { return new Promise < Content > ( ( resolve , reject ) => { this . generateRequest ( form , "GET" ) . then ( ( res ) => { console . debug ( "[binding-coap]" , `CoapsClient received ${ res . code } from ${ form . href }` ) ; let contentType ; if ( ! contentType ) contentType = form . contentType ; resolve ( { type : contentType , body : Readable . from ( res . payload ) } ) ; } ) . catch ( ( err ) => { reject ( err ) ; } ) ; } ) ; } public writeResource ( form , content ) { return new Promise < void > ( ( resolve , reject ) => { this . generateRequest ( form , "PUT" , content ) . then ( ( res ) => { console . debug ( "[binding-coap]" , `CoapsClient received ${ res . code } from ${ form . href }` ) ; resolve ( ) ; } ) . catch ( ( err ) => { reject ( err ) ; } ) ; } ) ; } public invokeResource ( form , content ? ) { return new Promise < Content > ( ( resolve , reject ) => { this . generateRequest ( form , "POST" , content ) . then ( ( res ) => { console . debug ( "[binding-coap]" , `CoapsClient received ${ res . code } from ${ form . href }` ) ; let contentType ; if ( ! contentType ) contentType = form . contentType ; resolve ( { type : contentType , body : Readable . from ( res . payload ) } ) ; } ) . catch ( ( err ) => { reject ( err ) ; } ) ; } ) ; } public unlinkResource ( form ) { return new Promise < void > ( ( resolve , reject ) => { this . generateRequest ( form , "DELETE" ) . then ( ( res ) => { console . debug ( "[binding-coap]" , `CoapsClient received ${ res . code } from ${ form . href }` ) ; console . debug ( "[binding-coap]" , `CoapsClient received headers: ${ JSON . stringify ( res . format ) }` ) ; resolve ( ) ; } ) . catch ( ( err ) => { reject ( err ) ; } ) ; } ) ; } public subscribeResource ( form , next , error ? , complete ? ) { return new Promise < Subscription > ( ( resolve , reject ) => { const requestUri = new URL ( form . href . replace ( /$coaps/ , "https" ) ) ; coaps . setSecurityParams ( requestUri . hostname , this . authorization ) ; const callback = ( resp ) => { if ( resp . payload != null ) { next ( { type : form ?. contentType , body : Readable . from ( resp . payload ) } ) ; } } ; coaps . observe ( form . href , "get" , callback ) . then ( ( ) => { resolve ( new Subscription ( ( ) => { coaps . stopObserving ( form . href ) ; complete ( ) ; } ) ) ; } ) . catch ( ( err ) => { error ( err ) ; reject ( err ) ; } ) ; } ) ; } public async start ( ) { } public async stop ( ) { } public setSecurity ( metadata , credentials ? ) { if ( metadata === undefined || ! Array . isArray ( metadata ) || metadata . length === 0 ) { console . warn ( "[binding-coap]" , `CoapsClient received empty security metadata` ) ; return false ; } const security = metadata [ 0 ] ; if ( security . scheme === "psk" ) { this . authorization = { psk : { } } ; this . authorization . psk [ credentials . identity ] = credentials . psk ; } else if ( security . scheme === "apikey" ) { console . error ( "[binding-coap]" , `CoapsClient cannot use Apikey: Not implemented` ) ; return false ; } else { console . error ( "[binding-coap]" , `CoapsClient cannot set security scheme '${ security . scheme }'` ) ; console . dir ( metadata ) ; return false ; } console . debug ( "[binding-coap]" , `CoapsClient using security scheme '${ security . scheme }'` ) ; return true ; } private determineRequestMethod ( formMethod , defaultMethod ) { if ( isSupportedCoapMethod ( formMethod ) ) { return formMethod ; } else if ( isValidCoapMethod ( formMethod ) ) { console . debug ( `[binding-coap] Method ${ formMethod } is not supported yet.` , `Using default method ${ defaultMethod } instead.` ) ; } else { console . debug ( `[binding-coap] Unknown method ${ formMethod } found.` , `Using default method ${ defaultMethod } instead.` ) ; } return defaultMethod ; } private async generateRequest ( form , defaultMethod , content ? ) { const requestUri = new URL ( form . href . replace ( /$coaps/ , "https" ) ) ; coaps . setSecurityParams ( requestUri . hostname , this . authorization ) ; let method ; if ( form [ "cov:methodName" ] != null ) { const formMethodName = form [ "cov:methodName" ] ; console . debug ( `[binding-coap] CoapClient got Form "methodName" ${ formMethodName }` ) ; method = this . determineRequestMethod ( formMethodName , defaultMethod ) ; } else { method = defaultMethod ; } console . debug ( "[binding-coap]" , `CoapsClient sending ${ method } to ${ form . href }` ) ; const body = content . body ? await ProtocolHelpers . readStreamFully ( content . body ) : undefined ; const req = coaps . request ( form . href , method . toLowerCase ( ) as RequestMethod , body ) ; return req ; } }