Skip to content

Commit

Permalink
Merge 3882b2b into 977d5ad
Browse files Browse the repository at this point in the history
  • Loading branch information
samuelthomas2774 committed Nov 2, 2020
2 parents 977d5ad + 3882b2b commit f55435b
Show file tree
Hide file tree
Showing 13 changed files with 363 additions and 116 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/nodejs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:
run: |
npm ci
npm run build --if-present
npm test-coverage
npm run test-coverage
env:
CI: true
- name: Coveralls Parallel
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@
"README.md",
"LICENSE",
"dist",
"!dist/accessories",
"!dist/scripts",
"@types"
],
"dependencies": {
Expand Down
3 changes: 1 addition & 2 deletions src/BridgedCore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import path from 'path';

import storage from 'node-persist';

import { Accessory, AccessoryEventTypes, AccessoryLoader, Bridge, Categories, uuid, VoidCallback } from './';
import { Accessory, AccessoryEventTypes, AccessoryLoader, Bridge, Categories, uuid, generateSetupCode, VoidCallback } from './';

console.log("HAP-NodeJS starting...");

Expand Down Expand Up @@ -34,7 +34,6 @@ accessories.forEach((accessory: Accessory) => {
bridge.publish({
username: "CC:22:3D:E3:CE:F6",
port: 51826,
pincode: "031-45-154",
category: Categories.BRIDGE
});

Expand Down
8 changes: 2 additions & 6 deletions src/Core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import path from 'path';

import storage from 'node-persist';

import { AccessoryLoader } from './';
import { AccessoryLoader, generateSetupCode } from './';
import { NodeCallback } from './types';

console.log("HAP-NodeJS starting...");

Expand All @@ -28,11 +29,6 @@ accessories.forEach((accessory) => {
throw new Error("Username not found on accessory '" + accessory.displayName +
"'. Core.js requires all accessories to define a unique 'username' property.");

// @ts-ignore
if (!accessory.pincode)
throw new Error("Pincode not found on accessory '" + accessory.displayName +
"'. Core.js requires all accessories to define a 'pincode' property.");

// publish this Accessory on the local network
accessory.publish({
port: targetPort++,
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export * from './lib/controller';

export * from './lib/util/clone';
export * from './lib/util/once';
export * from './lib/util/setupcode';
export * from './lib/util/tlv';
export * from './lib/util/hapStatusError';

Expand Down
101 changes: 49 additions & 52 deletions src/lib/Accessory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
Nullable,
VoidCallback,
WithUUID,
NodeCallback,
} from '../types';
import { Advertiser, AdvertiserEvent } from './Advertiser';
// noinspection JSDeprecatedSymbols
Expand Down Expand Up @@ -56,7 +57,8 @@ import {
RemovePairingCallback,
ResourceRequestCallback,
TLVErrorCode,
WriteCharacteristicsCallback
WriteCharacteristicsCallback,
PairIdentity
} from './HAPServer';
import { AccessoryInfo, PermissionTypes } from './model/AccessoryInfo';
import { ControllerStorage } from "./model/ControllerStorage";
Expand All @@ -66,7 +68,10 @@ import { clone } from './util/clone';
import { EventName, HAPConnection, HAPUsername } from "./util/eventedhttp";
import * as uuid from "./util/uuid";
import { toShortForm } from "./util/uuid";
import { generateSetupId, generateSetupUri } from './util/setupid';
import { generateSetupCode } from './util/setupcode';
import Timeout = NodeJS.Timeout;
import { Identity } from 'fast-srp-hap';

const debug = createDebug('HAP-NodeJS:Accessory');
const MAX_ACCESSORIES = 149; // Maximum number of bridged accessories per bridge.
Expand Down Expand Up @@ -154,7 +159,9 @@ export type CharacteristicEvents = Record<string, any>;

export interface PublishInfo {
username: MacAddress;
pincode: HAPPincode;
pincode?: HAPPincode
| ((callback: NodeCallback<string>, connection: HAPConnection) => void)
| {salt: Buffer; verifier: Buffer};
/**
* Specify the category for the HomeKit accessory.
* The category is used only in the mdns advertisement and specifies the devices type
Expand Down Expand Up @@ -243,6 +250,8 @@ const enum WriteRequestState {
TIMED_WRITE_REJECTED
}

export type SetupCode = Identity & {setupcode: string | null; setupuri: string | null};

// noinspection JSUnusedGlobalSymbols
/**
* @deprecated Use AccessoryEventTypes instead
Expand All @@ -252,6 +261,8 @@ export type EventAccessory = "identify" | "listening" | "service-configurationCh
export const enum AccessoryEventTypes {
IDENTIFY = "identify",
LISTENING = "listening",
PAIR_SETUP_STARTED = 'pair-setup-started',
PAIR_SETUP_FINISHED = 'pair-setup-finished',
SERVICE_CONFIGURATION_CHANGE = "service-configurationChange",
SERVICE_CHARACTERISTIC_CHANGE = "service-characteristic-change",
PAIRED = "paired",
Expand All @@ -267,6 +278,9 @@ export declare interface Accessory {
on(event: "service-configurationChange", listener: (change: ServiceConfigurationChange) => void): this;
on(event: "service-characteristic-change", listener: (change: AccessoryCharacteristicChange) => void): this;

on(event: "pair-setup-started", listener: (setupcode: SetupCode, connection: HAPConnection) => void): this;
on(event: "pair-setup-finished", listener: (err: Error | null, clientUsername: string | null, connection: HAPConnection) => void): this;

on(event: "paired", listener: () => void): this;
on(event: "unpaired", listener: () => void): this;

Expand All @@ -279,6 +293,9 @@ export declare interface Accessory {
emit(event: "service-configurationChange", change: ServiceConfigurationChange): boolean;
emit(event: "service-characteristic-change", change: AccessoryCharacteristicChange): boolean;

emit(event: "pair-setup-started", setupcode: SetupCode, connection: HAPConnection): boolean;
emit(event: "pair-setup-finished", err: Error | null, clientUsername: string | null, connection: HAPConnection): boolean;

emit(event: "paired"): boolean;
emit(event: "unpaired"): boolean;

Expand Down Expand Up @@ -788,39 +805,6 @@ export class Accessory extends EventEmitter {
});
}

setupURI(): string {
if (this._setupURI) {
return this._setupURI;
}

const buffer = Buffer.alloc(8);
const setupCode = this._accessoryInfo && parseInt(this._accessoryInfo.pincode.replace(/-/g, ''), 10);

let value_low = setupCode!;
const value_high = this._accessoryInfo && this._accessoryInfo.category >> 1;

value_low |= 1 << 28; // Supports IP;

buffer.writeUInt32BE(value_low, 4);

if (this._accessoryInfo && this._accessoryInfo.category & 1) {
buffer[4] = buffer[4] | 1 << 7;
}

buffer.writeUInt32BE(value_high!, 0);

let encodedPayload = (buffer.readUInt32BE(4) + (buffer.readUInt32BE(0) * Math.pow(2, 32))).toString(36).toUpperCase();

if (encodedPayload.length != 9) {
for (let i = 0; i <= 9 - encodedPayload.length; i++) {
encodedPayload = "0" + encodedPayload;
}
}

this._setupURI = "X-HM://" + encodedPayload + this._setupID;
return this._setupURI;
}

/**
* This method is called right before the accessory is published. It should be used to check for common
* mistakes in Accessory structured, which may lead to HomeKit rejecting the accessory when pairing.
Expand Down Expand Up @@ -990,8 +974,9 @@ export class Accessory extends EventEmitter {
* @param allowInsecureRequest - Will allow unencrypted and unauthenticated access to the http server
* @param {string} info.username - The "username" (formatted as a MAC address - like "CC:22:3D:E3:CE:F6") of
* this Accessory. Must be globally unique from all Accessories on your local network.
* @param {string} info.pincode - The 8-digit pincode for clients to use when pairing this Accessory. Must be formatted
* as a string like "031-45-154".
* @param {string|function} info.pincode - The 8-digit pincode for clients to use when pairing this Accessory. Must
* be formatted as a string like "031-45-154". You can also provide a function that
* generates a random setup code and presents it to the user.
* @param {string} info.category - One of the values of the Accessory.Category enum, like Accessory.Category.SWITCH.
* This is a hint to iOS clients about what "type" of Accessory this represents, so
* that for instance an appropriate icon can be drawn for the user while adding a
Expand Down Expand Up @@ -1032,7 +1017,7 @@ export class Accessory extends EventEmitter {
if (info.setupID) {
this._setupID = info.setupID;
} else if (this._accessoryInfo.setupID === undefined || this._accessoryInfo.setupID === "") {
this._setupID = Accessory._generateSetupID();
this._setupID = generateSetupId();
} else {
this._setupID = this._accessoryInfo.setupID;
}
Expand All @@ -1043,7 +1028,7 @@ export class Accessory extends EventEmitter {
this._accessoryInfo.displayName = this.displayName;
this._accessoryInfo.model = this.getService(Service.AccessoryInformation)!.getCharacteristic(Characteristic.Model).value as string;
this._accessoryInfo.category = info.category || Categories.OTHER;
this._accessoryInfo.pincode = info.pincode;
this._accessoryInfo.pincode = info.pincode && typeof info.pincode !== 'function' ? info.pincode : null;
this._accessoryInfo.save();

// create our IdentifierCache so we can provide clients with stable aid/iid's
Expand Down Expand Up @@ -1102,6 +1087,8 @@ export class Accessory extends EventEmitter {
this._server.allowInsecureRequest = !!allowInsecureRequest;
this._server.on(HAPServerEventTypes.LISTENING, this.onListening.bind(this));
this._server.on(HAPServerEventTypes.IDENTIFY, this.identificationRequest.bind(this, false));
this._server.on(HAPServerEventTypes.PAIR_SETUP_STARTED, this._handlePairSetupStarted.bind(this));
this._server.on(HAPServerEventTypes.PAIR_SETUP_FINISHED, this._handlePairSetupFinished.bind(this));
this._server.on(HAPServerEventTypes.PAIR, this.handleInitialPairSetupFinished.bind(this));
this._server.on(HAPServerEventTypes.ADD_PAIRING, this.handleAddPairing.bind(this));
this._server.on(HAPServerEventTypes.REMOVE_PAIRING, this.handleRemovePairing.bind(this));
Expand All @@ -1112,6 +1099,12 @@ export class Accessory extends EventEmitter {
this._server.on(HAPServerEventTypes.CONNECTION_CLOSED, this.handleHAPConnectionClosed.bind(this));
this._server.on(HAPServerEventTypes.REQUEST_RESOURCE, this.handleResource.bind(this));

if (typeof info.pincode === 'function') {
this._server.on(HAPServerEventTypes.GENERATE_SETUP_CODE, info.pincode);
} else if (typeof info.pincode !== 'string' && typeof info.pincode !== 'object') {
this._server.on(HAPServerEventTypes.GENERATE_SETUP_CODE, generateSetupCode);
}

this._server.listen(info.port, parsed.serverAddress);
}

Expand Down Expand Up @@ -1178,6 +1171,23 @@ export class Accessory extends EventEmitter {
this.emit(AccessoryEventTypes.LISTENING, port, hostname);
}

/** Called when starting the pair setup process after a setup code has been generated */
private _handlePairSetupStarted(i: PairIdentity, connection: HAPConnection) {
if (this.listenerCount(AccessoryEventTypes.PAIR_SETUP_STARTED)) {
if (!this._setupID) this._setupID = generateSetupId();
const setupuri = i.setupcode ? generateSetupUri(i.setupcode, this._setupID, this.category) : null;
this.emit(AccessoryEventTypes.PAIR_SETUP_STARTED, {...i, setupuri}, connection);
} else if (!this._accessoryInfo!.pincode) {
// If we're using random setup codes and there's nothing listening for the setup code print it to the console
console.log('[%s] Received pair request from %s', this.displayName, connection.remoteAddress, i.setupcode);
}
}

/** Called when the pair setup process has finished with the error or paired client username */
private _handlePairSetupFinished(err: Nullable<Error>, clientUsername: Nullable<string>, connection: HAPConnection) {
this.emit(AccessoryEventTypes.PAIR_SETUP_FINISHED, err, clientUsername, connection);
}

private handleInitialPairSetupFinished(username: string, publicKey: Buffer, callback: PairCallback): void {
debug("[%s] Paired with client %s", this.displayName, username);

Expand Down Expand Up @@ -1750,19 +1760,6 @@ export class Accessory extends EventEmitter {
});
}

private static _generateSetupID(): string {
const chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
const bytes = crypto.randomBytes(4);
let setupID = '';

for (let i = 0; i < 4; i++) {
const index = bytes.readUInt8(i) % 26;
setupID += chars.charAt(index);
}

return setupID;
}

// serialization and deserialization functions, mainly designed for homebridge to create a json copy to store on disk
public static serialize(accessory: Accessory): SerializedAccessory {
const json: SerializedAccessory = {
Expand Down
10 changes: 2 additions & 8 deletions src/lib/Advertiser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ import ciao, {
ServiceType
} from "@homebridge/ciao";
import { ServiceOptions } from "@homebridge/ciao/lib/CiaoService";
import crypto from 'crypto';
import { EventEmitter } from "events";
import { AccessoryInfo } from './model/AccessoryInfo';
import { generateSetupHash } from "./util/setupid";

/**
* This enum lists all bitmasks for all known status flags.
Expand Down Expand Up @@ -62,7 +62,7 @@ export class Advertiser extends EventEmitter {
constructor(accessoryInfo: AccessoryInfo, responderOptions?: MDNSServerOptions, serviceOptions?: Partial<ServiceOptions>) {
super();
this.accessoryInfo = accessoryInfo;
this.setupHash = this.computeSetupHash();
this.setupHash = generateSetupHash(this.accessoryInfo.username, this.accessoryInfo.setupID).toString('base64');

this.responder = ciao.getResponder(responderOptions);
this.advertisedService = this.responder.createService({
Expand Down Expand Up @@ -117,12 +117,6 @@ export class Advertiser extends EventEmitter {
};
}

private computeSetupHash(): string {
const hash = crypto.createHash('sha512');
hash.update(this.accessoryInfo.setupID + this.accessoryInfo.username.toUpperCase());
return hash.digest().slice(0, 4).toString('base64');
}

public static ff(...flags: PairingFeatureFlag[]): number {
let value = 0;
flags.forEach(flag => value |= flag);
Expand Down
Loading

0 comments on commit f55435b

Please sign in to comment.