@padosoft/react-native-ecr17 0.0.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/Ecr17.podspec +39 -39
  2. package/README.md +348 -348
  3. package/android/CMakeLists.txt +41 -41
  4. package/android/build.gradle +148 -148
  5. package/android/fix-prefab.gradle +50 -50
  6. package/android/gradle.properties +5 -5
  7. package/android/src/main/AndroidManifest.xml +2 -2
  8. package/android/src/main/cpp/cpp-adapter.cpp +8 -8
  9. package/android/src/main/java/com/margelo/nitro/ecr17/HybridEcr17Transport.kt +233 -233
  10. package/android/src/main/java/com/padosoft/ecr17/Ecr17Package.kt +30 -30
  11. package/cpp/Ecr17.hpp +1 -1
  12. package/cpp/Ecr17Client/HybridEcr17Client.cpp +598 -598
  13. package/cpp/Ecr17Client/HybridEcr17Client.hpp +85 -85
  14. package/cpp/Ecr17Protocol/Ecr17Protocol.cpp +277 -277
  15. package/cpp/Ecr17Protocol/Ecr17Protocol.hpp +103 -103
  16. package/cpp/Ecr17Response/Ecr17Response.cpp +155 -155
  17. package/cpp/Ecr17Response/Ecr17Response.hpp +113 -113
  18. package/cpp/Lcr/Lcr.cpp +42 -42
  19. package/cpp/Lcr/Lcr.hpp +21 -21
  20. package/cpp/PacketCodec/PacketCodec.cpp +145 -145
  21. package/cpp/PacketCodec/PacketCodec.hpp +47 -47
  22. package/cpp/Session/Ecr17Session.cpp +260 -260
  23. package/cpp/Session/Ecr17Session.hpp +97 -97
  24. package/cpp/Session/RetryPolicy.hpp +23 -23
  25. package/cpp/Transport/FakeTransport.hpp +95 -95
  26. package/cpp/Transport/NativeTransportAdapter.cpp +42 -42
  27. package/cpp/Transport/NativeTransportAdapter.hpp +32 -32
  28. package/cpp/Transport/Transport.hpp +30 -30
  29. package/cpp/tests/CMakeLists.txt +55 -55
  30. package/cpp/tests/PosixTcpTransport.hpp +105 -105
  31. package/cpp/tests/stubs/LrcMode.hpp +25 -25
  32. package/cpp/tests/test_flows.cpp +148 -148
  33. package/cpp/tests/test_integration_terminal.cpp +72 -72
  34. package/cpp/tests/test_lrc.cpp +66 -66
  35. package/cpp/tests/test_packet_codec.cpp +164 -164
  36. package/cpp/tests/test_protocol.cpp +102 -102
  37. package/cpp/tests/test_protocol_commands.cpp +190 -190
  38. package/cpp/tests/test_response.cpp +164 -164
  39. package/cpp/tests/test_retry_policy.cpp +28 -28
  40. package/cpp/tests/test_session.cpp +262 -262
  41. package/ios/HybridEcr17Transport.swift +103 -103
  42. package/lib/commonjs/index.js +50 -0
  43. package/lib/commonjs/index.js.map +1 -0
  44. package/lib/commonjs/package.json +1 -0
  45. package/lib/commonjs/specs/client.nitro.js +17 -0
  46. package/lib/commonjs/specs/client.nitro.js.map +1 -0
  47. package/lib/commonjs/specs/transport.nitro.js +6 -0
  48. package/lib/commonjs/specs/transport.nitro.js.map +1 -0
  49. package/lib/commonjs/types/client.js +2 -0
  50. package/lib/commonjs/types/client.js.map +1 -0
  51. package/lib/commonjs/utils/client.js +13 -0
  52. package/lib/commonjs/utils/client.js.map +1 -0
  53. package/lib/module/index.js +7 -0
  54. package/lib/module/index.js.map +1 -0
  55. package/lib/module/specs/client.nitro.js +13 -0
  56. package/lib/module/specs/client.nitro.js.map +1 -0
  57. package/lib/module/specs/transport.nitro.js +4 -0
  58. package/lib/module/specs/transport.nitro.js.map +1 -0
  59. package/lib/module/types/client.js +2 -0
  60. package/lib/module/types/client.js.map +1 -0
  61. package/lib/module/utils/client.js +9 -0
  62. package/lib/module/utils/client.js.map +1 -0
  63. package/lib/typescript/src/index.d.ts +5 -0
  64. package/lib/typescript/src/index.d.ts.map +1 -0
  65. package/lib/typescript/src/specs/client.nitro.d.ts +63 -0
  66. package/lib/typescript/src/specs/client.nitro.d.ts.map +1 -0
  67. package/lib/typescript/src/specs/transport.nitro.d.ts +13 -0
  68. package/lib/typescript/src/specs/transport.nitro.d.ts.map +1 -0
  69. package/lib/typescript/src/types/client.d.ts +138 -0
  70. package/lib/typescript/src/types/client.d.ts.map +1 -0
  71. package/lib/typescript/src/utils/client.d.ts +3 -0
  72. package/lib/typescript/src/utils/client.d.ts.map +1 -0
  73. package/nitro.json +30 -30
  74. package/package.json +4 -4
  75. package/react-native.config.js +18 -18
  76. package/src/index.ts +4 -4
  77. package/src/specs/client.nitro.ts +102 -102
  78. package/src/specs/transport.nitro.ts +25 -25
  79. package/src/types/client.ts +196 -196
  80. package/src/utils/client.ts +10 -10
@@ -1,113 +1,113 @@
1
- #pragma once
2
-
3
- #include <cstdint>
4
- #include <string>
5
-
6
- // Parsers for ECR17 terminal *response* application messages. They take the
7
- // application payload (the bytes between STX and ETX, i.e. DecodedPacket.payload)
8
- // and return plain C++ structs — intentionally independent of Nitro so they can
9
- // be unit-tested standalone. HybridEcr17Client maps these to the generated Nitro
10
- // result types. Field offsets follow the spec response tables in docs/.
11
- //
12
- // Parsing is defensive: fields beyond the payload length come back empty rather
13
- // than throwing, so a short/truncated response degrades gracefully.
14
-
15
- namespace margelo::nitro::ecr17 {
16
-
17
- enum class Outcome {
18
- Ok, // "00"
19
- Ko, // "01"
20
- CardNotPresent, // "05"
21
- UnknownTag, // "09"
22
- Unknown, // anything else / missing
23
- };
24
-
25
- Outcome outcomeFromCode(const std::string& code);
26
-
27
- // Optional DCC / currency-exchange block (parsed from a 'V'/'v' response).
28
- // Named DccInfo (not CurrencyExchange) to avoid clashing with the Nitro-generated
29
- // CurrencyExchange struct in the same namespace.
30
- struct DccInfo {
31
- bool applied = false;
32
- std::string rate; // 8 digits, 4 decimals
33
- std::string currencyCode; // alpha-3
34
- std::string amount; // 12 digits, in transaction currency
35
- std::string precision; // decimals
36
- };
37
-
38
- // Result of a payment-family response ('E' without DCC, 'V' with DCC). Reused
39
- // for reversal / card verification / pre-auth closure which share the layout.
40
- struct PaymentResponse {
41
- Outcome outcome = Outcome::Unknown;
42
- std::string resultCode; // raw "00"/"01"/"05"/"09"
43
- std::string pan; // positive
44
- std::string transactionType; // positive, raw "ICC"/"MAG"/...
45
- std::string authCode; // positive
46
- std::string hostDateTime; // positive, raw DDDHHMM
47
- std::string errorDescription; // negative
48
- std::string cardType; // common, raw "1"/"2"/"3"
49
- std::string acquirerId; // common
50
- std::string stan; // common
51
- std::string onlineId; // common
52
- DccInfo currency; // only when DCC present
53
- };
54
-
55
- struct StatusResponse {
56
- std::string terminalId;
57
- std::string dateTimeRaw; // "DDMMYYhhmm"
58
- int status = -1; // 0..6, -1 = unknown/missing
59
- std::string softwareRelease;
60
- };
61
-
62
- struct TotalsResponse {
63
- Outcome outcome = Outcome::Unknown;
64
- std::string resultCode;
65
- std::string posTotal; // 16 digits, cents
66
- };
67
-
68
- struct CloseResponse {
69
- Outcome outcome = Outcome::Unknown;
70
- std::string resultCode;
71
- std::string posTotal; // positive, 16 digits
72
- std::string hostTotal; // positive, 16 digits
73
- std::string errorDescription; // negative
74
- std::string actionCode; // negative
75
- };
76
-
77
- struct PreAuthResponse {
78
- Outcome outcome = Outcome::Unknown;
79
- std::string resultCode;
80
- std::string pan;
81
- std::string transactionType;
82
- std::string authCode;
83
- std::string preAuthorizedAmount; // 8 digits, cents
84
- std::string preAuthCode; // 9 digits, unique pre-auth id
85
- std::string actionCode;
86
- std::string hostDateTime;
87
- std::string errorDescription;
88
- std::string cardType;
89
- std::string acquirerId;
90
- std::string stan;
91
- std::string onlineId;
92
- };
93
-
94
- struct VasResponse {
95
- std::string responseId; // RESPID parsed from XML ("0" = OK), "" if absent
96
- std::string responseMessage; // RESPMSG
97
- std::string orderId; // ORDER_ID
98
- bool moreMessages = false; // concatenation flag "1"
99
- std::string idMessage; // 3-digit sequence
100
- std::string rawXml; // the XML body of this message
101
- };
102
-
103
- class Ecr17Response {
104
- public:
105
- static PaymentResponse parsePayment(const std::string& payload);
106
- static StatusResponse parseStatus(const std::string& payload);
107
- static TotalsResponse parseTotals(const std::string& payload);
108
- static CloseResponse parseClose(const std::string& payload);
109
- static PreAuthResponse parsePreAuth(const std::string& payload);
110
- static VasResponse parseVas(const std::string& payload);
111
- };
112
-
113
- } // namespace margelo::nitro::ecr17
1
+ #pragma once
2
+
3
+ #include <cstdint>
4
+ #include <string>
5
+
6
+ // Parsers for ECR17 terminal *response* application messages. They take the
7
+ // application payload (the bytes between STX and ETX, i.e. DecodedPacket.payload)
8
+ // and return plain C++ structs — intentionally independent of Nitro so they can
9
+ // be unit-tested standalone. HybridEcr17Client maps these to the generated Nitro
10
+ // result types. Field offsets follow the spec response tables in docs/.
11
+ //
12
+ // Parsing is defensive: fields beyond the payload length come back empty rather
13
+ // than throwing, so a short/truncated response degrades gracefully.
14
+
15
+ namespace margelo::nitro::ecr17 {
16
+
17
+ enum class Outcome {
18
+ Ok, // "00"
19
+ Ko, // "01"
20
+ CardNotPresent, // "05"
21
+ UnknownTag, // "09"
22
+ Unknown, // anything else / missing
23
+ };
24
+
25
+ Outcome outcomeFromCode(const std::string& code);
26
+
27
+ // Optional DCC / currency-exchange block (parsed from a 'V'/'v' response).
28
+ // Named DccInfo (not CurrencyExchange) to avoid clashing with the Nitro-generated
29
+ // CurrencyExchange struct in the same namespace.
30
+ struct DccInfo {
31
+ bool applied = false;
32
+ std::string rate; // 8 digits, 4 decimals
33
+ std::string currencyCode; // alpha-3
34
+ std::string amount; // 12 digits, in transaction currency
35
+ std::string precision; // decimals
36
+ };
37
+
38
+ // Result of a payment-family response ('E' without DCC, 'V' with DCC). Reused
39
+ // for reversal / card verification / pre-auth closure which share the layout.
40
+ struct PaymentResponse {
41
+ Outcome outcome = Outcome::Unknown;
42
+ std::string resultCode; // raw "00"/"01"/"05"/"09"
43
+ std::string pan; // positive
44
+ std::string transactionType; // positive, raw "ICC"/"MAG"/...
45
+ std::string authCode; // positive
46
+ std::string hostDateTime; // positive, raw DDDHHMM
47
+ std::string errorDescription; // negative
48
+ std::string cardType; // common, raw "1"/"2"/"3"
49
+ std::string acquirerId; // common
50
+ std::string stan; // common
51
+ std::string onlineId; // common
52
+ DccInfo currency; // only when DCC present
53
+ };
54
+
55
+ struct StatusResponse {
56
+ std::string terminalId;
57
+ std::string dateTimeRaw; // "DDMMYYhhmm"
58
+ int status = -1; // 0..6, -1 = unknown/missing
59
+ std::string softwareRelease;
60
+ };
61
+
62
+ struct TotalsResponse {
63
+ Outcome outcome = Outcome::Unknown;
64
+ std::string resultCode;
65
+ std::string posTotal; // 16 digits, cents
66
+ };
67
+
68
+ struct CloseResponse {
69
+ Outcome outcome = Outcome::Unknown;
70
+ std::string resultCode;
71
+ std::string posTotal; // positive, 16 digits
72
+ std::string hostTotal; // positive, 16 digits
73
+ std::string errorDescription; // negative
74
+ std::string actionCode; // negative
75
+ };
76
+
77
+ struct PreAuthResponse {
78
+ Outcome outcome = Outcome::Unknown;
79
+ std::string resultCode;
80
+ std::string pan;
81
+ std::string transactionType;
82
+ std::string authCode;
83
+ std::string preAuthorizedAmount; // 8 digits, cents
84
+ std::string preAuthCode; // 9 digits, unique pre-auth id
85
+ std::string actionCode;
86
+ std::string hostDateTime;
87
+ std::string errorDescription;
88
+ std::string cardType;
89
+ std::string acquirerId;
90
+ std::string stan;
91
+ std::string onlineId;
92
+ };
93
+
94
+ struct VasResponse {
95
+ std::string responseId; // RESPID parsed from XML ("0" = OK), "" if absent
96
+ std::string responseMessage; // RESPMSG
97
+ std::string orderId; // ORDER_ID
98
+ bool moreMessages = false; // concatenation flag "1"
99
+ std::string idMessage; // 3-digit sequence
100
+ std::string rawXml; // the XML body of this message
101
+ };
102
+
103
+ class Ecr17Response {
104
+ public:
105
+ static PaymentResponse parsePayment(const std::string& payload);
106
+ static StatusResponse parseStatus(const std::string& payload);
107
+ static TotalsResponse parseTotals(const std::string& payload);
108
+ static CloseResponse parseClose(const std::string& payload);
109
+ static PreAuthResponse parsePreAuth(const std::string& payload);
110
+ static VasResponse parseVas(const std::string& payload);
111
+ };
112
+
113
+ } // namespace margelo::nitro::ecr17
package/cpp/Lcr/Lcr.cpp CHANGED
@@ -1,42 +1,42 @@
1
- #include "Lcr.hpp"
2
-
3
- namespace margelo::nitro::ecr17 {
4
-
5
- static constexpr uint8_t STX = 0x02;
6
- static constexpr uint8_t ETX = 0x03;
7
-
8
- uint8_t Lrc::compute(const std::vector<uint8_t>& payload, LrcMode mode) {
9
- uint8_t lrc = BASE;
10
-
11
- switch (mode) {
12
- case LrcMode::STX:
13
- case LrcMode::STX_NOEXT:
14
- lrc ^= STX;
15
- break;
16
-
17
- default:
18
- break;
19
- }
20
-
21
- for (auto b : payload) {
22
- lrc ^= b;
23
- }
24
-
25
- switch (mode) {
26
- case LrcMode::STX:
27
- case LrcMode::NOEXT:
28
- lrc ^= ETX;
29
- break;
30
-
31
- default:
32
- break;
33
- }
34
-
35
- return lrc;
36
- }
37
-
38
- uint8_t Lrc::compute(const std::string& payload, LrcMode mode) {
39
- return compute(std::vector<uint8_t>(payload.begin(), payload.end()), mode);
40
- }
41
-
42
- } // namespace margelo::nitro::ecr17
1
+ #include "Lcr.hpp"
2
+
3
+ namespace margelo::nitro::ecr17 {
4
+
5
+ static constexpr uint8_t STX = 0x02;
6
+ static constexpr uint8_t ETX = 0x03;
7
+
8
+ uint8_t Lrc::compute(const std::vector<uint8_t>& payload, LrcMode mode) {
9
+ uint8_t lrc = BASE;
10
+
11
+ switch (mode) {
12
+ case LrcMode::STX:
13
+ case LrcMode::STX_NOEXT:
14
+ lrc ^= STX;
15
+ break;
16
+
17
+ default:
18
+ break;
19
+ }
20
+
21
+ for (auto b : payload) {
22
+ lrc ^= b;
23
+ }
24
+
25
+ switch (mode) {
26
+ case LrcMode::STX:
27
+ case LrcMode::NOEXT:
28
+ lrc ^= ETX;
29
+ break;
30
+
31
+ default:
32
+ break;
33
+ }
34
+
35
+ return lrc;
36
+ }
37
+
38
+ uint8_t Lrc::compute(const std::string& payload, LrcMode mode) {
39
+ return compute(std::vector<uint8_t>(payload.begin(), payload.end()), mode);
40
+ }
41
+
42
+ } // namespace margelo::nitro::ecr17
package/cpp/Lcr/Lcr.hpp CHANGED
@@ -1,22 +1,22 @@
1
- // cpp/core/Lrc.hpp
2
-
3
- #pragma once
4
-
5
- #include <cstdint>
6
- #include <string>
7
- #include <vector>
8
-
9
- #include "LrcMode.hpp"
10
-
11
- namespace margelo::nitro::ecr17 {
12
-
13
- class Lrc {
14
- public:
15
- static constexpr uint8_t BASE = 0x7F;
16
-
17
- static uint8_t compute(const std::vector<uint8_t>& payload, LrcMode mode);
18
-
19
- static uint8_t compute(const std::string& payload, LrcMode mode);
20
- };
21
-
1
+ // cpp/core/Lrc.hpp
2
+
3
+ #pragma once
4
+
5
+ #include <cstdint>
6
+ #include <string>
7
+ #include <vector>
8
+
9
+ #include "LrcMode.hpp"
10
+
11
+ namespace margelo::nitro::ecr17 {
12
+
13
+ class Lrc {
14
+ public:
15
+ static constexpr uint8_t BASE = 0x7F;
16
+
17
+ static uint8_t compute(const std::vector<uint8_t>& payload, LrcMode mode);
18
+
19
+ static uint8_t compute(const std::string& payload, LrcMode mode);
20
+ };
21
+
22
22
  } // namespace margelo::nitro::ecr17
@@ -1,146 +1,146 @@
1
- #include "PacketCodec.hpp"
2
-
3
- #include <algorithm> // std::find
4
- #include <iterator> // std::distance
5
-
6
- namespace margelo::nitro::ecr17 {
7
-
8
- PacketCodec::PacketCodec(LrcMode mode) : lrcMode_(mode) {}
9
-
10
- std::vector<uint8_t> PacketCodec::encodeApplication(const std::string& payload) {
11
- std::vector<uint8_t> frame;
12
-
13
- frame.push_back(STX);
14
-
15
- frame.insert(frame.end(), payload.begin(), payload.end());
16
-
17
- frame.push_back(ETX);
18
-
19
- uint8_t lrc = Lrc::compute(payload, lrcMode_);
20
-
21
- frame.push_back(lrc);
22
-
23
- return frame;
24
- }
25
-
26
- std::vector<uint8_t> PacketCodec::encodeControl(uint8_t ctrl) {
27
- std::vector<uint8_t> frame;
28
-
29
- frame.push_back(ctrl);
30
- frame.push_back(ETX);
31
-
32
- uint8_t lrc = Lrc::compute(std::vector<uint8_t>{ctrl}, lrcMode_);
33
-
34
- frame.push_back(lrc);
35
-
36
- return frame;
37
- }
38
-
39
- DecodedPacket PacketCodec::decode(const std::vector<uint8_t>& data) {
40
- if (data.empty()) {
41
- return {
42
- PacketType::UNKNOWN,
43
- "",
44
- false,
45
- };
46
- }
47
-
48
- uint8_t first = data[0];
49
-
50
- if (first == ACK) {
51
- return {
52
- PacketType::ACK,
53
- "",
54
- true,
55
- };
56
- }
57
-
58
- if (first == NAK) {
59
- return {
60
- PacketType::NAK,
61
- "",
62
- true,
63
- };
64
- }
65
-
66
- if (first == SOH) {
67
- // Progress update packet: SOH + message + EOT (no LRC). We need at least
68
- // SOH and the trailing EOT before stripping the first/last byte,
69
- // otherwise the iterator range below would be invalid (last < first).
70
- if (data.size() < 2) {
71
- return {
72
- PacketType::UNKNOWN,
73
- "",
74
- false,
75
- };
76
- }
77
-
78
- // The spec mandates SOH + 20-char message + EOT. Reject any frame whose
79
- // final byte is not EOT so that garbage or a truncated read is never
80
- // silently accepted as a valid progress update.
81
- if (data.back() != EOT) {
82
- return {
83
- PacketType::UNKNOWN,
84
- "",
85
- false,
86
- };
87
- }
88
-
89
- std::string payload(data.begin() + 1, data.end() - 1);
90
-
91
- return {
92
- PacketType::PROGRESS,
93
- payload,
94
- true,
95
- };
96
- }
97
-
98
- if (first == STX) {
99
- auto etxIt = std::find(data.begin(), data.end(), ETX);
100
-
101
- if (etxIt == data.end()) {
102
- return {
103
- PacketType::UNKNOWN,
104
- "",
105
- false,
106
- };
107
- }
108
-
109
- size_t etxIndex = std::distance(data.begin(), etxIt);
110
-
111
- // A well-formed application frame is exactly STX + payload + ETX + LRC,
112
- // so the LRC must be the final byte. Reject both a truncated frame (no
113
- // LRC after ETX) and a buffer with trailing bytes -- e.g. a coalesced
114
- // socket read holding a second frame ("STX..ETX LRC STX..") or garbage
115
- // after the LRC. Splitting a byte stream into individual frames is the
116
- // transport layer's responsibility, and DecodedPacket cannot carry the
117
- // unconsumed remainder.
118
- if (etxIndex + 2 != data.size()) {
119
- return {
120
- PacketType::UNKNOWN,
121
- "",
122
- false,
123
- };
124
- }
125
-
126
- std::string payload(data.begin() + 1, data.begin() + etxIndex);
127
-
128
- uint8_t rxLrc = data[etxIndex + 1];
129
-
130
- uint8_t calcLrc = Lrc::compute(payload, lrcMode_);
131
-
132
- return {
133
- PacketType::APPLICATION,
134
- payload,
135
- rxLrc == calcLrc,
136
- };
137
- }
138
-
139
- return {
140
- PacketType::UNKNOWN,
141
- "",
142
- false,
143
- };
144
- }
145
-
1
+ #include "PacketCodec.hpp"
2
+
3
+ #include <algorithm> // std::find
4
+ #include <iterator> // std::distance
5
+
6
+ namespace margelo::nitro::ecr17 {
7
+
8
+ PacketCodec::PacketCodec(LrcMode mode) : lrcMode_(mode) {}
9
+
10
+ std::vector<uint8_t> PacketCodec::encodeApplication(const std::string& payload) {
11
+ std::vector<uint8_t> frame;
12
+
13
+ frame.push_back(STX);
14
+
15
+ frame.insert(frame.end(), payload.begin(), payload.end());
16
+
17
+ frame.push_back(ETX);
18
+
19
+ uint8_t lrc = Lrc::compute(payload, lrcMode_);
20
+
21
+ frame.push_back(lrc);
22
+
23
+ return frame;
24
+ }
25
+
26
+ std::vector<uint8_t> PacketCodec::encodeControl(uint8_t ctrl) {
27
+ std::vector<uint8_t> frame;
28
+
29
+ frame.push_back(ctrl);
30
+ frame.push_back(ETX);
31
+
32
+ uint8_t lrc = Lrc::compute(std::vector<uint8_t>{ctrl}, lrcMode_);
33
+
34
+ frame.push_back(lrc);
35
+
36
+ return frame;
37
+ }
38
+
39
+ DecodedPacket PacketCodec::decode(const std::vector<uint8_t>& data) {
40
+ if (data.empty()) {
41
+ return {
42
+ PacketType::UNKNOWN,
43
+ "",
44
+ false,
45
+ };
46
+ }
47
+
48
+ uint8_t first = data[0];
49
+
50
+ if (first == ACK) {
51
+ return {
52
+ PacketType::ACK,
53
+ "",
54
+ true,
55
+ };
56
+ }
57
+
58
+ if (first == NAK) {
59
+ return {
60
+ PacketType::NAK,
61
+ "",
62
+ true,
63
+ };
64
+ }
65
+
66
+ if (first == SOH) {
67
+ // Progress update packet: SOH + message + EOT (no LRC). We need at least
68
+ // SOH and the trailing EOT before stripping the first/last byte,
69
+ // otherwise the iterator range below would be invalid (last < first).
70
+ if (data.size() < 2) {
71
+ return {
72
+ PacketType::UNKNOWN,
73
+ "",
74
+ false,
75
+ };
76
+ }
77
+
78
+ // The spec mandates SOH + 20-char message + EOT. Reject any frame whose
79
+ // final byte is not EOT so that garbage or a truncated read is never
80
+ // silently accepted as a valid progress update.
81
+ if (data.back() != EOT) {
82
+ return {
83
+ PacketType::UNKNOWN,
84
+ "",
85
+ false,
86
+ };
87
+ }
88
+
89
+ std::string payload(data.begin() + 1, data.end() - 1);
90
+
91
+ return {
92
+ PacketType::PROGRESS,
93
+ payload,
94
+ true,
95
+ };
96
+ }
97
+
98
+ if (first == STX) {
99
+ auto etxIt = std::find(data.begin(), data.end(), ETX);
100
+
101
+ if (etxIt == data.end()) {
102
+ return {
103
+ PacketType::UNKNOWN,
104
+ "",
105
+ false,
106
+ };
107
+ }
108
+
109
+ size_t etxIndex = std::distance(data.begin(), etxIt);
110
+
111
+ // A well-formed application frame is exactly STX + payload + ETX + LRC,
112
+ // so the LRC must be the final byte. Reject both a truncated frame (no
113
+ // LRC after ETX) and a buffer with trailing bytes -- e.g. a coalesced
114
+ // socket read holding a second frame ("STX..ETX LRC STX..") or garbage
115
+ // after the LRC. Splitting a byte stream into individual frames is the
116
+ // transport layer's responsibility, and DecodedPacket cannot carry the
117
+ // unconsumed remainder.
118
+ if (etxIndex + 2 != data.size()) {
119
+ return {
120
+ PacketType::UNKNOWN,
121
+ "",
122
+ false,
123
+ };
124
+ }
125
+
126
+ std::string payload(data.begin() + 1, data.begin() + etxIndex);
127
+
128
+ uint8_t rxLrc = data[etxIndex + 1];
129
+
130
+ uint8_t calcLrc = Lrc::compute(payload, lrcMode_);
131
+
132
+ return {
133
+ PacketType::APPLICATION,
134
+ payload,
135
+ rxLrc == calcLrc,
136
+ };
137
+ }
138
+
139
+ return {
140
+ PacketType::UNKNOWN,
141
+ "",
142
+ false,
143
+ };
144
+ }
145
+
146
146
  } // namespace margelo::nitro::ecr17