From 88d3025c567ef1d6ce3a617e59cf0f417a099710 Mon Sep 17 00:00:00 2001 From: REM Date: Wed, 26 Aug 2026 18:28:51 +0330 Subject: [PATCH 1/6] docs: clarify license files in README --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index 739b935..8803125 100644 --- a/README.md +++ b/README.md @@ -14,3 +14,13 @@ For more details I refer to the commit history. Please note that this fork is currently at version 1.4.0. Cleanup, more fixes and documentation will follow. + +## License + +This repository includes the applicable license texts in the repository root: + +- [GPL license text](gpl.txt) +- [LGPL v3.0 license text](lgpl-3.0.txt) + +Please review the applicable license terms before using, modifying, +or redistributing this software. From ebcf3b4059b58bc976ef389404d400f7bdd32b27 Mon Sep 17 00:00:00 2001 From: REM Date: Wed, 26 Aug 2026 20:09:38 +0330 Subject: [PATCH 2/6] security: add security policy and OT-safe deployment guides --- SECURITY.md | 13 ++++++++++ docs/industrial-networking.md | 49 +++++++++++++++++++++++++++++++++++ docs/secure-deployment.md | 29 +++++++++++++++++++++ 3 files changed, 91 insertions(+) create mode 100644 SECURITY.md create mode 100644 docs/industrial-networking.md create mode 100644 docs/secure-deployment.md diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..308464e --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,13 @@ +# Security Policy + +## Reporting a Vulnerability + +Please avoid publishing suspected security vulnerabilities in public issue trackers before maintainers have had an opportunity to assess them. + +When a private reporting channel is available, use that channel and include: + +- A clear description of the issue +- Affected versions or commits +- Steps to reproduce the issue +- Potential impact +- Any proposed mitigation, if available \ No newline at end of file diff --git a/docs/industrial-networking.md b/docs/industrial-networking.md new file mode 100644 index 0000000..0e5dc70 --- /dev/null +++ b/docs/industrial-networking.md @@ -0,0 +1,49 @@ +# Industrial Networking Guidance + +This document provides guidance on network architecture and communication patterns when using Snap7 in industrial environments. + +## Network Segmentation + +Industrial control systems should be separated from enterprise IT networks using: + +- **DMZ / Industrial DMZ**: A controlled boundary between IT and OT networks +- **Firewalls**: Stateful inspection firewalls with rules allowing only necessary protocols and ports +- **VLANs**: Logical separation of traffic types (e.g., HMI, historian, engineering, safety) +- **Unidirectional Gateways (Data Diodes)**: For high-security environments requiring data flow from OT to IT only + +## Communication Protocols + +Snap7 uses the Siemens S7 protocol over ISO-on-TCP (RFC 1006) typically on port 102. Consider: + +- Restricting port 102 access to authorized engineering stations only +- Using non-standard ports where possible to reduce automated scanning +- Implementing port knocking or SPA (Single Packet Authorization) for additional obscurity + +## Redundancy and Availability + +- Design network paths with redundancy for critical communication +- Implement monitoring for communication loss and latency anomalies +- Consider PLC-side communication load when multiple clients connect +- Use Snap7's connection management features to handle reconnections gracefully + +## Monitoring and Logging + +- Log all connection attempts (successful and failed) +- Monitor for unusual traffic patterns or unexpected PLC commands +- Correlate network logs with PLC diagnostic buffers +- Set up alerts for communication failures on critical assets + +## Remote Access + +If remote access is required: + +- Use a dedicated VPN with multi-factor authentication +- Implement jump hosts / bastion hosts for engineering access +- Record and audit all remote sessions +- Enforce time-limited access with automatic revocation + +## References + +- IEC 62443 Series - Industrial communication network security +- NIST SP 800-82 - Guide to Industrial Control Systems Security +- ISA/IEC 62443-3-3 - System security requirements and security levels \ No newline at end of file diff --git a/docs/secure-deployment.md b/docs/secure-deployment.md new file mode 100644 index 0000000..9c1eba9 --- /dev/null +++ b/docs/secure-deployment.md @@ -0,0 +1,29 @@ +# Secure Deployment Guide + +This guide provides security-focused deployment recommendations for systems using Snap7 to communicate with Siemens S7 PLCs. + +## Network Architecture + +- PLCs should not be exposed directly to the internet. +- Use network segmentation and firewall rules to isolate OT networks from IT networks. +- Use VPN or controlled remote-access mechanisms for remote engineering access. +- Limit access to authorized engineering and service networks only. +- Use least-privilege access where supported by the PLC and network infrastructure. + +## Operational Security + +- Treat PLC write operations as high-risk actions. +- Separate development/test devices from production assets. +- Log, monitor, and review communication failures and unexpected commands. +- Implement change management procedures for any modifications to PLC programs or configuration. + +## Important Limitations + +Snap7 is not a substitute for OT network architecture and operational security controls. It provides communication capabilities but does not implement: + +- Network-level authentication or encryption (relies on underlying transport) +- Industrial firewall or intrusion detection +- Safety system functions +- Compliance with specific industrial security standards (e.g., IEC 62443) + +These controls must be implemented at the network and system architecture level. \ No newline at end of file From d7ffef2b638219bd5e5e6d9c0044e1a0e02149ba Mon Sep 17 00:00:00 2001 From: REM Date: Wed, 26 Aug 2026 20:12:37 +0330 Subject: [PATCH 3/6] ci: add cross-platform build and smoke test workflows --- .github/workflows/build.yml | 70 ++++++++++++++++++++++++ .github/workflows/smoke-test.yml | 92 ++++++++++++++++++++++++++++++++ 2 files changed, 162 insertions(+) create mode 100644 .github/workflows/build.yml create mode 100644 .github/workflows/smoke-test.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..c9b95cc --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,70 @@ +name: Cross-Platform Build + +on: + push: + branches: [master] + pull_request: + branches: [master] + +jobs: + build: + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest] + architecture: [x64] + include: + - os: ubuntu-latest + architecture: x64 + makefile: build/unix/x86_64_linux.mk + target: libsnap7.so + - os: windows-latest + architecture: x64 + makefile: build/windows/MinGW64/Makefile + target: snap7.dll + runs-on: ${{ matrix.os }} + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up build environment (Linux) + if: runner.os == 'Linux' + run: | + sudo apt-get update + sudo apt-get install -y build-essential + + - name: Set up build environment (Windows) + if: runner.os == 'Windows' + uses: msys2/setup-msys2@v2 + with: + msystem: MINGW64 + install: mingw-w64-x86_64-gcc mingw-w64-x86_64-binutils make + + - name: Build (Linux) + if: runner.os == 'Linux' + working-directory: ${{ github.workspace }} + run: | + make -f ${{ matrix.makefile }} + ls -la build/bin/x86_64-linux/ + + - name: Build (Windows) + if: runner.os == 'Windows' + working-directory: ${{ github.workspace }}/build/windows/MinGW64 + shell: msys2 {0} + run: | + make + ls -la ../../bin/Legacy/win64/ + + - name: Upload Linux artifact + if: runner.os == 'Linux' + uses: actions/upload-artifact@v4 + with: + name: snap7-linux-x64 + path: build/bin/x86_64-linux/libsnap7.so + + - name: Upload Windows artifact + if: runner.os == 'Windows' + uses: actions/upload-artifact@v4 + with: + name: snap7-windows-x64 + path: build/bin/Legacy/win64/snap7.dll \ No newline at end of file diff --git a/.github/workflows/smoke-test.yml b/.github/workflows/smoke-test.yml new file mode 100644 index 0000000..cea26f0 --- /dev/null +++ b/.github/workflows/smoke-test.yml @@ -0,0 +1,92 @@ +name: Smoke Test + +on: + push: + branches: [master] + pull_request: + branches: [master] + +jobs: + smoke-test: + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest] + architecture: [x64] + runs-on: ${{ matrix.os }} + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up build environment (Linux) + if: runner.os == 'Linux' + run: | + sudo apt-get update + sudo apt-get install -y build-essential + + - name: Set up build environment (Windows) + if: runner.os == 'Windows' + uses: msys2/setup-msys2@v2 + with: + msystem: MINGW64 + install: mingw-w64-x86_64-gcc mingw-w64-x86_64-binutils make + + - name: Build (Linux) + if: runner.os == 'Linux' + working-directory: ${{ github.workspace }} + run: make -f build/unix/x86_64_linux.mk + + - name: Build (Windows) + if: runner.os == 'Windows' + working-directory: ${{ github.workspace }}/build/windows/MinGW64 + shell: msys2 {0} + run: make + + - name: Run Linux smoke test + if: runner.os == 'Linux' + working-directory: ${{ github.workspace }} + run: | + # Create a simple test to verify library loads + cat > /tmp/test_snap7.cpp << 'EOF' + #include + #include + #include "../../src/lib/snap7_libmain.h" + + int main() { + void* handle = dlopen("build/bin/x86_64-linux/libsnap7.so", RTLD_LAZY); + if (!handle) { + std::cerr << "Failed to load library: " << dlerror() << std::endl; + return 1; + } + std::cout << "Library loaded successfully" << std::endl; + dlclose(handle); + return 0; + } + EOF + g++ -std=c++11 /tmp/test_snap7.cpp -o /tmp/test_snap7 -ldl + /tmp/test_snap7 + + - name: Run Windows smoke test + if: runner.os == 'Windows' + working-directory: ${{ github.workspace }} + shell: msys2 {0} + run: | + # Create a simple test to verify library loads + cat > /tmp/test_snap7.cpp << 'EOF' + #include + #include + #include "../../../src/lib/snap7_libmain.h" + + int main() { + HMODULE handle = LoadLibrary("build/bin/Legacy/win64/snap7.dll"); + if (!handle) { + std::cerr << "Failed to load library, error: " << GetLastError() << std::endl; + return 1; + } + std::cout << "Library loaded successfully" << std::endl; + FreeLibrary(handle); + return 0; + } + EOF + g++ /tmp/test_snap7.cpp -o /tmp/test_snap7.exe + /tmp/test_snap7.exe \ No newline at end of file From 0d4474670c16e9419e3b850c9dc233e8edb5b86e Mon Sep 17 00:00:00 2001 From: REM Date: Wed, 26 Aug 2026 20:17:58 +0330 Subject: [PATCH 4/6] test: add loopback smoke test for client/server verification --- .github/workflows/smoke-test.yml | 65 ++---- examples/cpp/loopback_test.cpp | 346 +++++++++++++++++++++++++++++ examples/cpp/x86_64-linux/makefile | 4 +- 3 files changed, 370 insertions(+), 45 deletions(-) create mode 100644 examples/cpp/loopback_test.cpp diff --git a/.github/workflows/smoke-test.yml b/.github/workflows/smoke-test.yml index cea26f0..2bb7664 100644 --- a/.github/workflows/smoke-test.yml +++ b/.github/workflows/smoke-test.yml @@ -31,62 +31,39 @@ jobs: msystem: MINGW64 install: mingw-w64-x86_64-gcc mingw-w64-x86_64-binutils make - - name: Build (Linux) + - name: Build library (Linux) if: runner.os == 'Linux' working-directory: ${{ github.workspace }} run: make -f build/unix/x86_64_linux.mk - - name: Build (Windows) + - name: Build library (Windows) if: runner.os == 'Windows' working-directory: ${{ github.workspace }}/build/windows/MinGW64 shell: msys2 {0} run: make - - name: Run Linux smoke test + - name: Build loopback test (Linux) if: runner.os == 'Linux' - working-directory: ${{ github.workspace }} + working-directory: ${{ github.workspace }}/examples/cpp/x86_64-linux + run: make loopback_test + + - name: Build loopback test (Windows) + if: runner.os == 'Windows' + working-directory: ${{ github.workspace }}/examples/cpp/x86_64-linux + shell: msys2 {0} + run: make loopback_test + + - name: Run Linux loopback test + if: runner.os == 'Linux' + working-directory: ${{ github.workspace }}/examples/cpp/x86_64-linux run: | - # Create a simple test to verify library loads - cat > /tmp/test_snap7.cpp << 'EOF' - #include - #include - #include "../../src/lib/snap7_libmain.h" - - int main() { - void* handle = dlopen("build/bin/x86_64-linux/libsnap7.so", RTLD_LAZY); - if (!handle) { - std::cerr << "Failed to load library: " << dlerror() << std::endl; - return 1; - } - std::cout << "Library loaded successfully" << std::endl; - dlclose(handle); - return 0; - } - EOF - g++ -std=c++11 /tmp/test_snap7.cpp -o /tmp/test_snap7 -ldl - /tmp/test_snap7 + # Start test in background and give it time to run + timeout 60 ./loopback_test - - name: Run Windows smoke test + - name: Run Windows loopback test if: runner.os == 'Windows' - working-directory: ${{ github.workspace }} + working-directory: ${{ github.workspace }}/examples/cpp/x86_64-linux shell: msys2 {0} run: | - # Create a simple test to verify library loads - cat > /tmp/test_snap7.cpp << 'EOF' - #include - #include - #include "../../../src/lib/snap7_libmain.h" - - int main() { - HMODULE handle = LoadLibrary("build/bin/Legacy/win64/snap7.dll"); - if (!handle) { - std::cerr << "Failed to load library, error: " << GetLastError() << std::endl; - return 1; - } - std::cout << "Library loaded successfully" << std::endl; - FreeLibrary(handle); - return 0; - } - EOF - g++ /tmp/test_snap7.cpp -o /tmp/test_snap7.exe - /tmp/test_snap7.exe \ No newline at end of file + # Start test and give it time to run + timeout 60 ./loopback_test.exe \ No newline at end of file diff --git a/examples/cpp/loopback_test.cpp b/examples/cpp/loopback_test.cpp new file mode 100644 index 0000000..29695d7 --- /dev/null +++ b/examples/cpp/loopback_test.cpp @@ -0,0 +1,346 @@ +/*=============================================================================| +| PROJECT SNAP7 1.4.0 | +|==============================================================================| +| Copyright (C) 2013, 2014 Davide Nardella | +| All rights reserved. | +|==============================================================================| +| SNAP7 is free software: you can redistribute it and/or modify | +| it under the terms of the Lesser GNU General Public License as published by | +| the Free Software Foundation, either version 3 of the License, or | +| (at your option) any later version. | +| | +| It means that you can distribute your commercial software linked with | +| SNAP7 without the requirement to distribute the source code of your | +| application and without the requirement that your application be itself | +| distributed under LGPL. | +| | +| SNAP7 is distributed in the hope that it will be useful, | +| but WITHOUT ANY WARRANTY; without even the implied warranty of | +| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | +| Lesser GNU General Public License for more details. | +| | +| You should have received a copy of the GNU General Public License and a | +| copy of Lesser GNU General Public License along with Snap7. | +| If not, see http://www.gnu.org/licenses/ | +|==============================================================================| +| | +| Loopback Smoke Test | +| Tests local client/server communication without requiring real PLC hardware.| +|=============================================================================*/ +#include +#include +#include +#include +#include +#include "snap7.h" + +#ifdef OS_WINDOWS +# define WIN32_LEAN_AND_MEAN +# include +#else +# include +#endif + +// Test configuration - uses localhost and non-privileged port +static const char* TEST_HOST = "127.0.0.1"; +static const int TEST_PORT = 1102; // Non-privileged port for testing +static const int TEST_RACK = 0; +static const int TEST_SLOT = 1; + +// Test data +static byte test_db_data[256]; +static bool server_ready = false; +static int server_error = 0; + +// Server event callback +void S7API ServerEventCallback(void* usrPtr, PSrvEvent PEvent, int Size) { + // Print events for debugging + printf("[SERVER] %s\n", SrvEventText(PEvent).c_str()); +} + +// Server read event callback +void S7API ServerReadEventCallback(void* usrPtr, PSrvEvent PEvent, int Size) { + if (PEvent->EvtParam1 == S7AreaDB) { + int db_num = PEvent->EvtParam2; + if (db_num == 1) { + memset(test_db_data, 0xAA, sizeof(test_db_data)); + } + } +} + +// Server thread function +void ServerThread() { + TS7Server* Server = new TS7Server(); + + // Register a test DB + Server->RegisterArea(srvAreaDB, 1, test_db_data, sizeof(test_db_data)); + + // Set callbacks + Server->SetEventsCallback(ServerEventCallback, NULL); + Server->SetReadEventsCallback(ServerReadEventCallback, NULL); + + // Start server on localhost with test port + server_error = Server->StartTo(TEST_HOST); + if (server_error == 0) { + server_ready = true; + printf("[SERVER] Started on %s:%d\n", TEST_HOST, TEST_PORT); + + // Keep server running + while (server_ready) { +#ifdef OS_WINDOWS + Sleep(100); +#else + usleep(100000); +#endif + } + } else { + printf("[SERVER] Failed to start: %s\n", SrvErrorText(server_error).c_str()); + } + + Server->Stop(); + delete Server; +} + +bool Check(int Result, const char* Function) { + printf("\n+-----------------------------------------------------\n"); + printf("| %s\n", Function); + printf("+-----------------------------------------------------\n"); + if (Result == 0) { + printf("| Result : OK\n"); + printf("+-----------------------------------------------------\n"); + return true; + } else { + printf("| ERROR !!!\n"); + if (Result < 0) + printf("| Library Error (-1)\n"); + else + printf("| %s\n", CliErrorText(Result).c_str()); + printf("+-----------------------------------------------------\n"); + return false; + } +} + +// SysSleep for cross-platform sleep +void SysSleep(longword Delay_ms) { +#ifdef OS_WINDOWS + Sleep(Delay_ms); +#else + struct timespec ts; + ts.tv_sec = (time_t)(Delay_ms / 1000); + ts.tv_nsec = (long)((Delay_ms - ts.tv_sec * 1000) * 1000000); + nanosleep(&ts, NULL); +#endif +} + +int main() { + int ok = 0; + int ko = 0; + + printf("=== Snap7 Loopback Smoke Test ===\n"); + printf("Host: %s, Port: %d, Rack: %d, Slot: %d\n\n", TEST_HOST, TEST_PORT, TEST_RACK, TEST_SLOT); + + // Start server in background thread + std::thread server_thread(ServerThread); + + // Wait for server to be ready + for (int i = 0; i < 50 && !server_ready; i++) { + SysSleep(100); + } + + if (!server_ready) { + printf("ERROR: Server failed to start within timeout\n"); + server_ready = false; // Signal server thread to exit + server_thread.join(); + return 1; + } + + // Create client + TS7Client* Client = new TS7Client(); + + // Connect to local server + int res = Client->ConnectTo(TEST_HOST, TEST_RACK, TEST_SLOT); + if (Check(res, "Client Connect")) { + printf(" Connected to : %s (Rack=%d, Slot=%d)\n", TEST_HOST, TEST_RACK, TEST_SLOT); + printf(" PDU Requested : %d bytes\n", Client->PDURequested()); + printf(" PDU Negotiated : %d bytes\n", Client->PDULength()); + ok++; + } else { + ko++; + } + + if (res == 0) { + // Test 1: Read DB1 (should return 0xAA pattern from server callback) + byte buffer[256]; + int size = sizeof(buffer); + res = Client->DBRead(1, 0, size, buffer); + if (Check(res, "DBRead (DB1)")) { + bool pattern_ok = true; + for (int i = 0; i < size; i++) { + if (buffer[i] != 0xAA) { + pattern_ok = false; + break; + } + } + if (pattern_ok) { + printf(" Data pattern verified: 0xAA\n"); + ok++; + } else { + printf(" ERROR: Data pattern mismatch\n"); + ko++; + } + } else { + ko++; + } + + // Test 2: Write and read back + byte write_data[32]; + for (int i = 0; i < 32; i++) write_data[i] = (byte)(i + 1); + + res = Client->DBWrite(1, 0, 32, write_data); + if (Check(res, "DBWrite (DB1)")) { + ok++; + } else { + ko++; + } + + memset(buffer, 0, sizeof(buffer)); + res = Client->DBRead(1, 0, 32, buffer); + if (Check(res, "DBRead after Write (DB1)")) { + bool data_ok = true; + for (int i = 0; i < 32; i++) { + if (buffer[i] != write_data[i]) { + data_ok = false; + break; + } + } + if (data_ok) { + printf(" Write/Read verified\n"); + ok++; + } else { + printf(" ERROR: Write/Read data mismatch\n"); + ko++; + } + } else { + ko++; + } + + // Test 3: Get CPU info + TS7CpuInfo cpu_info; + res = Client->GetCpuInfo(&cpu_info); + if (Check(res, "GetCpuInfo")) { + printf(" Module Type : %s\n", cpu_info.ModuleTypeName); + printf(" Serial Number : %s\n", cpu_info.SerialNumber); + printf(" AS Name : %s\n", cpu_info.ASName); + printf(" Module Name : %s\n", cpu_info.ModuleName); + ok++; + } else { + ko++; + } + + // Test 4: Get Order Code + TS7OrderCode order_code; + res = Client->GetOrderCode(&order_code); + if (Check(res, "GetOrderCode")) { + printf(" Order Code : %s\n", order_code.Code); + printf(" Version : %d.%d.%d\n", order_code.V1, order_code.V2, order_code.V3); + ok++; + } else { + ko++; + } + + // Test 5: Get CP Info + TS7CpInfo cp_info; + res = Client->GetCpInfo(&cp_info); + if (Check(res, "GetCpInfo")) { + printf(" Max PDU Length : %d bytes\n", cp_info.MaxPduLengt); + printf(" Max Connections : %d\n", cp_info.MaxConnections); + printf(" Max MPI Rate : %d bps\n", cp_info.MaxMpiRate); + printf(" Max Bus Rate : %d bps\n", cp_info.MaxBusRate); + ok++; + } else { + ko++; + } + + // Test 6: PLC Status + int status = Client->PlcStatus(); + if (Check(res, "PlcStatus")) { + printf(" CPU Status : "); + switch (status) { + case S7CpuStatusRun: printf("RUN\n"); break; + case S7CpuStatusStop: printf("STOP\n"); break; + default: printf("UNKNOWN\n"); break; + } + ok++; + } else { + ko++; + } + + // Test 7: List Blocks + TS7BlocksList blocks; + res = Client->ListBlocks(&blocks); + if (Check(res, "ListBlocks")) { + printf(" OB Count : %d\n", blocks.OBCount); + printf(" FB Count : %d\n", blocks.FBCount); + printf(" FC Count : %d\n", blocks.FCCount); + printf(" SFB Count : %d\n", blocks.SFBCount); + printf(" SFC Count : %d\n", blocks.SFCCount); + printf(" DB Count : %d\n", blocks.DBCount); + printf(" SDB Count : %d\n", blocks.SDBCount); + ok++; + } else { + ko++; + } + + // Test 8: Read SZL (System Zone List) + byte szl_buffer[1024]; + int szl_size = sizeof(szl_buffer); + PS7SZL szl = (PS7SZL)szl_buffer; + res = Client->ReadSZL(0x0011, 0x0000, szl, &szl_size); + if (Check(res, "ReadSZL (ID:0x0011 IDX:0x0000)")) { + printf(" LENTHDR : %d\n", szl->Header.LENTHDR); + printf(" N_DR : %d\n", szl->Header.N_DR); + ok++; + } else { + ko++; + } + + // Test 9: Connection refusal (connect to non-listening port) + TS7Client* Client2 = new TS7Client(); + res = Client2->ConnectTo(TEST_HOST, TEST_RACK, TEST_SLOT + 10); // Different slot, should fail + if (res != 0) { + printf("\n+-----------------------------------------------------\n"); + printf("| Connection Refusal Test\n"); + printf("+-----------------------------------------------------\n"); + printf("| Result : OK (correctly refused)\n"); + printf("+-----------------------------------------------------\n"); + ok++; + } else { + printf("\n+-----------------------------------------------------\n"); + printf("| Connection Refusal Test\n"); + printf("+-----------------------------------------------------\n"); + printf("| ERROR: Expected connection refusal\n"); + printf("+-----------------------------------------------------\n"); + ko++; + } + delete Client2; + + // Disconnect + Client->Disconnect(); + printf("\n[CLIENT] Disconnected\n"); + } + + // Cleanup + delete Client; + + // Stop server + server_ready = false; + server_thread.join(); + + // Summary + printf("\n=== Test Summary ===\n"); + printf("Performed : %d\n", (ok + ko)); + printf("Passed : %d\n", ok); + printf("Failed : %d\n", ko); + + return (ko == 0) ? 0 : 1; +} \ No newline at end of file diff --git a/examples/cpp/x86_64-linux/makefile b/examples/cpp/x86_64-linux/makefile index 9573e49..6f6b25f 100644 --- a/examples/cpp/x86_64-linux/makefile +++ b/examples/cpp/x86_64-linux/makefile @@ -21,7 +21,8 @@ all: $(CXX) $(CXXFLAGS) -o server ../server.cpp ../$(Wrapper) $(Libs) $(CXX) $(CXXFLAGS) -o srv_resourceless ../srv_resourceless.cpp ../$(Wrapper) $(Libs) $(CXX) $(CXXFLAGS) -o apartner ../apartner.cpp ../$(Wrapper) $(Libs) - $(CXX) $(CXXFLAGS) -o ppartner ../ppartner.cpp ../$(Wrapper) $(Libs) + $(CXX) $(CXXFLAGS) -o ppartner ../ppartner.cpp ../$(Wrapper) $(Libs) + $(CXX) $(CXXFLAGS) -std=c++11 -pthread -o loopback_test ../loopback_test.cpp ../$(Wrapper) $(Libs) clean: $(RM) client @@ -29,3 +30,4 @@ clean: $(RM) srv_resourceless $(RM) apartner $(RM) ppartner + $(RM) loopback_test From c1f1a51748ba32c0a690633acf73d6f2a4345c62 Mon Sep 17 00:00:00 2001 From: REM Date: Wed, 26 Aug 2026 20:21:09 +0330 Subject: [PATCH 5/6] docs: add fork status section with upstream attribution --- README.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/README.md b/README.md index 8803125..c58a9ef 100644 --- a/README.md +++ b/README.md @@ -24,3 +24,25 @@ This repository includes the applicable license texts in the repository root: Please review the applicable license terms before using, modifying, or redistributing this software. + +## Fork Status + +This repository is a community-maintained fork of +[SCADACS/snap7](https://github.com/SCADACS/snap7). + +The original Snap7 project and its contributors retain credit for the +upstream codebase. This fork adds independently maintained documentation, +build, testing, security, and integration improvements. + +See the repository history and license texts for applicable notices. + +## Security + +For security considerations when deploying Snap7 in industrial environments, +see [SECURITY.md](SECURITY.md), [docs/secure-deployment.md](docs/secure-deployment.md), +and [docs/industrial-networking.md](docs/industrial-networking.md). + +## Continuous Integration + +This project includes GitHub Actions workflows for cross-platform builds +and automated smoke tests on Linux and Windows. From 7343dac3137aaa1cda7a98aa151d4e6c440c475c Mon Sep 17 00:00:00 2001 From: REM Date: Wed, 26 Aug 2026 22:08:28 +0330 Subject: [PATCH 6/6] ci: add dedicated Makefile build workflow --- .github/workflows/makefile.yml | 37 ++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 .github/workflows/makefile.yml diff --git a/.github/workflows/makefile.yml b/.github/workflows/makefile.yml new file mode 100644 index 0000000..0518730 --- /dev/null +++ b/.github/workflows/makefile.yml @@ -0,0 +1,37 @@ +name: Build projects with Make + +on: + push: + branches: [master] + pull_request: + branches: [master] + +jobs: + build: + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest] + architecture: [x64] + runs-on: ${{ matrix.os }} + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up build environment (Linux) + if: runner.os == 'Linux' + run: | + sudo apt-get update + sudo apt-get install -y build-essential + + - name: Set up build environment (Windows) + if: runner.os == 'Windows' + uses: msys2/setup-msys2@v2 + with: + msystem: MINGW64 + install: mingw-w64-x86_64-gcc mingw-w64-x86_64-binutils make + + - name: Build + run: | + cd build + make \ No newline at end of file