@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,97 +1,97 @@
|
|
|
1
|
-
#pragma once
|
|
2
|
-
|
|
3
|
-
#include <condition_variable>
|
|
4
|
-
#include <cstdint>
|
|
5
|
-
#include <functional>
|
|
6
|
-
#include <mutex>
|
|
7
|
-
#include <optional>
|
|
8
|
-
#include <string>
|
|
9
|
-
#include <vector>
|
|
10
|
-
|
|
11
|
-
#include "Lcr/Lcr.hpp" // brings LrcMode
|
|
12
|
-
#include "PacketCodec/PacketCodec.hpp"
|
|
13
|
-
#include "Transport/Transport.hpp"
|
|
14
|
-
|
|
15
|
-
namespace margelo::nitro::ecr17 {
|
|
16
|
-
|
|
17
|
-
struct SessionConfig {
|
|
18
|
-
LrcMode lrcMode = LrcMode::STD;
|
|
19
|
-
int ackTimeoutMs = 2000; // wait for the physical ACK/NAK
|
|
20
|
-
int responseTimeoutMs = 60000; // wait for the application response
|
|
21
|
-
int retryCount = 3; // retransmissions on NAK/timeout (spec: up to 3)
|
|
22
|
-
int retryDelayMs = 200; // delay between retransmissions
|
|
23
|
-
int receiptDrainMs = 0; // after the result, keep forwarding 'S' receipt
|
|
24
|
-
// lines until this many ms of silence (0 = off)
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
// Drives one ECR17 request/response exchange over a Transport: frames the
|
|
28
|
-
// request, handles the physical ACK/NAK handshake with retransmission, waits for
|
|
29
|
-
// the application response while forwarding progress (SOH) and receipt ('S')
|
|
30
|
-
// messages, and ACK/NAKs incoming frames per their LRC validity.
|
|
31
|
-
//
|
|
32
|
-
// Pure C++ and transport-agnostic: unit-tested against FakeTransport. A single
|
|
33
|
-
// exchange runs at a time (the protocol is one transaction per terminal).
|
|
34
|
-
class Ecr17Session {
|
|
35
|
-
public:
|
|
36
|
-
Ecr17Session(Transport& transport, const SessionConfig& config);
|
|
37
|
-
|
|
38
|
-
void setOnProgress(std::function<void(const std::string&)> cb) { onProgress_ = std::move(cb); }
|
|
39
|
-
void setOnReceiptLine(std::function<void(const std::string&)> cb) {
|
|
40
|
-
onReceiptLine_ = std::move(cb);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// Sends `requestPayload` (the application message, without STX/ETX) and
|
|
44
|
-
// returns the decoded application result. Throws std::runtime_error on
|
|
45
|
-
// retransmission exhaustion, ACK/response timeout, or transport disconnect.
|
|
46
|
-
DecodedPacket exchange(const std::string& requestPayload);
|
|
47
|
-
|
|
48
|
-
// Like exchange(), but sends an extra additional-data message (command 'U',
|
|
49
|
-
// tokenization) after the main request is ACKed, before the result: the
|
|
50
|
-
// documented flow is request(flag=1) -> ACK -> 'U' -> ACK -> result.
|
|
51
|
-
DecodedPacket exchangeWithAdditionalData(const std::string& requestPayload,
|
|
52
|
-
const std::string& additionalPayload);
|
|
53
|
-
|
|
54
|
-
// For commands whose only reply is the physical ACK (e.g. enable/disable ECR
|
|
55
|
-
// printing 'E'). Performs the ACK handshake with retransmission and returns
|
|
56
|
-
// once ACK is received; does NOT wait for an application response. Throws on
|
|
57
|
-
// retransmission exhaustion / timeout / disconnect.
|
|
58
|
-
void sendAckOnly(const std::string& requestPayload);
|
|
59
|
-
|
|
60
|
-
private:
|
|
61
|
-
void onData(const std::vector<uint8_t>& data);
|
|
62
|
-
void onDisconnect();
|
|
63
|
-
// Clears stale RX bytes and the disconnected flag so the session is reusable
|
|
64
|
-
// across reconnects (a new transaction starts from a clean state).
|
|
65
|
-
void resetForNewTransaction();
|
|
66
|
-
// Sends a request and completes the physical ACK handshake (with
|
|
67
|
-
// retransmission). Does NOT reset state — callers reset once per transaction.
|
|
68
|
-
void ackHandshake(const std::string& requestPayload);
|
|
69
|
-
// Waits for the application result after the ACK handshake, forwarding
|
|
70
|
-
// progress (SOH) and receipt ('S') frames, NAKing invalid-LRC frames.
|
|
71
|
-
DecodedPacket waitForResult();
|
|
72
|
-
void drainReceipts();
|
|
73
|
-
std::optional<std::vector<uint8_t>> extractFrameLocked();
|
|
74
|
-
std::optional<DecodedPacket> waitForFrame(int timeoutMs);
|
|
75
|
-
void sendControl(uint8_t control);
|
|
76
|
-
static bool isReceipt(const std::string& payload);
|
|
77
|
-
|
|
78
|
-
Transport& transport_;
|
|
79
|
-
SessionConfig config_;
|
|
80
|
-
PacketCodec codec_;
|
|
81
|
-
|
|
82
|
-
std::mutex mutex_;
|
|
83
|
-
std::condition_variable cv_;
|
|
84
|
-
std::vector<uint8_t> rxBuffer_;
|
|
85
|
-
bool disconnected_ = false;
|
|
86
|
-
|
|
87
|
-
// Holds an application response that arrived during the ACK handshake (some
|
|
88
|
-
// terminals send the result before/without a physical ACK). Consumed by
|
|
89
|
-
// waitForResult() so the completed transaction's result is never dropped.
|
|
90
|
-
// Touched only by the worker thread, never the data-callback thread.
|
|
91
|
-
std::optional<DecodedPacket> pendingResult_{};
|
|
92
|
-
|
|
93
|
-
std::function<void(const std::string&)> onProgress_{};
|
|
94
|
-
std::function<void(const std::string&)> onReceiptLine_{};
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
} // namespace margelo::nitro::ecr17
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <condition_variable>
|
|
4
|
+
#include <cstdint>
|
|
5
|
+
#include <functional>
|
|
6
|
+
#include <mutex>
|
|
7
|
+
#include <optional>
|
|
8
|
+
#include <string>
|
|
9
|
+
#include <vector>
|
|
10
|
+
|
|
11
|
+
#include "Lcr/Lcr.hpp" // brings LrcMode
|
|
12
|
+
#include "PacketCodec/PacketCodec.hpp"
|
|
13
|
+
#include "Transport/Transport.hpp"
|
|
14
|
+
|
|
15
|
+
namespace margelo::nitro::ecr17 {
|
|
16
|
+
|
|
17
|
+
struct SessionConfig {
|
|
18
|
+
LrcMode lrcMode = LrcMode::STD;
|
|
19
|
+
int ackTimeoutMs = 2000; // wait for the physical ACK/NAK
|
|
20
|
+
int responseTimeoutMs = 60000; // wait for the application response
|
|
21
|
+
int retryCount = 3; // retransmissions on NAK/timeout (spec: up to 3)
|
|
22
|
+
int retryDelayMs = 200; // delay between retransmissions
|
|
23
|
+
int receiptDrainMs = 0; // after the result, keep forwarding 'S' receipt
|
|
24
|
+
// lines until this many ms of silence (0 = off)
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
// Drives one ECR17 request/response exchange over a Transport: frames the
|
|
28
|
+
// request, handles the physical ACK/NAK handshake with retransmission, waits for
|
|
29
|
+
// the application response while forwarding progress (SOH) and receipt ('S')
|
|
30
|
+
// messages, and ACK/NAKs incoming frames per their LRC validity.
|
|
31
|
+
//
|
|
32
|
+
// Pure C++ and transport-agnostic: unit-tested against FakeTransport. A single
|
|
33
|
+
// exchange runs at a time (the protocol is one transaction per terminal).
|
|
34
|
+
class Ecr17Session {
|
|
35
|
+
public:
|
|
36
|
+
Ecr17Session(Transport& transport, const SessionConfig& config);
|
|
37
|
+
|
|
38
|
+
void setOnProgress(std::function<void(const std::string&)> cb) { onProgress_ = std::move(cb); }
|
|
39
|
+
void setOnReceiptLine(std::function<void(const std::string&)> cb) {
|
|
40
|
+
onReceiptLine_ = std::move(cb);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Sends `requestPayload` (the application message, without STX/ETX) and
|
|
44
|
+
// returns the decoded application result. Throws std::runtime_error on
|
|
45
|
+
// retransmission exhaustion, ACK/response timeout, or transport disconnect.
|
|
46
|
+
DecodedPacket exchange(const std::string& requestPayload);
|
|
47
|
+
|
|
48
|
+
// Like exchange(), but sends an extra additional-data message (command 'U',
|
|
49
|
+
// tokenization) after the main request is ACKed, before the result: the
|
|
50
|
+
// documented flow is request(flag=1) -> ACK -> 'U' -> ACK -> result.
|
|
51
|
+
DecodedPacket exchangeWithAdditionalData(const std::string& requestPayload,
|
|
52
|
+
const std::string& additionalPayload);
|
|
53
|
+
|
|
54
|
+
// For commands whose only reply is the physical ACK (e.g. enable/disable ECR
|
|
55
|
+
// printing 'E'). Performs the ACK handshake with retransmission and returns
|
|
56
|
+
// once ACK is received; does NOT wait for an application response. Throws on
|
|
57
|
+
// retransmission exhaustion / timeout / disconnect.
|
|
58
|
+
void sendAckOnly(const std::string& requestPayload);
|
|
59
|
+
|
|
60
|
+
private:
|
|
61
|
+
void onData(const std::vector<uint8_t>& data);
|
|
62
|
+
void onDisconnect();
|
|
63
|
+
// Clears stale RX bytes and the disconnected flag so the session is reusable
|
|
64
|
+
// across reconnects (a new transaction starts from a clean state).
|
|
65
|
+
void resetForNewTransaction();
|
|
66
|
+
// Sends a request and completes the physical ACK handshake (with
|
|
67
|
+
// retransmission). Does NOT reset state — callers reset once per transaction.
|
|
68
|
+
void ackHandshake(const std::string& requestPayload);
|
|
69
|
+
// Waits for the application result after the ACK handshake, forwarding
|
|
70
|
+
// progress (SOH) and receipt ('S') frames, NAKing invalid-LRC frames.
|
|
71
|
+
DecodedPacket waitForResult();
|
|
72
|
+
void drainReceipts();
|
|
73
|
+
std::optional<std::vector<uint8_t>> extractFrameLocked();
|
|
74
|
+
std::optional<DecodedPacket> waitForFrame(int timeoutMs);
|
|
75
|
+
void sendControl(uint8_t control);
|
|
76
|
+
static bool isReceipt(const std::string& payload);
|
|
77
|
+
|
|
78
|
+
Transport& transport_;
|
|
79
|
+
SessionConfig config_;
|
|
80
|
+
PacketCodec codec_;
|
|
81
|
+
|
|
82
|
+
std::mutex mutex_;
|
|
83
|
+
std::condition_variable cv_;
|
|
84
|
+
std::vector<uint8_t> rxBuffer_;
|
|
85
|
+
bool disconnected_ = false;
|
|
86
|
+
|
|
87
|
+
// Holds an application response that arrived during the ACK handshake (some
|
|
88
|
+
// terminals send the result before/without a physical ACK). Consumed by
|
|
89
|
+
// waitForResult() so the completed transaction's result is never dropped.
|
|
90
|
+
// Touched only by the worker thread, never the data-callback thread.
|
|
91
|
+
std::optional<DecodedPacket> pendingResult_{};
|
|
92
|
+
|
|
93
|
+
std::function<void(const std::string&)> onProgress_{};
|
|
94
|
+
std::function<void(const std::string&)> onReceiptLine_{};
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
} // namespace margelo::nitro::ecr17
|
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
#pragma once
|
|
2
|
-
|
|
3
|
-
namespace margelo::nitro::ecr17 {
|
|
4
|
-
|
|
5
|
-
// Decides whether a command may be safely RE-SENT after an auto-reconnect.
|
|
6
|
-
//
|
|
7
|
-
// ⚠️ MONEY-CRITICAL INVARIANT: a financial command (safeToRetry == false) must
|
|
8
|
-
// NEVER be retried. If the connection drops after the terminal has processed the
|
|
9
|
-
// payment but before the response arrives, a blind re-send would charge the
|
|
10
|
-
// cardholder twice. Such cases are recovered by querying the terminal's last
|
|
11
|
-
// result (command 'G' / sendLastResult), NOT by retransmitting the request.
|
|
12
|
-
//
|
|
13
|
-
// Only read-only / idempotent commands (status, totals, sendLastResult,
|
|
14
|
-
// enable-printing) pass safeToRetry == true.
|
|
15
|
-
//
|
|
16
|
-
// Reconnecting the socket is a separate, always-safe action; this function only
|
|
17
|
-
// governs whether the *request* is replayed.
|
|
18
|
-
inline bool shouldRetryAfterReconnect(bool autoReconnect, bool transportDropped,
|
|
19
|
-
bool safeToRetry) {
|
|
20
|
-
return autoReconnect && transportDropped && safeToRetry;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
} // namespace margelo::nitro::ecr17
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
namespace margelo::nitro::ecr17 {
|
|
4
|
+
|
|
5
|
+
// Decides whether a command may be safely RE-SENT after an auto-reconnect.
|
|
6
|
+
//
|
|
7
|
+
// ⚠️ MONEY-CRITICAL INVARIANT: a financial command (safeToRetry == false) must
|
|
8
|
+
// NEVER be retried. If the connection drops after the terminal has processed the
|
|
9
|
+
// payment but before the response arrives, a blind re-send would charge the
|
|
10
|
+
// cardholder twice. Such cases are recovered by querying the terminal's last
|
|
11
|
+
// result (command 'G' / sendLastResult), NOT by retransmitting the request.
|
|
12
|
+
//
|
|
13
|
+
// Only read-only / idempotent commands (status, totals, sendLastResult,
|
|
14
|
+
// enable-printing) pass safeToRetry == true.
|
|
15
|
+
//
|
|
16
|
+
// Reconnecting the socket is a separate, always-safe action; this function only
|
|
17
|
+
// governs whether the *request* is replayed.
|
|
18
|
+
inline bool shouldRetryAfterReconnect(bool autoReconnect, bool transportDropped,
|
|
19
|
+
bool safeToRetry) {
|
|
20
|
+
return autoReconnect && transportDropped && safeToRetry;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
} // namespace margelo::nitro::ecr17
|
|
@@ -1,95 +1,95 @@
|
|
|
1
|
-
#pragma once
|
|
2
|
-
|
|
3
|
-
#include <cstdint>
|
|
4
|
-
#include <functional>
|
|
5
|
-
#include <vector>
|
|
6
|
-
|
|
7
|
-
#include "Transport/Transport.hpp"
|
|
8
|
-
|
|
9
|
-
namespace margelo::nitro::ecr17 {
|
|
10
|
-
|
|
11
|
-
// In-memory Transport for unit tests. Deterministic and synchronous:
|
|
12
|
-
//
|
|
13
|
-
// - `enqueueResponse(bytes)` queues a scripted terminal reply.
|
|
14
|
-
// - Every time the session sends an APPLICATION request (a frame starting with
|
|
15
|
-
// STX, i.e. an initial send or a retransmit) the next queued response is
|
|
16
|
-
// delivered synchronously via the data callback. Control sends (ACK/NAK,
|
|
17
|
-
// which start with 0x06/0x15) are just recorded and never trigger a reply.
|
|
18
|
-
//
|
|
19
|
-
// This lets a test script "ACK + result", "NAK then (on retransmit) ACK+result",
|
|
20
|
-
// progress/receipt streams, or no reply at all (to exercise timeouts), without
|
|
21
|
-
// any real sockets or threads.
|
|
22
|
-
class FakeTransport : public Transport {
|
|
23
|
-
public:
|
|
24
|
-
static constexpr uint8_t STX = 0x02;
|
|
25
|
-
|
|
26
|
-
void connect() override { connected_ = true; }
|
|
27
|
-
void disconnect() override { connected_ = false; }
|
|
28
|
-
bool isConnected() const override { return connected_; }
|
|
29
|
-
|
|
30
|
-
void send(const std::vector<uint8_t>& bytes) override {
|
|
31
|
-
sent_.push_back(bytes);
|
|
32
|
-
const bool isApplicationRequest = !bytes.empty() && bytes.front() == STX;
|
|
33
|
-
if (isApplicationRequest && disconnectOnRequest_) {
|
|
34
|
-
triggerDisconnect();
|
|
35
|
-
return;
|
|
36
|
-
}
|
|
37
|
-
if (isApplicationRequest && !responses_.empty()) {
|
|
38
|
-
std::vector<uint8_t> next = std::move(responses_.front());
|
|
39
|
-
responses_.erase(responses_.begin());
|
|
40
|
-
if (dataCallback_) {
|
|
41
|
-
dataCallback_(next);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
void setDataCallback(DataCallback cb) override { dataCallback_ = std::move(cb); }
|
|
47
|
-
void setDisconnectCallback(DisconnectCallback cb) override { disconnectCallback_ = std::move(cb); }
|
|
48
|
-
|
|
49
|
-
// --- Test helpers ---
|
|
50
|
-
void enqueueResponse(std::vector<uint8_t> bytes) { responses_.push_back(std::move(bytes)); }
|
|
51
|
-
|
|
52
|
-
// Make the next application-request send drop the connection instead of replying.
|
|
53
|
-
void disconnectOnNextRequest() { disconnectOnRequest_ = true; }
|
|
54
|
-
|
|
55
|
-
// Simulate a successful reconnect: clear the drop flag and mark connected.
|
|
56
|
-
void rearm() {
|
|
57
|
-
disconnectOnRequest_ = false;
|
|
58
|
-
connected_ = true;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// Deliver bytes immediately as if received from the terminal.
|
|
62
|
-
void pushIncoming(const std::vector<uint8_t>& bytes) {
|
|
63
|
-
if (dataCallback_) {
|
|
64
|
-
dataCallback_(bytes);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
void triggerDisconnect() {
|
|
69
|
-
connected_ = false;
|
|
70
|
-
if (disconnectCallback_) {
|
|
71
|
-
disconnectCallback_();
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
const std::vector<std::vector<uint8_t>>& sentFrames() const { return sent_; }
|
|
76
|
-
size_t applicationRequestCount() const {
|
|
77
|
-
size_t count = 0;
|
|
78
|
-
for (const auto& f : sent_) {
|
|
79
|
-
if (!f.empty() && f.front() == STX) {
|
|
80
|
-
++count;
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
return count;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
private:
|
|
87
|
-
bool connected_ = false;
|
|
88
|
-
DataCallback dataCallback_{};
|
|
89
|
-
DisconnectCallback disconnectCallback_{};
|
|
90
|
-
std::vector<std::vector<uint8_t>> sent_{};
|
|
91
|
-
std::vector<std::vector<uint8_t>> responses_{};
|
|
92
|
-
bool disconnectOnRequest_ = false;
|
|
93
|
-
};
|
|
94
|
-
|
|
95
|
-
} // namespace margelo::nitro::ecr17
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <cstdint>
|
|
4
|
+
#include <functional>
|
|
5
|
+
#include <vector>
|
|
6
|
+
|
|
7
|
+
#include "Transport/Transport.hpp"
|
|
8
|
+
|
|
9
|
+
namespace margelo::nitro::ecr17 {
|
|
10
|
+
|
|
11
|
+
// In-memory Transport for unit tests. Deterministic and synchronous:
|
|
12
|
+
//
|
|
13
|
+
// - `enqueueResponse(bytes)` queues a scripted terminal reply.
|
|
14
|
+
// - Every time the session sends an APPLICATION request (a frame starting with
|
|
15
|
+
// STX, i.e. an initial send or a retransmit) the next queued response is
|
|
16
|
+
// delivered synchronously via the data callback. Control sends (ACK/NAK,
|
|
17
|
+
// which start with 0x06/0x15) are just recorded and never trigger a reply.
|
|
18
|
+
//
|
|
19
|
+
// This lets a test script "ACK + result", "NAK then (on retransmit) ACK+result",
|
|
20
|
+
// progress/receipt streams, or no reply at all (to exercise timeouts), without
|
|
21
|
+
// any real sockets or threads.
|
|
22
|
+
class FakeTransport : public Transport {
|
|
23
|
+
public:
|
|
24
|
+
static constexpr uint8_t STX = 0x02;
|
|
25
|
+
|
|
26
|
+
void connect() override { connected_ = true; }
|
|
27
|
+
void disconnect() override { connected_ = false; }
|
|
28
|
+
bool isConnected() const override { return connected_; }
|
|
29
|
+
|
|
30
|
+
void send(const std::vector<uint8_t>& bytes) override {
|
|
31
|
+
sent_.push_back(bytes);
|
|
32
|
+
const bool isApplicationRequest = !bytes.empty() && bytes.front() == STX;
|
|
33
|
+
if (isApplicationRequest && disconnectOnRequest_) {
|
|
34
|
+
triggerDisconnect();
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
if (isApplicationRequest && !responses_.empty()) {
|
|
38
|
+
std::vector<uint8_t> next = std::move(responses_.front());
|
|
39
|
+
responses_.erase(responses_.begin());
|
|
40
|
+
if (dataCallback_) {
|
|
41
|
+
dataCallback_(next);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
void setDataCallback(DataCallback cb) override { dataCallback_ = std::move(cb); }
|
|
47
|
+
void setDisconnectCallback(DisconnectCallback cb) override { disconnectCallback_ = std::move(cb); }
|
|
48
|
+
|
|
49
|
+
// --- Test helpers ---
|
|
50
|
+
void enqueueResponse(std::vector<uint8_t> bytes) { responses_.push_back(std::move(bytes)); }
|
|
51
|
+
|
|
52
|
+
// Make the next application-request send drop the connection instead of replying.
|
|
53
|
+
void disconnectOnNextRequest() { disconnectOnRequest_ = true; }
|
|
54
|
+
|
|
55
|
+
// Simulate a successful reconnect: clear the drop flag and mark connected.
|
|
56
|
+
void rearm() {
|
|
57
|
+
disconnectOnRequest_ = false;
|
|
58
|
+
connected_ = true;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Deliver bytes immediately as if received from the terminal.
|
|
62
|
+
void pushIncoming(const std::vector<uint8_t>& bytes) {
|
|
63
|
+
if (dataCallback_) {
|
|
64
|
+
dataCallback_(bytes);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
void triggerDisconnect() {
|
|
69
|
+
connected_ = false;
|
|
70
|
+
if (disconnectCallback_) {
|
|
71
|
+
disconnectCallback_();
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const std::vector<std::vector<uint8_t>>& sentFrames() const { return sent_; }
|
|
76
|
+
size_t applicationRequestCount() const {
|
|
77
|
+
size_t count = 0;
|
|
78
|
+
for (const auto& f : sent_) {
|
|
79
|
+
if (!f.empty() && f.front() == STX) {
|
|
80
|
+
++count;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return count;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
private:
|
|
87
|
+
bool connected_ = false;
|
|
88
|
+
DataCallback dataCallback_{};
|
|
89
|
+
DisconnectCallback disconnectCallback_{};
|
|
90
|
+
std::vector<std::vector<uint8_t>> sent_{};
|
|
91
|
+
std::vector<std::vector<uint8_t>> responses_{};
|
|
92
|
+
bool disconnectOnRequest_ = false;
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
} // namespace margelo::nitro::ecr17
|
|
@@ -1,42 +1,42 @@
|
|
|
1
|
-
#include "Transport/NativeTransportAdapter.hpp"
|
|
2
|
-
|
|
3
|
-
#include <NitroModules/ArrayBuffer.hpp>
|
|
4
|
-
|
|
5
|
-
#include <utility>
|
|
6
|
-
|
|
7
|
-
namespace margelo::nitro::ecr17 {
|
|
8
|
-
|
|
9
|
-
NativeTransportAdapter::NativeTransportAdapter(std::shared_ptr<HybridEcr17TransportSpec> transport)
|
|
10
|
-
: transport_(std::move(transport)) {}
|
|
11
|
-
|
|
12
|
-
void NativeTransportAdapter::connect() {
|
|
13
|
-
// Connection is initiated by HybridEcr17Client via the spec's async connect();
|
|
14
|
-
// nothing to do here.
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
void NativeTransportAdapter::disconnect() { transport_->disconnect(); }
|
|
18
|
-
|
|
19
|
-
bool NativeTransportAdapter::isConnected() const { return transport_->isConnected(); }
|
|
20
|
-
|
|
21
|
-
void NativeTransportAdapter::send(const std::vector<uint8_t>& bytes) {
|
|
22
|
-
transport_->send(ArrayBuffer::copy(bytes));
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
void NativeTransportAdapter::setDataCallback(DataCallback cb) {
|
|
26
|
-
transport_->setOnData([cb = std::move(cb)](const std::shared_ptr<ArrayBuffer>& buffer) {
|
|
27
|
-
if (!cb || buffer == nullptr) {
|
|
28
|
-
return;
|
|
29
|
-
}
|
|
30
|
-
const uint8_t* data = buffer->data();
|
|
31
|
-
if (data == nullptr) {
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
|
-
cb(std::vector<uint8_t>(data, data + buffer->size()));
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
void NativeTransportAdapter::setDisconnectCallback(DisconnectCallback cb) {
|
|
39
|
-
transport_->setOnDisconnect(std::move(cb));
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
} // namespace margelo::nitro::ecr17
|
|
1
|
+
#include "Transport/NativeTransportAdapter.hpp"
|
|
2
|
+
|
|
3
|
+
#include <NitroModules/ArrayBuffer.hpp>
|
|
4
|
+
|
|
5
|
+
#include <utility>
|
|
6
|
+
|
|
7
|
+
namespace margelo::nitro::ecr17 {
|
|
8
|
+
|
|
9
|
+
NativeTransportAdapter::NativeTransportAdapter(std::shared_ptr<HybridEcr17TransportSpec> transport)
|
|
10
|
+
: transport_(std::move(transport)) {}
|
|
11
|
+
|
|
12
|
+
void NativeTransportAdapter::connect() {
|
|
13
|
+
// Connection is initiated by HybridEcr17Client via the spec's async connect();
|
|
14
|
+
// nothing to do here.
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
void NativeTransportAdapter::disconnect() { transport_->disconnect(); }
|
|
18
|
+
|
|
19
|
+
bool NativeTransportAdapter::isConnected() const { return transport_->isConnected(); }
|
|
20
|
+
|
|
21
|
+
void NativeTransportAdapter::send(const std::vector<uint8_t>& bytes) {
|
|
22
|
+
transport_->send(ArrayBuffer::copy(bytes));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
void NativeTransportAdapter::setDataCallback(DataCallback cb) {
|
|
26
|
+
transport_->setOnData([cb = std::move(cb)](const std::shared_ptr<ArrayBuffer>& buffer) {
|
|
27
|
+
if (!cb || buffer == nullptr) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
const uint8_t* data = buffer->data();
|
|
31
|
+
if (data == nullptr) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
cb(std::vector<uint8_t>(data, data + buffer->size()));
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
void NativeTransportAdapter::setDisconnectCallback(DisconnectCallback cb) {
|
|
39
|
+
transport_->setOnDisconnect(std::move(cb));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
} // namespace margelo::nitro::ecr17
|
|
@@ -1,32 +1,32 @@
|
|
|
1
|
-
#pragma once
|
|
2
|
-
|
|
3
|
-
#include <memory>
|
|
4
|
-
#include <string>
|
|
5
|
-
#include <vector>
|
|
6
|
-
|
|
7
|
-
#include "HybridEcr17TransportSpec.hpp" // generated Nitro spec (Swift/Kotlin impl)
|
|
8
|
-
#include "Transport/Transport.hpp"
|
|
9
|
-
|
|
10
|
-
namespace margelo::nitro::ecr17 {
|
|
11
|
-
|
|
12
|
-
// Adapts the native Nitro transport (Ecr17Transport HybridObject, implemented in
|
|
13
|
-
// Swift/Kotlin) to the C++ Transport interface used by Ecr17Session. Converts
|
|
14
|
-
// between std::vector<uint8_t> and Nitro's ArrayBuffer. Connection lifecycle is
|
|
15
|
-
// driven by HybridEcr17Client directly via the spec (async Promise); the session
|
|
16
|
-
// only uses send + the data/disconnect callbacks.
|
|
17
|
-
class NativeTransportAdapter : public Transport {
|
|
18
|
-
public:
|
|
19
|
-
explicit NativeTransportAdapter(std::shared_ptr<HybridEcr17TransportSpec> transport);
|
|
20
|
-
|
|
21
|
-
void connect() override; // no-op: client drives connect() via the spec (async)
|
|
22
|
-
void disconnect() override;
|
|
23
|
-
bool isConnected() const override;
|
|
24
|
-
void send(const std::vector<uint8_t>& bytes) override;
|
|
25
|
-
void setDataCallback(DataCallback cb) override;
|
|
26
|
-
void setDisconnectCallback(DisconnectCallback cb) override;
|
|
27
|
-
|
|
28
|
-
private:
|
|
29
|
-
std::shared_ptr<HybridEcr17TransportSpec> transport_;
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
} // namespace margelo::nitro::ecr17
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <memory>
|
|
4
|
+
#include <string>
|
|
5
|
+
#include <vector>
|
|
6
|
+
|
|
7
|
+
#include "HybridEcr17TransportSpec.hpp" // generated Nitro spec (Swift/Kotlin impl)
|
|
8
|
+
#include "Transport/Transport.hpp"
|
|
9
|
+
|
|
10
|
+
namespace margelo::nitro::ecr17 {
|
|
11
|
+
|
|
12
|
+
// Adapts the native Nitro transport (Ecr17Transport HybridObject, implemented in
|
|
13
|
+
// Swift/Kotlin) to the C++ Transport interface used by Ecr17Session. Converts
|
|
14
|
+
// between std::vector<uint8_t> and Nitro's ArrayBuffer. Connection lifecycle is
|
|
15
|
+
// driven by HybridEcr17Client directly via the spec (async Promise); the session
|
|
16
|
+
// only uses send + the data/disconnect callbacks.
|
|
17
|
+
class NativeTransportAdapter : public Transport {
|
|
18
|
+
public:
|
|
19
|
+
explicit NativeTransportAdapter(std::shared_ptr<HybridEcr17TransportSpec> transport);
|
|
20
|
+
|
|
21
|
+
void connect() override; // no-op: client drives connect() via the spec (async)
|
|
22
|
+
void disconnect() override;
|
|
23
|
+
bool isConnected() const override;
|
|
24
|
+
void send(const std::vector<uint8_t>& bytes) override;
|
|
25
|
+
void setDataCallback(DataCallback cb) override;
|
|
26
|
+
void setDisconnectCallback(DisconnectCallback cb) override;
|
|
27
|
+
|
|
28
|
+
private:
|
|
29
|
+
std::shared_ptr<HybridEcr17TransportSpec> transport_;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
} // namespace margelo::nitro::ecr17
|
|
@@ -1,31 +1,31 @@
|
|
|
1
|
-
#pragma once
|
|
2
|
-
|
|
3
|
-
#include <cstdint>
|
|
4
|
-
#include <functional>
|
|
5
|
-
#include <string>
|
|
6
|
-
#include <vector>
|
|
7
|
-
|
|
8
|
-
namespace margelo::nitro::ecr17 {
|
|
9
|
-
|
|
10
|
-
using DataCallback = std::function<void(const std::vector<uint8_t>&)>;
|
|
11
|
-
|
|
12
|
-
using DisconnectCallback = std::function<void()>;
|
|
13
|
-
|
|
14
|
-
class Transport {
|
|
15
|
-
public:
|
|
16
|
-
virtual ~Transport() = default;
|
|
17
|
-
|
|
18
|
-
virtual void connect() = 0;
|
|
19
|
-
|
|
20
|
-
virtual void disconnect() = 0;
|
|
21
|
-
|
|
22
|
-
virtual bool isConnected() const = 0;
|
|
23
|
-
|
|
24
|
-
virtual void send(const std::vector<uint8_t>& bytes) = 0;
|
|
25
|
-
|
|
26
|
-
virtual void setDataCallback(DataCallback cb) = 0;
|
|
27
|
-
|
|
28
|
-
virtual void setDisconnectCallback(DisconnectCallback cb) = 0;
|
|
29
|
-
};
|
|
30
|
-
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <cstdint>
|
|
4
|
+
#include <functional>
|
|
5
|
+
#include <string>
|
|
6
|
+
#include <vector>
|
|
7
|
+
|
|
8
|
+
namespace margelo::nitro::ecr17 {
|
|
9
|
+
|
|
10
|
+
using DataCallback = std::function<void(const std::vector<uint8_t>&)>;
|
|
11
|
+
|
|
12
|
+
using DisconnectCallback = std::function<void()>;
|
|
13
|
+
|
|
14
|
+
class Transport {
|
|
15
|
+
public:
|
|
16
|
+
virtual ~Transport() = default;
|
|
17
|
+
|
|
18
|
+
virtual void connect() = 0;
|
|
19
|
+
|
|
20
|
+
virtual void disconnect() = 0;
|
|
21
|
+
|
|
22
|
+
virtual bool isConnected() const = 0;
|
|
23
|
+
|
|
24
|
+
virtual void send(const std::vector<uint8_t>& bytes) = 0;
|
|
25
|
+
|
|
26
|
+
virtual void setDataCallback(DataCallback cb) = 0;
|
|
27
|
+
|
|
28
|
+
virtual void setDisconnectCallback(DisconnectCallback cb) = 0;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
31
|
} // namespace margelo::nitro::ecr17
|