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