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
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
/// CRC-16/MODBUS — polynomial 0xA001, seed 0xFFFF, XOR of the **unsigned** byte.
///
/// **DEVIATION from C, deliberate — CBUG-F8.** C's `crc16`
/// (`sCalcPerform.c:192-229`) sign-extends any payload byte ≥ 0x80:
///
/// ```c
/// unsigned int crc; /* 32 bits, NOT 16 */
/// char tranInput[SCALC_STRING_SIZE]; /* `char` — SIGNED on x86-64/ARM64 Linux */
/// crc = 0xffff;
/// for (i=0; i<len; i++) {
/// /* crc = (crc&0x0ff00) | ((crc&0x0ff) ^ (unsigned int)tranInput[i]); */
/// crc ^= (unsigned int)tranInput[i];
/// for (j=0; j<8; j++) { ... crc >>= 1; crc ^= 0x0A001; ... }
/// }
/// ```
///
/// `(unsigned int)tranInput[i]` widens a signed `char` of 0x80..0xFF to
/// `0xFFFFFF80..`, polluting bits 16-31 of an accumulator that is never masked
/// back to 16; the eight `crc >>= 1` steps shift that garbage down into the
/// digest, where it also survives into the next byte. The commented-out line
/// directly above (`:211`) is the author's own low-byte-masked version — the
/// standard behaviour was intended and the cast is the slip.
///
/// The operator exists to checksum **Modbus frames**, and a real Modbus device
/// validates against the standard CRC, not against C's. C is therefore broken
/// against hardware for any payload with a byte ≥ 0x80 (function codes and
/// register data routinely have them). We implement the standard; an ASCII-only
/// payload is bit-identical to C either way (`CRC16("123456789")` = 0x4B37 in
/// both).
///
/// Compiled sCalc (`gcc -O0`, x86-64) vs this function:
/// `CRC16("\x80")` — C `\x41\x1f`, here `\xbe\xe0` (standard);
/// `CRC16("\xff")` — C `\x00\xff`, here `\xff\x00` (standard).
/// C `lrc` (`sCalcPerform.c:242-256`) — the ASCII-MODBUS longitudinal
/// redundancy check. The operand is ASCII HEX PAIRS (no escape translation,
/// unlike [`crc16`]'s and [`xor8`]'s operands), and the result is two UPPERCASE
/// hex characters, unescaped.
///
/// ```c
/// for (i=0, lrc=0; i<strlen(rawInput)-1; i+=2)
/// lrc += hex(rawInput[i])*0x10 + hex(rawInput[i+1]);
/// lrc &= 0xff; lrc = -lrc;
/// sprintf(output, "%02X", lrc&0xff);
/// return(0); /* never fails */
/// ```
///
/// C rejects nothing, so neither does this. Two consequences the port used to
/// get wrong by refusing the operand outright (which, since a failed helper
/// makes the opcode a no-op, silently left the frame unchecksummed):
///
/// * `hex()` (`:232`) answers **0** for a character that is not a hex digit,
/// so `LRC("0G")` is a byte 0x00 and the check is "00" — not an error.
/// * the loop steps in pairs and stops at `strlen-1`, so a trailing ODD
/// character is ignored: `LRC("010")` is `LRC("01")` is "FF".
///
/// Compiled sCalc: `LRC("010203")`="FA", `LRC("F7031389000A")`="60",
/// `LRC("010")`="FF", `LRC("0G")`="00".
///
/// `None` is for the EMPTY operand alone, and it is NOT what C does: for `""`,
/// `strlen(rawInput)-1` wraps (it is `size_t`), the loop runs, and C reads two
/// bytes past the end of a zero-length string. That is undefined behaviour —
/// the harness segfaults on it — so it is refused here rather than reproduced.
/// XOR8: XOR of all bytes, masked to 8 bits.
/// C `hex` (`sCalcPerform.c:232-240`): the digit's value, or **0** for anything
/// that is not a hex digit. It has no failure mode.