<commit_before>'use strict';

const dgram = require('dgram');
const Packet = require('./packet');

const EventEmitter = require('events');

const DeviceManagement = require('./management');

const IDENTITY_MAPPER  = v => v;

const ERRORS = {
	'-5001': (method, args, err) => err.message == 'invalid_arg' ? 'Invalid argument' : err.message,
	'-5005': (method, args, err) => err.message == 'params error' ? 'Invalid argument' : err.message,
	'-10000': (method) => 'Method `' + method + '` is not supported'
};

class Device extends EventEmitter {
	constructor(options) {
		super();

		this.id = options.id;
		this.type = 'generic';
		this.model = options.model || 'unknown';
		this.capabilities = [];

		this.address = options.address;
		this.port = options.port || 54321;
		this.writeOnly = options.writeOnly || false;

		this.packet = new Packet();
		if(typeof options.token === 'string') {
			this.packet.token = Buffer.from(options.token, 'hex');
		} else if(options.token instanceof Buffer) {
			this.packet.token = options.token;
		}

		this.socket = dgram.createSocket('udp4');
		this.socket.on('message', this._onMessage.bind(this));

		this._id = 0;
		this._promises = {};
		this._hasFailedToken = false;

		this._properties = {};
		this._propertiesToMonitor = [];
		this._propertyDefinitions = {};
		this._reversePropertyDefinitions = {};

		this._loadProperties = this._loadProperties.bind(this);

		this.management = new DeviceManagement(this);

		this.debug = require('debug')('miio.device.' + (options.id || '[' + options.address + ']'));
	}

	init() {
		// Default setup involves activating monitoring
		return this.monitor();
	}

	hasCapability(name) {
		return this.capabilities.indexOf(name) >= 0;
	}

	_onMessage(msg) {
		try {
			this.packet.raw = msg;
		} catch(ex) {
			this.debug('<- Unable to parse packet', ex);
			return;
		}

		if(this._tokenResolve) {
			this.debug('<-', 'Handshake reply:', this.packet.checksum);
			this.packet.handleHandshakeReply();

			this._lastToken = Date.now();

			this._tokenResolve();
		} else {
			let data = this.packet.data;
			if(! data) {
				this.debug('<-', null);
				return;
			}

			// Handle null-terminated strings
			if(data[data.length - 1] == 0) {
				data = data.slice(0, data.length - 1);
			}

			// Parse and handle the JSON message
			let str = data.toString('utf8');
			this.debug('<- Message: `' + str + '`');
			try {
				let object = JSON.parse(str);

				const p = this._promises[object.id];
				if(! p) return;
				if(typeof object.result !== 'undefined') {
					p.resolve(object.result);
				} else {
					p.reject(object.error);
				}
			} catch(ex) {
				this.debug('<- Invalid JSON');
			}
		}
	}

	_ensureToken() {
		if(! this.packet.needsHandshake) {
			return Promise.resolve(true);
		}

		if(this._hasFailedToken) {
			return Promise.reject(new Error('Token could not be auto-discovered'));
		}

		if(this._tokenPromise) {
			this.debug('Using existing promise');<commit_msg>Remove non-printable chars before parsing JSON<commit_after>'use strict';

const dgram = require('dgram');
const Packet = require('./packet');

const EventEmitter = require('events');

const DeviceManagement = require('./management');

const IDENTITY_MAPPER  = v => v;

const ERRORS = {
	'-5001': (method, args, err) => err.message == 'invalid_arg' ? 'Invalid argument' : err.message,
	'-5005': (method, args, err) => err.message == 'params error' ? 'Invalid argument' : err.message,
	'-10000': (method) => 'Method `' + method + '` is not supported'
};

class Device extends EventEmitter {
	constructor(options) {
		super();

		this.id = options.id;
		this.type = 'generic';
		this.model = options.model || 'unknown';
		this.capabilities = [];

		this.address = options.address;
		this.port = options.port || 54321;
		this.writeOnly = options.writeOnly || false;

		this.packet = new Packet();
		if(typeof options.token === 'string') {
			this.packet.token = Buffer.from(options.token, 'hex');
		} else if(options.token instanceof Buffer) {
			this.packet.token = options.token;
		}

		this.socket = dgram.createSocket('udp4');
		this.socket.on('message', this._onMessage.bind(this));

		this._id = 0;
		this._promises = {};
		this._hasFailedToken = false;

		this._properties = {};
		this._propertiesToMonitor = [];
		this._propertyDefinitions = {};
		this._reversePropertyDefinitions = {};

		this._loadProperties = this._loadProperties.bind(this);

		this.management = new DeviceManagement(this);

		this.debug = require('debug')('miio.device.' + (options.id || '[' + options.address + ']'));
	}

	init() {
		// Default setup involves activating monitoring
		return this.monitor();
	}

	hasCapability(name) {
		return this.capabilities.indexOf(name) >= 0;
	}

	_onMessage(msg) {
		try {
			this.packet.raw = msg;
		} catch(ex) {
			this.debug('<- Unable to parse packet', ex);
			return;
		}

		if(this._tokenResolve) {
			this.debug('<-', 'Handshake reply:', this.packet.checksum);
			this.packet.handleHandshakeReply();

			this._lastToken = Date.now();

			this._tokenResolve();
		} else {
			let data = this.packet.data;
			if(! data) {
				this.debug('<-', null);
				return;
			}

			// Handle null-terminated strings
			if(data[data.length - 1] == 0) {
				data = data.slice(0, data.length - 1);
			}

			// Parse and handle the JSON message
			let str = data.toString('utf8');

			// Remove non-printable characters to invalid JSON from devices
			str = str.replace(/[\x00-\x09\x0B-\x0C\x0E-\x1F\x7F-\x9F]/g, '');

			this.debug('<- Message: `' + str + '`');
			try {
				let object = JSON.parse(str);

				const p = this._promises[object.id];
				if(! p) return;
				if(typeof object.result !== 'undefined') {
					p.resolve(object.result);
				} else {
					p.reject(object.error);
				}
			} catch(ex) {
				this.debug('<- Invalid JSON', ex);
			}
		}
	}

	_ensureToken() {
		if(! this.packet.needsHandshake) {
			return Promise.resolve(true);
		}

		if(this._hasFailedToken) {
			return Promise.reject(new Error('Token could not be auto-discovered'));
		}

		if(this._tokenPromise) {
			this.debug('Using existing promise');
{'fraction_non_alphanumeric': 0.1712272652793708, 'fraction_numerical': 0.010158938227101426, 'mean_word_length': 3.407220216606498, 'pattern_counts': {'":': 0, '<': 13, '<?xml version=': 0, '>': 13, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '173941', 'n_tokens_mistral': 2336, 'n_tokens_neox': 1989, 'n_words': 427}