serialport_win.cpp 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964
  1. #include "./serialport.h"
  2. #include "./serialport_win.h"
  3. #include <napi.h>
  4. #include <uv.h>
  5. #include <list>
  6. #include <vector>
  7. #include <string.h>
  8. #include <windows.h>
  9. #include <Setupapi.h>
  10. #include <initguid.h>
  11. #include <devpkey.h>
  12. #include <devguid.h>
  13. #include <wchar.h>
  14. #pragma comment(lib, "setupapi.lib")
  15. #define ARRAY_SIZE(arr) (sizeof(arr)/sizeof(arr[0]))
  16. #define MAX_BUFFER_SIZE 1000
  17. // As per https://msdn.microsoft.com/en-us/library/windows/desktop/ms724872(v=vs.85).aspx
  18. #define MAX_REGISTRY_KEY_SIZE 255
  19. // Declare type of pointer to CancelIoEx function
  20. typedef BOOL (WINAPI *CancelIoExType)(HANDLE hFile, LPOVERLAPPED lpOverlapped);
  21. std::list<int> g_closingHandles;
  22. void ErrorCodeToString(const wchar_t* prefix, int errorCode, wchar_t *errorStr) {
  23. switch (errorCode) {
  24. case ERROR_FILE_NOT_FOUND:
  25. _snwprintf_s(errorStr, ERROR_STRING_SIZE, _TRUNCATE, L"%ls: File not found", prefix);
  26. break;
  27. case ERROR_INVALID_HANDLE:
  28. _snwprintf_s(errorStr, ERROR_STRING_SIZE, _TRUNCATE, L"%ls: Invalid handle", prefix);
  29. break;
  30. case ERROR_ACCESS_DENIED:
  31. _snwprintf_s(errorStr, ERROR_STRING_SIZE, _TRUNCATE, L"%ls: Access denied", prefix);
  32. break;
  33. case ERROR_OPERATION_ABORTED:
  34. _snwprintf_s(errorStr, ERROR_STRING_SIZE, _TRUNCATE, L"%ls: Operation aborted", prefix);
  35. break;
  36. case ERROR_INVALID_PARAMETER:
  37. _snwprintf_s(errorStr, ERROR_STRING_SIZE, _TRUNCATE, L"%ls: The parameter is incorrect %d", prefix, errorCode);
  38. break;
  39. default:
  40. _snwprintf_s(errorStr, ERROR_STRING_SIZE, _TRUNCATE, L"%ls: Unknown error code %d", prefix, errorCode);
  41. break;
  42. }
  43. }
  44. void ErrorCodeToString(const char* prefix, int errorCode, char *errorStr) {
  45. switch (errorCode) {
  46. case ERROR_FILE_NOT_FOUND:
  47. _snprintf_s(errorStr, ERROR_STRING_SIZE, _TRUNCATE, "%s: File not found", prefix);
  48. break;
  49. case ERROR_INVALID_HANDLE:
  50. _snprintf_s(errorStr, ERROR_STRING_SIZE, _TRUNCATE, "%s: Invalid handle", prefix);
  51. break;
  52. case ERROR_ACCESS_DENIED:
  53. _snprintf_s(errorStr, ERROR_STRING_SIZE, _TRUNCATE, "%s: Access denied", prefix);
  54. break;
  55. case ERROR_OPERATION_ABORTED:
  56. _snprintf_s(errorStr, ERROR_STRING_SIZE, _TRUNCATE, "%s: Operation aborted", prefix);
  57. break;
  58. case ERROR_INVALID_PARAMETER:
  59. _snprintf_s(errorStr, ERROR_STRING_SIZE, _TRUNCATE, "%s: The parameter is incorrect", prefix);
  60. break;
  61. default:
  62. _snprintf_s(errorStr, ERROR_STRING_SIZE, _TRUNCATE, "%s: Unknown error code %d", prefix, errorCode);
  63. break;
  64. }
  65. }
  66. void AsyncCloseCallback(uv_handle_t* handle) {
  67. uv_async_t* async = reinterpret_cast<uv_async_t*>(handle);
  68. delete async;
  69. }
  70. void OpenBaton::Execute() {
  71. char originalPath[1024];
  72. strncpy_s(originalPath, sizeof(originalPath), path, _TRUNCATE);
  73. // path is char[1024] but on Windows it has the form "COMx\0" or "COMxx\0"
  74. // We want to prepend "\\\\.\\" to it before we call CreateFile
  75. strncpy(path + 20, path, 10);
  76. strncpy(path, "\\\\.\\", 4);
  77. strncpy(path + 4, path + 20, 10);
  78. int shareMode = FILE_SHARE_READ | FILE_SHARE_WRITE;
  79. if (lock) {
  80. shareMode = 0;
  81. }
  82. HANDLE file = CreateFile(
  83. path,
  84. GENERIC_READ | GENERIC_WRITE,
  85. shareMode, // dwShareMode 0 Prevents other processes from opening if they request delete, read, or write access
  86. NULL,
  87. OPEN_EXISTING,
  88. FILE_FLAG_OVERLAPPED, // allows for reading and writing at the same time and sets the handle for asynchronous I/O
  89. NULL);
  90. if (file == INVALID_HANDLE_VALUE) {
  91. DWORD errorCode = GetLastError();
  92. char temp[100];
  93. _snprintf_s(temp, sizeof(temp), _TRUNCATE, "Opening %s", originalPath);
  94. ErrorCodeToString(temp, errorCode, errorString);
  95. this->SetError(errorString);
  96. return;
  97. }
  98. DCB dcb = { 0 };
  99. SecureZeroMemory(&dcb, sizeof(DCB));
  100. dcb.DCBlength = sizeof(DCB);
  101. if (!GetCommState(file, &dcb)) {
  102. ErrorCodeToString("Open (GetCommState)", GetLastError(), errorString);
  103. this->SetError(errorString);
  104. CloseHandle(file);
  105. return;
  106. }
  107. if (hupcl) {
  108. dcb.fDtrControl = DTR_CONTROL_ENABLE;
  109. } else {
  110. dcb.fDtrControl = DTR_CONTROL_DISABLE; // disable DTR to avoid reset
  111. }
  112. dcb.Parity = NOPARITY;
  113. dcb.StopBits = ONESTOPBIT;
  114. dcb.fOutxDsrFlow = FALSE;
  115. dcb.fOutxCtsFlow = FALSE;
  116. if (xon) {
  117. dcb.fOutX = TRUE;
  118. } else {
  119. dcb.fOutX = FALSE;
  120. }
  121. if (xoff) {
  122. dcb.fInX = TRUE;
  123. } else {
  124. dcb.fInX = FALSE;
  125. }
  126. if (rtscts) {
  127. switch (rtsMode) {
  128. case SERIALPORT_RTSMODE_ENABLE:
  129. dcb.fRtsControl = RTS_CONTROL_ENABLE;
  130. break;
  131. case SERIALPORT_RTSMODE_HANDSHAKE:
  132. dcb.fRtsControl = RTS_CONTROL_HANDSHAKE;
  133. break;
  134. case SERIALPORT_RTSMODE_TOGGLE:
  135. dcb.fRtsControl = RTS_CONTROL_TOGGLE;
  136. break;
  137. }
  138. dcb.fOutxCtsFlow = TRUE;
  139. } else {
  140. dcb.fRtsControl = RTS_CONTROL_DISABLE;
  141. }
  142. dcb.fBinary = true;
  143. dcb.BaudRate = baudRate;
  144. dcb.ByteSize = dataBits;
  145. switch (parity) {
  146. case SERIALPORT_PARITY_NONE:
  147. dcb.Parity = NOPARITY;
  148. break;
  149. case SERIALPORT_PARITY_MARK:
  150. dcb.Parity = MARKPARITY;
  151. break;
  152. case SERIALPORT_PARITY_EVEN:
  153. dcb.Parity = EVENPARITY;
  154. break;
  155. case SERIALPORT_PARITY_ODD:
  156. dcb.Parity = ODDPARITY;
  157. break;
  158. case SERIALPORT_PARITY_SPACE:
  159. dcb.Parity = SPACEPARITY;
  160. break;
  161. }
  162. switch (stopBits) {
  163. case SERIALPORT_STOPBITS_ONE:
  164. dcb.StopBits = ONESTOPBIT;
  165. break;
  166. case SERIALPORT_STOPBITS_ONE_FIVE:
  167. dcb.StopBits = ONE5STOPBITS;
  168. break;
  169. case SERIALPORT_STOPBITS_TWO:
  170. dcb.StopBits = TWOSTOPBITS;
  171. break;
  172. }
  173. if (!SetCommState(file, &dcb)) {
  174. ErrorCodeToString("Open (SetCommState)", GetLastError(), errorString);
  175. this->SetError(errorString);
  176. CloseHandle(file);
  177. return;
  178. }
  179. // Set the timeouts for read and write operations.
  180. // Read operation will wait for at least 1 byte to be received.
  181. COMMTIMEOUTS commTimeouts = {};
  182. commTimeouts.ReadIntervalTimeout = 0; // Never timeout, always wait for data.
  183. commTimeouts.ReadTotalTimeoutMultiplier = 0; // Do not allow big read timeout when big read buffer used
  184. commTimeouts.ReadTotalTimeoutConstant = 0; // Total read timeout (period of read loop)
  185. commTimeouts.WriteTotalTimeoutConstant = 0; // Const part of write timeout
  186. commTimeouts.WriteTotalTimeoutMultiplier = 0; // Variable part of write timeout (per byte)
  187. if (!SetCommTimeouts(file, &commTimeouts)) {
  188. ErrorCodeToString("Open (SetCommTimeouts)", GetLastError(), errorString);
  189. this->SetError(errorString);
  190. CloseHandle(file);
  191. return;
  192. }
  193. // Remove garbage data in RX/TX queues
  194. PurgeComm(file, PURGE_RXCLEAR);
  195. PurgeComm(file, PURGE_TXCLEAR);
  196. result = static_cast<int>(reinterpret_cast<uintptr_t>(file));
  197. }
  198. void ConnectionOptionsBaton::Execute() {
  199. DCB dcb = { 0 };
  200. SecureZeroMemory(&dcb, sizeof(DCB));
  201. dcb.DCBlength = sizeof(DCB);
  202. if (!GetCommState(int2handle(fd), &dcb)) {
  203. ErrorCodeToString("Update (GetCommState)", GetLastError(), errorString);
  204. this->SetError(errorString);
  205. return;
  206. }
  207. dcb.BaudRate = baudRate;
  208. if (!SetCommState(int2handle(fd), &dcb)) {
  209. ErrorCodeToString("Update (SetCommState)", GetLastError(), errorString);
  210. this->SetError(errorString);
  211. return;
  212. }
  213. }
  214. void SetBaton::Execute() {
  215. if (rts) {
  216. EscapeCommFunction(int2handle(fd), SETRTS);
  217. } else {
  218. EscapeCommFunction(int2handle(fd), CLRRTS);
  219. }
  220. if (dtr) {
  221. EscapeCommFunction(int2handle(fd), SETDTR);
  222. } else {
  223. EscapeCommFunction(int2handle(fd), CLRDTR);
  224. }
  225. if (brk) {
  226. EscapeCommFunction(int2handle(fd), SETBREAK);
  227. } else {
  228. EscapeCommFunction(int2handle(fd), CLRBREAK);
  229. }
  230. DWORD bits = 0;
  231. GetCommMask(int2handle(fd), &bits);
  232. bits &= ~(EV_CTS | EV_DSR);
  233. if (cts) {
  234. bits |= EV_CTS;
  235. }
  236. if (dsr) {
  237. bits |= EV_DSR;
  238. }
  239. if (!SetCommMask(int2handle(fd), bits)) {
  240. ErrorCodeToString("Setting options on COM port (SetCommMask)", GetLastError(), errorString);
  241. this->SetError(errorString);
  242. return;
  243. }
  244. }
  245. void GetBaton::Execute() {
  246. DWORD bits = 0;
  247. if (!GetCommModemStatus(int2handle(fd), &bits)) {
  248. ErrorCodeToString("Getting control settings on COM port (GetCommModemStatus)", GetLastError(), errorString);
  249. this->SetError(errorString);
  250. return;
  251. }
  252. cts = bits & MS_CTS_ON;
  253. dsr = bits & MS_DSR_ON;
  254. dcd = bits & MS_RLSD_ON;
  255. }
  256. void GetBaudRateBaton::Execute() {
  257. DCB dcb = { 0 };
  258. SecureZeroMemory(&dcb, sizeof(DCB));
  259. dcb.DCBlength = sizeof(DCB);
  260. if (!GetCommState(int2handle(fd), &dcb)) {
  261. ErrorCodeToString("Getting baud rate (GetCommState)", GetLastError(), errorString);
  262. this->SetError(errorString);
  263. return;
  264. }
  265. baudRate = static_cast<int>(dcb.BaudRate);
  266. }
  267. bool IsClosingHandle(int fd) {
  268. for (std::list<int>::iterator it = g_closingHandles.begin(); it != g_closingHandles.end(); ++it) {
  269. if (fd == *it) {
  270. g_closingHandles.remove(fd);
  271. return true;
  272. }
  273. }
  274. return false;
  275. }
  276. void __stdcall WriteIOCompletion(DWORD errorCode, DWORD bytesTransferred, OVERLAPPED* ov) {
  277. WriteBaton* baton = static_cast<WriteBaton*>(ov->hEvent);
  278. DWORD bytesWritten;
  279. if (!GetOverlappedResult(int2handle(baton->fd), ov, &bytesWritten, TRUE)) {
  280. errorCode = GetLastError();
  281. ErrorCodeToString("Writing to COM port (GetOverlappedResult)", errorCode, baton->errorString);
  282. baton->complete = true;
  283. return;
  284. }
  285. if (bytesWritten) {
  286. baton->offset += bytesWritten;
  287. if (baton->offset >= baton->bufferLength) {
  288. baton->complete = true;
  289. }
  290. }
  291. }
  292. DWORD __stdcall WriteThread(LPVOID param) {
  293. uv_async_t* async = static_cast<uv_async_t*>(param);
  294. WriteBaton* baton = static_cast<WriteBaton*>(async->data);
  295. OVERLAPPED* ov = new OVERLAPPED;
  296. memset(ov, 0, sizeof(OVERLAPPED));
  297. ov->hEvent = static_cast<void*>(baton);
  298. while (!baton->complete) {
  299. char* offsetPtr = baton->bufferData + baton->offset;
  300. // WriteFileEx requires calling GetLastError even upon success. Clear the error beforehand.
  301. SetLastError(0);
  302. WriteFileEx(int2handle(baton->fd), offsetPtr,
  303. static_cast<DWORD>(baton->bufferLength - baton->offset), ov, WriteIOCompletion);
  304. // Error codes when call is successful, such as ERROR_MORE_DATA.
  305. DWORD lastError = GetLastError();
  306. if (lastError != ERROR_SUCCESS) {
  307. ErrorCodeToString("Writing to COM port (WriteFileEx)", lastError, baton->errorString);
  308. break;
  309. }
  310. // IOCompletion routine is only called once this thread is in an alertable wait state.
  311. SleepEx(INFINITE, TRUE);
  312. }
  313. delete ov;
  314. // Signal the main thread to run the callback.
  315. uv_async_send(async);
  316. ExitThread(0);
  317. }
  318. void EIO_AfterWrite(uv_async_t* req) {
  319. WriteBaton* baton = static_cast<WriteBaton*>(req->data);
  320. Napi::Env env = baton->callback.Env();
  321. Napi::HandleScope scope(env);
  322. WaitForSingleObject(baton->hThread, INFINITE);
  323. CloseHandle(baton->hThread);
  324. uv_close(reinterpret_cast<uv_handle_t*>(req), AsyncCloseCallback);
  325. v8::Local<v8::Value> argv[1];
  326. if (baton->errorString[0]) {
  327. baton->callback.Call({Napi::Error::New(env, baton->errorString).Value()});
  328. } else {
  329. baton->callback.Call({env.Null()});
  330. }
  331. baton->buffer.Reset();
  332. delete baton;
  333. }
  334. Napi::Value Write(const Napi::CallbackInfo& info) {
  335. Napi::Env env = info.Env();
  336. // file descriptor
  337. if (!info[0].IsNumber()) {
  338. Napi::TypeError::New(env, "First argument must be an int").ThrowAsJavaScriptException();
  339. return env.Null();
  340. }
  341. int fd = info[0].As<Napi::Number>().Int32Value();
  342. // buffer
  343. if (!info[1].IsObject() || !info[1].IsBuffer()) {
  344. Napi::TypeError::New(env, "Second argument must be a buffer").ThrowAsJavaScriptException();
  345. return env.Null();
  346. }
  347. Napi::Buffer<char> buffer = info[1].As<Napi::Buffer<char>>();
  348. //getBufferFromObject(info[1].ToObject().ti);
  349. char* bufferData = buffer.Data(); //.As<Napi::Buffer<char>>().Data();
  350. size_t bufferLength = buffer.Length();//.As<Napi::Buffer<char>>().Length();
  351. // callback
  352. if (!info[2].IsFunction()) {
  353. Napi::TypeError::New(env, "Third argument must be a function").ThrowAsJavaScriptException();
  354. return env.Null();
  355. }
  356. WriteBaton* baton = new WriteBaton();
  357. baton->callback = Napi::Persistent(info[2].As<Napi::Function>());
  358. baton->fd = fd;
  359. baton->buffer.Reset(buffer);
  360. baton->bufferData = bufferData;
  361. baton->bufferLength = bufferLength;
  362. baton->offset = 0;
  363. baton->complete = false;
  364. uv_async_t* async = new uv_async_t;
  365. uv_async_init(uv_default_loop(), async, EIO_AfterWrite);
  366. async->data = baton;
  367. // WriteFileEx requires a thread that can block. Create a new thread to
  368. // run the write operation, saving the handle so it can be deallocated later.
  369. baton->hThread = CreateThread(NULL, 0, WriteThread, async, 0, NULL);
  370. return env.Null();
  371. }
  372. void __stdcall ReadIOCompletion(DWORD errorCode, DWORD bytesTransferred, OVERLAPPED* ov) {
  373. ReadBaton* baton = static_cast<ReadBaton*>(ov->hEvent);
  374. if (errorCode) {
  375. ErrorCodeToString("Reading from COM port (ReadIOCompletion)", errorCode, baton->errorString);
  376. baton->complete = true;
  377. return;
  378. }
  379. DWORD lastError;
  380. if (!GetOverlappedResult(int2handle(baton->fd), ov, &bytesTransferred, TRUE)) {
  381. lastError = GetLastError();
  382. ErrorCodeToString("Reading from COM port (GetOverlappedResult)", lastError, baton->errorString);
  383. baton->complete = true;
  384. return;
  385. }
  386. if (bytesTransferred) {
  387. baton->bytesToRead -= bytesTransferred;
  388. baton->bytesRead += bytesTransferred;
  389. baton->offset += bytesTransferred;
  390. }
  391. if (!baton->bytesToRead) {
  392. baton->complete = true;
  393. CloseHandle(ov->hEvent);
  394. return;
  395. }
  396. // ReadFileEx and GetOverlappedResult retrieved only 1 byte. Read any additional data in the input
  397. // buffer. Set the timeout to MAXDWORD in order to disable timeouts, so the read operation will
  398. // return immediately no matter how much data is available.
  399. COMMTIMEOUTS commTimeouts = {};
  400. commTimeouts.ReadIntervalTimeout = MAXDWORD;
  401. if (!SetCommTimeouts(int2handle(baton->fd), &commTimeouts)) {
  402. lastError = GetLastError();
  403. ErrorCodeToString("Setting COM timeout (SetCommTimeouts)", lastError, baton->errorString);
  404. baton->complete = true;
  405. // CloseHandle(ov->hEvent); // wondering if we need to close the handle here
  406. return;
  407. }
  408. // Store additional data after whatever data has already been read.
  409. char* offsetPtr = baton->bufferData + baton->offset;
  410. // ReadFile, unlike ReadFileEx, needs an event in the overlapped structure.
  411. memset(ov, 0, sizeof(OVERLAPPED));
  412. ov->hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
  413. if (!ReadFile(int2handle(baton->fd), offsetPtr, baton->bytesToRead, &bytesTransferred, ov)) {
  414. errorCode = GetLastError();
  415. if (errorCode != ERROR_IO_PENDING) {
  416. ErrorCodeToString("Reading from COM port (ReadFile)", errorCode, baton->errorString);
  417. baton->complete = true;
  418. CloseHandle(ov->hEvent);
  419. return;
  420. }
  421. if (!GetOverlappedResult(int2handle(baton->fd), ov, &bytesTransferred, TRUE)) {
  422. lastError = GetLastError();
  423. ErrorCodeToString("Reading from COM port (GetOverlappedResult)", lastError, baton->errorString);
  424. baton->complete = true;
  425. CloseHandle(ov->hEvent);
  426. return;
  427. }
  428. }
  429. baton->bytesToRead -= bytesTransferred;
  430. baton->bytesRead += bytesTransferred;
  431. baton->complete = true;
  432. CloseHandle(ov->hEvent);
  433. }
  434. DWORD __stdcall ReadThread(LPVOID param) {
  435. uv_async_t* async = static_cast<uv_async_t*>(param);
  436. ReadBaton* baton = static_cast<ReadBaton*>(async->data);
  437. DWORD lastError;
  438. OVERLAPPED* ov = new OVERLAPPED;
  439. memset(ov, 0, sizeof(OVERLAPPED));
  440. ov->hEvent = static_cast<void*>(baton);
  441. while (!baton->complete) {
  442. // Reset the read timeout to 0, so that it will block until more data arrives.
  443. COMMTIMEOUTS commTimeouts = {};
  444. commTimeouts.ReadIntervalTimeout = 0;
  445. if (!SetCommTimeouts(int2handle(baton->fd), &commTimeouts)) {
  446. lastError = GetLastError();
  447. ErrorCodeToString("Setting COM timeout (SetCommTimeouts)", lastError, baton->errorString);
  448. break;
  449. }
  450. // ReadFileEx doesn't use overlapped's hEvent, so it is reserved for user data.
  451. ov->hEvent = static_cast<HANDLE>(baton);
  452. char* offsetPtr = baton->bufferData + baton->offset;
  453. // ReadFileEx requires calling GetLastError even upon success. Clear the error beforehand.
  454. SetLastError(0);
  455. // Only read 1 byte, so that the callback will be triggered once any data arrives.
  456. ReadFileEx(int2handle(baton->fd), offsetPtr, 1, ov, ReadIOCompletion);
  457. // Error codes when call is successful, such as ERROR_MORE_DATA.
  458. lastError = GetLastError();
  459. if (lastError != ERROR_SUCCESS) {
  460. ErrorCodeToString("Reading from COM port (ReadFileEx)", lastError, baton->errorString);
  461. break;
  462. }
  463. // IOCompletion routine is only called once this thread is in an alertable wait state.
  464. SleepEx(INFINITE, TRUE);
  465. }
  466. delete ov;
  467. // Signal the main thread to run the callback.
  468. uv_async_send(async);
  469. ExitThread(0);
  470. }
  471. void EIO_AfterRead(uv_async_t* req) {
  472. ReadBaton* baton = static_cast<ReadBaton*>(req->data);
  473. Napi::Env env = baton->callback.Env();
  474. Napi::HandleScope scope(env);
  475. WaitForSingleObject(baton->hThread, INFINITE);
  476. CloseHandle(baton->hThread);
  477. uv_close(reinterpret_cast<uv_handle_t*>(req), AsyncCloseCallback);
  478. if (baton->errorString[0]) {
  479. baton->callback.Call({Napi::Error::New(env, baton->errorString).Value(), env.Undefined()});
  480. } else {
  481. baton->callback.Call({env.Null(), Napi::Number::New(env, static_cast<int>(baton->bytesRead))});
  482. }
  483. delete baton;
  484. }
  485. Napi::Value Read(const Napi::CallbackInfo& info) {
  486. Napi::Env env = info.Env();
  487. // file descriptor
  488. if (!info[0].IsNumber()) {
  489. Napi::TypeError::New(env, "First argument must be a fd").ThrowAsJavaScriptException();
  490. return env.Null();
  491. }
  492. int fd = info[0].As<Napi::Number>().Int32Value();
  493. // buffer
  494. if (!info[1].IsObject() || !info[1].IsBuffer()) {
  495. Napi::TypeError::New(env, "Second argument must be a buffer").ThrowAsJavaScriptException();
  496. return env.Null();
  497. }
  498. Napi::Object buffer = info[1].ToObject();
  499. size_t bufferLength = buffer.As<Napi::Buffer<char>>().Length();
  500. // offset
  501. if (!info[2].IsNumber()) {
  502. Napi::TypeError::New(env, "Third argument must be an int").ThrowAsJavaScriptException();
  503. return env.Null();
  504. }
  505. int offset = info[2].ToNumber().Int64Value();
  506. // bytes to read
  507. if (!info[3].IsNumber()) {
  508. Napi::TypeError::New(env, "Fourth argument must be an int").ThrowAsJavaScriptException();
  509. return env.Null();
  510. }
  511. size_t bytesToRead = info[3].ToNumber().Int64Value();
  512. if ((bytesToRead + offset) > bufferLength) {
  513. Napi::TypeError::New(env, "'bytesToRead' + 'offset' cannot be larger than the buffer's length").ThrowAsJavaScriptException();
  514. return env.Null();
  515. }
  516. // callback
  517. if (!info[4].IsFunction()) {
  518. Napi::TypeError::New(env, "Fifth argument must be a function").ThrowAsJavaScriptException();
  519. return env.Null();
  520. }
  521. ReadBaton* baton = new ReadBaton();
  522. baton->callback = Napi::Persistent(info[4].As<Napi::Function>());
  523. baton->fd = fd;
  524. baton->offset = offset;
  525. baton->bytesToRead = bytesToRead;
  526. baton->bufferLength = bufferLength;
  527. baton->bufferData = buffer.As<Napi::Buffer<char>>().Data();
  528. baton->complete = false;
  529. uv_async_t* async = new uv_async_t;
  530. uv_async_init(uv_default_loop(), async, EIO_AfterRead);
  531. async->data = baton;
  532. baton->hThread = CreateThread(NULL, 0, ReadThread, async, 0, NULL);
  533. // ReadFileEx requires a thread that can block. Create a new thread to
  534. // run the read operation, saving the handle so it can be deallocated later.
  535. return env.Null();
  536. }
  537. void CloseBaton::Execute() {
  538. g_closingHandles.push_back(fd);
  539. HMODULE hKernel32 = LoadLibrary("kernel32.dll");
  540. // Look up function address
  541. CancelIoExType pCancelIoEx = (CancelIoExType)GetProcAddress(hKernel32, "CancelIoEx");
  542. // Do something with it
  543. if (pCancelIoEx) {
  544. // Function exists so call it
  545. // Cancel all pending IO Requests for the current device
  546. pCancelIoEx(int2handle(fd), NULL);
  547. }
  548. if (!CloseHandle(int2handle(fd))) {
  549. ErrorCodeToString("Closing connection (CloseHandle)", GetLastError(), errorString);
  550. this->SetError(errorString);
  551. return;
  552. }
  553. }
  554. wchar_t *copySubstring(wchar_t *someString, int n) {
  555. wchar_t *new_ = reinterpret_cast<wchar_t*>(malloc(sizeof(wchar_t)*n + 1));
  556. wcsncpy_s(new_, n + 1, someString, n);
  557. new_[n] = '\0';
  558. return new_;
  559. }
  560. Napi::Value List(const Napi::CallbackInfo& info) {
  561. Napi::Env env = info.Env();
  562. // callback
  563. if (!info[0].IsFunction()) {
  564. Napi::TypeError::New(env, "First argument must be a function").ThrowAsJavaScriptException();
  565. return env.Null();
  566. }
  567. Napi::Function callback = info[0].As<Napi::Function>();
  568. ListBaton* baton = new ListBaton(callback);
  569. _snwprintf(baton->errorString, sizeof(baton->errorString), L"");
  570. baton->Queue();
  571. return env.Undefined();
  572. }
  573. // It's possible that the s/n is a construct and not the s/n of the parent USB
  574. // composite device. This performs some convoluted registry lookups to fetch the USB s/n.
  575. void getSerialNumber(const wchar_t *vid,
  576. const wchar_t *pid,
  577. const HDEVINFO hDevInfo,
  578. SP_DEVINFO_DATA deviceInfoData,
  579. const unsigned int maxSerialNumberLength,
  580. wchar_t* serialNumber) {
  581. _snwprintf_s(serialNumber, maxSerialNumberLength, _TRUNCATE, L"");
  582. if (vid == NULL || pid == NULL) {
  583. return;
  584. }
  585. DWORD dwSize;
  586. WCHAR szWUuidBuffer[MAX_BUFFER_SIZE];
  587. WCHAR wantedUuid[MAX_BUFFER_SIZE];
  588. // Fetch the "Container ID" for this device node. In USB context, this "Container
  589. // ID" refers to the composite USB device, i.e. the USB device as a whole, not
  590. // just one of its interfaces with a serial port driver attached.
  591. // From https://stackoverflow.com/questions/3438366/setupdigetdeviceproperty-usage-example:
  592. // Because this is not compiled with UNICODE defined, the call to SetupDiGetDevicePropertyW
  593. // has to be setup manually.
  594. DEVPROPTYPE ulPropertyType;
  595. typedef BOOL (WINAPI *FN_SetupDiGetDevicePropertyW)(
  596. __in HDEVINFO DeviceInfoSet,
  597. __in PSP_DEVINFO_DATA DeviceInfoData,
  598. __in const DEVPROPKEY *PropertyKey,
  599. __out DEVPROPTYPE *PropertyType,
  600. __out_opt PBYTE PropertyBuffer,
  601. __in DWORD PropertyBufferSize,
  602. __out_opt PDWORD RequiredSize,
  603. __in DWORD Flags);
  604. FN_SetupDiGetDevicePropertyW fn_SetupDiGetDevicePropertyW = (FN_SetupDiGetDevicePropertyW)
  605. GetProcAddress(GetModuleHandle(TEXT("Setupapi.dll")), "SetupDiGetDevicePropertyW");
  606. if (fn_SetupDiGetDevicePropertyW (
  607. hDevInfo,
  608. &deviceInfoData,
  609. &DEVPKEY_Device_ContainerId,
  610. &ulPropertyType,
  611. reinterpret_cast<BYTE*>(szWUuidBuffer),
  612. sizeof(szWUuidBuffer),
  613. &dwSize,
  614. 0)) {
  615. szWUuidBuffer[dwSize] = '\0';
  616. // Given the UUID bytes, build up a (widechar) string from it. There's some mangling
  617. // going on.
  618. StringFromGUID2((REFGUID)szWUuidBuffer, wantedUuid, ARRAY_SIZE(wantedUuid));
  619. } else {
  620. // Container UUID could not be fetched, return empty serial number.
  621. return;
  622. }
  623. // NOTE: Devices might have a containerUuid like {00000000-0000-0000-FFFF-FFFFFFFFFFFF}
  624. // This means they're non-removable, and are not handled (yet).
  625. // Maybe they should inherit the s/n from somewhere else.
  626. // Iterate through all the USB devices with the given VendorID/ProductID
  627. HKEY vendorProductHKey;
  628. DWORD retCode;
  629. wchar_t hkeyPath[MAX_BUFFER_SIZE];
  630. _snwprintf_s(hkeyPath, MAX_BUFFER_SIZE, _TRUNCATE, L"SYSTEM\\CurrentControlSet\\Enum\\USB\\VID_%s&PID_%s", vid, pid);
  631. retCode = RegOpenKeyExW(
  632. HKEY_LOCAL_MACHINE,
  633. hkeyPath,
  634. 0,
  635. KEY_READ,
  636. &vendorProductHKey);
  637. if (retCode == ERROR_SUCCESS) {
  638. DWORD serialNumbersCount = 0; // number of subkeys
  639. // Fetch how many subkeys there are for this VendorID/ProductID pair.
  640. // That's the number of devices for this VendorID/ProductID known to this machine.
  641. retCode = RegQueryInfoKey(
  642. vendorProductHKey, // hkey handle
  643. NULL, // buffer for class name
  644. NULL, // size of class string
  645. NULL, // reserved
  646. &serialNumbersCount, // number of subkeys
  647. NULL, // longest subkey size
  648. NULL, // longest class string
  649. NULL, // number of values for this key
  650. NULL, // longest value name
  651. NULL, // longest value data
  652. NULL, // security descriptor
  653. NULL); // last write time
  654. if (retCode == ERROR_SUCCESS && serialNumbersCount > 0) {
  655. for (unsigned int i=0; i < serialNumbersCount; i++) {
  656. // Each of the subkeys here is the serial number of a USB device with the
  657. // given VendorId/ProductId. Now fetch the string for the S/N.
  658. DWORD serialNumberLength = maxSerialNumberLength;
  659. retCode = RegEnumKeyExW(vendorProductHKey,
  660. i,
  661. reinterpret_cast<LPWSTR>(serialNumber),
  662. &serialNumberLength,
  663. NULL,
  664. NULL,
  665. NULL,
  666. NULL);
  667. if (retCode == ERROR_SUCCESS) {
  668. // Lookup info for VID_(vendorId)&PID_(productId)\(serialnumber)
  669. _snwprintf_s(hkeyPath, MAX_BUFFER_SIZE, _TRUNCATE,
  670. L"SYSTEM\\CurrentControlSet\\Enum\\USB\\VID_%ls&PID_%ls\\%ls",
  671. vid, pid, serialNumber);
  672. HKEY deviceHKey;
  673. if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, hkeyPath, 0, KEY_READ, &deviceHKey) == ERROR_SUCCESS) {
  674. wchar_t readUuid[MAX_BUFFER_SIZE];
  675. DWORD readSize = sizeof(readUuid);
  676. // Query VID_(vendorId)&PID_(productId)\(serialnumber)\ContainerID
  677. retCode = RegQueryValueExW(deviceHKey, L"ContainerID", NULL, NULL, (LPBYTE)&readUuid, &readSize);
  678. if (retCode == ERROR_SUCCESS) {
  679. readUuid[readSize] = '\0';
  680. if (wcscmp(wantedUuid, readUuid) == 0) {
  681. // The ContainerID UUIDs match, return now that serialNumber has
  682. // the right value.
  683. RegCloseKey(deviceHKey);
  684. RegCloseKey(vendorProductHKey);
  685. return;
  686. }
  687. }
  688. }
  689. RegCloseKey(deviceHKey);
  690. }
  691. }
  692. }
  693. /* In case we did not obtain the path, for whatever reason, we close the key and return an empty string. */
  694. RegCloseKey(vendorProductHKey);
  695. }
  696. _snwprintf_s(serialNumber, maxSerialNumberLength, _TRUNCATE, L"");
  697. return;
  698. }
  699. void ListBaton::Execute() {
  700. GUID *guidDev = (GUID*)& GUID_DEVCLASS_PORTS; // NOLINT
  701. HDEVINFO hDevInfo = SetupDiGetClassDevs(guidDev, NULL, NULL, DIGCF_PRESENT | DIGCF_PROFILE);
  702. SP_DEVINFO_DATA deviceInfoData;
  703. int memberIndex = 0;
  704. DWORD dwSize, dwPropertyRegDataType;
  705. wchar_t szBuffer[MAX_BUFFER_SIZE];
  706. wchar_t *pnpId;
  707. wchar_t *vendorId;
  708. wchar_t *productId;
  709. wchar_t *name;
  710. wchar_t *manufacturer;
  711. wchar_t *locationId;
  712. wchar_t *friendlyName;
  713. wchar_t serialNumber[MAX_REGISTRY_KEY_SIZE];
  714. bool isCom;
  715. while (true) {
  716. isCom = false;
  717. pnpId = NULL;
  718. vendorId = NULL;
  719. productId = NULL;
  720. name = NULL;
  721. manufacturer = NULL;
  722. locationId = NULL;
  723. friendlyName = NULL;
  724. ZeroMemory(&deviceInfoData, sizeof(SP_DEVINFO_DATA));
  725. deviceInfoData.cbSize = sizeof(SP_DEVINFO_DATA);
  726. if (SetupDiEnumDeviceInfo(hDevInfo, memberIndex, &deviceInfoData) == FALSE) {
  727. if (GetLastError() == ERROR_NO_MORE_ITEMS) {
  728. break;
  729. }
  730. }
  731. dwSize = sizeof(szBuffer);
  732. SetupDiGetDeviceInstanceIdW(hDevInfo, &deviceInfoData, reinterpret_cast<PWSTR>(szBuffer), dwSize, &dwSize);
  733. szBuffer[dwSize] = '\0';
  734. pnpId = wcsdup(szBuffer);
  735. vendorId = wcsstr(szBuffer, L"VID_");
  736. if (vendorId) {
  737. vendorId += 4;
  738. vendorId = copySubstring(vendorId, 4);
  739. }
  740. productId = wcsstr(szBuffer, L"PID_");
  741. if (productId) {
  742. productId += 4;
  743. productId = copySubstring(productId, 4);
  744. }
  745. getSerialNumber(vendorId, productId, hDevInfo, deviceInfoData, MAX_REGISTRY_KEY_SIZE, serialNumber);
  746. if (SetupDiGetDeviceRegistryPropertyW(hDevInfo, &deviceInfoData,
  747. SPDRP_LOCATION_INFORMATION, &dwPropertyRegDataType,
  748. reinterpret_cast<PBYTE>(szBuffer), sizeof(szBuffer), &dwSize)) {
  749. locationId = wcsdup(szBuffer);
  750. }
  751. if (SetupDiGetDeviceRegistryPropertyW(hDevInfo, &deviceInfoData,
  752. SPDRP_FRIENDLYNAME, &dwPropertyRegDataType,
  753. reinterpret_cast<PBYTE>(szBuffer), sizeof(szBuffer), &dwSize)) {
  754. friendlyName = wcsdup(szBuffer);
  755. }
  756. if (SetupDiGetDeviceRegistryPropertyW(hDevInfo, &deviceInfoData,
  757. SPDRP_MFG, &dwPropertyRegDataType,
  758. reinterpret_cast<PBYTE>(szBuffer), sizeof(szBuffer), &dwSize)) {
  759. manufacturer = wcsdup(szBuffer);
  760. }
  761. HKEY hkey = SetupDiOpenDevRegKey(hDevInfo, &deviceInfoData, DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_READ);
  762. if (hkey != INVALID_HANDLE_VALUE) {
  763. dwSize = sizeof(szBuffer);
  764. if (RegQueryValueExW(hkey, L"PortName", NULL, NULL, (LPBYTE)&szBuffer, &dwSize) == ERROR_SUCCESS) {
  765. name = wcsdup(szBuffer);
  766. szBuffer[dwSize] = '\0';
  767. isCom = wcsstr(szBuffer, L"COM") != NULL;
  768. }
  769. }
  770. if (isCom) {
  771. ListResultItem* resultItem = new ListResultItem();
  772. resultItem->path = name;
  773. resultItem->manufacturer = manufacturer;
  774. resultItem->pnpId = pnpId;
  775. if (vendorId) {
  776. resultItem->vendorId = vendorId;
  777. }
  778. if (productId) {
  779. resultItem->productId = productId;
  780. }
  781. resultItem->serialNumber = serialNumber;
  782. if (locationId) {
  783. resultItem->locationId = locationId;
  784. }
  785. if (friendlyName) {
  786. resultItem->friendlyName = friendlyName;
  787. }
  788. results.push_back(resultItem);
  789. }
  790. free(pnpId);
  791. free(vendorId);
  792. free(productId);
  793. free(locationId);
  794. free(manufacturer);
  795. free(name);
  796. RegCloseKey(hkey);
  797. memberIndex++;
  798. }
  799. if (hDevInfo) {
  800. SetupDiDestroyDeviceInfoList(hDevInfo);
  801. }
  802. }
  803. void setIfNotEmpty(Napi::Object item, std::string key, const char *value) {
  804. Napi::Env env = item.Env();
  805. Napi::String v8key = Napi::String::New(env, key);
  806. if (strlen(value) > 0) {
  807. (item).Set(v8key, Napi::String::New(env, value));
  808. } else {
  809. (item).Set(v8key, env.Undefined());
  810. }
  811. }
  812. void setIfNotEmpty(Napi::Object item, std::string key, const wchar_t *value) {
  813. Napi::Env env = item.Env();
  814. Napi::String v8key = Napi::String::New(env, key);
  815. if (wcslen(value) > 0) {
  816. (item).Set(v8key, Napi::String::New(env, (const char16_t*) value));
  817. } else {
  818. (item).Set(v8key, env.Undefined());
  819. }
  820. }
  821. void FlushBaton::Execute() {
  822. DWORD purge_all = PURGE_RXCLEAR | PURGE_TXABORT | PURGE_TXCLEAR;
  823. if (!PurgeComm(int2handle(fd), purge_all)) {
  824. ErrorCodeToString("Flushing connection (PurgeComm)", GetLastError(), errorString);
  825. this->SetError(errorString);
  826. return;
  827. }
  828. }
  829. void DrainBaton::Execute() {
  830. if (!FlushFileBuffers(int2handle(fd))) {
  831. ErrorCodeToString("Draining connection (FlushFileBuffers)", GetLastError(), errorString);
  832. this->SetError(errorString);
  833. return;
  834. }
  835. }