@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
package/cpp/tests/CMakeLists.txt
CHANGED
|
@@ -1,55 +1,55 @@
|
|
|
1
|
-
cmake_minimum_required(VERSION 3.16)
|
|
2
|
-
project(ecr17_cpp_tests CXX)
|
|
3
|
-
|
|
4
|
-
set(CMAKE_CXX_STANDARD 20)
|
|
5
|
-
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
|
6
|
-
set(CMAKE_CXX_EXTENSIONS OFF)
|
|
7
|
-
|
|
8
|
-
# GoogleTest via FetchContent (CI has network access).
|
|
9
|
-
include(FetchContent)
|
|
10
|
-
FetchContent_Declare(
|
|
11
|
-
googletest
|
|
12
|
-
URL https://github.com/google/googletest/archive/refs/tags/v1.15.2.tar.gz
|
|
13
|
-
)
|
|
14
|
-
# Keep CRT linkage consistent on Windows runners.
|
|
15
|
-
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
|
|
16
|
-
FetchContent_MakeAvailable(googletest)
|
|
17
|
-
|
|
18
|
-
enable_testing()
|
|
19
|
-
|
|
20
|
-
# Production C++ sources under test (../ is package/cpp).
|
|
21
|
-
set(ECR17_CPP_DIR ${CMAKE_CURRENT_SOURCE_DIR}/..)
|
|
22
|
-
|
|
23
|
-
add_executable(ecr17_cpp_tests
|
|
24
|
-
test_lrc.cpp
|
|
25
|
-
test_packet_codec.cpp
|
|
26
|
-
test_protocol.cpp
|
|
27
|
-
test_protocol_commands.cpp
|
|
28
|
-
test_response.cpp
|
|
29
|
-
test_session.cpp
|
|
30
|
-
test_retry_policy.cpp
|
|
31
|
-
test_integration_terminal.cpp
|
|
32
|
-
test_flows.cpp
|
|
33
|
-
${ECR17_CPP_DIR}/Lcr/Lcr.cpp
|
|
34
|
-
${ECR17_CPP_DIR}/PacketCodec/PacketCodec.cpp
|
|
35
|
-
${ECR17_CPP_DIR}/Ecr17Protocol/Ecr17Protocol.cpp
|
|
36
|
-
${ECR17_CPP_DIR}/Ecr17Response/Ecr17Response.cpp
|
|
37
|
-
${ECR17_CPP_DIR}/Session/Ecr17Session.cpp
|
|
38
|
-
)
|
|
39
|
-
|
|
40
|
-
target_include_directories(ecr17_cpp_tests PRIVATE
|
|
41
|
-
${ECR17_CPP_DIR} # resolves "Lcr/Lcr.hpp", "PacketCodec/..."
|
|
42
|
-
${CMAKE_CURRENT_SOURCE_DIR}/stubs # test-only LrcMode.hpp (Nitrogen-generated in real builds)
|
|
43
|
-
)
|
|
44
|
-
|
|
45
|
-
if(MSVC)
|
|
46
|
-
target_compile_options(ecr17_cpp_tests PRIVATE /W4)
|
|
47
|
-
else()
|
|
48
|
-
target_compile_options(ecr17_cpp_tests PRIVATE -Wall -Wextra)
|
|
49
|
-
endif()
|
|
50
|
-
|
|
51
|
-
find_package(Threads REQUIRED)
|
|
52
|
-
target_link_libraries(ecr17_cpp_tests PRIVATE GTest::gtest_main Threads::Threads)
|
|
53
|
-
|
|
54
|
-
include(GoogleTest)
|
|
55
|
-
gtest_discover_tests(ecr17_cpp_tests)
|
|
1
|
+
cmake_minimum_required(VERSION 3.16)
|
|
2
|
+
project(ecr17_cpp_tests CXX)
|
|
3
|
+
|
|
4
|
+
set(CMAKE_CXX_STANDARD 20)
|
|
5
|
+
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
|
6
|
+
set(CMAKE_CXX_EXTENSIONS OFF)
|
|
7
|
+
|
|
8
|
+
# GoogleTest via FetchContent (CI has network access).
|
|
9
|
+
include(FetchContent)
|
|
10
|
+
FetchContent_Declare(
|
|
11
|
+
googletest
|
|
12
|
+
URL https://github.com/google/googletest/archive/refs/tags/v1.15.2.tar.gz
|
|
13
|
+
)
|
|
14
|
+
# Keep CRT linkage consistent on Windows runners.
|
|
15
|
+
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
|
|
16
|
+
FetchContent_MakeAvailable(googletest)
|
|
17
|
+
|
|
18
|
+
enable_testing()
|
|
19
|
+
|
|
20
|
+
# Production C++ sources under test (../ is package/cpp).
|
|
21
|
+
set(ECR17_CPP_DIR ${CMAKE_CURRENT_SOURCE_DIR}/..)
|
|
22
|
+
|
|
23
|
+
add_executable(ecr17_cpp_tests
|
|
24
|
+
test_lrc.cpp
|
|
25
|
+
test_packet_codec.cpp
|
|
26
|
+
test_protocol.cpp
|
|
27
|
+
test_protocol_commands.cpp
|
|
28
|
+
test_response.cpp
|
|
29
|
+
test_session.cpp
|
|
30
|
+
test_retry_policy.cpp
|
|
31
|
+
test_integration_terminal.cpp
|
|
32
|
+
test_flows.cpp
|
|
33
|
+
${ECR17_CPP_DIR}/Lcr/Lcr.cpp
|
|
34
|
+
${ECR17_CPP_DIR}/PacketCodec/PacketCodec.cpp
|
|
35
|
+
${ECR17_CPP_DIR}/Ecr17Protocol/Ecr17Protocol.cpp
|
|
36
|
+
${ECR17_CPP_DIR}/Ecr17Response/Ecr17Response.cpp
|
|
37
|
+
${ECR17_CPP_DIR}/Session/Ecr17Session.cpp
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
target_include_directories(ecr17_cpp_tests PRIVATE
|
|
41
|
+
${ECR17_CPP_DIR} # resolves "Lcr/Lcr.hpp", "PacketCodec/..."
|
|
42
|
+
${CMAKE_CURRENT_SOURCE_DIR}/stubs # test-only LrcMode.hpp (Nitrogen-generated in real builds)
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
if(MSVC)
|
|
46
|
+
target_compile_options(ecr17_cpp_tests PRIVATE /W4)
|
|
47
|
+
else()
|
|
48
|
+
target_compile_options(ecr17_cpp_tests PRIVATE -Wall -Wextra)
|
|
49
|
+
endif()
|
|
50
|
+
|
|
51
|
+
find_package(Threads REQUIRED)
|
|
52
|
+
target_link_libraries(ecr17_cpp_tests PRIVATE GTest::gtest_main Threads::Threads)
|
|
53
|
+
|
|
54
|
+
include(GoogleTest)
|
|
55
|
+
gtest_discover_tests(ecr17_cpp_tests)
|
|
@@ -1,105 +1,105 @@
|
|
|
1
|
-
#pragma once
|
|
2
|
-
|
|
3
|
-
// Test-only POSIX TCP transport, used by the opt-in real-terminal integration
|
|
4
|
-
// test. Lets a developer run the C++ protocol core (builders + session + parsers)
|
|
5
|
-
// against an actual terminal over the LAN, without the native (Kotlin/Swift)
|
|
6
|
-
// transport. POSIX-only (Linux/macOS); excluded on Windows.
|
|
7
|
-
#if !defined(_WIN32)
|
|
8
|
-
|
|
9
|
-
#include <arpa/inet.h>
|
|
10
|
-
#include <netdb.h>
|
|
11
|
-
#include <sys/socket.h>
|
|
12
|
-
#include <unistd.h>
|
|
13
|
-
|
|
14
|
-
#include <atomic>
|
|
15
|
-
#include <cstdint>
|
|
16
|
-
#include <stdexcept>
|
|
17
|
-
#include <string>
|
|
18
|
-
#include <thread>
|
|
19
|
-
#include <vector>
|
|
20
|
-
|
|
21
|
-
#include "Transport/Transport.hpp"
|
|
22
|
-
|
|
23
|
-
namespace margelo::nitro::ecr17 {
|
|
24
|
-
|
|
25
|
-
class PosixTcpTransport : public Transport {
|
|
26
|
-
public:
|
|
27
|
-
PosixTcpTransport(std::string host, int port) : host_(std::move(host)), port_(port) {}
|
|
28
|
-
~PosixTcpTransport() override { disconnect(); }
|
|
29
|
-
|
|
30
|
-
void connect() override {
|
|
31
|
-
addrinfo hints{};
|
|
32
|
-
hints.ai_family = AF_UNSPEC;
|
|
33
|
-
hints.ai_socktype = SOCK_STREAM;
|
|
34
|
-
addrinfo* res = nullptr;
|
|
35
|
-
if (getaddrinfo(host_.c_str(), std::to_string(port_).c_str(), &hints, &res) != 0) {
|
|
36
|
-
throw std::runtime_error("PosixTcpTransport: host resolution failed");
|
|
37
|
-
}
|
|
38
|
-
fd_ = ::socket(res->ai_family, res->ai_socktype, res->ai_protocol);
|
|
39
|
-
if (fd_ < 0) {
|
|
40
|
-
freeaddrinfo(res);
|
|
41
|
-
throw std::runtime_error("PosixTcpTransport: socket() failed");
|
|
42
|
-
}
|
|
43
|
-
if (::connect(fd_, res->ai_addr, res->ai_addrlen) != 0) {
|
|
44
|
-
freeaddrinfo(res);
|
|
45
|
-
::close(fd_);
|
|
46
|
-
fd_ = -1;
|
|
47
|
-
throw std::runtime_error("PosixTcpTransport: connect() failed");
|
|
48
|
-
}
|
|
49
|
-
freeaddrinfo(res);
|
|
50
|
-
running_ = true;
|
|
51
|
-
reader_ = std::thread([this]() { readLoop(); });
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
void disconnect() override {
|
|
55
|
-
running_ = false;
|
|
56
|
-
if (fd_ >= 0) {
|
|
57
|
-
::shutdown(fd_, SHUT_RDWR);
|
|
58
|
-
::close(fd_);
|
|
59
|
-
fd_ = -1;
|
|
60
|
-
}
|
|
61
|
-
if (reader_.joinable()) {
|
|
62
|
-
reader_.join();
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
bool isConnected() const override { return fd_ >= 0; }
|
|
67
|
-
|
|
68
|
-
void send(const std::vector<uint8_t>& bytes) override {
|
|
69
|
-
if (fd_ >= 0) {
|
|
70
|
-
::send(fd_, bytes.data(), bytes.size(), 0);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
void setDataCallback(DataCallback cb) override { onData_ = std::move(cb); }
|
|
75
|
-
void setDisconnectCallback(DisconnectCallback cb) override { onDisconnect_ = std::move(cb); }
|
|
76
|
-
|
|
77
|
-
private:
|
|
78
|
-
void readLoop() {
|
|
79
|
-
uint8_t buffer[4096];
|
|
80
|
-
while (running_) {
|
|
81
|
-
ssize_t n = ::recv(fd_, buffer, sizeof(buffer), 0);
|
|
82
|
-
if (n <= 0) {
|
|
83
|
-
break;
|
|
84
|
-
}
|
|
85
|
-
if (onData_) {
|
|
86
|
-
onData_(std::vector<uint8_t>(buffer, buffer + n));
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
if (onDisconnect_) {
|
|
90
|
-
onDisconnect_();
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
std::string host_;
|
|
95
|
-
int port_;
|
|
96
|
-
int fd_ = -1;
|
|
97
|
-
std::atomic<bool> running_{false};
|
|
98
|
-
std::thread reader_;
|
|
99
|
-
DataCallback onData_{};
|
|
100
|
-
DisconnectCallback onDisconnect_{};
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
} // namespace margelo::nitro::ecr17
|
|
104
|
-
|
|
105
|
-
#endif // !_WIN32
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
// Test-only POSIX TCP transport, used by the opt-in real-terminal integration
|
|
4
|
+
// test. Lets a developer run the C++ protocol core (builders + session + parsers)
|
|
5
|
+
// against an actual terminal over the LAN, without the native (Kotlin/Swift)
|
|
6
|
+
// transport. POSIX-only (Linux/macOS); excluded on Windows.
|
|
7
|
+
#if !defined(_WIN32)
|
|
8
|
+
|
|
9
|
+
#include <arpa/inet.h>
|
|
10
|
+
#include <netdb.h>
|
|
11
|
+
#include <sys/socket.h>
|
|
12
|
+
#include <unistd.h>
|
|
13
|
+
|
|
14
|
+
#include <atomic>
|
|
15
|
+
#include <cstdint>
|
|
16
|
+
#include <stdexcept>
|
|
17
|
+
#include <string>
|
|
18
|
+
#include <thread>
|
|
19
|
+
#include <vector>
|
|
20
|
+
|
|
21
|
+
#include "Transport/Transport.hpp"
|
|
22
|
+
|
|
23
|
+
namespace margelo::nitro::ecr17 {
|
|
24
|
+
|
|
25
|
+
class PosixTcpTransport : public Transport {
|
|
26
|
+
public:
|
|
27
|
+
PosixTcpTransport(std::string host, int port) : host_(std::move(host)), port_(port) {}
|
|
28
|
+
~PosixTcpTransport() override { disconnect(); }
|
|
29
|
+
|
|
30
|
+
void connect() override {
|
|
31
|
+
addrinfo hints{};
|
|
32
|
+
hints.ai_family = AF_UNSPEC;
|
|
33
|
+
hints.ai_socktype = SOCK_STREAM;
|
|
34
|
+
addrinfo* res = nullptr;
|
|
35
|
+
if (getaddrinfo(host_.c_str(), std::to_string(port_).c_str(), &hints, &res) != 0) {
|
|
36
|
+
throw std::runtime_error("PosixTcpTransport: host resolution failed");
|
|
37
|
+
}
|
|
38
|
+
fd_ = ::socket(res->ai_family, res->ai_socktype, res->ai_protocol);
|
|
39
|
+
if (fd_ < 0) {
|
|
40
|
+
freeaddrinfo(res);
|
|
41
|
+
throw std::runtime_error("PosixTcpTransport: socket() failed");
|
|
42
|
+
}
|
|
43
|
+
if (::connect(fd_, res->ai_addr, res->ai_addrlen) != 0) {
|
|
44
|
+
freeaddrinfo(res);
|
|
45
|
+
::close(fd_);
|
|
46
|
+
fd_ = -1;
|
|
47
|
+
throw std::runtime_error("PosixTcpTransport: connect() failed");
|
|
48
|
+
}
|
|
49
|
+
freeaddrinfo(res);
|
|
50
|
+
running_ = true;
|
|
51
|
+
reader_ = std::thread([this]() { readLoop(); });
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
void disconnect() override {
|
|
55
|
+
running_ = false;
|
|
56
|
+
if (fd_ >= 0) {
|
|
57
|
+
::shutdown(fd_, SHUT_RDWR);
|
|
58
|
+
::close(fd_);
|
|
59
|
+
fd_ = -1;
|
|
60
|
+
}
|
|
61
|
+
if (reader_.joinable()) {
|
|
62
|
+
reader_.join();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
bool isConnected() const override { return fd_ >= 0; }
|
|
67
|
+
|
|
68
|
+
void send(const std::vector<uint8_t>& bytes) override {
|
|
69
|
+
if (fd_ >= 0) {
|
|
70
|
+
::send(fd_, bytes.data(), bytes.size(), 0);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
void setDataCallback(DataCallback cb) override { onData_ = std::move(cb); }
|
|
75
|
+
void setDisconnectCallback(DisconnectCallback cb) override { onDisconnect_ = std::move(cb); }
|
|
76
|
+
|
|
77
|
+
private:
|
|
78
|
+
void readLoop() {
|
|
79
|
+
uint8_t buffer[4096];
|
|
80
|
+
while (running_) {
|
|
81
|
+
ssize_t n = ::recv(fd_, buffer, sizeof(buffer), 0);
|
|
82
|
+
if (n <= 0) {
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
if (onData_) {
|
|
86
|
+
onData_(std::vector<uint8_t>(buffer, buffer + n));
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if (onDisconnect_) {
|
|
90
|
+
onDisconnect_();
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
std::string host_;
|
|
95
|
+
int port_;
|
|
96
|
+
int fd_ = -1;
|
|
97
|
+
std::atomic<bool> running_{false};
|
|
98
|
+
std::thread reader_;
|
|
99
|
+
DataCallback onData_{};
|
|
100
|
+
DisconnectCallback onDisconnect_{};
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
} // namespace margelo::nitro::ecr17
|
|
104
|
+
|
|
105
|
+
#endif // !_WIN32
|
|
@@ -1,25 +1,25 @@
|
|
|
1
|
-
#pragma once
|
|
2
|
-
|
|
3
|
-
// TEST-ONLY STUB.
|
|
4
|
-
//
|
|
5
|
-
// In a real build this header is generated by Nitrogen from the TypeScript
|
|
6
|
-
// union `type LrcMode = "stx" | "std" | "noext" | "stx_noext"` (see
|
|
7
|
-
// package/src/types/client.ts). The unit tests compile the pure-logic C++
|
|
8
|
-
// units (Lrc, PacketCodec, Ecr17Protocol) in isolation, without running
|
|
9
|
-
// Nitrogen, so we provide a minimal enum that mirrors the generated one.
|
|
10
|
-
//
|
|
11
|
-
// The member names MUST match what the production code references
|
|
12
|
-
// (Lrc.cpp uses LrcMode::STX, LrcMode::STX_NOEXT, LrcMode::NOEXT). If Nitrogen
|
|
13
|
-
// turns out to emit different casing, that is a real bug to reconcile when the
|
|
14
|
-
// codegen is run — this stub documents the contract the code assumes today.
|
|
15
|
-
|
|
16
|
-
namespace margelo::nitro::ecr17 {
|
|
17
|
-
|
|
18
|
-
enum class LrcMode {
|
|
19
|
-
STX,
|
|
20
|
-
STD,
|
|
21
|
-
NOEXT,
|
|
22
|
-
STX_NOEXT,
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
} // namespace margelo::nitro::ecr17
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
// TEST-ONLY STUB.
|
|
4
|
+
//
|
|
5
|
+
// In a real build this header is generated by Nitrogen from the TypeScript
|
|
6
|
+
// union `type LrcMode = "stx" | "std" | "noext" | "stx_noext"` (see
|
|
7
|
+
// package/src/types/client.ts). The unit tests compile the pure-logic C++
|
|
8
|
+
// units (Lrc, PacketCodec, Ecr17Protocol) in isolation, without running
|
|
9
|
+
// Nitrogen, so we provide a minimal enum that mirrors the generated one.
|
|
10
|
+
//
|
|
11
|
+
// The member names MUST match what the production code references
|
|
12
|
+
// (Lrc.cpp uses LrcMode::STX, LrcMode::STX_NOEXT, LrcMode::NOEXT). If Nitrogen
|
|
13
|
+
// turns out to emit different casing, that is a real bug to reconcile when the
|
|
14
|
+
// codegen is run — this stub documents the contract the code assumes today.
|
|
15
|
+
|
|
16
|
+
namespace margelo::nitro::ecr17 {
|
|
17
|
+
|
|
18
|
+
enum class LrcMode {
|
|
19
|
+
STX,
|
|
20
|
+
STD,
|
|
21
|
+
NOEXT,
|
|
22
|
+
STX_NOEXT,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
} // namespace margelo::nitro::ecr17
|
package/cpp/tests/test_flows.cpp
CHANGED
|
@@ -1,148 +1,148 @@
|
|
|
1
|
-
// End-to-end protocol *flow* tests, modelled on the documented Nexi ECR17
|
|
2
|
-
// sequences (basic payment, reversal/"annullamento", re-payment, status,
|
|
3
|
-
// NAK retransmission).
|
|
4
|
-
//
|
|
5
|
-
// These exercise the layers that exist today: request building
|
|
6
|
-
// (Ecr17Protocol) + physical framing (PacketCodec). Response *field* parsing
|
|
7
|
-
// is not implemented yet, so terminal responses are synthesized as spec-shaped
|
|
8
|
-
// payloads and asserted at the framing/classification level (packet type, LRC
|
|
9
|
-
// validity, message code position), not by parsed fields.
|
|
10
|
-
|
|
11
|
-
#include <gtest/gtest.h>
|
|
12
|
-
|
|
13
|
-
#include <cstdint>
|
|
14
|
-
#include <string>
|
|
15
|
-
#include <vector>
|
|
16
|
-
|
|
17
|
-
#include "Ecr17Protocol/Ecr17Protocol.hpp"
|
|
18
|
-
#include "PacketCodec/PacketCodec.hpp"
|
|
19
|
-
|
|
20
|
-
using namespace margelo::nitro::ecr17;
|
|
21
|
-
|
|
22
|
-
namespace {
|
|
23
|
-
|
|
24
|
-
constexpr uint8_t kStx = 0x02;
|
|
25
|
-
constexpr uint8_t kEtx = 0x03;
|
|
26
|
-
constexpr uint8_t kSoh = 0x01;
|
|
27
|
-
constexpr uint8_t kEot = 0x04;
|
|
28
|
-
constexpr uint8_t kAck = 0x06;
|
|
29
|
-
constexpr uint8_t kNak = 0x15;
|
|
30
|
-
|
|
31
|
-
const std::string kTerminal = "12345678";
|
|
32
|
-
const std::string kCashReg = "00000001";
|
|
33
|
-
|
|
34
|
-
// Terminal-side framing of a progress-update packet: SOH + 20-char message + EOT.
|
|
35
|
-
std::vector<uint8_t> progressFrame(const std::string& msg20) {
|
|
36
|
-
std::vector<uint8_t> f{kSoh};
|
|
37
|
-
f.insert(f.end(), msg20.begin(), msg20.end());
|
|
38
|
-
f.push_back(kEot);
|
|
39
|
-
return f;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// Terminal-side framing of a spec-shaped result message ('E', result "00" = OK).
|
|
43
|
-
std::vector<uint8_t> okResultFrame(PacketCodec& codec) {
|
|
44
|
-
std::string e = kTerminal + "0" + "E" + "00" + std::string(60, '0');
|
|
45
|
-
return codec.encodeApplication(e);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
} // namespace
|
|
49
|
-
|
|
50
|
-
// Documented "Basic Payment Flow": request -> ACK -> progress -> result -> ACK.
|
|
51
|
-
TEST(Flow, BasicPayment) {
|
|
52
|
-
PacketCodec codec(LrcMode::STD);
|
|
53
|
-
|
|
54
|
-
// 1. ECR -> POS: payment request on the wire (STX .. ETX .. LRC).
|
|
55
|
-
std::string req = Ecr17Protocol::buildPaymentMessage(kTerminal, kCashReg, 650);
|
|
56
|
-
auto reqFrame = codec.encodeApplication(req);
|
|
57
|
-
EXPECT_EQ(reqFrame.front(), kStx);
|
|
58
|
-
EXPECT_EQ(reqFrame[reqFrame.size() - 2], kEtx);
|
|
59
|
-
|
|
60
|
-
// POS validates and recovers the exact request payload.
|
|
61
|
-
DecodedPacket atPos = codec.decode(reqFrame);
|
|
62
|
-
EXPECT_EQ(atPos.type, PacketType::APPLICATION);
|
|
63
|
-
EXPECT_TRUE(atPos.validLrc);
|
|
64
|
-
EXPECT_EQ(atPos.payload, req);
|
|
65
|
-
EXPECT_EQ(atPos.payload[9], 'P');
|
|
66
|
-
|
|
67
|
-
// 2. POS -> ECR: physical ACK.
|
|
68
|
-
EXPECT_EQ(codec.decode(codec.encodeControl(kAck)).type, PacketType::ACK);
|
|
69
|
-
|
|
70
|
-
// 3. POS -> ECR: progress update while contacting the host (no LRC).
|
|
71
|
-
DecodedPacket prog = codec.decode(progressFrame("ATTENDERE PREGO "));
|
|
72
|
-
EXPECT_EQ(prog.type, PacketType::PROGRESS);
|
|
73
|
-
EXPECT_EQ(prog.payload, "ATTENDERE PREGO ");
|
|
74
|
-
|
|
75
|
-
// 4. POS -> ECR: positive result.
|
|
76
|
-
DecodedPacket result = codec.decode(okResultFrame(codec));
|
|
77
|
-
EXPECT_EQ(result.type, PacketType::APPLICATION);
|
|
78
|
-
EXPECT_TRUE(result.validLrc);
|
|
79
|
-
EXPECT_EQ(result.payload[9], 'E');
|
|
80
|
-
EXPECT_EQ(result.payload.substr(10, 2), "00");
|
|
81
|
-
|
|
82
|
-
// 5. ECR -> POS: ACK confirming receipt of the result.
|
|
83
|
-
EXPECT_EQ(codec.decode(codec.encodeControl(kAck)).type, PacketType::ACK);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// Documented NAK handling: a NAK triggers retransmission of the same message
|
|
87
|
-
// (up to 3 times). Framing is deterministic, so the retransmitted bytes match.
|
|
88
|
-
TEST(Flow, NakTriggersIdenticalRetransmit) {
|
|
89
|
-
PacketCodec codec(LrcMode::STD);
|
|
90
|
-
std::string req = Ecr17Protocol::buildPaymentMessage(kTerminal, kCashReg, 1999);
|
|
91
|
-
auto first = codec.encodeApplication(req);
|
|
92
|
-
|
|
93
|
-
EXPECT_EQ(codec.decode(codec.encodeControl(kNak)).type, PacketType::NAK);
|
|
94
|
-
|
|
95
|
-
auto retransmit = codec.encodeApplication(req);
|
|
96
|
-
EXPECT_EQ(first, retransmit);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// Documented "Reversal Last Transaction" (annullamento), command 'S'.
|
|
100
|
-
TEST(Flow, ReversalAnnullamento) {
|
|
101
|
-
PacketCodec codec(LrcMode::STD);
|
|
102
|
-
|
|
103
|
-
std::string req = Ecr17Protocol::buildReversalMessage(kTerminal, kCashReg, "000123");
|
|
104
|
-
ASSERT_EQ(req.size(), 26u);
|
|
105
|
-
EXPECT_EQ(req[9], 'S');
|
|
106
|
-
EXPECT_EQ(req.substr(18, 6), "000123");
|
|
107
|
-
|
|
108
|
-
DecodedPacket atPos = codec.decode(codec.encodeApplication(req));
|
|
109
|
-
EXPECT_EQ(atPos.type, PacketType::APPLICATION);
|
|
110
|
-
EXPECT_TRUE(atPos.validLrc);
|
|
111
|
-
EXPECT_EQ(atPos.payload, req);
|
|
112
|
-
|
|
113
|
-
EXPECT_EQ(codec.decode(codec.encodeControl(kAck)).type, PacketType::ACK);
|
|
114
|
-
DecodedPacket result = codec.decode(okResultFrame(codec));
|
|
115
|
-
EXPECT_TRUE(result.validLrc);
|
|
116
|
-
EXPECT_EQ(result.payload[9], 'E');
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// Pay -> annullamento -> ripaga (re-payment with the corrected amount).
|
|
120
|
-
TEST(Flow, PayReverseRepay) {
|
|
121
|
-
PacketCodec codec(LrcMode::STD);
|
|
122
|
-
|
|
123
|
-
auto pay1 = codec.encodeApplication(Ecr17Protocol::buildPaymentMessage(kTerminal, kCashReg, 650));
|
|
124
|
-
auto rev = codec.encodeApplication(Ecr17Protocol::buildReversalMessage(kTerminal, kCashReg));
|
|
125
|
-
auto pay2 = codec.encodeApplication(Ecr17Protocol::buildPaymentMessage(kTerminal, kCashReg, 720));
|
|
126
|
-
|
|
127
|
-
for (const auto* frame : {&pay1, &rev, &pay2}) {
|
|
128
|
-
DecodedPacket d = codec.decode(*frame);
|
|
129
|
-
EXPECT_EQ(d.type, PacketType::APPLICATION);
|
|
130
|
-
EXPECT_TRUE(d.validLrc);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
EXPECT_EQ(codec.decode(pay1).payload.substr(23, 8), "00000650");
|
|
134
|
-
EXPECT_EQ(codec.decode(pay2).payload.substr(23, 8), "00000720");
|
|
135
|
-
EXPECT_NE(pay1, pay2); // the corrected re-payment differs on the wire
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// Terminal status request/round-trip ('s').
|
|
139
|
-
TEST(Flow, StatusRequest) {
|
|
140
|
-
PacketCodec codec(LrcMode::STD);
|
|
141
|
-
std::string req = Ecr17Protocol::buildStatusMessage(kTerminal);
|
|
142
|
-
EXPECT_EQ(req, "123456780s");
|
|
143
|
-
|
|
144
|
-
DecodedPacket atPos = codec.decode(codec.encodeApplication(req));
|
|
145
|
-
EXPECT_EQ(atPos.type, PacketType::APPLICATION);
|
|
146
|
-
EXPECT_TRUE(atPos.validLrc);
|
|
147
|
-
EXPECT_EQ(atPos.payload, req);
|
|
148
|
-
}
|
|
1
|
+
// End-to-end protocol *flow* tests, modelled on the documented Nexi ECR17
|
|
2
|
+
// sequences (basic payment, reversal/"annullamento", re-payment, status,
|
|
3
|
+
// NAK retransmission).
|
|
4
|
+
//
|
|
5
|
+
// These exercise the layers that exist today: request building
|
|
6
|
+
// (Ecr17Protocol) + physical framing (PacketCodec). Response *field* parsing
|
|
7
|
+
// is not implemented yet, so terminal responses are synthesized as spec-shaped
|
|
8
|
+
// payloads and asserted at the framing/classification level (packet type, LRC
|
|
9
|
+
// validity, message code position), not by parsed fields.
|
|
10
|
+
|
|
11
|
+
#include <gtest/gtest.h>
|
|
12
|
+
|
|
13
|
+
#include <cstdint>
|
|
14
|
+
#include <string>
|
|
15
|
+
#include <vector>
|
|
16
|
+
|
|
17
|
+
#include "Ecr17Protocol/Ecr17Protocol.hpp"
|
|
18
|
+
#include "PacketCodec/PacketCodec.hpp"
|
|
19
|
+
|
|
20
|
+
using namespace margelo::nitro::ecr17;
|
|
21
|
+
|
|
22
|
+
namespace {
|
|
23
|
+
|
|
24
|
+
constexpr uint8_t kStx = 0x02;
|
|
25
|
+
constexpr uint8_t kEtx = 0x03;
|
|
26
|
+
constexpr uint8_t kSoh = 0x01;
|
|
27
|
+
constexpr uint8_t kEot = 0x04;
|
|
28
|
+
constexpr uint8_t kAck = 0x06;
|
|
29
|
+
constexpr uint8_t kNak = 0x15;
|
|
30
|
+
|
|
31
|
+
const std::string kTerminal = "12345678";
|
|
32
|
+
const std::string kCashReg = "00000001";
|
|
33
|
+
|
|
34
|
+
// Terminal-side framing of a progress-update packet: SOH + 20-char message + EOT.
|
|
35
|
+
std::vector<uint8_t> progressFrame(const std::string& msg20) {
|
|
36
|
+
std::vector<uint8_t> f{kSoh};
|
|
37
|
+
f.insert(f.end(), msg20.begin(), msg20.end());
|
|
38
|
+
f.push_back(kEot);
|
|
39
|
+
return f;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Terminal-side framing of a spec-shaped result message ('E', result "00" = OK).
|
|
43
|
+
std::vector<uint8_t> okResultFrame(PacketCodec& codec) {
|
|
44
|
+
std::string e = kTerminal + "0" + "E" + "00" + std::string(60, '0');
|
|
45
|
+
return codec.encodeApplication(e);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
} // namespace
|
|
49
|
+
|
|
50
|
+
// Documented "Basic Payment Flow": request -> ACK -> progress -> result -> ACK.
|
|
51
|
+
TEST(Flow, BasicPayment) {
|
|
52
|
+
PacketCodec codec(LrcMode::STD);
|
|
53
|
+
|
|
54
|
+
// 1. ECR -> POS: payment request on the wire (STX .. ETX .. LRC).
|
|
55
|
+
std::string req = Ecr17Protocol::buildPaymentMessage(kTerminal, kCashReg, 650);
|
|
56
|
+
auto reqFrame = codec.encodeApplication(req);
|
|
57
|
+
EXPECT_EQ(reqFrame.front(), kStx);
|
|
58
|
+
EXPECT_EQ(reqFrame[reqFrame.size() - 2], kEtx);
|
|
59
|
+
|
|
60
|
+
// POS validates and recovers the exact request payload.
|
|
61
|
+
DecodedPacket atPos = codec.decode(reqFrame);
|
|
62
|
+
EXPECT_EQ(atPos.type, PacketType::APPLICATION);
|
|
63
|
+
EXPECT_TRUE(atPos.validLrc);
|
|
64
|
+
EXPECT_EQ(atPos.payload, req);
|
|
65
|
+
EXPECT_EQ(atPos.payload[9], 'P');
|
|
66
|
+
|
|
67
|
+
// 2. POS -> ECR: physical ACK.
|
|
68
|
+
EXPECT_EQ(codec.decode(codec.encodeControl(kAck)).type, PacketType::ACK);
|
|
69
|
+
|
|
70
|
+
// 3. POS -> ECR: progress update while contacting the host (no LRC).
|
|
71
|
+
DecodedPacket prog = codec.decode(progressFrame("ATTENDERE PREGO "));
|
|
72
|
+
EXPECT_EQ(prog.type, PacketType::PROGRESS);
|
|
73
|
+
EXPECT_EQ(prog.payload, "ATTENDERE PREGO ");
|
|
74
|
+
|
|
75
|
+
// 4. POS -> ECR: positive result.
|
|
76
|
+
DecodedPacket result = codec.decode(okResultFrame(codec));
|
|
77
|
+
EXPECT_EQ(result.type, PacketType::APPLICATION);
|
|
78
|
+
EXPECT_TRUE(result.validLrc);
|
|
79
|
+
EXPECT_EQ(result.payload[9], 'E');
|
|
80
|
+
EXPECT_EQ(result.payload.substr(10, 2), "00");
|
|
81
|
+
|
|
82
|
+
// 5. ECR -> POS: ACK confirming receipt of the result.
|
|
83
|
+
EXPECT_EQ(codec.decode(codec.encodeControl(kAck)).type, PacketType::ACK);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Documented NAK handling: a NAK triggers retransmission of the same message
|
|
87
|
+
// (up to 3 times). Framing is deterministic, so the retransmitted bytes match.
|
|
88
|
+
TEST(Flow, NakTriggersIdenticalRetransmit) {
|
|
89
|
+
PacketCodec codec(LrcMode::STD);
|
|
90
|
+
std::string req = Ecr17Protocol::buildPaymentMessage(kTerminal, kCashReg, 1999);
|
|
91
|
+
auto first = codec.encodeApplication(req);
|
|
92
|
+
|
|
93
|
+
EXPECT_EQ(codec.decode(codec.encodeControl(kNak)).type, PacketType::NAK);
|
|
94
|
+
|
|
95
|
+
auto retransmit = codec.encodeApplication(req);
|
|
96
|
+
EXPECT_EQ(first, retransmit);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Documented "Reversal Last Transaction" (annullamento), command 'S'.
|
|
100
|
+
TEST(Flow, ReversalAnnullamento) {
|
|
101
|
+
PacketCodec codec(LrcMode::STD);
|
|
102
|
+
|
|
103
|
+
std::string req = Ecr17Protocol::buildReversalMessage(kTerminal, kCashReg, "000123");
|
|
104
|
+
ASSERT_EQ(req.size(), 26u);
|
|
105
|
+
EXPECT_EQ(req[9], 'S');
|
|
106
|
+
EXPECT_EQ(req.substr(18, 6), "000123");
|
|
107
|
+
|
|
108
|
+
DecodedPacket atPos = codec.decode(codec.encodeApplication(req));
|
|
109
|
+
EXPECT_EQ(atPos.type, PacketType::APPLICATION);
|
|
110
|
+
EXPECT_TRUE(atPos.validLrc);
|
|
111
|
+
EXPECT_EQ(atPos.payload, req);
|
|
112
|
+
|
|
113
|
+
EXPECT_EQ(codec.decode(codec.encodeControl(kAck)).type, PacketType::ACK);
|
|
114
|
+
DecodedPacket result = codec.decode(okResultFrame(codec));
|
|
115
|
+
EXPECT_TRUE(result.validLrc);
|
|
116
|
+
EXPECT_EQ(result.payload[9], 'E');
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Pay -> annullamento -> ripaga (re-payment with the corrected amount).
|
|
120
|
+
TEST(Flow, PayReverseRepay) {
|
|
121
|
+
PacketCodec codec(LrcMode::STD);
|
|
122
|
+
|
|
123
|
+
auto pay1 = codec.encodeApplication(Ecr17Protocol::buildPaymentMessage(kTerminal, kCashReg, 650));
|
|
124
|
+
auto rev = codec.encodeApplication(Ecr17Protocol::buildReversalMessage(kTerminal, kCashReg));
|
|
125
|
+
auto pay2 = codec.encodeApplication(Ecr17Protocol::buildPaymentMessage(kTerminal, kCashReg, 720));
|
|
126
|
+
|
|
127
|
+
for (const auto* frame : {&pay1, &rev, &pay2}) {
|
|
128
|
+
DecodedPacket d = codec.decode(*frame);
|
|
129
|
+
EXPECT_EQ(d.type, PacketType::APPLICATION);
|
|
130
|
+
EXPECT_TRUE(d.validLrc);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
EXPECT_EQ(codec.decode(pay1).payload.substr(23, 8), "00000650");
|
|
134
|
+
EXPECT_EQ(codec.decode(pay2).payload.substr(23, 8), "00000720");
|
|
135
|
+
EXPECT_NE(pay1, pay2); // the corrected re-payment differs on the wire
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Terminal status request/round-trip ('s').
|
|
139
|
+
TEST(Flow, StatusRequest) {
|
|
140
|
+
PacketCodec codec(LrcMode::STD);
|
|
141
|
+
std::string req = Ecr17Protocol::buildStatusMessage(kTerminal);
|
|
142
|
+
EXPECT_EQ(req, "123456780s");
|
|
143
|
+
|
|
144
|
+
DecodedPacket atPos = codec.decode(codec.encodeApplication(req));
|
|
145
|
+
EXPECT_EQ(atPos.type, PacketType::APPLICATION);
|
|
146
|
+
EXPECT_TRUE(atPos.validLrc);
|
|
147
|
+
EXPECT_EQ(atPos.payload, req);
|
|
148
|
+
}
|