deadbyte-mcp 0.15.21 → 0.15.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/MANIFEST.SHA256 +31 -22
- package/PROVENANCE.json +10 -10
- package/README.txt +7 -7
- package/RELEASE.json +45 -16
- package/SBOM.cdx.json +33 -7
- package/TEST-MATRIX.json +85 -18
- package/bin/deadbyte-hardware-observer-r45.exe +0 -0
- package/bin/deadbyte-hardware-observer-r45.obj +0 -0
- package/docs/R48-FULL-TOOL-CATALOG.json +124 -0
- package/native/deadbyte-hardware-observer.cpp +535 -12
- package/package.json +94 -93
- package/scripts/build-hardware-r45.ps1 +19 -0
- package/scripts/full-regression-r48-batch.mjs +27 -0
- package/scripts/promote-local-c-generation.ps1 +217 -0
- package/scripts/promote-local-d-generation.ps1 +217 -0
- package/scripts/test-r48-post-bump.ps1 +8 -0
- package/scripts/test-r48-windows-observation.ps1 +8 -0
- package/src/capability-ledger.mjs +7 -0
- package/src/capability-resolver.mjs +7 -0
- package/src/compact-gateway-mcp-tools.mjs +16 -2
- package/src/machine-fs.mjs +16 -3
- package/src/observation-mcp-tools.mjs +13 -1
- package/src/observation-output-contracts.mjs +8 -0
- package/src/observation-runtime.mjs +54 -2
- package/src/version.mjs +1 -1
- package/test/full-max-catalog-r47.test.mjs +1 -1
- package/test/full-max-catalog-r48.test.mjs +78 -0
- package/test/machine-fs-runtime.test.mjs +28 -3
- package/test/observation-mcp-tools.test.mjs +4 -3
- package/test/r48-release-contract.test.mjs +18 -14
- package/test/windows-observation-r48.test.mjs +63 -0
- package/test/windows-observation-surface-r48.test.mjs +70 -0
|
@@ -12,12 +12,15 @@
|
|
|
12
12
|
#include <cstring>
|
|
13
13
|
#include <iostream>
|
|
14
14
|
#include <sstream>
|
|
15
|
+
#include <stdexcept>
|
|
15
16
|
#include <string>
|
|
16
17
|
#include <vector>
|
|
17
18
|
|
|
19
|
+
#pragma comment(lib, "advapi32.lib")
|
|
18
20
|
#pragma comment(lib, "iphlpapi.lib")
|
|
19
21
|
#pragma comment(lib, "setupapi.lib")
|
|
20
22
|
#pragma comment(lib, "user32.lib")
|
|
23
|
+
#pragma comment(lib, "ws2_32.lib")
|
|
21
24
|
|
|
22
25
|
static std::string utf8(const wchar_t* text) {
|
|
23
26
|
if (!text || !*text) return {};
|
|
@@ -62,6 +65,12 @@ static std::string trim_ascii(std::string value) {
|
|
|
62
65
|
return value.substr(first);
|
|
63
66
|
}
|
|
64
67
|
|
|
68
|
+
static std::string win32_error(const char* operation, DWORD code = GetLastError()) {
|
|
69
|
+
std::ostringstream out;
|
|
70
|
+
out << operation << " failed win32=" << code;
|
|
71
|
+
return out.str();
|
|
72
|
+
}
|
|
73
|
+
|
|
65
74
|
static std::string cpu_json() {
|
|
66
75
|
int regs[4] = {};
|
|
67
76
|
__cpuid(regs, 0);
|
|
@@ -86,8 +95,8 @@ static std::string cpu_json() {
|
|
|
86
95
|
__cpuid(regs, 1);
|
|
87
96
|
const unsigned int eax = static_cast<unsigned int>(regs[0]);
|
|
88
97
|
stepping = eax & 0x0fU;
|
|
89
|
-
unsigned int base_model = (eax >> 4U) & 0x0fU;
|
|
90
|
-
unsigned int base_family = (eax >> 8U) & 0x0fU;
|
|
98
|
+
const unsigned int base_model = (eax >> 4U) & 0x0fU;
|
|
99
|
+
const unsigned int base_family = (eax >> 8U) & 0x0fU;
|
|
91
100
|
const unsigned int ext_model = (eax >> 16U) & 0x0fU;
|
|
92
101
|
const unsigned int ext_family = (eax >> 20U) & 0xffU;
|
|
93
102
|
family = base_family == 0x0fU ? base_family + ext_family : base_family;
|
|
@@ -230,7 +239,7 @@ static std::string get_device_property(HDEVINFO info, SP_DEVINFO_DATA& data, DWO
|
|
|
230
239
|
DWORD type = 0, required = 0;
|
|
231
240
|
SetupDiGetDeviceRegistryPropertyW(info, &data, property, &type, nullptr, 0, &required);
|
|
232
241
|
if (required == 0) return {};
|
|
233
|
-
std::vector<BYTE> bytes(required + sizeof(wchar_t), 0);
|
|
242
|
+
std::vector<BYTE> bytes(static_cast<size_t>(required) + sizeof(wchar_t), 0);
|
|
234
243
|
if (!SetupDiGetDeviceRegistryPropertyW(info, &data, property, &type, bytes.data(), static_cast<DWORD>(bytes.size()), nullptr)) return {};
|
|
235
244
|
return utf8(reinterpret_cast<const wchar_t*>(bytes.data()));
|
|
236
245
|
}
|
|
@@ -244,9 +253,9 @@ static std::string get_instance_id(HDEVINFO info, SP_DEVINFO_DATA& data) {
|
|
|
244
253
|
return utf8(buffer.data());
|
|
245
254
|
}
|
|
246
255
|
|
|
247
|
-
static std::string
|
|
248
|
-
HDEVINFO info = SetupDiGetClassDevsW(nullptr,
|
|
249
|
-
if (info == INVALID_HANDLE_VALUE) throw std::runtime_error("SetupDiGetClassDevsW
|
|
256
|
+
static std::string bus_json(const wchar_t* enumerator) {
|
|
257
|
+
HDEVINFO info = SetupDiGetClassDevsW(nullptr, enumerator, nullptr, DIGCF_ALLCLASSES | DIGCF_PRESENT);
|
|
258
|
+
if (info == INVALID_HANDLE_VALUE) throw std::runtime_error("SetupDiGetClassDevsW failed");
|
|
250
259
|
|
|
251
260
|
std::ostringstream out;
|
|
252
261
|
out << "{\"devices\":[";
|
|
@@ -275,7 +284,479 @@ static std::string pci_json() {
|
|
|
275
284
|
return out.str();
|
|
276
285
|
}
|
|
277
286
|
|
|
278
|
-
static std::string
|
|
287
|
+
static std::string pci_json() {
|
|
288
|
+
return bus_json(L"PCI");
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
static std::string usb_json() {
|
|
292
|
+
return bus_json(L"USB");
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
static std::string service_state_name(DWORD state) {
|
|
296
|
+
switch (state) {
|
|
297
|
+
case SERVICE_STOPPED: return "stopped";
|
|
298
|
+
case SERVICE_START_PENDING: return "start_pending";
|
|
299
|
+
case SERVICE_STOP_PENDING: return "stop_pending";
|
|
300
|
+
case SERVICE_RUNNING: return "running";
|
|
301
|
+
case SERVICE_CONTINUE_PENDING: return "continue_pending";
|
|
302
|
+
case SERVICE_PAUSE_PENDING: return "pause_pending";
|
|
303
|
+
case SERVICE_PAUSED: return "paused";
|
|
304
|
+
default: return "unknown";
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
static std::string services_json(DWORD service_type, const char* array_name) {
|
|
309
|
+
SC_HANDLE scm = OpenSCManagerW(nullptr, nullptr, SC_MANAGER_ENUMERATE_SERVICE);
|
|
310
|
+
if (!scm) throw std::runtime_error(win32_error("OpenSCManagerW"));
|
|
311
|
+
|
|
312
|
+
DWORD bytes_needed = 0;
|
|
313
|
+
DWORD count = 0;
|
|
314
|
+
DWORD resume = 0;
|
|
315
|
+
EnumServicesStatusExW(scm, SC_ENUM_PROCESS_INFO, service_type, SERVICE_STATE_ALL, nullptr, 0,
|
|
316
|
+
&bytes_needed, &count, &resume, nullptr);
|
|
317
|
+
const DWORD first_error = GetLastError();
|
|
318
|
+
if (bytes_needed == 0 && first_error != ERROR_MORE_DATA) {
|
|
319
|
+
CloseServiceHandle(scm);
|
|
320
|
+
if (first_error == ERROR_SUCCESS) return std::string("{\"") + array_name + "\":[]}";
|
|
321
|
+
throw std::runtime_error(win32_error("EnumServicesStatusExW(size)", first_error));
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
std::vector<BYTE> buffer(bytes_needed);
|
|
325
|
+
resume = 0;
|
|
326
|
+
if (!EnumServicesStatusExW(scm, SC_ENUM_PROCESS_INFO, service_type, SERVICE_STATE_ALL, buffer.data(),
|
|
327
|
+
static_cast<DWORD>(buffer.size()), &bytes_needed, &count, &resume, nullptr)) {
|
|
328
|
+
const DWORD error = GetLastError();
|
|
329
|
+
CloseServiceHandle(scm);
|
|
330
|
+
throw std::runtime_error(win32_error("EnumServicesStatusExW", error));
|
|
331
|
+
}
|
|
332
|
+
const auto* rows = reinterpret_cast<const ENUM_SERVICE_STATUS_PROCESSW*>(buffer.data());
|
|
333
|
+
|
|
334
|
+
std::ostringstream out;
|
|
335
|
+
out << "{\"" << array_name << "\":[";
|
|
336
|
+
for (DWORD index = 0; index < count; ++index) {
|
|
337
|
+
if (index) out << ',';
|
|
338
|
+
const auto& row = rows[index];
|
|
339
|
+
out << "{\"name\":" << quote(utf8(row.lpServiceName))
|
|
340
|
+
<< ",\"display_name\":" << quote(utf8(row.lpDisplayName))
|
|
341
|
+
<< ",\"service_type\":" << row.ServiceStatusProcess.dwServiceType
|
|
342
|
+
<< ",\"state\":" << quote(service_state_name(row.ServiceStatusProcess.dwCurrentState))
|
|
343
|
+
<< ",\"state_code\":" << row.ServiceStatusProcess.dwCurrentState
|
|
344
|
+
<< ",\"process_id\":" << row.ServiceStatusProcess.dwProcessId
|
|
345
|
+
<< ",\"controls_accepted\":" << row.ServiceStatusProcess.dwControlsAccepted
|
|
346
|
+
<< ",\"win32_exit_code\":" << row.ServiceStatusProcess.dwWin32ExitCode
|
|
347
|
+
<< ",\"service_specific_exit_code\":" << row.ServiceStatusProcess.dwServiceSpecificExitCode
|
|
348
|
+
<< "}";
|
|
349
|
+
}
|
|
350
|
+
out << "]}";
|
|
351
|
+
CloseServiceHandle(scm);
|
|
352
|
+
return out.str();
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
static std::string ipv4_text(DWORD address) {
|
|
356
|
+
IN_ADDR addr{};
|
|
357
|
+
addr.S_un.S_addr = address;
|
|
358
|
+
char buffer[INET_ADDRSTRLEN] = {};
|
|
359
|
+
if (!InetNtopA(AF_INET, &addr, buffer, static_cast<DWORD>(sizeof(buffer)))) return {};
|
|
360
|
+
return buffer;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
static std::string ipv6_text(const UCHAR* address) {
|
|
364
|
+
char buffer[INET6_ADDRSTRLEN] = {};
|
|
365
|
+
if (!InetNtopA(AF_INET6, const_cast<UCHAR*>(address), buffer, static_cast<DWORD>(sizeof(buffer)))) return {};
|
|
366
|
+
return buffer;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
static unsigned short port_host(DWORD value) {
|
|
370
|
+
return ntohs(static_cast<unsigned short>(value & 0xffffU));
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
static void append_tcp4(std::ostringstream& out, bool& first) {
|
|
374
|
+
DWORD size = 0;
|
|
375
|
+
DWORD rc = GetExtendedTcpTable(nullptr, &size, FALSE, AF_INET, TCP_TABLE_OWNER_PID_ALL, 0);
|
|
376
|
+
if (rc != ERROR_INSUFFICIENT_BUFFER) {
|
|
377
|
+
if (rc == NO_ERROR) return;
|
|
378
|
+
throw std::runtime_error("GetExtendedTcpTable IPv4 size failed");
|
|
379
|
+
}
|
|
380
|
+
std::vector<BYTE> buffer(size);
|
|
381
|
+
rc = GetExtendedTcpTable(buffer.data(), &size, FALSE, AF_INET, TCP_TABLE_OWNER_PID_ALL, 0);
|
|
382
|
+
if (rc != NO_ERROR) throw std::runtime_error("GetExtendedTcpTable IPv4 failed");
|
|
383
|
+
const auto* table = reinterpret_cast<const MIB_TCPTABLE_OWNER_PID*>(buffer.data());
|
|
384
|
+
for (DWORD index = 0; index < table->dwNumEntries; ++index) {
|
|
385
|
+
const auto& row = table->table[index];
|
|
386
|
+
if (!first) out << ',';
|
|
387
|
+
first = false;
|
|
388
|
+
out << "{\"protocol\":\"tcp\",\"family\":\"ipv4\",\"local_address\":" << quote(ipv4_text(row.dwLocalAddr))
|
|
389
|
+
<< ",\"local_port\":" << port_host(row.dwLocalPort)
|
|
390
|
+
<< ",\"remote_address\":" << quote(ipv4_text(row.dwRemoteAddr))
|
|
391
|
+
<< ",\"remote_port\":" << port_host(row.dwRemotePort)
|
|
392
|
+
<< ",\"state\":" << row.dwState
|
|
393
|
+
<< ",\"pid\":" << row.dwOwningPid
|
|
394
|
+
<< "}";
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
static void append_tcp6(std::ostringstream& out, bool& first) {
|
|
399
|
+
DWORD size = 0;
|
|
400
|
+
DWORD rc = GetExtendedTcpTable(nullptr, &size, FALSE, AF_INET6, TCP_TABLE_OWNER_PID_ALL, 0);
|
|
401
|
+
if (rc != ERROR_INSUFFICIENT_BUFFER) {
|
|
402
|
+
if (rc == NO_ERROR) return;
|
|
403
|
+
throw std::runtime_error("GetExtendedTcpTable IPv6 size failed");
|
|
404
|
+
}
|
|
405
|
+
std::vector<BYTE> buffer(size);
|
|
406
|
+
rc = GetExtendedTcpTable(buffer.data(), &size, FALSE, AF_INET6, TCP_TABLE_OWNER_PID_ALL, 0);
|
|
407
|
+
if (rc != NO_ERROR) throw std::runtime_error("GetExtendedTcpTable IPv6 failed");
|
|
408
|
+
const auto* table = reinterpret_cast<const MIB_TCP6TABLE_OWNER_PID*>(buffer.data());
|
|
409
|
+
for (DWORD index = 0; index < table->dwNumEntries; ++index) {
|
|
410
|
+
const auto& row = table->table[index];
|
|
411
|
+
if (!first) out << ',';
|
|
412
|
+
first = false;
|
|
413
|
+
out << "{\"protocol\":\"tcp\",\"family\":\"ipv6\",\"local_address\":" << quote(ipv6_text(row.ucLocalAddr))
|
|
414
|
+
<< ",\"local_scope_id\":" << row.dwLocalScopeId
|
|
415
|
+
<< ",\"local_port\":" << port_host(row.dwLocalPort)
|
|
416
|
+
<< ",\"remote_address\":" << quote(ipv6_text(row.ucRemoteAddr))
|
|
417
|
+
<< ",\"remote_scope_id\":" << row.dwRemoteScopeId
|
|
418
|
+
<< ",\"remote_port\":" << port_host(row.dwRemotePort)
|
|
419
|
+
<< ",\"state\":" << row.dwState
|
|
420
|
+
<< ",\"pid\":" << row.dwOwningPid
|
|
421
|
+
<< "}";
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
static void append_udp4(std::ostringstream& out, bool& first) {
|
|
426
|
+
DWORD size = 0;
|
|
427
|
+
DWORD rc = GetExtendedUdpTable(nullptr, &size, FALSE, AF_INET, UDP_TABLE_OWNER_PID, 0);
|
|
428
|
+
if (rc != ERROR_INSUFFICIENT_BUFFER) {
|
|
429
|
+
if (rc == NO_ERROR) return;
|
|
430
|
+
throw std::runtime_error("GetExtendedUdpTable IPv4 size failed");
|
|
431
|
+
}
|
|
432
|
+
std::vector<BYTE> buffer(size);
|
|
433
|
+
rc = GetExtendedUdpTable(buffer.data(), &size, FALSE, AF_INET, UDP_TABLE_OWNER_PID, 0);
|
|
434
|
+
if (rc != NO_ERROR) throw std::runtime_error("GetExtendedUdpTable IPv4 failed");
|
|
435
|
+
const auto* table = reinterpret_cast<const MIB_UDPTABLE_OWNER_PID*>(buffer.data());
|
|
436
|
+
for (DWORD index = 0; index < table->dwNumEntries; ++index) {
|
|
437
|
+
const auto& row = table->table[index];
|
|
438
|
+
if (!first) out << ',';
|
|
439
|
+
first = false;
|
|
440
|
+
out << "{\"protocol\":\"udp\",\"family\":\"ipv4\",\"local_address\":" << quote(ipv4_text(row.dwLocalAddr))
|
|
441
|
+
<< ",\"local_port\":" << port_host(row.dwLocalPort)
|
|
442
|
+
<< ",\"pid\":" << row.dwOwningPid
|
|
443
|
+
<< "}";
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
static void append_udp6(std::ostringstream& out, bool& first) {
|
|
448
|
+
DWORD size = 0;
|
|
449
|
+
DWORD rc = GetExtendedUdpTable(nullptr, &size, FALSE, AF_INET6, UDP_TABLE_OWNER_PID, 0);
|
|
450
|
+
if (rc != ERROR_INSUFFICIENT_BUFFER) {
|
|
451
|
+
if (rc == NO_ERROR) return;
|
|
452
|
+
throw std::runtime_error("GetExtendedUdpTable IPv6 size failed");
|
|
453
|
+
}
|
|
454
|
+
std::vector<BYTE> buffer(size);
|
|
455
|
+
rc = GetExtendedUdpTable(buffer.data(), &size, FALSE, AF_INET6, UDP_TABLE_OWNER_PID, 0);
|
|
456
|
+
if (rc != NO_ERROR) throw std::runtime_error("GetExtendedUdpTable IPv6 failed");
|
|
457
|
+
const auto* table = reinterpret_cast<const MIB_UDP6TABLE_OWNER_PID*>(buffer.data());
|
|
458
|
+
for (DWORD index = 0; index < table->dwNumEntries; ++index) {
|
|
459
|
+
const auto& row = table->table[index];
|
|
460
|
+
if (!first) out << ',';
|
|
461
|
+
first = false;
|
|
462
|
+
out << "{\"protocol\":\"udp\",\"family\":\"ipv6\",\"local_address\":" << quote(ipv6_text(row.ucLocalAddr))
|
|
463
|
+
<< ",\"local_scope_id\":" << row.dwLocalScopeId
|
|
464
|
+
<< ",\"local_port\":" << port_host(row.dwLocalPort)
|
|
465
|
+
<< ",\"pid\":" << row.dwOwningPid
|
|
466
|
+
<< "}";
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
static std::string sockets_json() {
|
|
471
|
+
WSADATA wsa{};
|
|
472
|
+
if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) throw std::runtime_error("WSAStartup failed");
|
|
473
|
+
try {
|
|
474
|
+
std::ostringstream out;
|
|
475
|
+
out << "{\"sockets\":[";
|
|
476
|
+
bool first = true;
|
|
477
|
+
append_tcp4(out, first);
|
|
478
|
+
append_tcp6(out, first);
|
|
479
|
+
append_udp4(out, first);
|
|
480
|
+
append_udp6(out, first);
|
|
481
|
+
out << "]}";
|
|
482
|
+
WSACleanup();
|
|
483
|
+
return out.str();
|
|
484
|
+
} catch (...) {
|
|
485
|
+
WSACleanup();
|
|
486
|
+
throw;
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
static std::vector<std::string> volume_paths(const wchar_t* volume_name) {
|
|
491
|
+
DWORD needed = 0;
|
|
492
|
+
GetVolumePathNamesForVolumeNameW(volume_name, nullptr, 0, &needed);
|
|
493
|
+
if (needed == 0) return {};
|
|
494
|
+
std::vector<wchar_t> buffer(static_cast<size_t>(needed) + 1U, L'\0');
|
|
495
|
+
if (!GetVolumePathNamesForVolumeNameW(volume_name, buffer.data(), static_cast<DWORD>(buffer.size()), &needed)) return {};
|
|
496
|
+
std::vector<std::string> paths;
|
|
497
|
+
for (const wchar_t* item = buffer.data(); *item; item += wcslen(item) + 1U) paths.push_back(utf8(item));
|
|
498
|
+
return paths;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
static std::string volumes_json() {
|
|
502
|
+
std::array<wchar_t, MAX_PATH + 1> volume{};
|
|
503
|
+
HANDLE find = FindFirstVolumeW(volume.data(), static_cast<DWORD>(volume.size()));
|
|
504
|
+
if (find == INVALID_HANDLE_VALUE) throw std::runtime_error(win32_error("FindFirstVolumeW"));
|
|
505
|
+
|
|
506
|
+
std::ostringstream out;
|
|
507
|
+
out << "{\"volumes\":[";
|
|
508
|
+
bool first = true;
|
|
509
|
+
for (;;) {
|
|
510
|
+
wchar_t label[MAX_PATH + 1] = {};
|
|
511
|
+
wchar_t filesystem[MAX_PATH + 1] = {};
|
|
512
|
+
DWORD serial = 0, max_component = 0, flags = 0;
|
|
513
|
+
const BOOL volume_ok = GetVolumeInformationW(volume.data(), label, MAX_PATH, &serial, &max_component, &flags, filesystem, MAX_PATH);
|
|
514
|
+
ULARGE_INTEGER available{}, total{}, free_bytes{};
|
|
515
|
+
const BOOL space_ok = GetDiskFreeSpaceExW(volume.data(), &available, &total, &free_bytes);
|
|
516
|
+
const auto paths = volume_paths(volume.data());
|
|
517
|
+
if (!first) out << ',';
|
|
518
|
+
first = false;
|
|
519
|
+
out << "{\"volume_name\":" << quote(utf8(volume.data()))
|
|
520
|
+
<< ",\"label\":" << quote(volume_ok ? utf8(label) : std::string())
|
|
521
|
+
<< ",\"filesystem\":" << quote(volume_ok ? utf8(filesystem) : std::string())
|
|
522
|
+
<< ",\"serial\":" << (volume_ok ? serial : 0U)
|
|
523
|
+
<< ",\"flags\":" << (volume_ok ? flags : 0U)
|
|
524
|
+
<< ",\"total_bytes\":" << (space_ok ? total.QuadPart : 0ULL)
|
|
525
|
+
<< ",\"free_bytes\":" << (space_ok ? free_bytes.QuadPart : 0ULL)
|
|
526
|
+
<< ",\"available_bytes\":" << (space_ok ? available.QuadPart : 0ULL)
|
|
527
|
+
<< ",\"paths\":[";
|
|
528
|
+
for (size_t index = 0; index < paths.size(); ++index) {
|
|
529
|
+
if (index) out << ',';
|
|
530
|
+
out << quote(paths[index]);
|
|
531
|
+
}
|
|
532
|
+
out << "]}";
|
|
533
|
+
|
|
534
|
+
if (FindNextVolumeW(find, volume.data(), static_cast<DWORD>(volume.size()))) continue;
|
|
535
|
+
const DWORD error = GetLastError();
|
|
536
|
+
FindVolumeClose(find);
|
|
537
|
+
if (error != ERROR_NO_MORE_FILES) throw std::runtime_error(win32_error("FindNextVolumeW", error));
|
|
538
|
+
break;
|
|
539
|
+
}
|
|
540
|
+
out << "]}";
|
|
541
|
+
return out.str();
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
static HKEY hive_from_name(const std::wstring& hive) {
|
|
545
|
+
if (hive == L"HKLM") return HKEY_LOCAL_MACHINE;
|
|
546
|
+
if (hive == L"HKCU") return HKEY_CURRENT_USER;
|
|
547
|
+
throw std::runtime_error("registry hive must be HKLM or HKCU");
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
static std::string registry_type_name(DWORD type) {
|
|
551
|
+
switch (type) {
|
|
552
|
+
case REG_NONE: return "REG_NONE";
|
|
553
|
+
case REG_SZ: return "REG_SZ";
|
|
554
|
+
case REG_EXPAND_SZ: return "REG_EXPAND_SZ";
|
|
555
|
+
case REG_BINARY: return "REG_BINARY";
|
|
556
|
+
case REG_DWORD: return "REG_DWORD";
|
|
557
|
+
case REG_MULTI_SZ: return "REG_MULTI_SZ";
|
|
558
|
+
case REG_QWORD: return "REG_QWORD";
|
|
559
|
+
default: return "REG_OTHER";
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
static std::string bytes_hex(const BYTE* data, DWORD size, DWORD limit = 4096) {
|
|
564
|
+
static const char hex[] = "0123456789abcdef";
|
|
565
|
+
const DWORD bounded = (std::min)(size, limit);
|
|
566
|
+
std::string out;
|
|
567
|
+
out.reserve(static_cast<size_t>(bounded) * 2U);
|
|
568
|
+
for (DWORD i = 0; i < bounded; ++i) {
|
|
569
|
+
out.push_back(hex[(data[i] >> 4U) & 0x0fU]);
|
|
570
|
+
out.push_back(hex[data[i] & 0x0fU]);
|
|
571
|
+
}
|
|
572
|
+
return out;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
static std::string registry_data_json(DWORD type, const BYTE* data, DWORD size) {
|
|
576
|
+
if ((type == REG_SZ || type == REG_EXPAND_SZ) && size >= sizeof(wchar_t)) {
|
|
577
|
+
return quote(utf8(reinterpret_cast<const wchar_t*>(data)));
|
|
578
|
+
}
|
|
579
|
+
if (type == REG_DWORD && size >= sizeof(DWORD)) {
|
|
580
|
+
DWORD value = 0;
|
|
581
|
+
std::memcpy(&value, data, sizeof(value));
|
|
582
|
+
return std::to_string(value);
|
|
583
|
+
}
|
|
584
|
+
if (type == REG_QWORD && size >= sizeof(ULONGLONG)) {
|
|
585
|
+
ULONGLONG value = 0;
|
|
586
|
+
std::memcpy(&value, data, sizeof(value));
|
|
587
|
+
return quote(std::to_string(value));
|
|
588
|
+
}
|
|
589
|
+
if (type == REG_MULTI_SZ && size >= sizeof(wchar_t)) {
|
|
590
|
+
const wchar_t* cursor = reinterpret_cast<const wchar_t*>(data);
|
|
591
|
+
const wchar_t* end = reinterpret_cast<const wchar_t*>(data + size);
|
|
592
|
+
std::ostringstream out;
|
|
593
|
+
out << '[';
|
|
594
|
+
bool first = true;
|
|
595
|
+
while (cursor < end && *cursor) {
|
|
596
|
+
if (!first) out << ',';
|
|
597
|
+
first = false;
|
|
598
|
+
out << quote(utf8(cursor));
|
|
599
|
+
cursor += wcslen(cursor) + 1U;
|
|
600
|
+
}
|
|
601
|
+
out << ']';
|
|
602
|
+
return out.str();
|
|
603
|
+
}
|
|
604
|
+
return quote(bytes_hex(data, size));
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
static std::string registry_value_json(HKEY key, const std::wstring& value_name) {
|
|
608
|
+
DWORD type = 0;
|
|
609
|
+
DWORD size = 0;
|
|
610
|
+
const wchar_t* name = value_name.empty() ? L"" : value_name.c_str();
|
|
611
|
+
LONG rc = RegQueryValueExW(key, name, nullptr, &type, nullptr, &size);
|
|
612
|
+
if (rc != ERROR_SUCCESS) throw std::runtime_error(win32_error("RegQueryValueExW(size)", static_cast<DWORD>(rc)));
|
|
613
|
+
if (size > 1024U * 1024U) throw std::runtime_error("registry value exceeds 1 MiB bound");
|
|
614
|
+
std::vector<BYTE> data(static_cast<size_t>(size) + sizeof(wchar_t), 0);
|
|
615
|
+
rc = RegQueryValueExW(key, name, nullptr, &type, data.data(), &size);
|
|
616
|
+
if (rc != ERROR_SUCCESS) throw std::runtime_error(win32_error("RegQueryValueExW", static_cast<DWORD>(rc)));
|
|
617
|
+
std::ostringstream out;
|
|
618
|
+
out << "{\"name\":" << quote(utf8(name))
|
|
619
|
+
<< ",\"type\":" << quote(registry_type_name(type))
|
|
620
|
+
<< ",\"data\":" << registry_data_json(type, data.data(), size)
|
|
621
|
+
<< "}";
|
|
622
|
+
return out.str();
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
static std::string registry_json(const std::wstring& hive_name, const std::wstring& key_name, const wchar_t* value_name) {
|
|
626
|
+
HKEY key = nullptr;
|
|
627
|
+
const LONG open_rc = RegOpenKeyExW(hive_from_name(hive_name), key_name.c_str(), 0, KEY_READ | KEY_WOW64_64KEY, &key);
|
|
628
|
+
if (open_rc != ERROR_SUCCESS) throw std::runtime_error(win32_error("RegOpenKeyExW", static_cast<DWORD>(open_rc)));
|
|
629
|
+
|
|
630
|
+
try {
|
|
631
|
+
std::ostringstream out;
|
|
632
|
+
out << "{\"hive\":" << quote(utf8(hive_name.c_str()))
|
|
633
|
+
<< ",\"key\":" << quote(utf8(key_name.c_str()));
|
|
634
|
+
if (value_name != nullptr) {
|
|
635
|
+
out << ",\"value\":" << registry_value_json(key, value_name) << "}";
|
|
636
|
+
RegCloseKey(key);
|
|
637
|
+
return out.str();
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
out << ",\"subkeys\":[";
|
|
641
|
+
bool first = true;
|
|
642
|
+
for (DWORD index = 0; index < 1024U; ++index) {
|
|
643
|
+
std::array<wchar_t, 16384> name{};
|
|
644
|
+
DWORD chars = static_cast<DWORD>(name.size());
|
|
645
|
+
FILETIME written{};
|
|
646
|
+
const LONG rc = RegEnumKeyExW(key, index, name.data(), &chars, nullptr, nullptr, nullptr, &written);
|
|
647
|
+
if (rc == ERROR_NO_MORE_ITEMS) break;
|
|
648
|
+
if (rc != ERROR_SUCCESS) throw std::runtime_error(win32_error("RegEnumKeyExW", static_cast<DWORD>(rc)));
|
|
649
|
+
if (!first) out << ',';
|
|
650
|
+
first = false;
|
|
651
|
+
out << quote(utf8(name.data()));
|
|
652
|
+
}
|
|
653
|
+
out << "],\"values\":[";
|
|
654
|
+
first = true;
|
|
655
|
+
for (DWORD index = 0; index < 1024U; ++index) {
|
|
656
|
+
std::array<wchar_t, 16384> name{};
|
|
657
|
+
DWORD chars = static_cast<DWORD>(name.size());
|
|
658
|
+
DWORD type = 0;
|
|
659
|
+
DWORD size = 0;
|
|
660
|
+
LONG rc = RegEnumValueW(key, index, name.data(), &chars, nullptr, &type, nullptr, &size);
|
|
661
|
+
if (rc == ERROR_NO_MORE_ITEMS) break;
|
|
662
|
+
if (rc != ERROR_SUCCESS && rc != ERROR_MORE_DATA) throw std::runtime_error(win32_error("RegEnumValueW(size)", static_cast<DWORD>(rc)));
|
|
663
|
+
const std::wstring value(name.data(), chars);
|
|
664
|
+
if (!first) out << ',';
|
|
665
|
+
first = false;
|
|
666
|
+
out << registry_value_json(key, value);
|
|
667
|
+
}
|
|
668
|
+
out << "]}";
|
|
669
|
+
RegCloseKey(key);
|
|
670
|
+
return out.str();
|
|
671
|
+
} catch (...) {
|
|
672
|
+
RegCloseKey(key);
|
|
673
|
+
throw;
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
static bool reg_read_string(HKEY key, const wchar_t* name, std::string& out_value) {
|
|
678
|
+
DWORD type = 0;
|
|
679
|
+
DWORD size = 0;
|
|
680
|
+
LONG rc = RegQueryValueExW(key, name, nullptr, &type, nullptr, &size);
|
|
681
|
+
if (rc != ERROR_SUCCESS || (type != REG_SZ && type != REG_EXPAND_SZ) || size < sizeof(wchar_t)) return false;
|
|
682
|
+
std::vector<BYTE> data(static_cast<size_t>(size) + sizeof(wchar_t), 0);
|
|
683
|
+
rc = RegQueryValueExW(key, name, nullptr, &type, data.data(), &size);
|
|
684
|
+
if (rc != ERROR_SUCCESS) return false;
|
|
685
|
+
out_value = utf8(reinterpret_cast<const wchar_t*>(data.data()));
|
|
686
|
+
return true;
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
static bool reg_read_dword(HKEY key, const wchar_t* name, DWORD& out_value) {
|
|
690
|
+
DWORD type = 0;
|
|
691
|
+
DWORD size = sizeof(out_value);
|
|
692
|
+
DWORD value = 0;
|
|
693
|
+
const LONG rc = RegQueryValueExW(key, name, nullptr, &type, reinterpret_cast<BYTE*>(&value), &size);
|
|
694
|
+
if (rc != ERROR_SUCCESS || type != REG_DWORD || size != sizeof(value)) return false;
|
|
695
|
+
out_value = value;
|
|
696
|
+
return true;
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
static std::string wsl_json() {
|
|
700
|
+
const wchar_t* lxss_path = L"Software\\Microsoft\\Windows\\CurrentVersion\\Lxss";
|
|
701
|
+
HKEY root = nullptr;
|
|
702
|
+
const LONG open_rc = RegOpenKeyExW(HKEY_CURRENT_USER, lxss_path, 0, KEY_READ | KEY_WOW64_64KEY, &root);
|
|
703
|
+
if (open_rc == ERROR_FILE_NOT_FOUND) return "{\"distributions\":[]}";
|
|
704
|
+
if (open_rc != ERROR_SUCCESS) throw std::runtime_error(win32_error("RegOpenKeyExW(WSL)", static_cast<DWORD>(open_rc)));
|
|
705
|
+
|
|
706
|
+
std::ostringstream out;
|
|
707
|
+
out << "{\"distributions\":[";
|
|
708
|
+
bool first = true;
|
|
709
|
+
try {
|
|
710
|
+
for (DWORD index = 0; index < 1024U; ++index) {
|
|
711
|
+
std::array<wchar_t, 256> subkey_name{};
|
|
712
|
+
DWORD chars = static_cast<DWORD>(subkey_name.size());
|
|
713
|
+
FILETIME written{};
|
|
714
|
+
const LONG rc = RegEnumKeyExW(root, index, subkey_name.data(), &chars, nullptr, nullptr, nullptr, &written);
|
|
715
|
+
if (rc == ERROR_NO_MORE_ITEMS) break;
|
|
716
|
+
if (rc != ERROR_SUCCESS) throw std::runtime_error(win32_error("RegEnumKeyExW(WSL)", static_cast<DWORD>(rc)));
|
|
717
|
+
HKEY distro = nullptr;
|
|
718
|
+
if (RegOpenKeyExW(root, subkey_name.data(), 0, KEY_READ, &distro) != ERROR_SUCCESS) continue;
|
|
719
|
+
std::string name;
|
|
720
|
+
std::string base_path;
|
|
721
|
+
DWORD version = 0;
|
|
722
|
+
DWORD default_uid = 0;
|
|
723
|
+
DWORD flags = 0;
|
|
724
|
+
const bool has_name = reg_read_string(distro, L"DistributionName", name);
|
|
725
|
+
reg_read_string(distro, L"BasePath", base_path);
|
|
726
|
+
reg_read_dword(distro, L"Version", version);
|
|
727
|
+
reg_read_dword(distro, L"DefaultUid", default_uid);
|
|
728
|
+
reg_read_dword(distro, L"Flags", flags);
|
|
729
|
+
RegCloseKey(distro);
|
|
730
|
+
if (!has_name) continue;
|
|
731
|
+
if (!first) out << ',';
|
|
732
|
+
first = false;
|
|
733
|
+
out << "{\"id\":" << quote(utf8(subkey_name.data()))
|
|
734
|
+
<< ",\"name\":" << quote(name)
|
|
735
|
+
<< ",\"base_path\":" << quote(base_path)
|
|
736
|
+
<< ",\"version\":" << version
|
|
737
|
+
<< ",\"default_uid\":" << default_uid
|
|
738
|
+
<< ",\"flags\":" << flags
|
|
739
|
+
<< "}";
|
|
740
|
+
}
|
|
741
|
+
RegCloseKey(root);
|
|
742
|
+
} catch (...) {
|
|
743
|
+
RegCloseKey(root);
|
|
744
|
+
throw;
|
|
745
|
+
}
|
|
746
|
+
out << "]}";
|
|
747
|
+
return out.str();
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
static bool is_hardware_query(const std::string& query) {
|
|
751
|
+
return query == "cpu" || query == "memory" || query == "storage" || query == "network" || query == "gpu" || query == "pci";
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
static bool is_windows_query(const std::string& query) {
|
|
755
|
+
return query == "registry" || query == "services" || query == "drivers" || query == "sockets" ||
|
|
756
|
+
query == "volumes" || query == "usb" || query == "wsl";
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
static std::string build_hardware_data(const std::string& query) {
|
|
279
760
|
if (query == "cpu") return cpu_json();
|
|
280
761
|
if (query == "memory") return memory_json();
|
|
281
762
|
if (query == "storage") return storage_json();
|
|
@@ -285,6 +766,39 @@ static std::string build_data(const std::string& query) {
|
|
|
285
766
|
throw std::runtime_error("unsupported hardware query");
|
|
286
767
|
}
|
|
287
768
|
|
|
769
|
+
static std::string build_windows_data(const std::string& query, int argc, wchar_t** argv) {
|
|
770
|
+
if (query == "services") {
|
|
771
|
+
if (argc != 2) throw std::runtime_error("services query takes no selectors");
|
|
772
|
+
return services_json(SERVICE_WIN32, "services");
|
|
773
|
+
}
|
|
774
|
+
if (query == "drivers") {
|
|
775
|
+
if (argc != 2) throw std::runtime_error("drivers query takes no selectors");
|
|
776
|
+
return services_json(SERVICE_DRIVER, "drivers");
|
|
777
|
+
}
|
|
778
|
+
if (query == "sockets") {
|
|
779
|
+
if (argc != 2) throw std::runtime_error("sockets query takes no selectors");
|
|
780
|
+
return sockets_json();
|
|
781
|
+
}
|
|
782
|
+
if (query == "volumes") {
|
|
783
|
+
if (argc != 2) throw std::runtime_error("volumes query takes no selectors");
|
|
784
|
+
return volumes_json();
|
|
785
|
+
}
|
|
786
|
+
if (query == "usb") {
|
|
787
|
+
if (argc != 2) throw std::runtime_error("usb query takes no selectors");
|
|
788
|
+
return usb_json();
|
|
789
|
+
}
|
|
790
|
+
if (query == "wsl") {
|
|
791
|
+
if (argc != 2) throw std::runtime_error("wsl query takes no selectors");
|
|
792
|
+
return wsl_json();
|
|
793
|
+
}
|
|
794
|
+
if (query == "registry") {
|
|
795
|
+
if (argc != 4 && argc != 5) throw std::runtime_error("registry usage: registry <HKLM|HKCU> <key> [value]");
|
|
796
|
+
const wchar_t* value = argc == 5 ? argv[4] : nullptr;
|
|
797
|
+
return registry_json(argv[2], argv[3], value);
|
|
798
|
+
}
|
|
799
|
+
throw std::runtime_error("unsupported Windows query");
|
|
800
|
+
}
|
|
801
|
+
|
|
288
802
|
int wmain(int argc, wchar_t** argv) {
|
|
289
803
|
try {
|
|
290
804
|
if (argc == 2 && std::wstring(argv[1]) == L"--selftest") {
|
|
@@ -293,14 +807,23 @@ int wmain(int argc, wchar_t** argv) {
|
|
|
293
807
|
std::cout << "{\"status\":\"passed\",\"schema\":\"deadbyte.hardware-observer-selftest.v1\"}\n";
|
|
294
808
|
return 0;
|
|
295
809
|
}
|
|
296
|
-
if (argc
|
|
297
|
-
std::cerr << "usage: deadbyte-hardware-observer-r45.exe <cpu|gpu|memory|storage|network|pci
|
|
810
|
+
if (argc < 2) {
|
|
811
|
+
std::cerr << "usage: deadbyte-hardware-observer-r45.exe <cpu|gpu|memory|storage|network|pci|services|drivers|sockets|volumes|usb|wsl|registry> [selectors]\n";
|
|
298
812
|
return 2;
|
|
299
813
|
}
|
|
300
814
|
const std::string query = utf8(argv[1]);
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
815
|
+
if (is_hardware_query(query)) {
|
|
816
|
+
if (argc != 2) throw std::runtime_error("hardware query takes no selectors");
|
|
817
|
+
const std::string data = build_hardware_data(query);
|
|
818
|
+
std::cout << "{\"schema\":\"deadbyte.hardware-query.v1\",\"query\":" << quote(query) << ",\"data\":" << data << "}\n";
|
|
819
|
+
return 0;
|
|
820
|
+
}
|
|
821
|
+
if (is_windows_query(query)) {
|
|
822
|
+
const std::string data = build_windows_data(query, argc, argv);
|
|
823
|
+
std::cout << "{\"schema\":\"deadbyte.windows-query.v1\",\"query\":" << quote(query) << ",\"data\":" << data << "}\n";
|
|
824
|
+
return 0;
|
|
825
|
+
}
|
|
826
|
+
throw std::runtime_error("unsupported query");
|
|
304
827
|
} catch (const std::exception& error) {
|
|
305
828
|
std::cerr << error.what() << '\n';
|
|
306
829
|
return 1;
|