1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "services/device/serial/serial_io_handler_posix.h"
6 
7 #include <sys/ioctl.h>
8 #include <termios.h>
9 
10 #include <algorithm>
11 #include <utility>
12 
13 #include "base/bind.h"
14 #include "base/files/file_util.h"
15 #include "base/posix/eintr_wrapper.h"
16 #include "build/build_config.h"
17 
18 #if defined(OS_LINUX)
19 #include <asm-generic/ioctls.h>
20 #include <linux/serial.h>
21 
22 // The definition of struct termios2 is copied from asm-generic/termbits.h
23 // because including that header directly conflicts with termios.h.
24 extern "C" {
25 struct termios2 {
26   tcflag_t c_iflag;  // input mode flags
27   tcflag_t c_oflag;  // output mode flags
28   tcflag_t c_cflag;  // control mode flags
29   tcflag_t c_lflag;  // local mode flags
30   cc_t c_line;       // line discipline
31   cc_t c_cc[19];     // control characters
32   speed_t c_ispeed;  // input speed
33   speed_t c_ospeed;  // output speed
34 };
35 }
36 
37 #endif  // defined(OS_LINUX)
38 
39 #if defined(OS_MACOSX)
40 #include <IOKit/serial/ioss.h>
41 #endif
42 
43 namespace {
44 
45 // Convert an integral bit rate to a nominal one. Returns |true|
46 // if the conversion was successful and |false| otherwise.
BitrateToSpeedConstant(int bitrate,speed_t * speed)47 bool BitrateToSpeedConstant(int bitrate, speed_t* speed) {
48 #define BITRATE_TO_SPEED_CASE(x) \
49   case x:                        \
50     *speed = B##x;               \
51     return true;
52   switch (bitrate) {
53     BITRATE_TO_SPEED_CASE(0)
54     BITRATE_TO_SPEED_CASE(50)
55     BITRATE_TO_SPEED_CASE(75)
56     BITRATE_TO_SPEED_CASE(110)
57     BITRATE_TO_SPEED_CASE(134)
58     BITRATE_TO_SPEED_CASE(150)
59     BITRATE_TO_SPEED_CASE(200)
60     BITRATE_TO_SPEED_CASE(300)
61     BITRATE_TO_SPEED_CASE(600)
62     BITRATE_TO_SPEED_CASE(1200)
63     BITRATE_TO_SPEED_CASE(1800)
64     BITRATE_TO_SPEED_CASE(2400)
65     BITRATE_TO_SPEED_CASE(4800)
66     BITRATE_TO_SPEED_CASE(9600)
67     BITRATE_TO_SPEED_CASE(19200)
68     BITRATE_TO_SPEED_CASE(38400)
69 #if !defined(OS_MACOSX) && !defined(OS_BSD)
70     BITRATE_TO_SPEED_CASE(57600)
71     BITRATE_TO_SPEED_CASE(115200)
72     BITRATE_TO_SPEED_CASE(230400)
73     BITRATE_TO_SPEED_CASE(460800)
74     BITRATE_TO_SPEED_CASE(576000)
75     BITRATE_TO_SPEED_CASE(921600)
76 #endif
77     default:
78       return false;
79   }
80 #undef BITRATE_TO_SPEED_CASE
81 }
82 
83 #if !defined(OS_LINUX)
84 // Convert a known nominal speed into an integral bitrate. Returns |true|
85 // if the conversion was successful and |false| otherwise.
SpeedConstantToBitrate(speed_t speed,int * bitrate)86 bool SpeedConstantToBitrate(speed_t speed, int* bitrate) {
87 #define SPEED_TO_BITRATE_CASE(x) \
88   case B##x:                     \
89     *bitrate = x;                \
90     return true;
91   switch (speed) {
92     SPEED_TO_BITRATE_CASE(0)
93     SPEED_TO_BITRATE_CASE(50)
94     SPEED_TO_BITRATE_CASE(75)
95     SPEED_TO_BITRATE_CASE(110)
96     SPEED_TO_BITRATE_CASE(134)
97     SPEED_TO_BITRATE_CASE(150)
98     SPEED_TO_BITRATE_CASE(200)
99     SPEED_TO_BITRATE_CASE(300)
100     SPEED_TO_BITRATE_CASE(600)
101     SPEED_TO_BITRATE_CASE(1200)
102     SPEED_TO_BITRATE_CASE(1800)
103     SPEED_TO_BITRATE_CASE(2400)
104     SPEED_TO_BITRATE_CASE(4800)
105     SPEED_TO_BITRATE_CASE(9600)
106     SPEED_TO_BITRATE_CASE(19200)
107     SPEED_TO_BITRATE_CASE(38400)
108     default:
109       return false;
110   }
111 #undef SPEED_TO_BITRATE_CASE
112 }
113 #endif
114 
115 }  // namespace
116 
117 namespace device {
118 
119 // static
Create(const base::FilePath & port,scoped_refptr<base::SingleThreadTaskRunner> ui_thread_task_runner)120 scoped_refptr<SerialIoHandler> SerialIoHandler::Create(
121     const base::FilePath& port,
122     scoped_refptr<base::SingleThreadTaskRunner> ui_thread_task_runner) {
123   return new SerialIoHandlerPosix(port, std::move(ui_thread_task_runner));
124 }
125 
ReadImpl()126 void SerialIoHandlerPosix::ReadImpl() {
127   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
128   DCHECK(pending_read_buffer());
129 
130   if (!file().IsValid()) {
131     QueueReadCompleted(0, mojom::SerialReceiveError::DISCONNECTED);
132     return;
133   }
134 
135   // Try to read immediately. This is needed because on some platforms
136   // (e.g., OSX) there may not be a notification from the message loop
137   // when the fd is ready to read immediately after it is opened. There
138   // is no danger of blocking because the fd is opened with async flag.
139   AttemptRead(true);
140 }
141 
WriteImpl()142 void SerialIoHandlerPosix::WriteImpl() {
143   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
144   DCHECK(pending_write_buffer());
145 
146   if (!file().IsValid()) {
147     QueueWriteCompleted(0, mojom::SerialSendError::DISCONNECTED);
148     return;
149   }
150 
151   EnsureWatchingWrites();
152 }
153 
CancelReadImpl()154 void SerialIoHandlerPosix::CancelReadImpl() {
155   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
156   StopWatchingFileRead();
157   QueueReadCompleted(0, read_cancel_reason());
158 }
159 
CancelWriteImpl()160 void SerialIoHandlerPosix::CancelWriteImpl() {
161   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
162   StopWatchingFileWrite();
163   QueueWriteCompleted(0, write_cancel_reason());
164 }
165 
ConfigurePortImpl()166 bool SerialIoHandlerPosix::ConfigurePortImpl() {
167 #if defined(OS_LINUX)
168   struct termios2 config;
169   if (ioctl(file().GetPlatformFile(), TCGETS2, &config) < 0) {
170 #else
171   struct termios config;
172   if (tcgetattr(file().GetPlatformFile(), &config) != 0) {
173 #endif
174     VPLOG(1) << "Failed to get port configuration";
175     return false;
176   }
177 
178   // Set flags for 'raw' operation
179   config.c_lflag &= ~(ICANON | ECHO | ECHOE | ECHONL | ISIG);
180   config.c_iflag &= ~(IGNBRK | BRKINT | ISTRIP | INLCR | IGNCR | ICRNL | IXON);
181   config.c_iflag |= PARMRK;
182   config.c_oflag &= ~OPOST;
183 
184   // CLOCAL causes the system to disregard the DCD signal state.
185   // CREAD enables reading from the port.
186   config.c_cflag |= (CLOCAL | CREAD);
187 
188   DCHECK(options().bitrate);
189   speed_t bitrate_opt = B0;
190 #if defined(OS_MACOSX)
191   bool need_iossiospeed = false;
192 #endif
193   if (BitrateToSpeedConstant(options().bitrate, &bitrate_opt)) {
194 #if defined(OS_LINUX)
195     config.c_cflag &= ~CBAUD;
196     config.c_cflag |= bitrate_opt;
197 #else
198     cfsetispeed(&config, bitrate_opt);
199     cfsetospeed(&config, bitrate_opt);
200 #endif
201   } else {
202     // Attempt to set a custom speed.
203 #if defined(OS_LINUX)
204     config.c_cflag &= ~CBAUD;
205     config.c_cflag |= CBAUDEX;
206     config.c_ispeed = config.c_ospeed = options().bitrate;
207 #elif defined(OS_MACOSX)
208     // cfsetispeed and cfsetospeed sometimes work for custom baud rates on OS
209     // X but the IOSSIOSPEED ioctl is more reliable but has to be done after
210     // the rest of the port parameters are set or else it will be overwritten.
211     need_iossiospeed = true;
212 #else
213     return false;
214 #endif
215   }
216 
217   DCHECK(options().data_bits != mojom::SerialDataBits::NONE);
218   config.c_cflag &= ~CSIZE;
219   switch (options().data_bits) {
220     case mojom::SerialDataBits::SEVEN:
221       config.c_cflag |= CS7;
222       break;
223     case mojom::SerialDataBits::EIGHT:
224     default:
225       config.c_cflag |= CS8;
226       break;
227   }
228 
229   DCHECK(options().parity_bit != mojom::SerialParityBit::NONE);
230   switch (options().parity_bit) {
231     case mojom::SerialParityBit::EVEN:
232       config.c_cflag |= PARENB;
233       config.c_cflag &= ~PARODD;
234       break;
235     case mojom::SerialParityBit::ODD:
236       config.c_cflag |= (PARODD | PARENB);
237       break;
238     case mojom::SerialParityBit::NO_PARITY:
239     default:
240       config.c_cflag &= ~(PARODD | PARENB);
241       break;
242   }
243 
244   error_detect_state_ = ErrorDetectState::NO_ERROR;
245   num_chars_stashed_ = 0;
246 
247   if (config.c_cflag & PARENB) {
248     config.c_iflag &= ~IGNPAR;
249     config.c_iflag |= INPCK;
250     parity_check_enabled_ = true;
251   } else {
252     config.c_iflag |= IGNPAR;
253     config.c_iflag &= ~INPCK;
254     parity_check_enabled_ = false;
255   }
256 
257   DCHECK(options().stop_bits != mojom::SerialStopBits::NONE);
258   switch (options().stop_bits) {
259     case mojom::SerialStopBits::TWO:
260       config.c_cflag |= CSTOPB;
261       break;
262     case mojom::SerialStopBits::ONE:
263     default:
264       config.c_cflag &= ~CSTOPB;
265       break;
266   }
267 
268   DCHECK(options().has_cts_flow_control);
269   if (options().cts_flow_control) {
270     config.c_cflag |= CRTSCTS;
271   } else {
272     config.c_cflag &= ~CRTSCTS;
273   }
274 
275 #if defined(OS_LINUX)
276   if (ioctl(file().GetPlatformFile(), TCSETS2, &config) < 0) {
277 #else
278   if (tcsetattr(file().GetPlatformFile(), TCSANOW, &config) != 0) {
279 #endif
280     VPLOG(1) << "Failed to set port attributes";
281     return false;
282   }
283 
284 #if defined(OS_MACOSX)
285   if (need_iossiospeed) {
286     speed_t bitrate = options().bitrate;
287     if (ioctl(file().GetPlatformFile(), IOSSIOSPEED, &bitrate) == -1) {
288       VPLOG(1) << "Failed to set custom baud rate";
289       return false;
290     }
291   }
292 #endif
293 
294   return true;
295 }
296 
297 bool SerialIoHandlerPosix::PostOpen() {
298 #if defined(OS_CHROMEOS)
299   // The Chrome OS permission broker does not open devices in async mode.
300   return base::SetNonBlocking(file().GetPlatformFile());
301 #else
302   return true;
303 #endif
304 }
305 
306 void SerialIoHandlerPosix::PreClose() {
307   StopWatchingFileRead();
308   StopWatchingFileWrite();
309 }
310 
311 SerialIoHandlerPosix::SerialIoHandlerPosix(
312     const base::FilePath& port,
313     scoped_refptr<base::SingleThreadTaskRunner> ui_thread_task_runner)
314     : SerialIoHandler(port, std::move(ui_thread_task_runner)) {}
315 
316 SerialIoHandlerPosix::~SerialIoHandlerPosix() = default;
317 
318 void SerialIoHandlerPosix::AttemptRead(bool within_read) {
319   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
320 
321   if (pending_read_buffer()) {
322     int bytes_read =
323         HANDLE_EINTR(read(file().GetPlatformFile(), pending_read_buffer(),
324                           pending_read_buffer_len()));
325     if (bytes_read < 0) {
326       if (errno == EAGAIN) {
327         // The fd does not have data to read yet so continue waiting.
328         EnsureWatchingReads();
329       } else if (errno == ENXIO) {
330         RunReadCompleted(within_read, 0,
331                          mojom::SerialReceiveError::DEVICE_LOST);
332       } else {
333         RunReadCompleted(within_read, 0,
334                          mojom::SerialReceiveError::SYSTEM_ERROR);
335       }
336     } else if (bytes_read == 0) {
337       RunReadCompleted(within_read, 0, mojom::SerialReceiveError::DEVICE_LOST);
338     } else {
339       bool break_detected = false;
340       bool parity_error_detected = false;
341       int new_bytes_read =
342           CheckReceiveError(pending_read_buffer(), pending_read_buffer_len(),
343                             bytes_read, break_detected, parity_error_detected);
344 
345       if (break_detected) {
346         RunReadCompleted(within_read, new_bytes_read,
347                          mojom::SerialReceiveError::BREAK);
348       } else if (parity_error_detected) {
349         RunReadCompleted(within_read, new_bytes_read,
350                          mojom::SerialReceiveError::PARITY_ERROR);
351       } else {
352         RunReadCompleted(within_read, new_bytes_read,
353                          mojom::SerialReceiveError::NONE);
354       }
355     }
356   } else {
357     // Stop watching the fd if we get notifications with no pending
358     // reads or writes to avoid starving the message loop.
359     StopWatchingFileRead();
360   }
361 }
362 
363 void SerialIoHandlerPosix::RunReadCompleted(bool within_read,
364                                             int bytes_read,
365                                             mojom::SerialReceiveError error) {
366   if (within_read) {
367     // Stop watching the fd to avoid more reads until the queued ReadCompleted()
368     // completes and releases the pending_read_buffer.
369     StopWatchingFileRead();
370 
371     QueueReadCompleted(bytes_read, error);
372   } else {
373     ReadCompleted(bytes_read, error);
374   }
375 }
376 
377 void SerialIoHandlerPosix::OnFileCanWriteWithoutBlocking() {
378   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
379 
380   if (pending_write_buffer()) {
381     int bytes_written =
382         HANDLE_EINTR(write(file().GetPlatformFile(), pending_write_buffer(),
383                            pending_write_buffer_len()));
384     if (bytes_written < 0) {
385       WriteCompleted(0, mojom::SerialSendError::SYSTEM_ERROR);
386     } else {
387       WriteCompleted(bytes_written, mojom::SerialSendError::NONE);
388     }
389   } else {
390     // Stop watching the fd if we get notifications with no pending
391     // writes to avoid starving the message loop.
392     StopWatchingFileWrite();
393   }
394 }
395 
396 void SerialIoHandlerPosix::EnsureWatchingReads() {
397   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
398   DCHECK(file().IsValid());
399   if (!file_read_watcher_) {
400     file_read_watcher_ = base::FileDescriptorWatcher::WatchReadable(
401         file().GetPlatformFile(),
402         base::BindRepeating(&SerialIoHandlerPosix::AttemptRead,
403                             base::Unretained(this), false));
404   }
405 }
406 
407 void SerialIoHandlerPosix::EnsureWatchingWrites() {
408   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
409   DCHECK(file().IsValid());
410   if (!file_write_watcher_) {
411     file_write_watcher_ = base::FileDescriptorWatcher::WatchWritable(
412         file().GetPlatformFile(),
413         base::BindRepeating(
414             &SerialIoHandlerPosix::OnFileCanWriteWithoutBlocking,
415             base::Unretained(this)));
416   }
417 }
418 
419 void SerialIoHandlerPosix::StopWatchingFileRead() {
420   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
421   if (file_read_watcher_) {
422     // Check that file is valid before stopping the watch, to avoid getting a
423     // hard to diagnose crash in MessagePumpLibEvent. https://crbug.com/996777
424     CHECK(file().IsValid());
425     file_read_watcher_.reset();
426   }
427 }
428 
429 void SerialIoHandlerPosix::StopWatchingFileWrite() {
430   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
431   if (file_write_watcher_) {
432     // Check that file is valid before stopping the watch, to avoid getting a
433     // hard to diagnose crash in MessagePumpLibEvent. https://crbug.com/996777
434     CHECK(file().IsValid());
435     file_write_watcher_.reset();
436   }
437 }
438 
439 bool SerialIoHandlerPosix::Flush() const {
440   if (tcflush(file().GetPlatformFile(), TCIOFLUSH) != 0) {
441     VPLOG(1) << "Failed to flush port";
442     return false;
443   }
444   return true;
445 }
446 
447 mojom::SerialPortControlSignalsPtr SerialIoHandlerPosix::GetControlSignals()
448     const {
449   int status;
450   if (ioctl(file().GetPlatformFile(), TIOCMGET, &status) == -1) {
451     VPLOG(1) << "Failed to get port control signals";
452     return mojom::SerialPortControlSignalsPtr();
453   }
454 
455   auto signals = mojom::SerialPortControlSignals::New();
456   signals->dcd = (status & TIOCM_CAR) != 0;
457   signals->cts = (status & TIOCM_CTS) != 0;
458   signals->dsr = (status & TIOCM_DSR) != 0;
459   signals->ri = (status & TIOCM_RI) != 0;
460   return signals;
461 }
462 
463 bool SerialIoHandlerPosix::SetControlSignals(
464     const mojom::SerialHostControlSignals& signals) {
465   // Collect signals that need to be set or cleared on the port.
466   int set = 0;
467   int clear = 0;
468 
469   if (signals.has_dtr) {
470     if (signals.dtr) {
471       set |= TIOCM_DTR;
472     } else {
473       clear |= TIOCM_DTR;
474     }
475   }
476 
477   if (signals.has_rts) {
478     if (signals.rts) {
479       set |= TIOCM_RTS;
480     } else {
481       clear |= TIOCM_RTS;
482     }
483   }
484 
485   if (set && ioctl(file().GetPlatformFile(), TIOCMBIS, &set) != 0) {
486     VPLOG(1) << "Failed to set port control signals";
487     return false;
488   }
489 
490   if (clear && ioctl(file().GetPlatformFile(), TIOCMBIC, &clear) != 0) {
491     VPLOG(1) << "Failed to clear port control signals";
492     return false;
493   }
494 
495   if (signals.has_brk) {
496     if (signals.brk) {
497       if (ioctl(file().GetPlatformFile(), TIOCSBRK, 0) != 0) {
498         VPLOG(1) << "Failed to set break";
499         return false;
500       }
501     } else {
502       if (ioctl(file().GetPlatformFile(), TIOCCBRK, 0) != 0) {
503         VPLOG(1) << "Failed to clear break";
504         return false;
505       }
506     }
507   }
508 
509   return true;
510 }
511 
512 mojom::SerialConnectionInfoPtr SerialIoHandlerPosix::GetPortInfo() const {
513 #if defined(OS_LINUX)
514   struct termios2 config;
515   if (ioctl(file().GetPlatformFile(), TCGETS2, &config) < 0) {
516 #else
517   struct termios config;
518   if (tcgetattr(file().GetPlatformFile(), &config) == -1) {
519 #endif
520     VPLOG(1) << "Failed to get port info";
521     return mojom::SerialConnectionInfoPtr();
522   }
523 
524   auto info = mojom::SerialConnectionInfo::New();
525 #if defined(OS_LINUX)
526   // Linux forces c_ospeed to contain the correct value, which is nice.
527   info->bitrate = config.c_ospeed;
528 #else
529   speed_t ispeed = cfgetispeed(&config);
530   speed_t ospeed = cfgetospeed(&config);
531   if (ispeed == ospeed) {
532     int bitrate = 0;
533     if (SpeedConstantToBitrate(ispeed, &bitrate)) {
534       info->bitrate = bitrate;
535     } else if (ispeed > 0) {
536       info->bitrate = static_cast<int>(ispeed);
537     }
538   }
539 #endif
540 
541   if ((config.c_cflag & CSIZE) == CS7) {
542     info->data_bits = mojom::SerialDataBits::SEVEN;
543   } else if ((config.c_cflag & CSIZE) == CS8) {
544     info->data_bits = mojom::SerialDataBits::EIGHT;
545   } else {
546     info->data_bits = mojom::SerialDataBits::NONE;
547   }
548   if (config.c_cflag & PARENB) {
549     info->parity_bit = (config.c_cflag & PARODD) ? mojom::SerialParityBit::ODD
550                                                  : mojom::SerialParityBit::EVEN;
551   } else {
552     info->parity_bit = mojom::SerialParityBit::NO_PARITY;
553   }
554   info->stop_bits = (config.c_cflag & CSTOPB) ? mojom::SerialStopBits::TWO
555                                               : mojom::SerialStopBits::ONE;
556   info->cts_flow_control = (config.c_cflag & CRTSCTS) != 0;
557   return info;
558 }
559 
560 // break sequence:
561 // '\377'       -->        ErrorDetectState::MARK_377_SEEN
562 // '\0'         -->          ErrorDetectState::MARK_0_SEEN
563 // '\0'         -->                         break detected
564 //
565 // parity error sequence:
566 // '\377'       -->        ErrorDetectState::MARK_377_SEEN
567 // '\0'         -->          ErrorDetectState::MARK_0_SEEN
568 // character with parity error  -->  parity error detected
569 //
570 // break/parity error sequences are removed from the byte stream
571 // '\377' '\377' sequence is replaced with '\377'
572 int SerialIoHandlerPosix::CheckReceiveError(char* buffer,
573                                             int buffer_len,
574                                             int bytes_read,
575                                             bool& break_detected,
576                                             bool& parity_error_detected) {
577   int new_bytes_read = num_chars_stashed_;
578   DCHECK_LE(new_bytes_read, 2);
579 
580   for (int i = 0; i < bytes_read; ++i) {
581     char ch = buffer[i];
582     if (new_bytes_read == 0) {
583       chars_stashed_[0] = ch;
584     } else if (new_bytes_read == 1) {
585       chars_stashed_[1] = ch;
586     } else {
587       buffer[new_bytes_read - 2] = ch;
588     }
589     ++new_bytes_read;
590     switch (error_detect_state_) {
591       case ErrorDetectState::NO_ERROR:
592         if (ch == '\377') {
593           error_detect_state_ = ErrorDetectState::MARK_377_SEEN;
594         }
595         break;
596       case ErrorDetectState::MARK_377_SEEN:
597         DCHECK_GE(new_bytes_read, 2);
598         if (ch == '\0') {
599           error_detect_state_ = ErrorDetectState::MARK_0_SEEN;
600         } else {
601           if (ch == '\377') {
602             // receive two bytes '\377' '\377', since ISTRIP is not set and
603             // PARMRK is set, a valid byte '\377' is passed to the program as
604             // two bytes, '\377' '\377'. Replace these two bytes with one byte
605             // of '\377', and set error_detect_state_ back to
606             // ErrorDetectState::NO_ERROR.
607             --new_bytes_read;
608           }
609           error_detect_state_ = ErrorDetectState::NO_ERROR;
610         }
611         break;
612       case ErrorDetectState::MARK_0_SEEN:
613         DCHECK_GE(new_bytes_read, 3);
614         if (ch == '\0') {
615           break_detected = true;
616           new_bytes_read -= 3;
617           error_detect_state_ = ErrorDetectState::NO_ERROR;
618         } else {
619           if (parity_check_enabled_) {
620             parity_error_detected = true;
621             new_bytes_read -= 3;
622             error_detect_state_ = ErrorDetectState::NO_ERROR;
623           } else if (ch == '\377') {
624             error_detect_state_ = ErrorDetectState::MARK_377_SEEN;
625           } else {
626             error_detect_state_ = ErrorDetectState::NO_ERROR;
627           }
628         }
629         break;
630     }
631   }
632   // Now new_bytes_read bytes should be returned to the caller (including the
633   // previously stashed characters that were stored at chars_stashed_[]) and are
634   // now stored at: chars_stashed_[0], chars_stashed_[1], buffer[...].
635 
636   // Stash up to 2 characters that are potentially part of a break/parity error
637   // sequence. The buffer may also not be large enough to store all the bytes.
638   // tmp[] stores the characters that need to be stashed for this read.
639   char tmp[2];
640   num_chars_stashed_ = 0;
641   if (error_detect_state_ == ErrorDetectState::MARK_0_SEEN ||
642       new_bytes_read - buffer_len == 2) {
643     // need to stash the last two characters
644     if (new_bytes_read == 2) {
645       memcpy(tmp, chars_stashed_, new_bytes_read);
646     } else {
647       if (new_bytes_read == 3) {
648         tmp[0] = chars_stashed_[1];
649       } else {
650         tmp[0] = buffer[new_bytes_read - 4];
651       }
652       tmp[1] = buffer[new_bytes_read - 3];
653     }
654     num_chars_stashed_ = 2;
655   } else if (error_detect_state_ == ErrorDetectState::MARK_377_SEEN ||
656              new_bytes_read - buffer_len == 1) {
657     // need to stash the last character
658     if (new_bytes_read <= 2) {
659       tmp[0] = chars_stashed_[new_bytes_read - 1];
660     } else {
661       tmp[0] = buffer[new_bytes_read - 3];
662     }
663     num_chars_stashed_ = 1;
664   }
665 
666   new_bytes_read -= num_chars_stashed_;
667   if (new_bytes_read > 2) {
668     // right shift two bytes to store bytes from chars_stashed_[]
669     memmove(buffer + 2, buffer, new_bytes_read - 2);
670   }
671   memcpy(buffer, chars_stashed_, std::min(new_bytes_read, 2));
672   memcpy(chars_stashed_, tmp, num_chars_stashed_);
673   return new_bytes_read;
674 }
675 
676 }  // namespace device
677