FreeRDP
comm_ioctl.c
1 
23 #include <winpr/config.h>
24 
25 #include <winpr/assert.h>
26 #include <errno.h>
27 
28 #include <winpr/wlog.h>
29 
30 #include "comm.h"
31 #include "comm_ioctl.h"
32 #include "comm_serial_sys.h"
33 #include "comm_sercx_sys.h"
34 #include "comm_sercx2_sys.h"
35 
36 /* NB: MS-RDPESP's recommendation:
37  *
38  * <2> Section 3.2.5.1.6: Windows Implementations use IOCTL constants
39  * for IoControlCode values. The content and values of the IOCTLs are
40  * opaque to the protocol. On the server side, the data contained in
41  * an IOCTL is simply packaged and sent to the client side. For
42  * maximum compatibility between the different versions of the Windows
43  * operating system, the client implementation only singles out
44  * critical IOCTLs and invokes the applicable Win32 port API. The
45  * other IOCTLS are passed directly to the client-side driver, and the
46  * processing of this value depends on the drivers installed on the
47  * client side. The values and parameters for these IOCTLS can be
48  * found in [MSFT-W2KDDK] Volume 2, Part 2—Serial and Parallel
49  * Drivers, and in [MSDN-PORTS].
50  */
51 static BOOL s_CommDeviceIoControl(HANDLE hDevice, DWORD dwIoControlCode, LPVOID lpInBuffer,
52  DWORD nInBufferSize, LPVOID lpOutBuffer, DWORD nOutBufferSize,
53  LPDWORD lpBytesReturned, LPOVERLAPPED lpOverlapped)
54 {
55  WINPR_COMM* pComm = (WINPR_COMM*)hDevice;
56  const SERIAL_DRIVER* pServerSerialDriver = NULL;
57 
58  if (!CommIsHandleValid(hDevice))
59  return FALSE;
60 
61  if (lpOverlapped)
62  {
63  SetLastError(ERROR_NOT_SUPPORTED);
64  return FALSE;
65  }
66 
67  if (lpBytesReturned == NULL)
68  {
69  SetLastError(ERROR_INVALID_PARAMETER); /* since we doesn't suppport lpOverlapped != NULL */
70  return FALSE;
71  }
72 
73  /* clear any previous last error */
74  SetLastError(ERROR_SUCCESS);
75 
76  *lpBytesReturned = 0; /* will be ajusted if required ... */
77 
78  CommLog_Print(WLOG_DEBUG, "CommDeviceIoControl: IoControlCode: 0x%0.8x", dwIoControlCode);
79 
80  /* remoteSerialDriver to be use ...
81  *
82  * FIXME: might prefer to use an automatic rather than static structure
83  */
84  switch (pComm->serverSerialDriverId)
85  {
86  case SerialDriverSerialSys:
87  pServerSerialDriver = SerialSys_s();
88  break;
89 
90  case SerialDriverSerCxSys:
91  pServerSerialDriver = SerCxSys_s();
92  break;
93 
94  case SerialDriverSerCx2Sys:
95  pServerSerialDriver = SerCx2Sys_s();
96  break;
97 
98  case SerialDriverUnknown:
99  default:
100  CommLog_Print(WLOG_DEBUG, "Unknown remote serial driver (%d), using SerCx2.sys",
101  pComm->serverSerialDriverId);
102  pServerSerialDriver = SerCx2Sys_s();
103  break;
104  }
105 
106  WINPR_ASSERT(pServerSerialDriver != NULL);
107 
108  switch (dwIoControlCode)
109  {
110  case IOCTL_USBPRINT_GET_1284_ID:
111  {
112  /* FIXME:
113  * http://msdn.microsoft.com/en-us/library/windows/hardware/ff551803(v=vs.85).aspx */
114  *lpBytesReturned = nOutBufferSize; /* an empty OutputBuffer will be returned */
115  SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
116  return FALSE;
117  }
118  case IOCTL_SERIAL_SET_BAUD_RATE:
119  {
120  if (pServerSerialDriver->set_baud_rate)
121  {
122  SERIAL_BAUD_RATE* pBaudRate = (SERIAL_BAUD_RATE*)lpInBuffer;
123 
124  WINPR_ASSERT(nInBufferSize >= sizeof(SERIAL_BAUD_RATE));
125  if (nInBufferSize < sizeof(SERIAL_BAUD_RATE))
126  {
127  SetLastError(ERROR_INVALID_PARAMETER);
128  return FALSE;
129  }
130 
131  return pServerSerialDriver->set_baud_rate(pComm, pBaudRate);
132  }
133  break;
134  }
135  case IOCTL_SERIAL_GET_BAUD_RATE:
136  {
137  if (pServerSerialDriver->get_baud_rate)
138  {
139  SERIAL_BAUD_RATE* pBaudRate = (SERIAL_BAUD_RATE*)lpOutBuffer;
140 
141  WINPR_ASSERT(nOutBufferSize >= sizeof(SERIAL_BAUD_RATE));
142  if (nOutBufferSize < sizeof(SERIAL_BAUD_RATE))
143  {
144  SetLastError(ERROR_INSUFFICIENT_BUFFER);
145  return FALSE;
146  }
147 
148  if (!pServerSerialDriver->get_baud_rate(pComm, pBaudRate))
149  return FALSE;
150 
151  *lpBytesReturned = sizeof(SERIAL_BAUD_RATE);
152  return TRUE;
153  }
154  break;
155  }
156  case IOCTL_SERIAL_GET_PROPERTIES:
157  {
158  if (pServerSerialDriver->get_properties)
159  {
160  COMMPROP* pProperties = (COMMPROP*)lpOutBuffer;
161 
162  WINPR_ASSERT(nOutBufferSize >= sizeof(COMMPROP));
163  if (nOutBufferSize < sizeof(COMMPROP))
164  {
165  SetLastError(ERROR_INSUFFICIENT_BUFFER);
166  return FALSE;
167  }
168 
169  if (!pServerSerialDriver->get_properties(pComm, pProperties))
170  return FALSE;
171 
172  *lpBytesReturned = sizeof(COMMPROP);
173  return TRUE;
174  }
175  break;
176  }
177  case IOCTL_SERIAL_SET_CHARS:
178  {
179  if (pServerSerialDriver->set_serial_chars)
180  {
181  SERIAL_CHARS* pSerialChars = (SERIAL_CHARS*)lpInBuffer;
182 
183  WINPR_ASSERT(nInBufferSize >= sizeof(SERIAL_CHARS));
184  if (nInBufferSize < sizeof(SERIAL_CHARS))
185  {
186  SetLastError(ERROR_INVALID_PARAMETER);
187  return FALSE;
188  }
189 
190  return pServerSerialDriver->set_serial_chars(pComm, pSerialChars);
191  }
192  break;
193  }
194  case IOCTL_SERIAL_GET_CHARS:
195  {
196  if (pServerSerialDriver->get_serial_chars)
197  {
198  SERIAL_CHARS* pSerialChars = (SERIAL_CHARS*)lpOutBuffer;
199 
200  WINPR_ASSERT(nOutBufferSize >= sizeof(SERIAL_CHARS));
201  if (nOutBufferSize < sizeof(SERIAL_CHARS))
202  {
203  SetLastError(ERROR_INSUFFICIENT_BUFFER);
204  return FALSE;
205  }
206 
207  if (!pServerSerialDriver->get_serial_chars(pComm, pSerialChars))
208  return FALSE;
209 
210  *lpBytesReturned = sizeof(SERIAL_CHARS);
211  return TRUE;
212  }
213  break;
214  }
215  case IOCTL_SERIAL_SET_LINE_CONTROL:
216  {
217  if (pServerSerialDriver->set_line_control)
218  {
219  SERIAL_LINE_CONTROL* pLineControl = (SERIAL_LINE_CONTROL*)lpInBuffer;
220 
221  WINPR_ASSERT(nInBufferSize >= sizeof(SERIAL_LINE_CONTROL));
222  if (nInBufferSize < sizeof(SERIAL_LINE_CONTROL))
223  {
224  SetLastError(ERROR_INVALID_PARAMETER);
225  return FALSE;
226  }
227 
228  return pServerSerialDriver->set_line_control(pComm, pLineControl);
229  }
230  break;
231  }
232  case IOCTL_SERIAL_GET_LINE_CONTROL:
233  {
234  if (pServerSerialDriver->get_line_control)
235  {
236  SERIAL_LINE_CONTROL* pLineControl = (SERIAL_LINE_CONTROL*)lpOutBuffer;
237 
238  WINPR_ASSERT(nOutBufferSize >= sizeof(SERIAL_LINE_CONTROL));
239  if (nOutBufferSize < sizeof(SERIAL_LINE_CONTROL))
240  {
241  SetLastError(ERROR_INSUFFICIENT_BUFFER);
242  return FALSE;
243  }
244 
245  if (!pServerSerialDriver->get_line_control(pComm, pLineControl))
246  return FALSE;
247 
248  *lpBytesReturned = sizeof(SERIAL_LINE_CONTROL);
249  return TRUE;
250  }
251  break;
252  }
253  case IOCTL_SERIAL_SET_HANDFLOW:
254  {
255  if (pServerSerialDriver->set_handflow)
256  {
257  SERIAL_HANDFLOW* pHandflow = (SERIAL_HANDFLOW*)lpInBuffer;
258 
259  WINPR_ASSERT(nInBufferSize >= sizeof(SERIAL_HANDFLOW));
260  if (nInBufferSize < sizeof(SERIAL_HANDFLOW))
261  {
262  SetLastError(ERROR_INVALID_PARAMETER);
263  return FALSE;
264  }
265 
266  return pServerSerialDriver->set_handflow(pComm, pHandflow);
267  }
268  break;
269  }
270  case IOCTL_SERIAL_GET_HANDFLOW:
271  {
272  if (pServerSerialDriver->get_handflow)
273  {
274  SERIAL_HANDFLOW* pHandflow = (SERIAL_HANDFLOW*)lpOutBuffer;
275 
276  WINPR_ASSERT(nOutBufferSize >= sizeof(SERIAL_HANDFLOW));
277  if (nOutBufferSize < sizeof(SERIAL_HANDFLOW))
278  {
279  SetLastError(ERROR_INSUFFICIENT_BUFFER);
280  return FALSE;
281  }
282 
283  if (!pServerSerialDriver->get_handflow(pComm, pHandflow))
284  return FALSE;
285 
286  *lpBytesReturned = sizeof(SERIAL_HANDFLOW);
287  return TRUE;
288  }
289  break;
290  }
291  case IOCTL_SERIAL_SET_TIMEOUTS:
292  {
293  if (pServerSerialDriver->set_timeouts)
294  {
295  SERIAL_TIMEOUTS* pHandflow = (SERIAL_TIMEOUTS*)lpInBuffer;
296 
297  WINPR_ASSERT(nInBufferSize >= sizeof(SERIAL_TIMEOUTS));
298  if (nInBufferSize < sizeof(SERIAL_TIMEOUTS))
299  {
300  SetLastError(ERROR_INVALID_PARAMETER);
301  return FALSE;
302  }
303 
304  return pServerSerialDriver->set_timeouts(pComm, pHandflow);
305  }
306  break;
307  }
308  case IOCTL_SERIAL_GET_TIMEOUTS:
309  {
310  if (pServerSerialDriver->get_timeouts)
311  {
312  SERIAL_TIMEOUTS* pHandflow = (SERIAL_TIMEOUTS*)lpOutBuffer;
313 
314  WINPR_ASSERT(nOutBufferSize >= sizeof(SERIAL_TIMEOUTS));
315  if (nOutBufferSize < sizeof(SERIAL_TIMEOUTS))
316  {
317  SetLastError(ERROR_INSUFFICIENT_BUFFER);
318  return FALSE;
319  }
320 
321  if (!pServerSerialDriver->get_timeouts(pComm, pHandflow))
322  return FALSE;
323 
324  *lpBytesReturned = sizeof(SERIAL_TIMEOUTS);
325  return TRUE;
326  }
327  break;
328  }
329  case IOCTL_SERIAL_SET_DTR:
330  {
331  if (pServerSerialDriver->set_dtr)
332  {
333  return pServerSerialDriver->set_dtr(pComm);
334  }
335  break;
336  }
337  case IOCTL_SERIAL_CLR_DTR:
338  {
339  if (pServerSerialDriver->clear_dtr)
340  {
341  return pServerSerialDriver->clear_dtr(pComm);
342  }
343  break;
344  }
345  case IOCTL_SERIAL_SET_RTS:
346  {
347  if (pServerSerialDriver->set_rts)
348  {
349  return pServerSerialDriver->set_rts(pComm);
350  }
351  break;
352  }
353  case IOCTL_SERIAL_CLR_RTS:
354  {
355  if (pServerSerialDriver->clear_rts)
356  {
357  return pServerSerialDriver->clear_rts(pComm);
358  }
359  break;
360  }
361  case IOCTL_SERIAL_GET_MODEMSTATUS:
362  {
363  if (pServerSerialDriver->get_modemstatus)
364  {
365  ULONG* pRegister = (ULONG*)lpOutBuffer;
366 
367  WINPR_ASSERT(nOutBufferSize >= sizeof(ULONG));
368  if (nOutBufferSize < sizeof(ULONG))
369  {
370  SetLastError(ERROR_INSUFFICIENT_BUFFER);
371  return FALSE;
372  }
373 
374  if (!pServerSerialDriver->get_modemstatus(pComm, pRegister))
375  return FALSE;
376 
377  *lpBytesReturned = sizeof(ULONG);
378  return TRUE;
379  }
380  break;
381  }
382  case IOCTL_SERIAL_SET_WAIT_MASK:
383  {
384  if (pServerSerialDriver->set_wait_mask)
385  {
386  ULONG* pWaitMask = (ULONG*)lpInBuffer;
387 
388  WINPR_ASSERT(nInBufferSize >= sizeof(ULONG));
389  if (nInBufferSize < sizeof(ULONG))
390  {
391  SetLastError(ERROR_INVALID_PARAMETER);
392  return FALSE;
393  }
394 
395  return pServerSerialDriver->set_wait_mask(pComm, pWaitMask);
396  }
397  break;
398  }
399  case IOCTL_SERIAL_GET_WAIT_MASK:
400  {
401  if (pServerSerialDriver->get_wait_mask)
402  {
403  ULONG* pWaitMask = (ULONG*)lpOutBuffer;
404 
405  WINPR_ASSERT(nOutBufferSize >= sizeof(ULONG));
406  if (nOutBufferSize < sizeof(ULONG))
407  {
408  SetLastError(ERROR_INSUFFICIENT_BUFFER);
409  return FALSE;
410  }
411 
412  if (!pServerSerialDriver->get_wait_mask(pComm, pWaitMask))
413  return FALSE;
414 
415  *lpBytesReturned = sizeof(ULONG);
416  return TRUE;
417  }
418  break;
419  }
420  case IOCTL_SERIAL_WAIT_ON_MASK:
421  {
422  if (pServerSerialDriver->wait_on_mask)
423  {
424  ULONG* pOutputMask = (ULONG*)lpOutBuffer;
425 
426  WINPR_ASSERT(nOutBufferSize >= sizeof(ULONG));
427  if (nOutBufferSize < sizeof(ULONG))
428  {
429  SetLastError(ERROR_INSUFFICIENT_BUFFER);
430  return FALSE;
431  }
432 
433  if (!pServerSerialDriver->wait_on_mask(pComm, pOutputMask))
434  {
435  *lpBytesReturned = sizeof(ULONG);
436  return FALSE;
437  }
438 
439  *lpBytesReturned = sizeof(ULONG);
440  return TRUE;
441  }
442  break;
443  }
444  case IOCTL_SERIAL_SET_QUEUE_SIZE:
445  {
446  if (pServerSerialDriver->set_queue_size)
447  {
448  SERIAL_QUEUE_SIZE* pQueueSize = (SERIAL_QUEUE_SIZE*)lpInBuffer;
449 
450  WINPR_ASSERT(nInBufferSize >= sizeof(SERIAL_QUEUE_SIZE));
451  if (nInBufferSize < sizeof(SERIAL_QUEUE_SIZE))
452  {
453  SetLastError(ERROR_INVALID_PARAMETER);
454  return FALSE;
455  }
456 
457  return pServerSerialDriver->set_queue_size(pComm, pQueueSize);
458  }
459  break;
460  }
461  case IOCTL_SERIAL_PURGE:
462  {
463  if (pServerSerialDriver->purge)
464  {
465  ULONG* pPurgeMask = (ULONG*)lpInBuffer;
466 
467  WINPR_ASSERT(nInBufferSize >= sizeof(ULONG));
468  if (nInBufferSize < sizeof(ULONG))
469  {
470  SetLastError(ERROR_INVALID_PARAMETER);
471  return FALSE;
472  }
473 
474  return pServerSerialDriver->purge(pComm, pPurgeMask);
475  }
476  break;
477  }
478  case IOCTL_SERIAL_GET_COMMSTATUS:
479  {
480  if (pServerSerialDriver->get_commstatus)
481  {
482  SERIAL_STATUS* pCommstatus = (SERIAL_STATUS*)lpOutBuffer;
483 
484  WINPR_ASSERT(nOutBufferSize >= sizeof(SERIAL_STATUS));
485  if (nOutBufferSize < sizeof(SERIAL_STATUS))
486  {
487  SetLastError(ERROR_INSUFFICIENT_BUFFER);
488  return FALSE;
489  }
490 
491  if (!pServerSerialDriver->get_commstatus(pComm, pCommstatus))
492  return FALSE;
493 
494  *lpBytesReturned = sizeof(SERIAL_STATUS);
495  return TRUE;
496  }
497  break;
498  }
499  case IOCTL_SERIAL_SET_BREAK_ON:
500  {
501  if (pServerSerialDriver->set_break_on)
502  {
503  return pServerSerialDriver->set_break_on(pComm);
504  }
505  break;
506  }
507  case IOCTL_SERIAL_SET_BREAK_OFF:
508  {
509  if (pServerSerialDriver->set_break_off)
510  {
511  return pServerSerialDriver->set_break_off(pComm);
512  }
513  break;
514  }
515  case IOCTL_SERIAL_SET_XOFF:
516  {
517  if (pServerSerialDriver->set_xoff)
518  {
519  return pServerSerialDriver->set_xoff(pComm);
520  }
521  break;
522  }
523  case IOCTL_SERIAL_SET_XON:
524  {
525  if (pServerSerialDriver->set_xon)
526  {
527  return pServerSerialDriver->set_xon(pComm);
528  }
529  break;
530  }
531  case IOCTL_SERIAL_GET_DTRRTS:
532  {
533  if (pServerSerialDriver->get_dtrrts)
534  {
535  ULONG* pMask = (ULONG*)lpOutBuffer;
536 
537  WINPR_ASSERT(nOutBufferSize >= sizeof(ULONG));
538  if (nOutBufferSize < sizeof(ULONG))
539  {
540  SetLastError(ERROR_INSUFFICIENT_BUFFER);
541  return FALSE;
542  }
543 
544  if (!pServerSerialDriver->get_dtrrts(pComm, pMask))
545  return FALSE;
546 
547  *lpBytesReturned = sizeof(ULONG);
548  return TRUE;
549  }
550  break;
551  }
552  case IOCTL_SERIAL_CONFIG_SIZE:
553  {
554  if (pServerSerialDriver->config_size)
555  {
556  ULONG* pSize = (ULONG*)lpOutBuffer;
557 
558  WINPR_ASSERT(nOutBufferSize >= sizeof(ULONG));
559  if (nOutBufferSize < sizeof(ULONG))
560  {
561  SetLastError(ERROR_INSUFFICIENT_BUFFER);
562  return FALSE;
563  }
564 
565  if (!pServerSerialDriver->config_size(pComm, pSize))
566  return FALSE;
567 
568  *lpBytesReturned = sizeof(ULONG);
569  return TRUE;
570  }
571  break;
572  }
573  case IOCTL_SERIAL_IMMEDIATE_CHAR:
574  {
575  if (pServerSerialDriver->immediate_char)
576  {
577  UCHAR* pChar = (UCHAR*)lpInBuffer;
578 
579  WINPR_ASSERT(nInBufferSize >= sizeof(UCHAR));
580  if (nInBufferSize < sizeof(UCHAR))
581  {
582  SetLastError(ERROR_INVALID_PARAMETER);
583  return FALSE;
584  }
585 
586  return pServerSerialDriver->immediate_char(pComm, pChar);
587  }
588  break;
589  }
590  case IOCTL_SERIAL_RESET_DEVICE:
591  {
592  if (pServerSerialDriver->reset_device)
593  {
594  return pServerSerialDriver->reset_device(pComm);
595  }
596  break;
597  }
598  default:
599  break;
600  }
601 
602  CommLog_Print(
603  WLOG_WARN, _T("unsupported IoControlCode=[0x%08" PRIX32 "] %s (remote serial driver: %s)"),
604  dwIoControlCode, _comm_serial_ioctl_name(dwIoControlCode), pServerSerialDriver->name);
605  SetLastError(ERROR_CALL_NOT_IMPLEMENTED); /* => STATUS_NOT_IMPLEMENTED */
606  return FALSE;
607 }
608 
621 BOOL CommDeviceIoControl(HANDLE hDevice, DWORD dwIoControlCode, LPVOID lpInBuffer,
622  DWORD nInBufferSize, LPVOID lpOutBuffer, DWORD nOutBufferSize,
623  LPDWORD lpBytesReturned, LPOVERLAPPED lpOverlapped)
624 {
625  WINPR_COMM* pComm = (WINPR_COMM*)hDevice;
626  BOOL result = 0;
627 
628  if (hDevice == INVALID_HANDLE_VALUE)
629  {
630  SetLastError(ERROR_INVALID_HANDLE);
631  return FALSE;
632  }
633 
634  if (!CommIsHandled(hDevice))
635  return FALSE;
636 
637  if (!pComm->fd)
638  {
639  SetLastError(ERROR_INVALID_HANDLE);
640  return FALSE;
641  }
642 
643  result = s_CommDeviceIoControl(hDevice, dwIoControlCode, lpInBuffer, nInBufferSize, lpOutBuffer,
644  nOutBufferSize, lpBytesReturned, lpOverlapped);
645 
646  if (lpBytesReturned && *lpBytesReturned != nOutBufferSize)
647  {
648  /* This might be a hint for a bug, especially when result==TRUE */
649  CommLog_Print(WLOG_WARN,
650  "lpBytesReturned=%" PRIu32 " and nOutBufferSize=%" PRIu32 " are different!",
651  *lpBytesReturned, nOutBufferSize);
652  }
653 
654  if (pComm->permissive)
655  {
656  if (!result)
657  {
658  CommLog_Print(
659  WLOG_WARN,
660  "[permissive]: whereas it failed, made to succeed IoControlCode=[0x%08" PRIX32
661  "] %s, last-error: 0x%08" PRIX32 "",
662  dwIoControlCode, _comm_serial_ioctl_name(dwIoControlCode), GetLastError());
663  }
664 
665  return TRUE; /* always! */
666  }
667 
668  return result;
669 }
670 
671 int _comm_ioctl_tcsetattr(int fd, int optional_actions, const struct termios* termios_p)
672 {
673  int result = 0;
674  struct termios currentState = { 0 };
675 
676  if ((result = tcsetattr(fd, optional_actions, termios_p)) < 0)
677  {
678  CommLog_Print(WLOG_WARN, "tcsetattr failure, errno: %d", errno);
679  return result;
680  }
681 
682  /* NB: tcsetattr() can succeed even if not all changes have been applied. */
683  if ((result = tcgetattr(fd, &currentState)) < 0)
684  {
685  CommLog_Print(WLOG_WARN, "tcgetattr failure, errno: %d", errno);
686  return result;
687  }
688 
689  // NOLINTNEXTLINE(bugprone-suspicious-memory-comparison,cert-exp42-c,cert-flp37-c)
690  if (memcmp(&currentState, termios_p, sizeof(struct termios)) != 0)
691  {
692  CommLog_Print(WLOG_DEBUG,
693  "all termios parameters are not set yet, doing a second attempt...");
694  if ((result = tcsetattr(fd, optional_actions, termios_p)) < 0)
695  {
696  CommLog_Print(WLOG_WARN, "2nd tcsetattr failure, errno: %d", errno);
697  return result;
698  }
699 
700  ZeroMemory(&currentState, sizeof(struct termios));
701  if ((result = tcgetattr(fd, &currentState)) < 0)
702  {
703  CommLog_Print(WLOG_WARN, "tcgetattr failure, errno: %d", errno);
704  return result;
705  }
706 
707  // NOLINTNEXTLINE(bugprone-suspicious-memory-comparison,cert-exp42-c,cert-flp37-c)
708  if (memcmp(&currentState, termios_p, sizeof(struct termios)) != 0)
709  {
710  CommLog_Print(WLOG_WARN,
711  "Failure: all termios parameters are still not set on a second attempt");
712  return -1;
713  }
714  }
715 
716  return 0;
717 }