2Copyright 2018-2021 Intel Corporation
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
8 http://www.apache.org/licenses/LICENSE-2.0
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
17var Q = require('queue');
20function retry_pthi_later()
22 if (++g_internal.errorCount < 20)
24 g_internal.timeout = setTimeout(function (p)
26 p.connect(require('heci').GUIDS.AMT, { noPipeline: 1 });
31 this.Parent.emit('error', 'PTHI Connection could not be established');
37 var emitterUtils = require('events').inherits(this);
38 emitterUtils.createEvent('error');
40 var heci = require('heci');
41 var sendConsole = function (msg) { try { require('MeshAgent').SendCommand({ "action": "msg", "type": "console", "value": msg }); } catch (ex) { } }
43 this._ObjectID = "pthi";
45 if (g_internal == null)
47 g_internal = { _rq: new Q(), _amt: null, errorCount: 0 };
48 g_internal._setupPTHI = function _g_setupPTHI()
50 console.info1('setupPTHI()');
51 this._amt = heci.create();
52 this._amt.descriptorMetadata = "amt-pthi";
53 this._amt.BiosVersionLen = 65;
54 this._amt.UnicodeStringLen = 20;
55 this._amt.Parent = that;
57 this._amt.on('error', function _amtOnError(e)
59 console.info1('PTHIError: ' + e);
60 if (g_internal._rq.isEmpty())
62 console.info1(' Queue is empty');
63 this.Parent.emit('error', e); // No pending requests, so propagate the error up
67 console.info1(' Queue is NOT empty');
70 retry_pthi_later.call(this);
73 this._amt.on('connect', function _amtOnConnect()
75 g_internal.errorCount = 0;
76 this.on('data', function _amtOnData(chunk)
78 //console.log("Received: " + chunk.length + " bytes");
79 var header = this.Parent.getCommand(chunk);
80 console.info1("CMD = " + header.Command + " (Status: " + header.Status + ") Response = " + header.IsResponse);
82 var user = g_internal._rq.deQueue();
83 var params = user.optional;
84 var callback = user.func;
86 params.unshift(header);
87 callback.apply(this.Parent, params);
89 if (g_internal._rq.isEmpty())
91 console.info1('No more requests, disconnecting');
93 // No More Requests, we can close PTHI
94 g_internal._amt.disconnect();
95 g_internal._amt = null;
99 // Send the next request
100 console.info1('Sending Next Request');
101 this.write(g_internal._rq.peekQueue().send);
105 // Start sending requests
106 this.write(g_internal._rq.peekQueue().send);
115 function trim(x) { var y = x.indexOf('\0'); if (y >= 0) { return x.substring(0, y); } else { return x; } }
116 this.getCommand = function getCommand(chunk) {
117 var command = chunk.length == 0 ? (g_internal._rq.peekQueue().cmd | 0x800000) : chunk.readUInt32LE(4);
118 var ret = { IsResponse: (command & 0x800000) == 0x800000 ? true : false, Command: (command & 0x7FFFFF), Status: chunk.length != 0 ? chunk.readUInt32LE(12) : -1, Data: chunk.length != 0 ? chunk.slice(16) : null };
122 this.sendCommand = function sendCommand()
124 if (arguments.length < 3 || typeof (arguments[0]) != 'number' || typeof (arguments[1]) != 'object' || typeof (arguments[2]) != 'function') { throw ('invalid parameters'); }
126 for (var i = 3; i < arguments.length; ++i) { args.push(arguments[i]); }
128 console.info1('sendCommand(' + arguments[0] + ')', this._hashCode());
130 var header = Buffer.from('010100000000000000000000', 'hex');
131 header.writeUInt32LE(arguments[0] | 0x04000000, 4);
132 header.writeUInt32LE(arguments[1] == null ? 0 : arguments[1].length, 8);
134 g_internal._rq.enQueue({ cmd: arguments[0], func: arguments[2], optional: args, send: (arguments[1] == null ? header : Buffer.concat([header, arguments[1]])) });
135 if (!g_internal._amt)
137 g_internal._setupPTHI();
138 g_internal._amt.connect(heci.GUIDS.AMT, { noPipeline: 1 });
142 this.getVersion = function getVersion(callback) {
144 for (var i = 1; i < arguments.length; ++i) { optional.push(arguments[i]); }
145 this.sendCommand(26, null, function (header, fn, opt) {
146 if (header.Status == 0) {
147 var i, CodeVersion = header.Data, val = { BiosVersion: CodeVersion.slice(0, g_internal._amt.BiosVersionLen).toString(), Versions: [] }, v = CodeVersion.slice(g_internal._amt.BiosVersionLen + 4);
148 for (i = 0; i < CodeVersion.readUInt32LE(g_internal._amt.BiosVersionLen) ; ++i)
150 val.Versions[i] = { Description: v.slice(2, v.readUInt16LE(0) + 2).toString(), Version: v.slice(4 + g_internal._amt.UnicodeStringLen, 4 + g_internal._amt.UnicodeStringLen + v.readUInt16LE(2 + g_internal._amt.UnicodeStringLen)).toString() };
151 v = v.slice(4 + (2 * g_internal._amt.UnicodeStringLen));
153 if (val.BiosVersion.indexOf('\0') > 0) { val.BiosVersion = val.BiosVersion.substring(0, val.BiosVersion.indexOf('\0')); }
159 }, callback, optional);
162 // Fill the left with zeros until the string is of a given length
163 function zeroLeftPad(str, len) {
164 if ((len == null) && (typeof (len) != 'number')) { return null; }
165 if (str == null) str = ''; // If null, this is to generate zero leftpad string
167 for (var i = 0; i < len - str.length; i++) { zlp += '0'; }
171 this.getUuid = function getUuid(callback) {
173 for (var i = 1; i < arguments.length; ++i) { optional.push(arguments[i]); }
174 this.sendCommand(0x5c, null, function (header, fn, opt) {
175 if (header.Status == 0) {
177 result.uuid = [zeroLeftPad(header.Data.readUInt32LE(0).toString(16), 8),
178 zeroLeftPad(header.Data.readUInt16LE(4).toString(16), 4),
179 zeroLeftPad(header.Data.readUInt16LE(6).toString(16), 4),
180 zeroLeftPad(header.Data.readUInt16BE(8).toString(16), 4),
181 zeroLeftPad(header.Data.slice(10).toString('hex').toLowerCase(), 12)].join('-');
187 }, callback, optional);
190 this.getProvisioningState = function getProvisioningState(callback) {
192 for (var i = 1; i < arguments.length; ++i) { optional.push(arguments[i]); }
193 this.sendCommand(17, null, function (header, fn, opt) {
194 if (header.Status == 0) {
196 result.state = header.Data.readUInt32LE(0);
197 if (result.state < 3) { result.stateStr = ["PRE", "IN", "POST"][result.state]; }
203 }, callback, optional);
205 this.getProvisioningMode = function getProvisioningMode(callback) {
207 for (var i = 1; i < arguments.length; ++i) { optional.push(arguments[i]); }
208 this.sendCommand(8, null, function (header, fn, opt) {
209 if (header.Status == 0) {
211 result.mode = header.Data.readUInt32LE(0);
212 if (result.mode < 4) { result.modeStr = ["NONE", "ENTERPRISE", "SMALL_BUSINESS", "REMOTE_ASSISTANCE"][result.mode]; }
213 result.legacy = header.Data.readUInt32LE(4) == 0 ? false : true;
219 }, callback, optional);
221 this.getEHBCState = function getEHBCState(callback) {
223 for (var i = 1; i < arguments.length; ++i) { optional.push(arguments[i]); }
224 this.sendCommand(132, null, function (header, fn, opt) {
225 if (header.Status == 0) {
226 opt.unshift({ EHBC: header.Data.readUInt32LE(0) != 0 });
231 }, callback, optional);
233 this.getControlMode = function getControlMode(callback) {
235 for (var i = 1; i < arguments.length; ++i) { optional.push(arguments[i]); }
236 this.sendCommand(107, null, function (header, fn, opt) {
237 if (header.Status == 0) {
239 result.controlMode = header.Data.readUInt32LE(0);
240 if (result.controlMode < 3) { result.controlModeStr = ["NONE_RPAT", "CLIENT", "ADMIN", "REMOTE_ASSISTANCE"][result.controlMode]; }
246 }, callback, optional);
248 this.getMACAddresses = function getMACAddresses(callback) {
250 for (var i = 1; i < arguments.length; ++i) { optional.push(arguments[i]); }
251 this.sendCommand(37, null, function (header, fn, opt) {
252 if (header.Status == 0) {
253 opt.unshift({ DedicatedMAC: header.Data.slice(0, 6).toString('hex:'), HostMAC: header.Data.slice(6, 12).toString('hex:') });
254 } else { opt.unshift({ DedicatedMAC: null, HostMAC: null }); }
256 }, callback, optional);
258 this.getDnsSuffix = function getDnsSuffix(callback) {
260 for (var i = 1; i < arguments.length; ++i) { optional.push(arguments[i]); }
261 this.sendCommand(54, null, function (header, fn, opt) {
262 if (header.Status == 0) {
263 var resultLen = header.Data.readUInt16LE(0);
264 if (resultLen > 0) { opt.unshift(header.Data.slice(2, 2 + resultLen).toString()); } else { opt.unshift(null); }
269 }, callback, optional);
271 this.getHashHandles = function getHashHandles(callback) {
273 for (var i = 1; i < arguments.length; ++i) { optional.push(arguments[i]); }
274 this.sendCommand(0x2C, null, function (header, fn, opt) {
276 if (header.Status == 0) {
277 var resultLen = header.Data.readUInt32LE(0);
278 for (var i = 0; i < resultLen; ++i) {
279 result.push(header.Data.readUInt32LE(4 + (4 * i)));
284 }, callback, optional);
286 this.getCertHashEntry = function getCertHashEntry(handle, callback) {
288 for (var i = 2; i < arguments.length; ++i) { optional.push(arguments[i]); }
290 var data = Buffer.alloc(4);
291 data.writeUInt32LE(handle, 0);
293 this.sendCommand(0x2D, data, function (header, fn, opt) {
294 if (header.Status == 0) {
296 result.isDefault = header.Data.readUInt32LE(0);
297 result.isActive = header.Data.readUInt32LE(4);
298 result.hashAlgorithm = header.Data.readUInt8(72);
299 if (result.hashAlgorithm < 4) {
300 result.hashAlgorithmStr = ["MD5", "SHA1", "SHA256", "SHA512"][result.hashAlgorithm];
301 result.hashAlgorithmSize = [16, 20, 32, 64][result.hashAlgorithm];
302 result.certificateHash = header.Data.slice(8, 8 + result.hashAlgorithmSize).toString('hex');
304 result.name = header.Data.slice(73 + 2, 73 + 2 + header.Data.readUInt16LE(73)).toString();
310 }, callback, optional);
312 this.getCertHashEntries = function getCertHashEntries(callback) {
314 for (var i = 1; i < arguments.length; ++i) { optional.push(arguments[i]); }
316 this.getHashHandles(function (handles, fn, opt) {
318 this.getCertHashEntry(handles.shift(), this._getHashEntrySink, fn, opt, entries, handles);
319 }, callback, optional);
322 this._getHashEntrySink = function _getHashEntrySink(result, fn, opt, entries, handles) {
323 entries.push(result);
324 if (handles.length > 0) {
325 this.getCertHashEntry(handles.shift(), this._getHashEntrySink, fn, opt, entries, handles);
327 opt.unshift(entries);
331 this.getLocalSystemAccount = function getLocalSystemAccount(callback) {
333 for (var i = 1; i < arguments.length; ++i) { optional.push(arguments[i]); }
334 this.sendCommand(103, Buffer.alloc(40), function (header, fn, opt) {
335 if (header.Status == 0 && header.Data.length == 68) {
336 opt.unshift({ user: trim(header.Data.slice(0, 33).toString()), pass: trim(header.Data.slice(33, 67).toString()), raw: header.Data });
342 }, callback, optional);
344 this.getLanInterfaceSettings = function getLanInterfaceSettings(index, callback) {
346 for (var i = 2; i < arguments.length; ++i) { optional.push(arguments[i]); }
347 var ifx = Buffer.alloc(4);
348 ifx.writeUInt32LE(index);
349 this.sendCommand(0x48, ifx, function onGetLanInterfaceSettings(header, fn, opt) {
350 if (header.Status == 0) {
352 info.enabled = header.Data.readUInt32LE(0);
353 info.dhcpEnabled = header.Data.readUInt32LE(8);
354 switch (header.Data[12]) {
356 info.dhcpMode = 'ACTIVE'
359 info.dhcpMode = 'PASSIVE'
362 info.dhcpMode = 'UNKNOWN';
365 info.mac = header.Data.slice(14).toString('hex:');
367 var addr = header.Data.readUInt32LE(4);
368 info.address = ((addr >> 24) & 255) + '.' + ((addr >> 16) & 255) + '.' + ((addr >> 8) & 255) + '.' + (addr & 255);
376 }, callback, optional);
379 this.unprovision = function unprovision(mode, callback) {
381 for (var i = 2; i < arguments.length; ++i) { optional.push(arguments[i]); }
382 var data = Buffer.alloc(4);
383 data.writeUInt32LE(mode, 0);
384 this.sendCommand(16, data, function (header, fn, opt) {
385 opt.unshift(header.Status);
387 }, callback, optional);
389 this.startConfiguration = function startConfiguration(callback) {
391 for (var i = 1; i < arguments.length; ++i) { optional.push(arguments[i]); }
392 this.sendCommand(0x29, null, function (header, fn, opt) { opt.unshift(header.Status); fn.apply(this, opt); }, callback, optional);
394 this.stopConfiguration = function stopConfiguration(callback) {
396 for (var i = 1; i < arguments.length; ++i) { optional.push(arguments[i]); }
397 this.sendCommand(0x5E, null, function (header, fn, opt) { opt.unshift(header.Status); fn.apply(this, opt); }, callback, optional);
399 this.openUserInitiatedConnection = function openUserInitiatedConnection(callback) {
401 for (var i = 1; i < arguments.length; ++i) { optional.push(arguments[i]); }
402 this.sendCommand(0x44, null, function (header, fn, opt) { opt.unshift(header.Status); fn.apply(this, opt); }, callback, optional);
404 this.closeUserInitiatedConnection = function closeUnserInitiatedConnected(callback) {
406 for (var i = 1; i < arguments.length; ++i) { optional.push(arguments[i]); }
407 this.sendCommand(0x45, null, function (header, fn, opt) { opt.unshift(header.Status); fn.apply(this, opt); }, callback, optional);
409 this.getRemoteAccessConnectionStatus = function getRemoteAccessConnectionStatus(callback) {
411 for (var i = 1; i < arguments.length; ++i) { optional.push(arguments[i]); }
412 this.sendCommand(0x46, null, function (header, fn, opt) {
413 if (header.Status == 0) {
414 var hostname = header.Data.slice(14, header.Data.readUInt16LE(12) + 14).toString()
415 opt.unshift({ status: header.Status, networkStatus: header.Data.readUInt32LE(0), remoteAccessStatus: header.Data.readUInt32LE(4), remoteAccessTrigger: header.Data.readUInt32LE(8), mpsHostname: hostname, raw: header.Data });
417 opt.unshift({ status: header.Status });
420 }, callback, optional);
422 this.getProtocolVersion = function getProtocolVersion(callback) {
424 for (var i = 1; i < arguments.length; ++i) { opt.push(arguments[i]); }
426 if (!this._tmpSession) { this._tmpSession = heci.create(); this._tmpSession.parent = this; }
427 this._tmpSession.doIoctl(heci.IOCTL.HECI_VERSION, Buffer.alloc(5), Buffer.alloc(5), function (status, buffer, self, fn, opt) {
429 var result = buffer.readUInt8(0).toString() + '.' + buffer.readUInt8(1).toString() + '.' + buffer.readUInt8(2).toString() + '.' + buffer.readUInt16BE(3).toString();
438 }, this, callback, optional);
440 this.startConfigurationHBased = function startConfigurationHBased(certHash, hostVpn, dnsSuffixList, func) {
441 if ((certHash == null) || ((certHash.length != 32) && (certHash.length != 48))) { func({ status: -101 }); }
442 this.stopConfiguration(function (status) {
444 // We stopped the configuration, wait 20 seconds before starting up again.
445 var f = function tf() { delete tf.parent.xtimeout; tf.parent.startConfigurationHBasedEx(certHash, hostVpn, dnsSuffixList, func); }
447 this.xtimeout = setTimeout(f, 20000);
449 // We are not in the connect mode, this is good, start configuration right away.
450 this.startConfigurationHBasedEx(certHash, hostVpn, dnsSuffixList, func);
454 this.startConfigurationHBasedEx = function startConfigurationHBased(certHash, hostVpn, dnsSuffixList, func) {
456 for (var i = 4; i < arguments.length; ++i) { optional.push(arguments[i]); }
458 // Format the command
459 var data = Buffer.alloc(1 + 64 + 4 + 4 + ((dnsSuffixList != null) ? 320 : 0));
460 data[0] = (certHash.length == 48) ? 3 : 2 // Write certificate hash type: SHA256 = 2, SHA384 = 3
461 certHash.copy(data, 1); // Write the hash
462 data.writeUInt32LE(hostVpn ? 1 : 0, 65); // Write is HostVPN is enabled
463 if (dnsSuffixList != null) {
464 data.writeUInt32LE(dnsSuffixList.length, 69); // Write the number of DNS Suffix, from 0 to 4
466 for (var i = 0; i < dnsSuffixList.length; i++) { ptr += data.write(dnsSuffixList[i], ptr) + 1; } // Write up to 4 DNS Suffix with null seperation.
470 this.sendCommand(139, data, function (header, fn, opt) {
471 if (header.Status == 0) {
473 if (header.Data[0] == 2) { amtHash = header.Data.slice(1, 33); } // SHA256
474 if (header.Data[0] == 3) { amtHash = header.Data.slice(1, 49); } // SHA384
475 opt.unshift({ status: header.Status, hash: amtHash.toString('hex') });
477 opt.unshift({ status: header.Status });
484module.exports = amt_heci;
488AMT_STATUS_SUCCESS = 0,
489AMT_STATUS_INTERNAL_ERROR = 1,
490AMT_STATUS_INVALID_AMT_MODE = 3,
491AMT_STATUS_INVALID_MESSAGE_LENGTH = 4,
492AMT_STATUS_MAX_LIMIT_REACHED = 23,
493AMT_STATUS_INVALID_PARAMETER = 36,
494AMT_STATUS_RNG_GENERATION_IN_PROGRESS = 47,
495AMT_STATUS_RNG_NOT_READY = 48,
496AMT_STATUS_CERTIFICATE_NOT_READY = 49,
497AMT_STATUS_INVALID_HANDLE = 2053
498AMT_STATUS_NOT_FOUND = 2068,