Repository navigation
Expand file tree
/
Copy pathrtc_usb_protocol.cpp
More file actions
100 lines (94 loc) · 3.67 KB
/
Copy pathrtc_usb_protocol.cpp
File metadata and controls
100 lines (94 loc) · 3.67 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
#include "rtc_usb_protocol.h"
#include "watch_model.h"
#include <cstdio>
#include <cstring>
// parse USB RTC commands without accessing hardware.
namespace rtcusb {
// private decimal and command parsers.
namespace {
// consume unsigned decimal digits with overflow checks.
bool number(const char*& input, uint32_t& value) {
// require an initial digit.
if (*input < '0' || *input > '9') return false;
value = 0;
// accumulate consecutive digits.
do {
const uint32_t digit = static_cast<uint32_t>(*input++ - '0');
// reject overflow before multiplying.
if (value > (UINT32_MAX - digit) / 10) return false;
value = value * 10 + digit;
} while (*input >= '0' && *input <= '9');
return true;
}
// validate the complete command before returning its nonce and UTC.
bool parse(const char* line, Command& output) {
Command next;
// select writes by their command prefix.
if (std::strncmp(line, "RTCSET,", 7) == 0) next.operation = Operation::Set;
// otherwise require the read prefix.
else if (std::strncmp(line, "RTCGET,", 7) != 0) return false;
const char* input = line + 7;
// require the reply-correlation nonce.
if (!number(input, next.nonce)) return false;
// writes also require a valid UTC field.
if (next.operation == Operation::Set) {
// reject missing delimiters, malformed numbers and unsupported dates.
if (*input++ != ',' || !number(input, next.utc) || !watch::validUtc(next.utc)) return false;
}
// reject trailing fields or bytes.
if (*input != '\0') return false;
output = next;
return true;
}
} // namespace
// discard partial frames and reset receive timing.
void Framer::reset() {
length_ = 0;
lastByteMs_ = 0;
discarding_ = false;
}
// consume USB bytes; return only complete commands or standalone diagnostics.
Input Framer::push(uint8_t byte, uint32_t nowMs, Command& command) {
// discard incomplete commands after two seconds between bytes.
if (length_ && static_cast<uint32_t>(nowMs - lastByteMs_) >= 2000) {
discarding_ = true;
length_ = 0;
}
lastByteMs_ = nowMs;
// finish at LF and validate a non-discarded frame.
if (byte == '\n') {
const bool complete = !discarding_ && length_ != 0;
// accept optional CR before LF.
if (length_ && line_[length_ - 1] == '\r') --length_;
line_[length_] = '\0';
reset();
return complete && parse(line_, command) ? Input::Command : Input::None;
}
// ignore the rest of a rejected frame through LF.
if (discarding_) return Input::None;
// allow diagnostics only outside a command.
if (byte == '?' && length_ == 0) return Input::Status;
// reject non-ASCII/control bytes and preserve space for NUL.
if ((byte < 32 && byte != '\r') || byte > 126 || length_ == sizeof(line_) - 1) {
length_ = 0;
discarding_ = true;
return Input::None;
}
line_[length_++] = static_cast<char>(byte);
return Input::None;
}
// format a nonce-matched success/error line; never report truncated replies.
size_t formatReply(const Command& command, bool ok, uint32_t utc,
char* output, size_t capacity) {
// require writable output space.
if (!output || capacity == 0) return 0;
// include UTC only when the hardware operation succeeded.
const char* name = command.operation == Operation::Set ? "RTCSET" : "RTCGET";
const int count = ok ?
std::snprintf(output, capacity, "%s,%lu,OK,%lu\n", name,
static_cast<unsigned long>(command.nonce), static_cast<unsigned long>(utc)) :
std::snprintf(output, capacity, "%s,%lu,ERR\n", name,
static_cast<unsigned long>(command.nonce));
return count > 0 && static_cast<size_t>(count) < capacity ? static_cast<size_t>(count) : 0;
}
} // namespace rtcusb