@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.
- package/Ecr17.podspec +39 -39
- package/README.md +348 -348
- package/android/CMakeLists.txt +41 -41
- package/android/build.gradle +148 -148
- package/android/fix-prefab.gradle +50 -50
- package/android/gradle.properties +5 -5
- package/android/src/main/AndroidManifest.xml +2 -2
- package/android/src/main/cpp/cpp-adapter.cpp +8 -8
- package/android/src/main/java/com/margelo/nitro/ecr17/HybridEcr17Transport.kt +233 -233
- package/android/src/main/java/com/padosoft/ecr17/Ecr17Package.kt +30 -30
- package/cpp/Ecr17.hpp +1 -1
- package/cpp/Ecr17Client/HybridEcr17Client.cpp +598 -598
- package/cpp/Ecr17Client/HybridEcr17Client.hpp +85 -85
- package/cpp/Ecr17Protocol/Ecr17Protocol.cpp +277 -277
- package/cpp/Ecr17Protocol/Ecr17Protocol.hpp +103 -103
- package/cpp/Ecr17Response/Ecr17Response.cpp +155 -155
- package/cpp/Ecr17Response/Ecr17Response.hpp +113 -113
- package/cpp/Lcr/Lcr.cpp +42 -42
- package/cpp/Lcr/Lcr.hpp +21 -21
- package/cpp/PacketCodec/PacketCodec.cpp +145 -145
- package/cpp/PacketCodec/PacketCodec.hpp +47 -47
- package/cpp/Session/Ecr17Session.cpp +260 -260
- package/cpp/Session/Ecr17Session.hpp +97 -97
- package/cpp/Session/RetryPolicy.hpp +23 -23
- package/cpp/Transport/FakeTransport.hpp +95 -95
- package/cpp/Transport/NativeTransportAdapter.cpp +42 -42
- package/cpp/Transport/NativeTransportAdapter.hpp +32 -32
- package/cpp/Transport/Transport.hpp +30 -30
- package/cpp/tests/CMakeLists.txt +55 -55
- package/cpp/tests/PosixTcpTransport.hpp +105 -105
- package/cpp/tests/stubs/LrcMode.hpp +25 -25
- package/cpp/tests/test_flows.cpp +148 -148
- package/cpp/tests/test_integration_terminal.cpp +72 -72
- package/cpp/tests/test_lrc.cpp +66 -66
- package/cpp/tests/test_packet_codec.cpp +164 -164
- package/cpp/tests/test_protocol.cpp +102 -102
- package/cpp/tests/test_protocol_commands.cpp +190 -190
- package/cpp/tests/test_response.cpp +164 -164
- package/cpp/tests/test_retry_policy.cpp +28 -28
- package/cpp/tests/test_session.cpp +262 -262
- package/ios/HybridEcr17Transport.swift +103 -103
- package/lib/commonjs/index.js +50 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/package.json +1 -0
- package/lib/commonjs/specs/client.nitro.js +17 -0
- package/lib/commonjs/specs/client.nitro.js.map +1 -0
- package/lib/commonjs/specs/transport.nitro.js +6 -0
- package/lib/commonjs/specs/transport.nitro.js.map +1 -0
- package/lib/commonjs/types/client.js +2 -0
- package/lib/commonjs/types/client.js.map +1 -0
- package/lib/commonjs/utils/client.js +13 -0
- package/lib/commonjs/utils/client.js.map +1 -0
- package/lib/module/index.js +7 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/specs/client.nitro.js +13 -0
- package/lib/module/specs/client.nitro.js.map +1 -0
- package/lib/module/specs/transport.nitro.js +4 -0
- package/lib/module/specs/transport.nitro.js.map +1 -0
- package/lib/module/types/client.js +2 -0
- package/lib/module/types/client.js.map +1 -0
- package/lib/module/utils/client.js +9 -0
- package/lib/module/utils/client.js.map +1 -0
- package/lib/typescript/src/index.d.ts +5 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/specs/client.nitro.d.ts +63 -0
- package/lib/typescript/src/specs/client.nitro.d.ts.map +1 -0
- package/lib/typescript/src/specs/transport.nitro.d.ts +13 -0
- package/lib/typescript/src/specs/transport.nitro.d.ts.map +1 -0
- package/lib/typescript/src/types/client.d.ts +138 -0
- package/lib/typescript/src/types/client.d.ts.map +1 -0
- package/lib/typescript/src/utils/client.d.ts +3 -0
- package/lib/typescript/src/utils/client.d.ts.map +1 -0
- package/nitro.json +30 -30
- package/package.json +4 -4
- package/react-native.config.js +18 -18
- package/src/index.ts +4 -4
- package/src/specs/client.nitro.ts +102 -102
- package/src/specs/transport.nitro.ts +25 -25
- package/src/types/client.ts +196 -196
- package/src/utils/client.ts +10 -10
|
@@ -1,262 +1,262 @@
|
|
|
1
|
-
// Orchestration tests for Ecr17Session driven by the in-memory FakeTransport:
|
|
2
|
-
// ACK/NAK handshake, retransmission, timeouts, LRC-failure NAK, and progress /
|
|
3
|
-
// receipt forwarding. Timeouts are tiny so the suite stays fast.
|
|
4
|
-
|
|
5
|
-
#include <gtest/gtest.h>
|
|
6
|
-
|
|
7
|
-
#include <cstdint>
|
|
8
|
-
#include <string>
|
|
9
|
-
#include <vector>
|
|
10
|
-
|
|
11
|
-
#include "PacketCodec/PacketCodec.hpp"
|
|
12
|
-
#include "Session/Ecr17Session.hpp"
|
|
13
|
-
#include "Transport/FakeTransport.hpp"
|
|
14
|
-
|
|
15
|
-
using namespace margelo::nitro::ecr17;
|
|
16
|
-
|
|
17
|
-
namespace {
|
|
18
|
-
|
|
19
|
-
SessionConfig fastConfig() {
|
|
20
|
-
SessionConfig c;
|
|
21
|
-
c.lrcMode = LrcMode::STD;
|
|
22
|
-
c.ackTimeoutMs = 40;
|
|
23
|
-
c.responseTimeoutMs = 40;
|
|
24
|
-
c.retryCount = 2;
|
|
25
|
-
c.retryDelayMs = 1;
|
|
26
|
-
return c;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
void append(std::vector<uint8_t>& a, const std::vector<uint8_t>& b) {
|
|
30
|
-
a.insert(a.end(), b.begin(), b.end());
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
std::vector<uint8_t> progressFrame(const std::string& msg20) {
|
|
34
|
-
std::vector<uint8_t> f{0x01}; // SOH
|
|
35
|
-
f.insert(f.end(), msg20.begin(), msg20.end());
|
|
36
|
-
f.push_back(0x04); // EOT
|
|
37
|
-
return f;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const std::string kResultPayload = "123456780E0000DATA"; // code 'E' at pos 10 -> result
|
|
41
|
-
const std::string kReceiptPayload = "123456780SLINE 1"; // code 'S' at pos 10 -> receipt
|
|
42
|
-
|
|
43
|
-
} // namespace
|
|
44
|
-
|
|
45
|
-
TEST(Session, HappyPathReturnsResultAndAcks) {
|
|
46
|
-
FakeTransport t;
|
|
47
|
-
PacketCodec codec(LrcMode::STD);
|
|
48
|
-
std::vector<uint8_t> response = codec.encodeControl(PacketCodec::ACK);
|
|
49
|
-
append(response, codec.encodeApplication(kResultPayload));
|
|
50
|
-
t.enqueueResponse(response);
|
|
51
|
-
|
|
52
|
-
Ecr17Session session(t, fastConfig());
|
|
53
|
-
DecodedPacket result = session.exchange("123456780P...");
|
|
54
|
-
EXPECT_EQ(result.type, PacketType::APPLICATION);
|
|
55
|
-
EXPECT_TRUE(result.validLrc);
|
|
56
|
-
EXPECT_EQ(result.payload, kResultPayload);
|
|
57
|
-
EXPECT_EQ(t.applicationRequestCount(), 1u);
|
|
58
|
-
|
|
59
|
-
// The session must have ACKed the received result frame.
|
|
60
|
-
bool sentAck = false;
|
|
61
|
-
for (const auto& f : t.sentFrames()) {
|
|
62
|
-
if (!f.empty() && f.front() == PacketCodec::ACK) sentAck = true;
|
|
63
|
-
}
|
|
64
|
-
EXPECT_TRUE(sentAck);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
TEST(Session, NakTriggersRetransmitThenSucceeds) {
|
|
68
|
-
FakeTransport t;
|
|
69
|
-
PacketCodec codec(LrcMode::STD);
|
|
70
|
-
t.enqueueResponse(codec.encodeControl(PacketCodec::NAK)); // reply to attempt 1
|
|
71
|
-
std::vector<uint8_t> ok = codec.encodeControl(PacketCodec::ACK);
|
|
72
|
-
append(ok, codec.encodeApplication(kResultPayload));
|
|
73
|
-
t.enqueueResponse(ok); // reply to retransmit
|
|
74
|
-
|
|
75
|
-
Ecr17Session session(t, fastConfig());
|
|
76
|
-
DecodedPacket result = session.exchange("123456780P...");
|
|
77
|
-
EXPECT_EQ(result.payload, kResultPayload);
|
|
78
|
-
EXPECT_EQ(t.applicationRequestCount(), 2u); // initial + 1 retransmit
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
TEST(Session, AckTimeoutExhaustsRetriesThenThrows) {
|
|
82
|
-
FakeTransport t; // no responses queued
|
|
83
|
-
Ecr17Session session(t, fastConfig());
|
|
84
|
-
EXPECT_THROW(session.exchange("123456780P..."), std::runtime_error);
|
|
85
|
-
// initial + retryCount retransmissions
|
|
86
|
-
EXPECT_EQ(t.applicationRequestCount(), 3u);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
TEST(Session, BadLrcResponseSendsNak) {
|
|
90
|
-
FakeTransport t;
|
|
91
|
-
PacketCodec codec(LrcMode::STD);
|
|
92
|
-
std::vector<uint8_t> response = codec.encodeControl(PacketCodec::ACK);
|
|
93
|
-
auto bad = codec.encodeApplication(kResultPayload);
|
|
94
|
-
bad.back() ^= 0xFF; // corrupt LRC
|
|
95
|
-
append(response, bad);
|
|
96
|
-
t.enqueueResponse(response);
|
|
97
|
-
|
|
98
|
-
Ecr17Session session(t, fastConfig());
|
|
99
|
-
EXPECT_THROW(session.exchange("123456780P..."), std::runtime_error); // no valid result
|
|
100
|
-
|
|
101
|
-
bool sentNak = false;
|
|
102
|
-
for (const auto& f : t.sentFrames()) {
|
|
103
|
-
if (!f.empty() && f.front() == PacketCodec::NAK) sentNak = true;
|
|
104
|
-
}
|
|
105
|
-
EXPECT_TRUE(sentNak);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
TEST(Session, ProgressMessagesForwarded) {
|
|
109
|
-
FakeTransport t;
|
|
110
|
-
PacketCodec codec(LrcMode::STD);
|
|
111
|
-
std::vector<uint8_t> response = codec.encodeControl(PacketCodec::ACK);
|
|
112
|
-
append(response, progressFrame("ATTENDERE PREGO "));
|
|
113
|
-
append(response, codec.encodeApplication(kResultPayload));
|
|
114
|
-
t.enqueueResponse(response);
|
|
115
|
-
|
|
116
|
-
std::vector<std::string> progress;
|
|
117
|
-
Ecr17Session session(t, fastConfig());
|
|
118
|
-
session.setOnProgress([&](const std::string& m) { progress.push_back(m); });
|
|
119
|
-
|
|
120
|
-
DecodedPacket result = session.exchange("123456780P...");
|
|
121
|
-
EXPECT_EQ(result.payload, kResultPayload);
|
|
122
|
-
ASSERT_EQ(progress.size(), 1u);
|
|
123
|
-
EXPECT_EQ(progress[0], "ATTENDERE PREGO ");
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
TEST(Session, ReceiptLinesForwardedBeforeResult) {
|
|
127
|
-
FakeTransport t;
|
|
128
|
-
PacketCodec codec(LrcMode::STD);
|
|
129
|
-
std::vector<uint8_t> response = codec.encodeControl(PacketCodec::ACK);
|
|
130
|
-
append(response, codec.encodeApplication(kReceiptPayload));
|
|
131
|
-
append(response, codec.encodeApplication(kResultPayload));
|
|
132
|
-
t.enqueueResponse(response);
|
|
133
|
-
|
|
134
|
-
std::vector<std::string> receipts;
|
|
135
|
-
Ecr17Session session(t, fastConfig());
|
|
136
|
-
session.setOnReceiptLine([&](const std::string& l) { receipts.push_back(l); });
|
|
137
|
-
|
|
138
|
-
DecodedPacket result = session.exchange("123456780P...");
|
|
139
|
-
EXPECT_EQ(result.payload, kResultPayload);
|
|
140
|
-
ASSERT_EQ(receipts.size(), 1u);
|
|
141
|
-
EXPECT_EQ(receipts[0], kReceiptPayload);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// Regression: some terminals send the application RESULT before (or instead of)
|
|
145
|
-
// the physical ACK. The handshake must not discard that frame — losing the
|
|
146
|
-
// result of a completed financial transaction is the worst failure mode.
|
|
147
|
-
TEST(Session, ResultBeforeAckIsNotLost) {
|
|
148
|
-
FakeTransport t;
|
|
149
|
-
PacketCodec codec(LrcMode::STD);
|
|
150
|
-
t.enqueueResponse(codec.encodeApplication(kResultPayload)); // result, no leading ACK
|
|
151
|
-
|
|
152
|
-
Ecr17Session session(t, fastConfig());
|
|
153
|
-
DecodedPacket result = session.exchange("123456780P...");
|
|
154
|
-
EXPECT_EQ(result.type, PacketType::APPLICATION);
|
|
155
|
-
EXPECT_EQ(result.payload, kResultPayload);
|
|
156
|
-
EXPECT_EQ(t.applicationRequestCount(), 1u); // no spurious retransmit
|
|
157
|
-
|
|
158
|
-
// The accepted result must still be ACKed back to the terminal.
|
|
159
|
-
bool sentAck = false;
|
|
160
|
-
for (const auto& f : t.sentFrames()) {
|
|
161
|
-
if (!f.empty() && f.front() == PacketCodec::ACK) sentAck = true;
|
|
162
|
-
}
|
|
163
|
-
EXPECT_TRUE(sentAck);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
TEST(Session, ResponseTimeoutAfterAckThrows) {
|
|
167
|
-
FakeTransport t;
|
|
168
|
-
PacketCodec codec(LrcMode::STD);
|
|
169
|
-
t.enqueueResponse(codec.encodeControl(PacketCodec::ACK)); // ACK only, no result
|
|
170
|
-
|
|
171
|
-
Ecr17Session session(t, fastConfig());
|
|
172
|
-
EXPECT_THROW(session.exchange("123456780P..."), std::runtime_error);
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
TEST(Session, DisconnectDuringExchangeThrows) {
|
|
176
|
-
FakeTransport t;
|
|
177
|
-
t.disconnectOnNextRequest();
|
|
178
|
-
Ecr17Session session(t, fastConfig());
|
|
179
|
-
EXPECT_THROW(session.exchange("123456780P..."), std::runtime_error);
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
// Scenario: the connection drops mid-exchange, then the client reconnects and
|
|
183
|
-
// retries (the auto-reconnect path for safe commands). The session must recover
|
|
184
|
-
// — it must NOT stay stuck in the disconnected state after a drop.
|
|
185
|
-
TEST(Session, RecoversAndSucceedsAfterReconnect) {
|
|
186
|
-
FakeTransport t;
|
|
187
|
-
t.disconnectOnNextRequest();
|
|
188
|
-
Ecr17Session session(t, fastConfig());
|
|
189
|
-
|
|
190
|
-
// First attempt drops.
|
|
191
|
-
EXPECT_THROW(session.exchange("123456780P..."), std::runtime_error);
|
|
192
|
-
|
|
193
|
-
// Client reconnects the transport and the next transaction must work.
|
|
194
|
-
t.rearm();
|
|
195
|
-
PacketCodec codec(LrcMode::STD);
|
|
196
|
-
std::vector<uint8_t> ok = codec.encodeControl(PacketCodec::ACK);
|
|
197
|
-
append(ok, codec.encodeApplication(kResultPayload));
|
|
198
|
-
t.enqueueResponse(ok);
|
|
199
|
-
|
|
200
|
-
DecodedPacket result = session.exchange("123456780P...");
|
|
201
|
-
EXPECT_EQ(result.type, PacketType::APPLICATION);
|
|
202
|
-
EXPECT_EQ(result.payload, kResultPayload);
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
TEST(Session, SendAckOnlyReturnsOnAck) {
|
|
206
|
-
FakeTransport t;
|
|
207
|
-
PacketCodec codec(LrcMode::STD);
|
|
208
|
-
t.enqueueResponse(codec.encodeControl(PacketCodec::ACK)); // ACK, no app result
|
|
209
|
-
Ecr17Session session(t, fastConfig());
|
|
210
|
-
EXPECT_NO_THROW(session.sendAckOnly("123456780E1"));
|
|
211
|
-
EXPECT_EQ(t.applicationRequestCount(), 1u);
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
TEST(Session, SendAckOnlyRetransmitsOnNak) {
|
|
215
|
-
FakeTransport t;
|
|
216
|
-
PacketCodec codec(LrcMode::STD);
|
|
217
|
-
t.enqueueResponse(codec.encodeControl(PacketCodec::NAK));
|
|
218
|
-
t.enqueueResponse(codec.encodeControl(PacketCodec::ACK));
|
|
219
|
-
Ecr17Session session(t, fastConfig());
|
|
220
|
-
EXPECT_NO_THROW(session.sendAckOnly("123456780E0"));
|
|
221
|
-
EXPECT_EQ(t.applicationRequestCount(), 2u);
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
TEST(Session, SendAckOnlyTimesOut) {
|
|
225
|
-
FakeTransport t; // no ACK
|
|
226
|
-
Ecr17Session session(t, fastConfig());
|
|
227
|
-
EXPECT_THROW(session.sendAckOnly("123456780E1"), std::runtime_error);
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
TEST(Session, ExchangeWithAdditionalDataSendsTwoRequests) {
|
|
231
|
-
FakeTransport t;
|
|
232
|
-
PacketCodec codec(LrcMode::STD);
|
|
233
|
-
t.enqueueResponse(codec.encodeControl(PacketCodec::ACK)); // ACK for the main 'P'
|
|
234
|
-
std::vector<uint8_t> ok = codec.encodeControl(PacketCodec::ACK);
|
|
235
|
-
append(ok, codec.encodeApplication(kResultPayload));
|
|
236
|
-
t.enqueueResponse(ok); // ACK for 'U' + the result
|
|
237
|
-
|
|
238
|
-
Ecr17Session session(t, fastConfig());
|
|
239
|
-
DecodedPacket result = session.exchangeWithAdditionalData("123456780P...", "123456780U...");
|
|
240
|
-
EXPECT_EQ(result.payload, kResultPayload);
|
|
241
|
-
EXPECT_EQ(t.applicationRequestCount(), 2u); // P + U
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
TEST(Session, ReceiptDrainForwardsReceiptsAfterResult) {
|
|
245
|
-
FakeTransport t;
|
|
246
|
-
PacketCodec codec(LrcMode::STD);
|
|
247
|
-
std::vector<uint8_t> response = codec.encodeControl(PacketCodec::ACK);
|
|
248
|
-
append(response, codec.encodeApplication(kResultPayload)); // result first
|
|
249
|
-
append(response, codec.encodeApplication(kReceiptPayload)); // then a receipt line
|
|
250
|
-
t.enqueueResponse(response);
|
|
251
|
-
|
|
252
|
-
SessionConfig cfg = fastConfig();
|
|
253
|
-
cfg.receiptDrainMs = 30; // drain receipts that follow the result
|
|
254
|
-
std::vector<std::string> receipts;
|
|
255
|
-
Ecr17Session session(t, cfg);
|
|
256
|
-
session.setOnReceiptLine([&](const std::string& l) { receipts.push_back(l); });
|
|
257
|
-
|
|
258
|
-
DecodedPacket result = session.exchange("123456780P...");
|
|
259
|
-
EXPECT_EQ(result.payload, kResultPayload);
|
|
260
|
-
ASSERT_EQ(receipts.size(), 1u);
|
|
261
|
-
EXPECT_EQ(receipts[0], kReceiptPayload);
|
|
262
|
-
}
|
|
1
|
+
// Orchestration tests for Ecr17Session driven by the in-memory FakeTransport:
|
|
2
|
+
// ACK/NAK handshake, retransmission, timeouts, LRC-failure NAK, and progress /
|
|
3
|
+
// receipt forwarding. Timeouts are tiny so the suite stays fast.
|
|
4
|
+
|
|
5
|
+
#include <gtest/gtest.h>
|
|
6
|
+
|
|
7
|
+
#include <cstdint>
|
|
8
|
+
#include <string>
|
|
9
|
+
#include <vector>
|
|
10
|
+
|
|
11
|
+
#include "PacketCodec/PacketCodec.hpp"
|
|
12
|
+
#include "Session/Ecr17Session.hpp"
|
|
13
|
+
#include "Transport/FakeTransport.hpp"
|
|
14
|
+
|
|
15
|
+
using namespace margelo::nitro::ecr17;
|
|
16
|
+
|
|
17
|
+
namespace {
|
|
18
|
+
|
|
19
|
+
SessionConfig fastConfig() {
|
|
20
|
+
SessionConfig c;
|
|
21
|
+
c.lrcMode = LrcMode::STD;
|
|
22
|
+
c.ackTimeoutMs = 40;
|
|
23
|
+
c.responseTimeoutMs = 40;
|
|
24
|
+
c.retryCount = 2;
|
|
25
|
+
c.retryDelayMs = 1;
|
|
26
|
+
return c;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
void append(std::vector<uint8_t>& a, const std::vector<uint8_t>& b) {
|
|
30
|
+
a.insert(a.end(), b.begin(), b.end());
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
std::vector<uint8_t> progressFrame(const std::string& msg20) {
|
|
34
|
+
std::vector<uint8_t> f{0x01}; // SOH
|
|
35
|
+
f.insert(f.end(), msg20.begin(), msg20.end());
|
|
36
|
+
f.push_back(0x04); // EOT
|
|
37
|
+
return f;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const std::string kResultPayload = "123456780E0000DATA"; // code 'E' at pos 10 -> result
|
|
41
|
+
const std::string kReceiptPayload = "123456780SLINE 1"; // code 'S' at pos 10 -> receipt
|
|
42
|
+
|
|
43
|
+
} // namespace
|
|
44
|
+
|
|
45
|
+
TEST(Session, HappyPathReturnsResultAndAcks) {
|
|
46
|
+
FakeTransport t;
|
|
47
|
+
PacketCodec codec(LrcMode::STD);
|
|
48
|
+
std::vector<uint8_t> response = codec.encodeControl(PacketCodec::ACK);
|
|
49
|
+
append(response, codec.encodeApplication(kResultPayload));
|
|
50
|
+
t.enqueueResponse(response);
|
|
51
|
+
|
|
52
|
+
Ecr17Session session(t, fastConfig());
|
|
53
|
+
DecodedPacket result = session.exchange("123456780P...");
|
|
54
|
+
EXPECT_EQ(result.type, PacketType::APPLICATION);
|
|
55
|
+
EXPECT_TRUE(result.validLrc);
|
|
56
|
+
EXPECT_EQ(result.payload, kResultPayload);
|
|
57
|
+
EXPECT_EQ(t.applicationRequestCount(), 1u);
|
|
58
|
+
|
|
59
|
+
// The session must have ACKed the received result frame.
|
|
60
|
+
bool sentAck = false;
|
|
61
|
+
for (const auto& f : t.sentFrames()) {
|
|
62
|
+
if (!f.empty() && f.front() == PacketCodec::ACK) sentAck = true;
|
|
63
|
+
}
|
|
64
|
+
EXPECT_TRUE(sentAck);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
TEST(Session, NakTriggersRetransmitThenSucceeds) {
|
|
68
|
+
FakeTransport t;
|
|
69
|
+
PacketCodec codec(LrcMode::STD);
|
|
70
|
+
t.enqueueResponse(codec.encodeControl(PacketCodec::NAK)); // reply to attempt 1
|
|
71
|
+
std::vector<uint8_t> ok = codec.encodeControl(PacketCodec::ACK);
|
|
72
|
+
append(ok, codec.encodeApplication(kResultPayload));
|
|
73
|
+
t.enqueueResponse(ok); // reply to retransmit
|
|
74
|
+
|
|
75
|
+
Ecr17Session session(t, fastConfig());
|
|
76
|
+
DecodedPacket result = session.exchange("123456780P...");
|
|
77
|
+
EXPECT_EQ(result.payload, kResultPayload);
|
|
78
|
+
EXPECT_EQ(t.applicationRequestCount(), 2u); // initial + 1 retransmit
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
TEST(Session, AckTimeoutExhaustsRetriesThenThrows) {
|
|
82
|
+
FakeTransport t; // no responses queued
|
|
83
|
+
Ecr17Session session(t, fastConfig());
|
|
84
|
+
EXPECT_THROW(session.exchange("123456780P..."), std::runtime_error);
|
|
85
|
+
// initial + retryCount retransmissions
|
|
86
|
+
EXPECT_EQ(t.applicationRequestCount(), 3u);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
TEST(Session, BadLrcResponseSendsNak) {
|
|
90
|
+
FakeTransport t;
|
|
91
|
+
PacketCodec codec(LrcMode::STD);
|
|
92
|
+
std::vector<uint8_t> response = codec.encodeControl(PacketCodec::ACK);
|
|
93
|
+
auto bad = codec.encodeApplication(kResultPayload);
|
|
94
|
+
bad.back() ^= 0xFF; // corrupt LRC
|
|
95
|
+
append(response, bad);
|
|
96
|
+
t.enqueueResponse(response);
|
|
97
|
+
|
|
98
|
+
Ecr17Session session(t, fastConfig());
|
|
99
|
+
EXPECT_THROW(session.exchange("123456780P..."), std::runtime_error); // no valid result
|
|
100
|
+
|
|
101
|
+
bool sentNak = false;
|
|
102
|
+
for (const auto& f : t.sentFrames()) {
|
|
103
|
+
if (!f.empty() && f.front() == PacketCodec::NAK) sentNak = true;
|
|
104
|
+
}
|
|
105
|
+
EXPECT_TRUE(sentNak);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
TEST(Session, ProgressMessagesForwarded) {
|
|
109
|
+
FakeTransport t;
|
|
110
|
+
PacketCodec codec(LrcMode::STD);
|
|
111
|
+
std::vector<uint8_t> response = codec.encodeControl(PacketCodec::ACK);
|
|
112
|
+
append(response, progressFrame("ATTENDERE PREGO "));
|
|
113
|
+
append(response, codec.encodeApplication(kResultPayload));
|
|
114
|
+
t.enqueueResponse(response);
|
|
115
|
+
|
|
116
|
+
std::vector<std::string> progress;
|
|
117
|
+
Ecr17Session session(t, fastConfig());
|
|
118
|
+
session.setOnProgress([&](const std::string& m) { progress.push_back(m); });
|
|
119
|
+
|
|
120
|
+
DecodedPacket result = session.exchange("123456780P...");
|
|
121
|
+
EXPECT_EQ(result.payload, kResultPayload);
|
|
122
|
+
ASSERT_EQ(progress.size(), 1u);
|
|
123
|
+
EXPECT_EQ(progress[0], "ATTENDERE PREGO ");
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
TEST(Session, ReceiptLinesForwardedBeforeResult) {
|
|
127
|
+
FakeTransport t;
|
|
128
|
+
PacketCodec codec(LrcMode::STD);
|
|
129
|
+
std::vector<uint8_t> response = codec.encodeControl(PacketCodec::ACK);
|
|
130
|
+
append(response, codec.encodeApplication(kReceiptPayload));
|
|
131
|
+
append(response, codec.encodeApplication(kResultPayload));
|
|
132
|
+
t.enqueueResponse(response);
|
|
133
|
+
|
|
134
|
+
std::vector<std::string> receipts;
|
|
135
|
+
Ecr17Session session(t, fastConfig());
|
|
136
|
+
session.setOnReceiptLine([&](const std::string& l) { receipts.push_back(l); });
|
|
137
|
+
|
|
138
|
+
DecodedPacket result = session.exchange("123456780P...");
|
|
139
|
+
EXPECT_EQ(result.payload, kResultPayload);
|
|
140
|
+
ASSERT_EQ(receipts.size(), 1u);
|
|
141
|
+
EXPECT_EQ(receipts[0], kReceiptPayload);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Regression: some terminals send the application RESULT before (or instead of)
|
|
145
|
+
// the physical ACK. The handshake must not discard that frame — losing the
|
|
146
|
+
// result of a completed financial transaction is the worst failure mode.
|
|
147
|
+
TEST(Session, ResultBeforeAckIsNotLost) {
|
|
148
|
+
FakeTransport t;
|
|
149
|
+
PacketCodec codec(LrcMode::STD);
|
|
150
|
+
t.enqueueResponse(codec.encodeApplication(kResultPayload)); // result, no leading ACK
|
|
151
|
+
|
|
152
|
+
Ecr17Session session(t, fastConfig());
|
|
153
|
+
DecodedPacket result = session.exchange("123456780P...");
|
|
154
|
+
EXPECT_EQ(result.type, PacketType::APPLICATION);
|
|
155
|
+
EXPECT_EQ(result.payload, kResultPayload);
|
|
156
|
+
EXPECT_EQ(t.applicationRequestCount(), 1u); // no spurious retransmit
|
|
157
|
+
|
|
158
|
+
// The accepted result must still be ACKed back to the terminal.
|
|
159
|
+
bool sentAck = false;
|
|
160
|
+
for (const auto& f : t.sentFrames()) {
|
|
161
|
+
if (!f.empty() && f.front() == PacketCodec::ACK) sentAck = true;
|
|
162
|
+
}
|
|
163
|
+
EXPECT_TRUE(sentAck);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
TEST(Session, ResponseTimeoutAfterAckThrows) {
|
|
167
|
+
FakeTransport t;
|
|
168
|
+
PacketCodec codec(LrcMode::STD);
|
|
169
|
+
t.enqueueResponse(codec.encodeControl(PacketCodec::ACK)); // ACK only, no result
|
|
170
|
+
|
|
171
|
+
Ecr17Session session(t, fastConfig());
|
|
172
|
+
EXPECT_THROW(session.exchange("123456780P..."), std::runtime_error);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
TEST(Session, DisconnectDuringExchangeThrows) {
|
|
176
|
+
FakeTransport t;
|
|
177
|
+
t.disconnectOnNextRequest();
|
|
178
|
+
Ecr17Session session(t, fastConfig());
|
|
179
|
+
EXPECT_THROW(session.exchange("123456780P..."), std::runtime_error);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Scenario: the connection drops mid-exchange, then the client reconnects and
|
|
183
|
+
// retries (the auto-reconnect path for safe commands). The session must recover
|
|
184
|
+
// — it must NOT stay stuck in the disconnected state after a drop.
|
|
185
|
+
TEST(Session, RecoversAndSucceedsAfterReconnect) {
|
|
186
|
+
FakeTransport t;
|
|
187
|
+
t.disconnectOnNextRequest();
|
|
188
|
+
Ecr17Session session(t, fastConfig());
|
|
189
|
+
|
|
190
|
+
// First attempt drops.
|
|
191
|
+
EXPECT_THROW(session.exchange("123456780P..."), std::runtime_error);
|
|
192
|
+
|
|
193
|
+
// Client reconnects the transport and the next transaction must work.
|
|
194
|
+
t.rearm();
|
|
195
|
+
PacketCodec codec(LrcMode::STD);
|
|
196
|
+
std::vector<uint8_t> ok = codec.encodeControl(PacketCodec::ACK);
|
|
197
|
+
append(ok, codec.encodeApplication(kResultPayload));
|
|
198
|
+
t.enqueueResponse(ok);
|
|
199
|
+
|
|
200
|
+
DecodedPacket result = session.exchange("123456780P...");
|
|
201
|
+
EXPECT_EQ(result.type, PacketType::APPLICATION);
|
|
202
|
+
EXPECT_EQ(result.payload, kResultPayload);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
TEST(Session, SendAckOnlyReturnsOnAck) {
|
|
206
|
+
FakeTransport t;
|
|
207
|
+
PacketCodec codec(LrcMode::STD);
|
|
208
|
+
t.enqueueResponse(codec.encodeControl(PacketCodec::ACK)); // ACK, no app result
|
|
209
|
+
Ecr17Session session(t, fastConfig());
|
|
210
|
+
EXPECT_NO_THROW(session.sendAckOnly("123456780E1"));
|
|
211
|
+
EXPECT_EQ(t.applicationRequestCount(), 1u);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
TEST(Session, SendAckOnlyRetransmitsOnNak) {
|
|
215
|
+
FakeTransport t;
|
|
216
|
+
PacketCodec codec(LrcMode::STD);
|
|
217
|
+
t.enqueueResponse(codec.encodeControl(PacketCodec::NAK));
|
|
218
|
+
t.enqueueResponse(codec.encodeControl(PacketCodec::ACK));
|
|
219
|
+
Ecr17Session session(t, fastConfig());
|
|
220
|
+
EXPECT_NO_THROW(session.sendAckOnly("123456780E0"));
|
|
221
|
+
EXPECT_EQ(t.applicationRequestCount(), 2u);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
TEST(Session, SendAckOnlyTimesOut) {
|
|
225
|
+
FakeTransport t; // no ACK
|
|
226
|
+
Ecr17Session session(t, fastConfig());
|
|
227
|
+
EXPECT_THROW(session.sendAckOnly("123456780E1"), std::runtime_error);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
TEST(Session, ExchangeWithAdditionalDataSendsTwoRequests) {
|
|
231
|
+
FakeTransport t;
|
|
232
|
+
PacketCodec codec(LrcMode::STD);
|
|
233
|
+
t.enqueueResponse(codec.encodeControl(PacketCodec::ACK)); // ACK for the main 'P'
|
|
234
|
+
std::vector<uint8_t> ok = codec.encodeControl(PacketCodec::ACK);
|
|
235
|
+
append(ok, codec.encodeApplication(kResultPayload));
|
|
236
|
+
t.enqueueResponse(ok); // ACK for 'U' + the result
|
|
237
|
+
|
|
238
|
+
Ecr17Session session(t, fastConfig());
|
|
239
|
+
DecodedPacket result = session.exchangeWithAdditionalData("123456780P...", "123456780U...");
|
|
240
|
+
EXPECT_EQ(result.payload, kResultPayload);
|
|
241
|
+
EXPECT_EQ(t.applicationRequestCount(), 2u); // P + U
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
TEST(Session, ReceiptDrainForwardsReceiptsAfterResult) {
|
|
245
|
+
FakeTransport t;
|
|
246
|
+
PacketCodec codec(LrcMode::STD);
|
|
247
|
+
std::vector<uint8_t> response = codec.encodeControl(PacketCodec::ACK);
|
|
248
|
+
append(response, codec.encodeApplication(kResultPayload)); // result first
|
|
249
|
+
append(response, codec.encodeApplication(kReceiptPayload)); // then a receipt line
|
|
250
|
+
t.enqueueResponse(response);
|
|
251
|
+
|
|
252
|
+
SessionConfig cfg = fastConfig();
|
|
253
|
+
cfg.receiptDrainMs = 30; // drain receipts that follow the result
|
|
254
|
+
std::vector<std::string> receipts;
|
|
255
|
+
Ecr17Session session(t, cfg);
|
|
256
|
+
session.setOnReceiptLine([&](const std::string& l) { receipts.push_back(l); });
|
|
257
|
+
|
|
258
|
+
DecodedPacket result = session.exchange("123456780P...");
|
|
259
|
+
EXPECT_EQ(result.payload, kResultPayload);
|
|
260
|
+
ASSERT_EQ(receipts.size(), 1u);
|
|
261
|
+
EXPECT_EQ(receipts[0], kReceiptPayload);
|
|
262
|
+
}
|