diff --git a/packages/binding-coap/src/coap-server.ts b/packages/binding-coap/src/coap-server.ts index b2bad7c1f..25b2ab3ab 100644 --- a/packages/binding-coap/src/coap-server.ts +++ b/packages/binding-coap/src/coap-server.ts @@ -333,14 +333,21 @@ export default class CoapServer implements ProtocolServer { * * @param req The incoming request. * @param res The outgoing response. - * @param thing The ExposedThing whose TD is requested. + * @param thingKey The key of the ExposedThing whose TD is requested. */ - private async handleTdRequest(req: IncomingMessage, res: OutgoingMessage, thing: ExposedThing) { + private async handleTdRequest(req: IncomingMessage, res: OutgoingMessage, thingKey: string) { if (req.method !== "GET") { this.sendErrorResponse(res, "4.05", "Method Not Allowed"); return; } + const thing = this.things.get(thingKey); + + if (thing == null) { + this.sendErrorResponse(res, "4.04", "Not Found"); + return; + } + const accept = req.headers.Accept; const contentSerdes = ContentSerdes.get(); @@ -378,32 +385,18 @@ export default class CoapServer implements ProtocolServer { ); }); - let contentType; + const contentType = this.getContentTypeFromRequest(req); - try { - contentType = this.getContentTypeFromRequest(req); - } catch (exception) { - // FIXME - this.sendErrorResponse(res, "415", exception.msg); + if (!this.checkContentTypeSupportForInput(res, req.method, contentType)) { + this.sendErrorResponse(res, "4.15", "Unsupported Media Type"); return; } const requestUri = this.processRequestUri(req); - const segments = decodeURI(requestUri).split("/"); + const { thingKey, affordanceType, affordanceKey } = this.parseUriSegments(requestUri); if (requestUri === "/") { - // no path -> list all Things - if (req.method !== "GET") { - this.sendErrorResponse(res, "4.05", "Method Not Allowed"); - return; - } - - res.setHeader("Content-Format", ContentSerdes.DEFAULT); - res.code = "2.05"; - const payload = JSON.stringify(this.getThingDescriptionPayload()); - res.end(payload); - - // resource found and response sent + this.handleThingsRequest(req, res); return; } @@ -412,23 +405,11 @@ export default class CoapServer implements ProtocolServer { return; } - // path -> select Thing - const thing = this.things.get(segments[1]); - - if (thing == null) { - this.sendErrorResponse(res, "4.04", "Not Found"); - return; - } - - const affordanceType = segments[2]; - if (segments.length === 2 || affordanceType === "") { - // Thing root -> send TD - await this.handleTdRequest(req, res, thing); + if (affordanceType == null || affordanceType === "") { + await this.handleTdRequest(req, res, thingKey); return; } - const affordanceKey = segments[3]; - switch (affordanceType) { case this.PROPERTY_DIR: this.handlePropertyRequest(thing, affordanceKey, req, res, contentType); @@ -454,6 +435,18 @@ export default class CoapServer implements ProtocolServer { return uri; } + private handleThingsRequest(method: string, res: OutgoingMessage) { + if (method !== "GET") { + this.sendErrorResponse(res, "4.05", "Method Not Allowed"); + return; + } + + res.setHeader("Content-Format", ContentSerdes.DEFAULT); + res.code = "2.05"; + const payload = JSON.stringify(this.getThingDescriptionPayload()); + res.end(payload); + } + private async handlePropertyRequest( thing: ExposedThing, affordanceKey: string, @@ -596,6 +589,7 @@ export default class CoapServer implements ProtocolServer { const action = thing.actions[affordanceKey]; if (action == null) { + this.sendErrorResponse(res, "4.04", "Not Found"); return; } @@ -638,10 +632,10 @@ export default class CoapServer implements ProtocolServer { res: OutgoingMessage, contentType?: string ) { - // sub-path -> select Event const event = thing.events[affordanceKey]; if (event == null) { + this.sendErrorResponse(res, "4.04", "Not Found"); return; } @@ -650,8 +644,19 @@ export default class CoapServer implements ProtocolServer { return; } - // subscribeevent - if (req.headers.Observe === 0) { + const observe = req.headers.Observe; + + if (observe == null) { + debug( + `CoapServer on port ${this.getPort()} rejects '${affordanceKey}' read from ${Helpers.toUriLiteral( + req.rsinfo.address + )}:${req.rsinfo.port}` + ); + this.sendErrorResponse(res, "4.00", "No Observe Option"); + return; + } + + if (observe === 0) { // work-around to avoid duplicate requests (resend due to no response) // (node-coap does not deduplicate when Observe is set) const packet = res._packet; @@ -703,7 +708,7 @@ export default class CoapServer implements ProtocolServer { ); thing.handleUnsubscribeEvent(affordanceKey, listener, options); }); - } else if (req.headers.Observe > 0) { + } else if (observe > 0) { debug( `CoapServer on port ${this.getPort()} sends '${affordanceKey}' response to ${Helpers.toUriLiteral( req.rsinfo.address @@ -712,34 +717,27 @@ export default class CoapServer implements ProtocolServer { // TODO: Check if this has been fixed in the meantime // node-coap does not support GET cancellation this.sendErrorResponse(res, "5.01", "node-coap issue: no GET cancellation, send RST"); - } else { - debug( - `CoapServer on port ${this.getPort()} rejects '${affordanceKey}' read from ${Helpers.toUriLiteral( - req.rsinfo.address - )}:${req.rsinfo.port}` - ); - res.code = "4.00"; - res.end("No Observe Option"); } } - private getContentTypeFromRequest(req: IncomingMessage): string | undefined { + private getContentTypeFromRequest(req: IncomingMessage): string { const contentType = req.headers["Content-Format"] as string; - if (req.method === "PUT" || req.method === "POST") { - if (contentType != null && req.payload.length > 0) { - warn( - `CoapServer on port ${this.getPort()} received no Content-Format from ${Helpers.toUriLiteral( - req.rsinfo.address - )}:${req.rsinfo.port}` - ); - return ContentSerdes.DEFAULT; - } else if (ContentSerdes.get().getSupportedMediaTypes().includes(ContentSerdes.getMediaType(contentType))) { - throw Error("Unsupported Media Type"); - } + if (contentType == null) { + warn( + `CoapServer on port ${this.getPort()} received no Content-Format from ${Helpers.toUriLiteral( + req.rsinfo.address + )}:${req.rsinfo.port}` + ); } - return undefined; + return contentType ?? ContentSerdes.DEFAULT; + } + + private checkContentTypeSupportForInput(res: OutgoingMessage, method: string, contentType: string) { + const methodsWithPayload: string[] = ["PUT", "POST", "FETCH", "iPATCH", "PATCH"]; + + return methodsWithPayload.includes(method) && ContentSerdes.get().getSupportedMediaTypes().includes(ContentSerdes.getMediaType(contentType)); } private getThingDescriptionPayload() { @@ -753,6 +751,16 @@ export default class CoapServer implements ProtocolServer { ); } + private parseUriSegments(requestUri: string) { + const segments = decodeURI(requestUri).split("/"); + + return { + thingKey: segments[1], + affordanceType: segments[2], + affordanceKey: segments[3], + } + } + private sendErrorResponse(res: OutgoingMessage, errorCode: string, errorMessage: string) { res.code = errorCode; res.end(errorMessage);