Skip to content

Commit

Permalink
Merge pull request #20 from mfucci/add-subscribe
Browse files Browse the repository at this point in the history
Placeholder for subscribe
  • Loading branch information
mfucci authored Sep 7, 2022
2 parents 8402568 + b58e4c7 commit 7faa215
Show file tree
Hide file tree
Showing 14 changed files with 175 additions and 22 deletions.
31 changes: 31 additions & 0 deletions .github/node.js.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions

name: Node.js CI

on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]

jobs:
build:

runs-on: ubuntu-latest

strategy:
matrix:
node-version: [12.x, 14.x, 16.x]
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/

steps:
- uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- run: npm ci
- run: npm run build --if-present
- run: npm test
4 changes: 4 additions & 0 deletions src/Devices.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export const DEVICE = {
ROOT: { name: "MA-rootdevice", code: 0x0016 },
ON_OFF_LIGHT: { name: "MA-onofflight", code: 0x0100 },
}
7 changes: 4 additions & 3 deletions src/Main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { GeneralCommissioningCluster } from "./interaction/cluster/GeneralCommis
import { OperationalCredentialsCluster } from "./interaction/cluster/OperationalCredentialsCluster";
import { OnOffCluster } from "./interaction/cluster/OnOffCluster";
import { execSync } from "child_process";
import { DEVICE } from "./Devices";

const commandArguments = process.argv.slice(2);

Expand Down Expand Up @@ -63,13 +64,13 @@ class Main {
new CasePairing(),
))
.addProtocolHandler(Protocol.INTERACTION_MODEL, new InteractionProtocol(new Device([
new Endpoint(0x00, "MA-rootdevice", [
new Endpoint(0x00, DEVICE.ROOT, [
new BasicCluster({ vendorName, vendorId, productName, productId }),
new GeneralCommissioningCluster(),
new OperationalCredentialsCluster({devicePrivateKey: DevicePrivateKey, deviceCertificate: DeviceCertificate, deviceIntermediateCertificate: ProductIntermediateCertificate, certificateDeclaration: CertificateDeclaration}),
]),
new Endpoint(0x01, "MA-OnOff", [
new OnOffCluster(commandExecutor("on"), commandExecutor("off")),
new Endpoint(0x01, DEVICE.ON_OFF_LIGHT, [
new OnOffCluster(commandExecutor("on"), commandExecutor("off")),
]),
])))
.start()
Expand Down
47 changes: 41 additions & 6 deletions src/interaction/InteractionMessages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,16 @@
*/

import { TlvType } from "../codec/TlvCodec";
import { AnyT, ArrayT, BooleanT, Field, ObjectT, OptionalField, UnsignedIntT } from "../codec/TlvObjectCodec";
import { AnyT, ArrayT, BooleanT, Field, ObjectT, OptionalField, UnsignedIntT, UnsignedLongT } from "../codec/TlvObjectCodec";

const AttributePathT = ObjectT({
endpointId: OptionalField(2, UnsignedIntT),
clusterId: OptionalField(3, UnsignedIntT),
attributeId: OptionalField(4, UnsignedIntT),
}, TlvType.List);

export const ReadRequestT = ObjectT({
attributes: Field(0, ArrayT(ObjectT({
endpointId: OptionalField(2, UnsignedIntT),
clusterId: OptionalField(3, UnsignedIntT),
attributeId: OptionalField(4, UnsignedIntT),
}, TlvType.List))),
attributes: Field(0, ArrayT(AttributePathT)),
isFabricFiltered: Field(3, BooleanT),
interactionModelRevision: Field(0xFF, UnsignedIntT),
});
Expand All @@ -33,6 +35,39 @@ export const ReadResponseT = ObjectT({
interactionModelRevision: Field(0xFF, UnsignedIntT),
});

export const SubscribeRequestT = ObjectT({
keepSubscriptions: Field(0, BooleanT),
minIntervalFloorSeconds: Field(1, UnsignedIntT),
maxIntervalCeilingSeconds: Field(2, UnsignedIntT),
attributeRequests: OptionalField(3, ArrayT(AttributePathT)),
eventRequests: OptionalField(4, ArrayT(ObjectT({
node: Field(0, UnsignedIntT),
endpoint: Field(1, UnsignedIntT),
cluster: Field(2, UnsignedIntT),
event: Field(3, UnsignedIntT),
isUrgent: Field(4, BooleanT),
}, TlvType.List))),
eventFilters: OptionalField(5, ArrayT(ObjectT({
node: Field(0, UnsignedIntT),
eventMin: Field(1, UnsignedLongT),
}, TlvType.List))),
isFabricFiltered: Field(7, BooleanT),
dataVersionFilters: OptionalField(8, ArrayT(ObjectT({
path: Field(0, ObjectT({
node: Field(0, UnsignedIntT),
endpoint: Field(1, UnsignedIntT),
cluster: Field(2, UnsignedIntT),
}, TlvType.List)),
dataVersion: Field(1, UnsignedIntT),
}))),
});

export const SubscribeResponseT = ObjectT({
subscriptionId: Field(0, UnsignedIntT),
minIntervalFloorSeconds: Field(1, UnsignedIntT),
maxIntervalCeilingSeconds: Field(2, UnsignedIntT),
});

export const InvokeRequestT = ObjectT({
suppressResponse: Field(0, BooleanT),
timedRequest: Field(1, BooleanT),
Expand Down
14 changes: 11 additions & 3 deletions src/interaction/InteractionMessenger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

import { JsType, TlvObjectCodec } from "../codec/TlvObjectCodec";
import { MessageExchange } from "../server/MessageExchange";
import { InvokeRequestT, InvokeResponseT, ReadRequestT, ReadResponseT } from "./InteractionMessages";
import { InvokeRequestT, InvokeResponseT, ReadRequestT, ReadResponseT, SubscribeRequestT, SubscribeResponseT } from "./InteractionMessages";

export const enum MessageType {
StatusResponse = 0x01,
Expand All @@ -21,16 +21,19 @@ export const enum MessageType {
TimedRequest = 0x0a,
}

export type InvokeRequest = JsType<typeof InvokeRequestT>;
export type InvokeResponse = JsType<typeof InvokeResponseT>;
export type ReadRequest = JsType<typeof ReadRequestT>;
export type ReadResponse = JsType<typeof ReadResponseT>;
export type SubscribeRequest = JsType<typeof SubscribeRequestT>;
export type SubscribeResponse = JsType<typeof SubscribeResponseT>;
export type InvokeRequest = JsType<typeof InvokeRequestT>;
export type InvokeResponse = JsType<typeof InvokeResponseT>;

export class InteractionMessenger {

constructor(
private readonly exchange: MessageExchange,
private readonly handleReadRequest: (request: ReadRequest) => ReadResponse,
private readonly handleSubscribeRequest: (request: SubscribeRequest) => SubscribeResponse,
private readonly handleInvokeRequest: (request: InvokeRequest) => Promise<InvokeResponse>,
) {}

Expand All @@ -42,6 +45,11 @@ export class InteractionMessenger {
const readResponse = this.handleReadRequest(readRequest);
this.exchange.send(MessageType.ReportData, TlvObjectCodec.encode(readResponse, ReadResponseT));
break;
case MessageType.SubscribeRequest:
const subscribeRequest = TlvObjectCodec.decode(message.payload, SubscribeRequestT);
const subscribeResponse = this.handleSubscribeRequest(subscribeRequest);
this.exchange.send(MessageType.SubscribeResponse, TlvObjectCodec.encode(subscribeResponse, SubscribeResponseT));
break;
case MessageType.InvokeCommandRequest:
const invokeRequest = TlvObjectCodec.decode(message.payload, InvokeRequestT);
const invokeResponse = await this.handleInvokeRequest(invokeRequest);
Expand Down
15 changes: 14 additions & 1 deletion src/interaction/InteractionProtocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import { Device } from "./model/Device";
import { ProtocolHandler } from "../server/MatterServer";
import { MessageExchange } from "../server/MessageExchange";
import { InteractionMessenger, InvokeRequest, InvokeResponse, ReadRequest, ReadResponse } from "./InteractionMessenger";
import { InteractionMessenger, InvokeRequest, InvokeResponse, ReadRequest, ReadResponse, SubscribeRequest, SubscribeResponse } from "./InteractionMessenger";

export class InteractionProtocol implements ProtocolHandler {
constructor(
Expand All @@ -18,6 +18,7 @@ export class InteractionProtocol implements ProtocolHandler {
const messenger = new InteractionMessenger(
exchange,
readRequest => this.handleReadRequest(exchange, readRequest),
subscribeRequest => this.handleSubscribeRequest(exchange, subscribeRequest),
invokeRequest => this.handleInvokeRequest(exchange, invokeRequest),
);
await messenger.handleRequest();
Expand All @@ -33,6 +34,18 @@ export class InteractionProtocol implements ProtocolHandler {
};
}

handleSubscribeRequest(exchange: MessageExchange, { minIntervalFloorSeconds, maxIntervalCeilingSeconds }: SubscribeRequest): SubscribeResponse {
console.log(`Received subscribe request from ${exchange.channel.getName()}`);

// TODO: implement this

return {
subscriptionId: 0,
minIntervalFloorSeconds,
maxIntervalCeilingSeconds,
};
}

async handleInvokeRequest(exchange: MessageExchange, {invokes}: InvokeRequest): Promise<InvokeResponse> {
console.log(`Received invoke request from ${exchange.channel.getName()}: ${invokes.map(({path: {endpointId, clusterId, commandId}}) => `${endpointId}/${clusterId}/${commandId}`).join(", ")}`);

Expand Down
34 changes: 34 additions & 0 deletions src/interaction/cluster/DescriptorCluster.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/**
* @license
* Copyright 2022 Marco Fucci di Napoli ([email protected])
* SPDX-License-Identifier: Apache-2.0
*/

import { ArrayT, Field, ObjectT, UnsignedIntT } from "../../codec/TlvObjectCodec";
import { Attribute } from "../model/Attribute";
import { Cluster } from "../model/Cluster";
import { Endpoint } from "../model/Endpoint";

const CLUSTER_ID = 0x1d;

export class DescriptorCluster extends Cluster {
constructor(endpoint: Endpoint, allEndpoints: Endpoint[]) {
super(
CLUSTER_ID,
"Descriptor",
[],
[
new Attribute(0, "DeviceList", ArrayT(ObjectT({
type: Field(0, UnsignedIntT),
revision: Field(1, UnsignedIntT),
})), [{
type: endpoint.device.code,
revision: 1,
}]),
new Attribute(1, "ServerList", ArrayT(UnsignedIntT), [CLUSTER_ID, ...endpoint.getClusterIds()]),
new Attribute(3, "ClientList", ArrayT(UnsignedIntT), []),
new Attribute(4, "PartsList", ArrayT(UnsignedIntT), endpoint.id === 0 ? allEndpoints.map(endpoint => endpoint.id).filter(endpointId => endpointId !== 0) : []),
],
);
}
}
5 changes: 4 additions & 1 deletion src/interaction/model/Device.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@ export class Device {
private readonly endpointsMap = new Map<number, Endpoint>();

constructor(endpoints: Endpoint[]) {
endpoints.forEach(endpoint => this.endpointsMap.set(endpoint.id, endpoint));
endpoints.forEach(endpoint => {
endpoint.addDescriptorCluster(endpoints);
this.endpointsMap.set(endpoint.id, endpoint);
});
}

getAttributeValues({endpointId, clusterId, attributeId}: AttributePath) {
Expand Down
12 changes: 11 additions & 1 deletion src/interaction/model/Endpoint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,25 @@

import { Element } from "../../codec/TlvCodec";
import { Session } from "../../session/Session";
import { DescriptorCluster } from "../cluster/DescriptorCluster";
import { Cluster } from "./Cluster";

export class Endpoint {
private readonly clustersMap = new Map<number, Cluster>();

constructor(
readonly id: number,
readonly name: string,
readonly device: {name: string, code: number},
clusters: Cluster[],
) {
clusters.forEach(cluster => this.clustersMap.set(cluster.id, cluster));
}

addDescriptorCluster(endpoints: Endpoint[]) {
const descriptorCluster = new DescriptorCluster(this, endpoints);
this.clustersMap.set(descriptorCluster.id, descriptorCluster);
}

getAttributeValue(clusterId?: number, attributeId?: number) {
// If clusterId is not provided, iterate over all clusters
var clusterIds = (clusterId === undefined) ? [...this.clustersMap.keys()] : [ clusterId ];
Expand All @@ -30,6 +36,10 @@ export class Endpoint {
})
}

getClusterIds() {
return [...this.clustersMap.keys()];
}

async invoke(session: Session, clusterId: number, commandId: number, args: Element) {
return this.clustersMap.get(clusterId)?.invoke(session, commandId, args);
}
Expand Down
8 changes: 4 additions & 4 deletions src/util/Network.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ export function getIpMacAddresses(): {ip: string, mac: string}[] {
const interfaces = networkInterfaces();
for (const name in interfaces) {
const netInterfaces = interfaces[name] as NetworkInterfaceInfo[];
for (const {internal, family, mac, address} of netInterfaces) {
if (internal || family !== "IPv4") continue;
for (const {family, mac, address} of netInterfaces) {
if (family !== "IPv4") continue;
result.push({ip: address, mac});
}
}
Expand All @@ -33,8 +33,8 @@ export function getIpMacOnInterface(remoteAddress: string): {ip: string, mac: st
const interfaces = networkInterfaces();
for (const name in interfaces) {
const netInterfaces = interfaces[name] as NetworkInterfaceInfo[];
for (const {internal, family, mac, address, netmask} of netInterfaces) {
if (internal || family !== "IPv4") continue;
for (const {family, mac, address, netmask} of netInterfaces) {
if (family !== "IPv4") continue;
const netmaskNumber = ipToNumber(netmask);
const ipNumber = ipToNumber(address);
if ((ipNumber & netmaskNumber) !== (remoteAddressNumber & netmaskNumber)) continue;
Expand Down
2 changes: 1 addition & 1 deletion test/codec/DnsTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ const DNS_RESPONSE: DnsMessage = {
],
}

const RESULT = Buffer.from("000080000000000400000004095f7365727669636573075f646e732d7364045f756470056c6f63616c00000c0001000000780014075f6d6174746572045f746370056c6f63616c00095f7365727669636573075f646e732d7364045f756470056c6f63616c00000c000100000078002c125f4944353539414633363135343941394132045f737562075f6d6174746572045f746370056c6f63616c00075f6d6174746572045f746370056c6f63616c00000c000100000078003621443535394146333631353439413941322d30303030303030303030303030303039075f6d6174746572045f746370056c6f63616c00125f4944353539414633363135343941394132045f737562075f6d6174746572045f746370056c6f63616c00000c000100000078003621443535394146333631353439413941322d30303030303030303030303030303039075f6d6174746572045f746370056c6f63616c001044434136333241303239354630303030056c6f63616c0000010001000000780004c0a8c82e1044434136333241303239354630303030056c6f63616c00001c0001000000780010fe800000000000009580b7336f549f4321443535394146333631353439413941322d30303030303030303030303030303039075f6d6174746572045f746370056c6f63616c000021000100000078001e0000000015a41044434136333241303239354630303030056c6f63616c0021443535394146333631353439413941322d30303030303030303030303030303039075f6d6174746572045f746370056c6f63616c0000100001000000780015085349493d35303030075341493d33303003543d31", "hex");
const RESULT = Buffer.from("000084000000000400000004095f7365727669636573075f646e732d7364045f756470056c6f63616c00000c0001000000780014075f6d6174746572045f746370056c6f63616c00095f7365727669636573075f646e732d7364045f756470056c6f63616c00000c000100000078002c125f4944353539414633363135343941394132045f737562075f6d6174746572045f746370056c6f63616c00075f6d6174746572045f746370056c6f63616c00000c000100000078003621443535394146333631353439413941322d30303030303030303030303030303039075f6d6174746572045f746370056c6f63616c00125f4944353539414633363135343941394132045f737562075f6d6174746572045f746370056c6f63616c00000c000100000078003621443535394146333631353439413941322d30303030303030303030303030303039075f6d6174746572045f746370056c6f63616c001044434136333241303239354630303030056c6f63616c0000010001000000780004c0a8c82e1044434136333241303239354630303030056c6f63616c00001c0001000000780010fe800000000000009580b7336f549f4321443535394146333631353439413941322d30303030303030303030303030303039075f6d6174746572045f746370056c6f63616c000021000100000078001e0000000015a41044434136333241303239354630303030056c6f63616c0021443535394146333631353439413941322d30303030303030303030303030303039075f6d6174746572045f746370056c6f63616c0000100001000000780015085349493d35303030075341493d33303003543d31", "hex");

const ENCODED = Buffer.from("000000000003000200000001026c62075f646e732d7364045f756470056c6f63616c00000c00010f5f636f6d70616e696f6e2d6c696e6b045f746370c01c000c0001085f686f6d656b6974c037000c0001c027000c000100001194000a074b69746368656ec027c042000c00010000119400272441423645433741312d333837422d353235332d413835342d394441353236333535363746c04200002905a00000119400120004000e0099929387b033db4275a6a31b2d", "hex");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { ReadRequest, ReadResponse } from "../../src/interaction/InteractionMess
import { Device } from "../../src/interaction/model/Device";
import { Endpoint } from "../../src/interaction/model/Endpoint";
import { MessageExchange } from "../../src/server/MessageExchange";
import { DEVICE } from "../../src/Devices";

const READ_REQUEST: ReadRequest = {
interactionModelRevision: 1,
Expand Down Expand Up @@ -61,7 +62,7 @@ describe("InteractionProtocol", () => {
context("handleReadRequest", () => {
it("replies with attribute values", () => {
const interactionProtocol = new InteractionProtocol(new Device([
new Endpoint(0, "root", [
new Endpoint(0, DEVICE.ROOT, [
new BasicCluster({vendorName: "vendor", vendorId: 1, productName: "product", productId: 2}),
])
]));
Expand Down
3 changes: 2 additions & 1 deletion test/session/SecureSessionTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import assert from "assert";
import { Message, MessageCodec, SessionType } from "../../src/codec/MessageCodec";
import { MatterServer } from "../../src/server/MatterServer";
import { SecureSession } from "../../src/session/SecureSession";
import { UNDEFINED_NODE_ID } from "../../src/session/SessionManager";

Expand Down Expand Up @@ -33,7 +34,7 @@ const MESSAGE: Message = {
const ENCRYPTED_BYTES = Buffer.from("1f9c4e278a2e2a755ebb4fcb9478211efb09aa9518fcafb56d74f135544636037c16fb6b62347794da0c5bde142e1a8b1cc96575e9e55471c08b58f7640b7d7f4173c8ff967c39e9961f30a29cb1f64f68df4b5bc1e742587f778eeb9ec586c162ff384558596792a2c1e43c150cd0e9ec1484c50950f17cd6c084d07caed94ce45c20004210cbde48da44ebcf7d931657f03e07e3ea29ae41868b804bf39e628323cd025507773f07268301aa1e77a82927fce041241839cee4114f6307b6befe3befde87a2d3f13eeef96b27b36e788d907b44bef2d195aa802692f4f12acc015aede3cd29da272d1e4b7f3f59683d25bf08f0e29fba2a8a9b", "hex");

describe("SecureSession", () => {
const secureSession = new SecureSession(1, UNDEFINED_NODE_ID, UNDEFINED_NODE_ID, 0x8d4b, Buffer.alloc(0), DECRYPT_KEY, ENCRYPT_KEY, Buffer.alloc(0));
const secureSession = new SecureSession({} as MatterServer, 1, UNDEFINED_NODE_ID, UNDEFINED_NODE_ID, 0x8d4b, Buffer.alloc(0), DECRYPT_KEY, ENCRYPT_KEY, Buffer.alloc(0));

context("decrypt", () => {
it("decrypts a message", () => {
Expand Down
12 changes: 12 additions & 0 deletions test/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"compilerOptions": {
"target": "es6",
"module": "CommonJS",
"noImplicitAny": true,
"removeComments": true,
"esModuleInterop": true,
"declaration": true,
"strict": true
},
"include": ["**/*.ts", "../src/**/*.ts"],
}

0 comments on commit 7faa215

Please sign in to comment.