diff --git a/core/src/drivers/plugins/native/s7comm/CMakeLists.txt b/core/src/drivers/plugins/native/s7comm/CMakeLists.txt new file mode 100644 index 00000000..be15fd78 --- /dev/null +++ b/core/src/drivers/plugins/native/s7comm/CMakeLists.txt @@ -0,0 +1,151 @@ +# CMakeLists.txt for S7Comm Plugin +# Builds a self-contained S7Comm plugin with Snap7 library included + +cmake_minimum_required(VERSION 3.10) +project(s7comm_plugin CXX C) + +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_POSITION_INDEPENDENT_CODE ON) + +# Determine OpenPLC root directory for finding common headers +# When building standalone: calculate from plugin location +# When building from main project: pass -DOPENPLC_ROOT= +if(NOT DEFINED OPENPLC_ROOT) + get_filename_component(OPENPLC_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../" ABSOLUTE) +endif() + +message(STATUS "S7Comm Plugin - OpenPLC root: ${OPENPLC_ROOT}") + +# ============================================================================= +# Snap7 Source Files +# ============================================================================= + +set(SNAP7_CORE_SOURCES + ${CMAKE_CURRENT_SOURCE_DIR}/snap7/core/s7_server.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/snap7/core/s7_isotcp.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/snap7/core/s7_peer.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/snap7/core/s7_text.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/snap7/core/s7_client.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/snap7/core/s7_micro_client.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/snap7/core/s7_partner.cpp +) + +set(SNAP7_SYS_SOURCES + ${CMAKE_CURRENT_SOURCE_DIR}/snap7/sys/snap_msgsock.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/snap7/sys/snap_tcpsrvr.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/snap7/sys/snap_threads.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/snap7/sys/snap_sysutils.cpp +) + +set(SNAP7_LIB_SOURCES + ${CMAKE_CURRENT_SOURCE_DIR}/snap7/lib/snap7_libmain.cpp +) + +# ============================================================================= +# cJSON Library (embedded for JSON configuration parsing) +# ============================================================================= + +set(CJSON_SOURCES + ${CMAKE_CURRENT_SOURCE_DIR}/cjson/cJSON.c +) + +# ============================================================================= +# Plugin Source Files +# ============================================================================= + +set(PLUGIN_SOURCES + ${CMAKE_CURRENT_SOURCE_DIR}/s7comm_plugin.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/s7comm_config.c + ${OPENPLC_ROOT}/core/src/drivers/plugins/native/plugin_logger.c +) + +# ============================================================================= +# Include Directories +# ============================================================================= + +include_directories( + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/snap7/core + ${CMAKE_CURRENT_SOURCE_DIR}/snap7/sys + ${CMAKE_CURRENT_SOURCE_DIR}/snap7/lib + ${CMAKE_CURRENT_SOURCE_DIR}/cjson + ${OPENPLC_ROOT}/core/src/drivers + ${OPENPLC_ROOT}/core/src/drivers/plugins/native + ${OPENPLC_ROOT}/core/src/lib +) + +# ============================================================================= +# Create Shared Library +# ============================================================================= + +add_library(s7comm_plugin SHARED + ${SNAP7_CORE_SOURCES} + ${SNAP7_SYS_SOURCES} + ${SNAP7_LIB_SOURCES} + ${CJSON_SOURCES} + ${PLUGIN_SOURCES} +) + +# ============================================================================= +# Compiler Definitions +# ============================================================================= + +target_compile_definitions(s7comm_plugin PRIVATE + # Snap7 definitions + $<$>:OS_UNIX> +) + +# ============================================================================= +# Compiler Options +# ============================================================================= + +target_compile_options(s7comm_plugin PRIVATE + -fPIC + # Suppress Snap7 warnings (it's third-party code) + -Wno-unused-parameter + -Wno-sign-compare + -Wno-deprecated-declarations + # C++-only warning flags (only apply to C++ files) + $<$,$>:-Wno-class-memaccess> + $<$,$>:-Wno-macro-redefined> +) + +# ============================================================================= +# Link Libraries +# ============================================================================= + +target_link_libraries(s7comm_plugin PRIVATE + pthread + ${CMAKE_DL_LIBS} +) + +# Platform-specific libraries +if(UNIX AND NOT APPLE) + # Linux requires librt for clock functions + target_link_libraries(s7comm_plugin PRIVATE rt) +endif() + +# ============================================================================= +# Output Settings +# ============================================================================= + +set_target_properties(s7comm_plugin PROPERTIES + LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/plugins + PREFIX "lib" + OUTPUT_NAME "s7comm_plugin" +) + +# On Linux, use .so extension +if(UNIX) + set_target_properties(s7comm_plugin PROPERTIES SUFFIX ".so") +endif() + +# ============================================================================= +# Install Target +# ============================================================================= + +install(TARGETS s7comm_plugin + LIBRARY DESTINATION lib/openplc/plugins + RUNTIME DESTINATION lib/openplc/plugins +) diff --git a/core/src/drivers/plugins/native/s7comm/cjson/cJSON.c b/core/src/drivers/plugins/native/s7comm/cjson/cJSON.c new file mode 100644 index 00000000..61483d90 --- /dev/null +++ b/core/src/drivers/plugins/native/s7comm/cjson/cJSON.c @@ -0,0 +1,3143 @@ +/* + Copyright (c) 2009-2017 Dave Gamble and cJSON contributors + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +/* cJSON */ +/* JSON parser in C. */ + +/* disable warnings about old C89 functions in MSVC */ +#if !defined(_CRT_SECURE_NO_DEPRECATE) && defined(_MSC_VER) +#define _CRT_SECURE_NO_DEPRECATE +#endif + +#ifdef __GNUC__ +#pragma GCC visibility push(default) +#endif +#if defined(_MSC_VER) +#pragma warning (push) +/* disable warning about single line comments in system headers */ +#pragma warning (disable : 4001) +#endif + +#include +#include +#include +#include +#include +#include +#include + +#ifdef ENABLE_LOCALES +#include +#endif + +#if defined(_MSC_VER) +#pragma warning (pop) +#endif +#ifdef __GNUC__ +#pragma GCC visibility pop +#endif + +#include "cJSON.h" + +/* define our own boolean type */ +#ifdef true +#undef true +#endif +#define true ((cJSON_bool)1) + +#ifdef false +#undef false +#endif +#define false ((cJSON_bool)0) + +/* define isnan and isinf for ANSI C, if in C99 or above, isnan and isinf has been defined in math.h */ +#ifndef isinf +#define isinf(d) (isnan((d - d)) && !isnan(d)) +#endif +#ifndef isnan +#define isnan(d) (d != d) +#endif + +#ifndef NAN +#ifdef _WIN32 +#define NAN sqrt(-1.0) +#else +#define NAN 0.0/0.0 +#endif +#endif + +typedef struct { + const unsigned char *json; + size_t position; +} error; +static error global_error = { NULL, 0 }; + +CJSON_PUBLIC(const char *) cJSON_GetErrorPtr(void) +{ + return (const char*) (global_error.json + global_error.position); +} + +CJSON_PUBLIC(char *) cJSON_GetStringValue(const cJSON * const item) +{ + if (!cJSON_IsString(item)) + { + return NULL; + } + + return item->valuestring; +} + +CJSON_PUBLIC(double) cJSON_GetNumberValue(const cJSON * const item) +{ + if (!cJSON_IsNumber(item)) + { + return (double) NAN; + } + + return item->valuedouble; +} + +/* This is a safeguard to prevent copy-pasters from using incompatible C and header files */ +#if (CJSON_VERSION_MAJOR != 1) || (CJSON_VERSION_MINOR != 7) || (CJSON_VERSION_PATCH != 18) + #error cJSON.h and cJSON.c have different versions. Make sure that both have the same. +#endif + +CJSON_PUBLIC(const char*) cJSON_Version(void) +{ + static char version[15]; + sprintf(version, "%i.%i.%i", CJSON_VERSION_MAJOR, CJSON_VERSION_MINOR, CJSON_VERSION_PATCH); + + return version; +} + +/* Case insensitive string comparison, doesn't consider two NULL pointers equal though */ +static int case_insensitive_strcmp(const unsigned char *string1, const unsigned char *string2) +{ + if ((string1 == NULL) || (string2 == NULL)) + { + return 1; + } + + if (string1 == string2) + { + return 0; + } + + for(; tolower(*string1) == tolower(*string2); (void)string1++, string2++) + { + if (*string1 == '\0') + { + return 0; + } + } + + return tolower(*string1) - tolower(*string2); +} + +typedef struct internal_hooks +{ + void *(CJSON_CDECL *allocate)(size_t size); + void (CJSON_CDECL *deallocate)(void *pointer); + void *(CJSON_CDECL *reallocate)(void *pointer, size_t size); +} internal_hooks; + +#if defined(_MSC_VER) +/* work around MSVC error C2322: '...' address of dllimport '...' is not static */ +static void * CJSON_CDECL internal_malloc(size_t size) +{ + return malloc(size); +} +static void CJSON_CDECL internal_free(void *pointer) +{ + free(pointer); +} +static void * CJSON_CDECL internal_realloc(void *pointer, size_t size) +{ + return realloc(pointer, size); +} +#else +#define internal_malloc malloc +#define internal_free free +#define internal_realloc realloc +#endif + +/* strlen of character literals resolved at compile time */ +#define static_strlen(string_literal) (sizeof(string_literal) - sizeof("")) + +static internal_hooks global_hooks = { internal_malloc, internal_free, internal_realloc }; + +static unsigned char* cJSON_strdup(const unsigned char* string, const internal_hooks * const hooks) +{ + size_t length = 0; + unsigned char *copy = NULL; + + if (string == NULL) + { + return NULL; + } + + length = strlen((const char*)string) + sizeof(""); + copy = (unsigned char*)hooks->allocate(length); + if (copy == NULL) + { + return NULL; + } + memcpy(copy, string, length); + + return copy; +} + +CJSON_PUBLIC(void) cJSON_InitHooks(cJSON_Hooks* hooks) +{ + if (hooks == NULL) + { + /* Reset hooks */ + global_hooks.allocate = malloc; + global_hooks.deallocate = free; + global_hooks.reallocate = realloc; + return; + } + + global_hooks.allocate = malloc; + if (hooks->malloc_fn != NULL) + { + global_hooks.allocate = hooks->malloc_fn; + } + + global_hooks.deallocate = free; + if (hooks->free_fn != NULL) + { + global_hooks.deallocate = hooks->free_fn; + } + + /* use realloc only if both free and malloc are used */ + global_hooks.reallocate = NULL; + if ((global_hooks.allocate == malloc) && (global_hooks.deallocate == free)) + { + global_hooks.reallocate = realloc; + } +} + +/* Internal constructor. */ +static cJSON *cJSON_New_Item(const internal_hooks * const hooks) +{ + cJSON* node = (cJSON*)hooks->allocate(sizeof(cJSON)); + if (node) + { + memset(node, '\0', sizeof(cJSON)); + } + + return node; +} + +/* Delete a cJSON structure. */ +CJSON_PUBLIC(void) cJSON_Delete(cJSON *item) +{ + cJSON *next = NULL; + while (item != NULL) + { + next = item->next; + if (!(item->type & cJSON_IsReference) && (item->child != NULL)) + { + cJSON_Delete(item->child); + } + if (!(item->type & cJSON_IsReference) && (item->valuestring != NULL)) + { + global_hooks.deallocate(item->valuestring); + item->valuestring = NULL; + } + if (!(item->type & cJSON_StringIsConst) && (item->string != NULL)) + { + global_hooks.deallocate(item->string); + item->string = NULL; + } + global_hooks.deallocate(item); + item = next; + } +} + +/* get the decimal point character of the current locale */ +static unsigned char get_decimal_point(void) +{ +#ifdef ENABLE_LOCALES + struct lconv *lconv = localeconv(); + return (unsigned char) lconv->decimal_point[0]; +#else + return '.'; +#endif +} + +typedef struct +{ + const unsigned char *content; + size_t length; + size_t offset; + size_t depth; /* How deeply nested (in arrays/objects) is the input at the current offset. */ + internal_hooks hooks; +} parse_buffer; + +/* check if the given size is left to read in a given parse buffer (starting with 1) */ +#define can_read(buffer, size) ((buffer != NULL) && (((buffer)->offset + size) <= (buffer)->length)) +/* check if the buffer can be accessed at the given index (starting with 0) */ +#define can_access_at_index(buffer, index) ((buffer != NULL) && (((buffer)->offset + index) < (buffer)->length)) +#define cannot_access_at_index(buffer, index) (!can_access_at_index(buffer, index)) +/* get a pointer to the buffer at the position */ +#define buffer_at_offset(buffer) ((buffer)->content + (buffer)->offset) + +/* Parse the input text to generate a number, and populate the result into item. */ +static cJSON_bool parse_number(cJSON * const item, parse_buffer * const input_buffer) +{ + double number = 0; + unsigned char *after_end = NULL; + unsigned char number_c_string[64]; + unsigned char decimal_point = get_decimal_point(); + size_t i = 0; + + if ((input_buffer == NULL) || (input_buffer->content == NULL)) + { + return false; + } + + /* copy the number into a temporary buffer and replace '.' with the decimal point + * of the current locale (for strtod) + * This also takes care of '\0' not necessarily being available for marking the end of the input */ + for (i = 0; (i < (sizeof(number_c_string) - 1)) && can_access_at_index(input_buffer, i); i++) + { + switch (buffer_at_offset(input_buffer)[i]) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '+': + case '-': + case 'e': + case 'E': + number_c_string[i] = buffer_at_offset(input_buffer)[i]; + break; + + case '.': + number_c_string[i] = decimal_point; + break; + + default: + goto loop_end; + } + } +loop_end: + number_c_string[i] = '\0'; + + number = strtod((const char*)number_c_string, (char**)&after_end); + if (number_c_string == after_end) + { + return false; /* parse_error */ + } + + item->valuedouble = number; + + /* use saturation in case of overflow */ + if (number >= INT_MAX) + { + item->valueint = INT_MAX; + } + else if (number <= (double)INT_MIN) + { + item->valueint = INT_MIN; + } + else + { + item->valueint = (int)number; + } + + item->type = cJSON_Number; + + input_buffer->offset += (size_t)(after_end - number_c_string); + return true; +} + +/* don't ask me, but the original cJSON_SetNumberValue returns an integer or double */ +CJSON_PUBLIC(double) cJSON_SetNumberHelper(cJSON *object, double number) +{ + if (number >= INT_MAX) + { + object->valueint = INT_MAX; + } + else if (number <= (double)INT_MIN) + { + object->valueint = INT_MIN; + } + else + { + object->valueint = (int)number; + } + + return object->valuedouble = number; +} + +/* Note: when passing a NULL valuestring, cJSON_SetValuestring treats this as an error and return NULL */ +CJSON_PUBLIC(char*) cJSON_SetValuestring(cJSON *object, const char *valuestring) +{ + char *copy = NULL; + /* if object's type is not cJSON_String or is cJSON_IsReference, it should not set valuestring */ + if ((object == NULL) || !(object->type & cJSON_String) || (object->type & cJSON_IsReference)) + { + return NULL; + } + /* return NULL if the object is corrupted or valuestring is NULL */ + if (object->valuestring == NULL || valuestring == NULL) + { + return NULL; + } + if (strlen(valuestring) <= strlen(object->valuestring)) + { + strcpy(object->valuestring, valuestring); + return object->valuestring; + } + copy = (char*) cJSON_strdup((const unsigned char*)valuestring, &global_hooks); + if (copy == NULL) + { + return NULL; + } + if (object->valuestring != NULL) + { + cJSON_free(object->valuestring); + } + object->valuestring = copy; + + return copy; +} + +typedef struct +{ + unsigned char *buffer; + size_t length; + size_t offset; + size_t depth; /* current nesting depth (for formatted printing) */ + cJSON_bool noalloc; + cJSON_bool format; /* is this print a formatted print */ + internal_hooks hooks; +} printbuffer; + +/* realloc printbuffer if necessary to have at least "needed" bytes more */ +static unsigned char* ensure(printbuffer * const p, size_t needed) +{ + unsigned char *newbuffer = NULL; + size_t newsize = 0; + + if ((p == NULL) || (p->buffer == NULL)) + { + return NULL; + } + + if ((p->length > 0) && (p->offset >= p->length)) + { + /* make sure that offset is valid */ + return NULL; + } + + if (needed > INT_MAX) + { + /* sizes bigger than INT_MAX are currently not supported */ + return NULL; + } + + needed += p->offset + 1; + if (needed <= p->length) + { + return p->buffer + p->offset; + } + + if (p->noalloc) { + return NULL; + } + + /* calculate new buffer size */ + if (needed > (INT_MAX / 2)) + { + /* overflow of int, use INT_MAX if possible */ + if (needed <= INT_MAX) + { + newsize = INT_MAX; + } + else + { + return NULL; + } + } + else + { + newsize = needed * 2; + } + + if (p->hooks.reallocate != NULL) + { + /* reallocate with realloc if available */ + newbuffer = (unsigned char*)p->hooks.reallocate(p->buffer, newsize); + if (newbuffer == NULL) + { + p->hooks.deallocate(p->buffer); + p->length = 0; + p->buffer = NULL; + + return NULL; + } + } + else + { + /* otherwise reallocate manually */ + newbuffer = (unsigned char*)p->hooks.allocate(newsize); + if (!newbuffer) + { + p->hooks.deallocate(p->buffer); + p->length = 0; + p->buffer = NULL; + + return NULL; + } + + memcpy(newbuffer, p->buffer, p->offset + 1); + p->hooks.deallocate(p->buffer); + } + p->length = newsize; + p->buffer = newbuffer; + + return newbuffer + p->offset; +} + +/* calculate the new length of the string in a printbuffer and update the offset */ +static void update_offset(printbuffer * const buffer) +{ + const unsigned char *buffer_pointer = NULL; + if ((buffer == NULL) || (buffer->buffer == NULL)) + { + return; + } + buffer_pointer = buffer->buffer + buffer->offset; + + buffer->offset += strlen((const char*)buffer_pointer); +} + +/* securely comparison of floating-point variables */ +static cJSON_bool compare_double(double a, double b) +{ + double maxVal = fabs(a) > fabs(b) ? fabs(a) : fabs(b); + return (fabs(a - b) <= maxVal * DBL_EPSILON); +} + +/* Render the number nicely from the given item into a string. */ +static cJSON_bool print_number(const cJSON * const item, printbuffer * const output_buffer) +{ + unsigned char *output_pointer = NULL; + double d = item->valuedouble; + int length = 0; + size_t i = 0; + unsigned char number_buffer[26] = {0}; /* temporary buffer to print the number into */ + unsigned char decimal_point = get_decimal_point(); + double test = 0.0; + + if (output_buffer == NULL) + { + return false; + } + + /* This checks for NaN and Infinity */ + if (isnan(d) || isinf(d)) + { + length = sprintf((char*)number_buffer, "null"); + } + else if(d == (double)item->valueint) + { + length = sprintf((char*)number_buffer, "%d", item->valueint); + } + else + { + /* Try 15 decimal places of precision to avoid nonsignificant nonzero digits */ + length = sprintf((char*)number_buffer, "%1.15g", d); + + /* Check whether the original double can be recovered */ + if ((sscanf((char*)number_buffer, "%lg", &test) != 1) || !compare_double((double)test, d)) + { + /* If not, print with 17 decimal places of precision */ + length = sprintf((char*)number_buffer, "%1.17g", d); + } + } + + /* sprintf failed or buffer overrun occurred */ + if ((length < 0) || (length > (int)(sizeof(number_buffer) - 1))) + { + return false; + } + + /* reserve appropriate space in the output */ + output_pointer = ensure(output_buffer, (size_t)length + sizeof("")); + if (output_pointer == NULL) + { + return false; + } + + /* copy the printed number to the output and replace locale + * dependent decimal point with '.' */ + for (i = 0; i < ((size_t)length); i++) + { + if (number_buffer[i] == decimal_point) + { + output_pointer[i] = '.'; + continue; + } + + output_pointer[i] = number_buffer[i]; + } + output_pointer[i] = '\0'; + + output_buffer->offset += (size_t)length; + + return true; +} + +/* parse 4 digit hexadecimal number */ +static unsigned parse_hex4(const unsigned char * const input) +{ + unsigned int h = 0; + size_t i = 0; + + for (i = 0; i < 4; i++) + { + /* parse digit */ + if ((input[i] >= '0') && (input[i] <= '9')) + { + h += (unsigned int) input[i] - '0'; + } + else if ((input[i] >= 'A') && (input[i] <= 'F')) + { + h += (unsigned int) 10 + input[i] - 'A'; + } + else if ((input[i] >= 'a') && (input[i] <= 'f')) + { + h += (unsigned int) 10 + input[i] - 'a'; + } + else /* invalid */ + { + return 0; + } + + if (i < 3) + { + /* shift left to make place for the next nibble */ + h = h << 4; + } + } + + return h; +} + +/* converts a UTF-16 literal to UTF-8 + * A literal can be one or two sequences of the form \uXXXX */ +static unsigned char utf16_literal_to_utf8(const unsigned char * const input_pointer, const unsigned char * const input_end, unsigned char **output_pointer) +{ + long unsigned int codepoint = 0; + unsigned int first_code = 0; + const unsigned char *first_sequence = input_pointer; + unsigned char utf8_length = 0; + unsigned char utf8_position = 0; + unsigned char sequence_length = 0; + unsigned char first_byte_mark = 0; + + if ((input_end - first_sequence) < 6) + { + /* input ends unexpectedly */ + goto fail; + } + + /* get the first utf16 sequence */ + first_code = parse_hex4(first_sequence + 2); + + /* check that the code is valid */ + if (((first_code >= 0xDC00) && (first_code <= 0xDFFF))) + { + goto fail; + } + + /* UTF16 surrogate pair */ + if ((first_code >= 0xD800) && (first_code <= 0xDBFF)) + { + const unsigned char *second_sequence = first_sequence + 6; + unsigned int second_code = 0; + sequence_length = 12; /* \uXXXX\uXXXX */ + + if ((input_end - second_sequence) < 6) + { + /* input ends unexpectedly */ + goto fail; + } + + if ((second_sequence[0] != '\\') || (second_sequence[1] != 'u')) + { + /* missing second half of the surrogate pair */ + goto fail; + } + + /* get the second utf16 sequence */ + second_code = parse_hex4(second_sequence + 2); + /* check that the code is valid */ + if ((second_code < 0xDC00) || (second_code > 0xDFFF)) + { + /* invalid second half of the surrogate pair */ + goto fail; + } + + + /* calculate the unicode codepoint from the surrogate pair */ + codepoint = 0x10000 + (((first_code & 0x3FF) << 10) | (second_code & 0x3FF)); + } + else + { + sequence_length = 6; /* \uXXXX */ + codepoint = first_code; + } + + /* encode as UTF-8 + * takes at maximum 4 bytes to encode: + * 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx */ + if (codepoint < 0x80) + { + /* normal ascii, encoding 0xxxxxxx */ + utf8_length = 1; + } + else if (codepoint < 0x800) + { + /* two bytes, encoding 110xxxxx 10xxxxxx */ + utf8_length = 2; + first_byte_mark = 0xC0; /* 11000000 */ + } + else if (codepoint < 0x10000) + { + /* three bytes, encoding 1110xxxx 10xxxxxx 10xxxxxx */ + utf8_length = 3; + first_byte_mark = 0xE0; /* 11100000 */ + } + else if (codepoint <= 0x10FFFF) + { + /* four bytes, encoding 1110xxxx 10xxxxxx 10xxxxxx 10xxxxxx */ + utf8_length = 4; + first_byte_mark = 0xF0; /* 11110000 */ + } + else + { + /* invalid unicode codepoint */ + goto fail; + } + + /* encode as utf8 */ + for (utf8_position = (unsigned char)(utf8_length - 1); utf8_position > 0; utf8_position--) + { + /* 10xxxxxx */ + (*output_pointer)[utf8_position] = (unsigned char)((codepoint | 0x80) & 0xBF); + codepoint >>= 6; + } + /* encode first byte */ + if (utf8_length > 1) + { + (*output_pointer)[0] = (unsigned char)((codepoint | first_byte_mark) & 0xFF); + } + else + { + (*output_pointer)[0] = (unsigned char)(codepoint & 0x7F); + } + + *output_pointer += utf8_length; + + return sequence_length; + +fail: + return 0; +} + +/* Parse the input text into an unescaped cinput, and populate item. */ +static cJSON_bool parse_string(cJSON * const item, parse_buffer * const input_buffer) +{ + const unsigned char *input_pointer = buffer_at_offset(input_buffer) + 1; + const unsigned char *input_end = buffer_at_offset(input_buffer) + 1; + unsigned char *output_pointer = NULL; + unsigned char *output = NULL; + + /* not a string */ + if (buffer_at_offset(input_buffer)[0] != '\"') + { + goto fail; + } + + { + /* calculate approximate size of the output (overestimate) */ + size_t allocation_length = 0; + size_t skipped_bytes = 0; + while (((size_t)(input_end - input_buffer->content) < input_buffer->length) && (*input_end != '\"')) + { + /* is escape sequence */ + if (input_end[0] == '\\') + { + if ((size_t)(input_end + 1 - input_buffer->content) >= input_buffer->length) + { + /* prevent buffer overflow when last input character is a backslash */ + goto fail; + } + skipped_bytes++; + input_end++; + } + input_end++; + } + if (((size_t)(input_end - input_buffer->content) >= input_buffer->length) || (*input_end != '\"')) + { + goto fail; /* string ended unexpectedly */ + } + + /* This is at most how much we need for the output */ + allocation_length = (size_t) (input_end - buffer_at_offset(input_buffer)) - skipped_bytes; + output = (unsigned char*)input_buffer->hooks.allocate(allocation_length + sizeof("")); + if (output == NULL) + { + goto fail; /* allocation failure */ + } + } + + output_pointer = output; + /* loop through the string literal */ + while (input_pointer < input_end) + { + if (*input_pointer != '\\') + { + *output_pointer++ = *input_pointer++; + } + /* escape sequence */ + else + { + unsigned char sequence_length = 2; + if ((input_end - input_pointer) < 1) + { + goto fail; + } + + switch (input_pointer[1]) + { + case 'b': + *output_pointer++ = '\b'; + break; + case 'f': + *output_pointer++ = '\f'; + break; + case 'n': + *output_pointer++ = '\n'; + break; + case 'r': + *output_pointer++ = '\r'; + break; + case 't': + *output_pointer++ = '\t'; + break; + case '\"': + case '\\': + case '/': + *output_pointer++ = input_pointer[1]; + break; + + /* UTF-16 literal */ + case 'u': + sequence_length = utf16_literal_to_utf8(input_pointer, input_end, &output_pointer); + if (sequence_length == 0) + { + /* failed to convert UTF16-literal to UTF-8 */ + goto fail; + } + break; + + default: + goto fail; + } + input_pointer += sequence_length; + } + } + + /* zero terminate the output */ + *output_pointer = '\0'; + + item->type = cJSON_String; + item->valuestring = (char*)output; + + input_buffer->offset = (size_t) (input_end - input_buffer->content); + input_buffer->offset++; + + return true; + +fail: + if (output != NULL) + { + input_buffer->hooks.deallocate(output); + output = NULL; + } + + if (input_pointer != NULL) + { + input_buffer->offset = (size_t)(input_pointer - input_buffer->content); + } + + return false; +} + +/* Render the cstring provided to an escaped version that can be printed. */ +static cJSON_bool print_string_ptr(const unsigned char * const input, printbuffer * const output_buffer) +{ + const unsigned char *input_pointer = NULL; + unsigned char *output = NULL; + unsigned char *output_pointer = NULL; + size_t output_length = 0; + /* numbers of additional characters needed for escaping */ + size_t escape_characters = 0; + + if (output_buffer == NULL) + { + return false; + } + + /* empty string */ + if (input == NULL) + { + output = ensure(output_buffer, sizeof("\"\"")); + if (output == NULL) + { + return false; + } + strcpy((char*)output, "\"\""); + + return true; + } + + /* set "flag" to 1 if something needs to be escaped */ + for (input_pointer = input; *input_pointer; input_pointer++) + { + switch (*input_pointer) + { + case '\"': + case '\\': + case '\b': + case '\f': + case '\n': + case '\r': + case '\t': + /* one character escape sequence */ + escape_characters++; + break; + default: + if (*input_pointer < 32) + { + /* UTF-16 escape sequence uXXXX */ + escape_characters += 5; + } + break; + } + } + output_length = (size_t)(input_pointer - input) + escape_characters; + + output = ensure(output_buffer, output_length + sizeof("\"\"")); + if (output == NULL) + { + return false; + } + + /* no characters have to be escaped */ + if (escape_characters == 0) + { + output[0] = '\"'; + memcpy(output + 1, input, output_length); + output[output_length + 1] = '\"'; + output[output_length + 2] = '\0'; + + return true; + } + + output[0] = '\"'; + output_pointer = output + 1; + /* copy the string */ + for (input_pointer = input; *input_pointer != '\0'; (void)input_pointer++, output_pointer++) + { + if ((*input_pointer > 31) && (*input_pointer != '\"') && (*input_pointer != '\\')) + { + /* normal character, copy */ + *output_pointer = *input_pointer; + } + else + { + /* character needs to be escaped */ + *output_pointer++ = '\\'; + switch (*input_pointer) + { + case '\\': + *output_pointer = '\\'; + break; + case '\"': + *output_pointer = '\"'; + break; + case '\b': + *output_pointer = 'b'; + break; + case '\f': + *output_pointer = 'f'; + break; + case '\n': + *output_pointer = 'n'; + break; + case '\r': + *output_pointer = 'r'; + break; + case '\t': + *output_pointer = 't'; + break; + default: + /* escape and print as unicode codepoint */ + sprintf((char*)output_pointer, "u%04x", *input_pointer); + output_pointer += 4; + break; + } + } + } + output[output_length + 1] = '\"'; + output[output_length + 2] = '\0'; + + return true; +} + +/* Invoke print_string_ptr (which is useful) on an item. */ +static cJSON_bool print_string(const cJSON * const item, printbuffer * const p) +{ + return print_string_ptr((unsigned char*)item->valuestring, p); +} + +/* Predeclare these prototypes. */ +static cJSON_bool parse_value(cJSON * const item, parse_buffer * const input_buffer); +static cJSON_bool print_value(const cJSON * const item, printbuffer * const output_buffer); +static cJSON_bool parse_array(cJSON * const item, parse_buffer * const input_buffer); +static cJSON_bool print_array(const cJSON * const item, printbuffer * const output_buffer); +static cJSON_bool parse_object(cJSON * const item, parse_buffer * const input_buffer); +static cJSON_bool print_object(const cJSON * const item, printbuffer * const output_buffer); + +/* Utility to jump whitespace and cr/lf */ +static parse_buffer *buffer_skip_whitespace(parse_buffer * const buffer) +{ + if ((buffer == NULL) || (buffer->content == NULL)) + { + return NULL; + } + + if (cannot_access_at_index(buffer, 0)) + { + return buffer; + } + + while (can_access_at_index(buffer, 0) && (buffer_at_offset(buffer)[0] <= 32)) + { + buffer->offset++; + } + + if (buffer->offset == buffer->length) + { + buffer->offset--; + } + + return buffer; +} + +/* skip the UTF-8 BOM (byte order mark) if it is at the beginning of a buffer */ +static parse_buffer *skip_utf8_bom(parse_buffer * const buffer) +{ + if ((buffer == NULL) || (buffer->content == NULL) || (buffer->offset != 0)) + { + return NULL; + } + + if (can_access_at_index(buffer, 4) && (strncmp((const char*)buffer_at_offset(buffer), "\xEF\xBB\xBF", 3) == 0)) + { + buffer->offset += 3; + } + + return buffer; +} + +CJSON_PUBLIC(cJSON *) cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cJSON_bool require_null_terminated) +{ + size_t buffer_length; + + if (NULL == value) + { + return NULL; + } + + /* Adding null character size due to require_null_terminated. */ + buffer_length = strlen(value) + sizeof(""); + + return cJSON_ParseWithLengthOpts(value, buffer_length, return_parse_end, require_null_terminated); +} + +/* Parse an object - create a new root, and populate. */ +CJSON_PUBLIC(cJSON *) cJSON_ParseWithLengthOpts(const char *value, size_t buffer_length, const char **return_parse_end, cJSON_bool require_null_terminated) +{ + parse_buffer buffer = { 0, 0, 0, 0, { 0, 0, 0 } }; + cJSON *item = NULL; + + /* reset error position */ + global_error.json = NULL; + global_error.position = 0; + + if (value == NULL || 0 == buffer_length) + { + goto fail; + } + + buffer.content = (const unsigned char*)value; + buffer.length = buffer_length; + buffer.offset = 0; + buffer.hooks = global_hooks; + + item = cJSON_New_Item(&global_hooks); + if (item == NULL) /* memory fail */ + { + goto fail; + } + + if (!parse_value(item, buffer_skip_whitespace(skip_utf8_bom(&buffer)))) + { + /* parse failure. ep is set. */ + goto fail; + } + + /* if we require null-terminated JSON without appended garbage, skip and then check for a null terminator */ + if (require_null_terminated) + { + buffer_skip_whitespace(&buffer); + if ((buffer.offset >= buffer.length) || buffer_at_offset(&buffer)[0] != '\0') + { + goto fail; + } + } + if (return_parse_end) + { + *return_parse_end = (const char*)buffer_at_offset(&buffer); + } + + return item; + +fail: + if (item != NULL) + { + cJSON_Delete(item); + } + + if (value != NULL) + { + error local_error; + local_error.json = (const unsigned char*)value; + local_error.position = 0; + + if (buffer.offset < buffer.length) + { + local_error.position = buffer.offset; + } + else if (buffer.length > 0) + { + local_error.position = buffer.length - 1; + } + + if (return_parse_end != NULL) + { + *return_parse_end = (const char*)local_error.json + local_error.position; + } + + global_error = local_error; + } + + return NULL; +} + +/* Default options for cJSON_Parse */ +CJSON_PUBLIC(cJSON *) cJSON_Parse(const char *value) +{ + return cJSON_ParseWithOpts(value, 0, 0); +} + +CJSON_PUBLIC(cJSON *) cJSON_ParseWithLength(const char *value, size_t buffer_length) +{ + return cJSON_ParseWithLengthOpts(value, buffer_length, 0, 0); +} + +#define cjson_min(a, b) (((a) < (b)) ? (a) : (b)) + +static unsigned char *print(const cJSON * const item, cJSON_bool format, const internal_hooks * const hooks) +{ + static const size_t default_buffer_size = 256; + printbuffer buffer[1]; + unsigned char *printed = NULL; + + memset(buffer, 0, sizeof(buffer)); + + /* create buffer */ + buffer->buffer = (unsigned char*) hooks->allocate(default_buffer_size); + buffer->length = default_buffer_size; + buffer->format = format; + buffer->hooks = *hooks; + if (buffer->buffer == NULL) + { + goto fail; + } + + /* print the value */ + if (!print_value(item, buffer)) + { + goto fail; + } + update_offset(buffer); + + /* check if reallocate is available */ + if (hooks->reallocate != NULL) + { + printed = (unsigned char*) hooks->reallocate(buffer->buffer, buffer->offset + 1); + if (printed == NULL) { + goto fail; + } + buffer->buffer = NULL; + } + else /* otherwise copy the JSON over to a new buffer */ + { + printed = (unsigned char*) hooks->allocate(buffer->offset + 1); + if (printed == NULL) + { + goto fail; + } + memcpy(printed, buffer->buffer, cjson_min(buffer->length, buffer->offset + 1)); + printed[buffer->offset] = '\0'; /* just to be sure */ + + /* free the buffer */ + hooks->deallocate(buffer->buffer); + buffer->buffer = NULL; + } + + return printed; + +fail: + if (buffer->buffer != NULL) + { + hooks->deallocate(buffer->buffer); + buffer->buffer = NULL; + } + + if (printed != NULL) + { + hooks->deallocate(printed); + printed = NULL; + } + + return NULL; +} + +/* Render a cJSON item/entity/structure to text. */ +CJSON_PUBLIC(char *) cJSON_Print(const cJSON *item) +{ + return (char*)print(item, true, &global_hooks); +} + +CJSON_PUBLIC(char *) cJSON_PrintUnformatted(const cJSON *item) +{ + return (char*)print(item, false, &global_hooks); +} + +CJSON_PUBLIC(char *) cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON_bool fmt) +{ + printbuffer p = { 0, 0, 0, 0, 0, 0, { 0, 0, 0 } }; + + if (prebuffer < 0) + { + return NULL; + } + + p.buffer = (unsigned char*)global_hooks.allocate((size_t)prebuffer); + if (!p.buffer) + { + return NULL; + } + + p.length = (size_t)prebuffer; + p.offset = 0; + p.noalloc = false; + p.format = fmt; + p.hooks = global_hooks; + + if (!print_value(item, &p)) + { + global_hooks.deallocate(p.buffer); + p.buffer = NULL; + return NULL; + } + + return (char*)p.buffer; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_PrintPreallocated(cJSON *item, char *buffer, const int length, const cJSON_bool format) +{ + printbuffer p = { 0, 0, 0, 0, 0, 0, { 0, 0, 0 } }; + + if ((length < 0) || (buffer == NULL)) + { + return false; + } + + p.buffer = (unsigned char*)buffer; + p.length = (size_t)length; + p.offset = 0; + p.noalloc = true; + p.format = format; + p.hooks = global_hooks; + + return print_value(item, &p); +} + +/* Parser core - when encountering text, process appropriately. */ +static cJSON_bool parse_value(cJSON * const item, parse_buffer * const input_buffer) +{ + if ((input_buffer == NULL) || (input_buffer->content == NULL)) + { + return false; /* no input */ + } + + /* parse the different types of values */ + /* null */ + if (can_read(input_buffer, 4) && (strncmp((const char*)buffer_at_offset(input_buffer), "null", 4) == 0)) + { + item->type = cJSON_NULL; + input_buffer->offset += 4; + return true; + } + /* false */ + if (can_read(input_buffer, 5) && (strncmp((const char*)buffer_at_offset(input_buffer), "false", 5) == 0)) + { + item->type = cJSON_False; + input_buffer->offset += 5; + return true; + } + /* true */ + if (can_read(input_buffer, 4) && (strncmp((const char*)buffer_at_offset(input_buffer), "true", 4) == 0)) + { + item->type = cJSON_True; + item->valueint = 1; + input_buffer->offset += 4; + return true; + } + /* string */ + if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '\"')) + { + return parse_string(item, input_buffer); + } + /* number */ + if (can_access_at_index(input_buffer, 0) && ((buffer_at_offset(input_buffer)[0] == '-') || ((buffer_at_offset(input_buffer)[0] >= '0') && (buffer_at_offset(input_buffer)[0] <= '9')))) + { + return parse_number(item, input_buffer); + } + /* array */ + if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '[')) + { + return parse_array(item, input_buffer); + } + /* object */ + if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '{')) + { + return parse_object(item, input_buffer); + } + + return false; +} + +/* Render a value to text. */ +static cJSON_bool print_value(const cJSON * const item, printbuffer * const output_buffer) +{ + unsigned char *output = NULL; + + if ((item == NULL) || (output_buffer == NULL)) + { + return false; + } + + switch ((item->type) & 0xFF) + { + case cJSON_NULL: + output = ensure(output_buffer, 5); + if (output == NULL) + { + return false; + } + strcpy((char*)output, "null"); + return true; + + case cJSON_False: + output = ensure(output_buffer, 6); + if (output == NULL) + { + return false; + } + strcpy((char*)output, "false"); + return true; + + case cJSON_True: + output = ensure(output_buffer, 5); + if (output == NULL) + { + return false; + } + strcpy((char*)output, "true"); + return true; + + case cJSON_Number: + return print_number(item, output_buffer); + + case cJSON_Raw: + { + size_t raw_length = 0; + if (item->valuestring == NULL) + { + return false; + } + + raw_length = strlen(item->valuestring) + sizeof(""); + output = ensure(output_buffer, raw_length); + if (output == NULL) + { + return false; + } + memcpy(output, item->valuestring, raw_length); + return true; + } + + case cJSON_String: + return print_string(item, output_buffer); + + case cJSON_Array: + return print_array(item, output_buffer); + + case cJSON_Object: + return print_object(item, output_buffer); + + default: + return false; + } +} + +/* Build an array from input text. */ +static cJSON_bool parse_array(cJSON * const item, parse_buffer * const input_buffer) +{ + cJSON *head = NULL; /* head of the linked list */ + cJSON *current_item = NULL; + + if (input_buffer->depth >= CJSON_NESTING_LIMIT) + { + return false; /* to deeply nested */ + } + input_buffer->depth++; + + if (buffer_at_offset(input_buffer)[0] != '[') + { + /* not an array */ + goto fail; + } + + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == ']')) + { + /* empty array */ + goto success; + } + + /* check if we skipped to the end of the buffer */ + if (cannot_access_at_index(input_buffer, 0)) + { + input_buffer->offset--; + goto fail; + } + + /* step back to character in front of the first element */ + input_buffer->offset--; + /* loop through the comma separated array elements */ + do + { + /* allocate next item */ + cJSON *new_item = cJSON_New_Item(&(input_buffer->hooks)); + if (new_item == NULL) + { + goto fail; /* allocation failure */ + } + + /* attach next item to list */ + if (head == NULL) + { + /* start the linked list */ + current_item = head = new_item; + } + else + { + /* add to the end and advance */ + current_item->next = new_item; + new_item->prev = current_item; + current_item = new_item; + } + + /* parse next value */ + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if (!parse_value(current_item, input_buffer)) + { + goto fail; /* failed to parse value */ + } + buffer_skip_whitespace(input_buffer); + } + while (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == ',')); + + if (cannot_access_at_index(input_buffer, 0) || buffer_at_offset(input_buffer)[0] != ']') + { + goto fail; /* expected end of array */ + } + +success: + input_buffer->depth--; + + if (head != NULL) { + head->prev = current_item; + } + + item->type = cJSON_Array; + item->child = head; + + input_buffer->offset++; + + return true; + +fail: + if (head != NULL) + { + cJSON_Delete(head); + } + + return false; +} + +/* Render an array to text */ +static cJSON_bool print_array(const cJSON * const item, printbuffer * const output_buffer) +{ + unsigned char *output_pointer = NULL; + size_t length = 0; + cJSON *current_element = item->child; + + if (output_buffer == NULL) + { + return false; + } + + /* Compose the output array. */ + /* opening square bracket */ + output_pointer = ensure(output_buffer, 1); + if (output_pointer == NULL) + { + return false; + } + + *output_pointer = '['; + output_buffer->offset++; + output_buffer->depth++; + + while (current_element != NULL) + { + if (!print_value(current_element, output_buffer)) + { + return false; + } + update_offset(output_buffer); + if (current_element->next) + { + length = (size_t) (output_buffer->format ? 2 : 1); + output_pointer = ensure(output_buffer, length + 1); + if (output_pointer == NULL) + { + return false; + } + *output_pointer++ = ','; + if(output_buffer->format) + { + *output_pointer++ = ' '; + } + *output_pointer = '\0'; + output_buffer->offset += length; + } + current_element = current_element->next; + } + + output_pointer = ensure(output_buffer, 2); + if (output_pointer == NULL) + { + return false; + } + *output_pointer++ = ']'; + *output_pointer = '\0'; + output_buffer->depth--; + + return true; +} + +/* Build an object from the text. */ +static cJSON_bool parse_object(cJSON * const item, parse_buffer * const input_buffer) +{ + cJSON *head = NULL; /* linked list head */ + cJSON *current_item = NULL; + + if (input_buffer->depth >= CJSON_NESTING_LIMIT) + { + return false; /* to deeply nested */ + } + input_buffer->depth++; + + if (cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != '{')) + { + goto fail; /* not an object */ + } + + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '}')) + { + goto success; /* empty object */ + } + + /* check if we skipped to the end of the buffer */ + if (cannot_access_at_index(input_buffer, 0)) + { + input_buffer->offset--; + goto fail; + } + + /* step back to character in front of the first element */ + input_buffer->offset--; + /* loop through the comma separated array elements */ + do + { + /* allocate next item */ + cJSON *new_item = cJSON_New_Item(&(input_buffer->hooks)); + if (new_item == NULL) + { + goto fail; /* allocation failure */ + } + + /* attach next item to list */ + if (head == NULL) + { + /* start the linked list */ + current_item = head = new_item; + } + else + { + /* add to the end and advance */ + current_item->next = new_item; + new_item->prev = current_item; + current_item = new_item; + } + + if (cannot_access_at_index(input_buffer, 1)) + { + goto fail; /* nothing comes after the comma */ + } + + /* parse the name of the child */ + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if (!parse_string(current_item, input_buffer)) + { + goto fail; /* failed to parse name */ + } + buffer_skip_whitespace(input_buffer); + + /* swap valuestring and string, because we parsed the name */ + current_item->string = current_item->valuestring; + current_item->valuestring = NULL; + + if (cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != ':')) + { + goto fail; /* invalid object */ + } + + /* parse the value */ + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if (!parse_value(current_item, input_buffer)) + { + goto fail; /* failed to parse value */ + } + buffer_skip_whitespace(input_buffer); + } + while (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == ',')); + + if (cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != '}')) + { + goto fail; /* expected end of object */ + } + +success: + input_buffer->depth--; + + if (head != NULL) { + head->prev = current_item; + } + + item->type = cJSON_Object; + item->child = head; + + input_buffer->offset++; + return true; + +fail: + if (head != NULL) + { + cJSON_Delete(head); + } + + return false; +} + +/* Render an object to text. */ +static cJSON_bool print_object(const cJSON * const item, printbuffer * const output_buffer) +{ + unsigned char *output_pointer = NULL; + size_t length = 0; + cJSON *current_item = item->child; + + if (output_buffer == NULL) + { + return false; + } + + /* Compose the output: */ + length = (size_t) (output_buffer->format ? 2 : 1); /* fmt: {\n */ + output_pointer = ensure(output_buffer, length + 1); + if (output_pointer == NULL) + { + return false; + } + + *output_pointer++ = '{'; + output_buffer->depth++; + if (output_buffer->format) + { + *output_pointer++ = '\n'; + } + output_buffer->offset += length; + + while (current_item) + { + if (output_buffer->format) + { + size_t i; + output_pointer = ensure(output_buffer, output_buffer->depth); + if (output_pointer == NULL) + { + return false; + } + for (i = 0; i < output_buffer->depth; i++) + { + *output_pointer++ = '\t'; + } + output_buffer->offset += output_buffer->depth; + } + + /* print key */ + if (!print_string_ptr((unsigned char*)current_item->string, output_buffer)) + { + return false; + } + update_offset(output_buffer); + + length = (size_t) (output_buffer->format ? 2 : 1); + output_pointer = ensure(output_buffer, length); + if (output_pointer == NULL) + { + return false; + } + *output_pointer++ = ':'; + if (output_buffer->format) + { + *output_pointer++ = '\t'; + } + output_buffer->offset += length; + + /* print value */ + if (!print_value(current_item, output_buffer)) + { + return false; + } + update_offset(output_buffer); + + /* print comma if not last */ + length = ((size_t)(output_buffer->format ? 1 : 0) + (size_t)(current_item->next ? 1 : 0)); + output_pointer = ensure(output_buffer, length + 1); + if (output_pointer == NULL) + { + return false; + } + if (current_item->next) + { + *output_pointer++ = ','; + } + + if (output_buffer->format) + { + *output_pointer++ = '\n'; + } + *output_pointer = '\0'; + output_buffer->offset += length; + + current_item = current_item->next; + } + + output_pointer = ensure(output_buffer, output_buffer->format ? (output_buffer->depth + 1) : 2); + if (output_pointer == NULL) + { + return false; + } + if (output_buffer->format) + { + size_t i; + for (i = 0; i < (output_buffer->depth - 1); i++) + { + *output_pointer++ = '\t'; + } + } + *output_pointer++ = '}'; + *output_pointer = '\0'; + output_buffer->depth--; + + return true; +} + +/* Get Array size/item / object item. */ +CJSON_PUBLIC(int) cJSON_GetArraySize(const cJSON *array) +{ + cJSON *child = NULL; + size_t size = 0; + + if (array == NULL) + { + return 0; + } + + child = array->child; + + while(child != NULL) + { + size++; + child = child->next; + } + + /* FIXME: Can overflow here. Cannot be fixed without breaking the API */ + + return (int)size; +} + +static cJSON* get_array_item(const cJSON *array, size_t index) +{ + cJSON *current_child = NULL; + + if (array == NULL) + { + return NULL; + } + + current_child = array->child; + while ((current_child != NULL) && (index > 0)) + { + index--; + current_child = current_child->next; + } + + return current_child; +} + +CJSON_PUBLIC(cJSON *) cJSON_GetArrayItem(const cJSON *array, int index) +{ + if (index < 0) + { + return NULL; + } + + return get_array_item(array, (size_t)index); +} + +static cJSON *get_object_item(const cJSON * const object, const char * const name, const cJSON_bool case_sensitive) +{ + cJSON *current_element = NULL; + + if ((object == NULL) || (name == NULL)) + { + return NULL; + } + + current_element = object->child; + if (case_sensitive) + { + while ((current_element != NULL) && (current_element->string != NULL) && (strcmp(name, current_element->string) != 0)) + { + current_element = current_element->next; + } + } + else + { + while ((current_element != NULL) && (case_insensitive_strcmp((const unsigned char*)name, (const unsigned char*)(current_element->string)) != 0)) + { + current_element = current_element->next; + } + } + + if ((current_element == NULL) || (current_element->string == NULL)) { + return NULL; + } + + return current_element; +} + +CJSON_PUBLIC(cJSON *) cJSON_GetObjectItem(const cJSON * const object, const char * const string) +{ + return get_object_item(object, string, false); +} + +CJSON_PUBLIC(cJSON *) cJSON_GetObjectItemCaseSensitive(const cJSON * const object, const char * const string) +{ + return get_object_item(object, string, true); +} + +CJSON_PUBLIC(cJSON_bool) cJSON_HasObjectItem(const cJSON *object, const char *string) +{ + return cJSON_GetObjectItem(object, string) ? 1 : 0; +} + +/* Utility for array list handling. */ +static void suffix_object(cJSON *prev, cJSON *item) +{ + prev->next = item; + item->prev = prev; +} + +/* Utility for handling references. */ +static cJSON *create_reference(const cJSON *item, const internal_hooks * const hooks) +{ + cJSON *reference = NULL; + if (item == NULL) + { + return NULL; + } + + reference = cJSON_New_Item(hooks); + if (reference == NULL) + { + return NULL; + } + + memcpy(reference, item, sizeof(cJSON)); + reference->string = NULL; + reference->type |= cJSON_IsReference; + reference->next = reference->prev = NULL; + return reference; +} + +static cJSON_bool add_item_to_array(cJSON *array, cJSON *item) +{ + cJSON *child = NULL; + + if ((item == NULL) || (array == NULL) || (array == item)) + { + return false; + } + + child = array->child; + /* + * To find the last item in array quickly, we use prev in array + */ + if (child == NULL) + { + /* list is empty, start new one */ + array->child = item; + item->prev = item; + item->next = NULL; + } + else + { + /* append to the end */ + if (child->prev) + { + suffix_object(child->prev, item); + array->child->prev = item; + } + } + + return true; +} + +/* Add item to array/object. */ +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToArray(cJSON *array, cJSON *item) +{ + return add_item_to_array(array, item); +} + +#if defined(__clang__) || (defined(__GNUC__) && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ > 5)))) + #pragma GCC diagnostic push +#endif +#ifdef __GNUC__ +#pragma GCC diagnostic ignored "-Wcast-qual" +#endif +/* helper function to cast away const */ +static void* cast_away_const(const void* string) +{ + return (void*)string; +} +#if defined(__clang__) || (defined(__GNUC__) && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ > 5)))) + #pragma GCC diagnostic pop +#endif + + +static cJSON_bool add_item_to_object(cJSON * const object, const char * const string, cJSON * const item, const internal_hooks * const hooks, const cJSON_bool constant_key) +{ + char *new_key = NULL; + int new_type = cJSON_Invalid; + + if ((object == NULL) || (string == NULL) || (item == NULL) || (object == item)) + { + return false; + } + + if (constant_key) + { + new_key = (char*)cast_away_const(string); + new_type = item->type | cJSON_StringIsConst; + } + else + { + new_key = (char*)cJSON_strdup((const unsigned char*)string, hooks); + if (new_key == NULL) + { + return false; + } + + new_type = item->type & ~cJSON_StringIsConst; + } + + if (!(item->type & cJSON_StringIsConst) && (item->string != NULL)) + { + hooks->deallocate(item->string); + } + + item->string = new_key; + item->type = new_type; + + return add_item_to_array(object, item); +} + +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item) +{ + return add_item_to_object(object, string, item, &global_hooks, false); +} + +/* Add an item to an object with constant string as key */ +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObjectCS(cJSON *object, const char *string, cJSON *item) +{ + return add_item_to_object(object, string, item, &global_hooks, true); +} + +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item) +{ + if (array == NULL) + { + return false; + } + + return add_item_to_array(array, create_reference(item, &global_hooks)); +} + +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToObject(cJSON *object, const char *string, cJSON *item) +{ + if ((object == NULL) || (string == NULL)) + { + return false; + } + + return add_item_to_object(object, string, create_reference(item, &global_hooks), &global_hooks, false); +} + +CJSON_PUBLIC(cJSON*) cJSON_AddNullToObject(cJSON * const object, const char * const name) +{ + cJSON *null = cJSON_CreateNull(); + if (add_item_to_object(object, name, null, &global_hooks, false)) + { + return null; + } + + cJSON_Delete(null); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddTrueToObject(cJSON * const object, const char * const name) +{ + cJSON *true_item = cJSON_CreateTrue(); + if (add_item_to_object(object, name, true_item, &global_hooks, false)) + { + return true_item; + } + + cJSON_Delete(true_item); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddFalseToObject(cJSON * const object, const char * const name) +{ + cJSON *false_item = cJSON_CreateFalse(); + if (add_item_to_object(object, name, false_item, &global_hooks, false)) + { + return false_item; + } + + cJSON_Delete(false_item); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddBoolToObject(cJSON * const object, const char * const name, const cJSON_bool boolean) +{ + cJSON *bool_item = cJSON_CreateBool(boolean); + if (add_item_to_object(object, name, bool_item, &global_hooks, false)) + { + return bool_item; + } + + cJSON_Delete(bool_item); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddNumberToObject(cJSON * const object, const char * const name, const double number) +{ + cJSON *number_item = cJSON_CreateNumber(number); + if (add_item_to_object(object, name, number_item, &global_hooks, false)) + { + return number_item; + } + + cJSON_Delete(number_item); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddStringToObject(cJSON * const object, const char * const name, const char * const string) +{ + cJSON *string_item = cJSON_CreateString(string); + if (add_item_to_object(object, name, string_item, &global_hooks, false)) + { + return string_item; + } + + cJSON_Delete(string_item); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddRawToObject(cJSON * const object, const char * const name, const char * const raw) +{ + cJSON *raw_item = cJSON_CreateRaw(raw); + if (add_item_to_object(object, name, raw_item, &global_hooks, false)) + { + return raw_item; + } + + cJSON_Delete(raw_item); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddObjectToObject(cJSON * const object, const char * const name) +{ + cJSON *object_item = cJSON_CreateObject(); + if (add_item_to_object(object, name, object_item, &global_hooks, false)) + { + return object_item; + } + + cJSON_Delete(object_item); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddArrayToObject(cJSON * const object, const char * const name) +{ + cJSON *array = cJSON_CreateArray(); + if (add_item_to_object(object, name, array, &global_hooks, false)) + { + return array; + } + + cJSON_Delete(array); + return NULL; +} + +CJSON_PUBLIC(cJSON *) cJSON_DetachItemViaPointer(cJSON *parent, cJSON * const item) +{ + if ((parent == NULL) || (item == NULL)) + { + return NULL; + } + + if (item != parent->child) + { + /* not the first element */ + item->prev->next = item->next; + } + if (item->next != NULL) + { + /* not the last element */ + item->next->prev = item->prev; + } + + if (item == parent->child) + { + /* first element */ + parent->child = item->next; + } + else if (item->next == NULL) + { + /* last element */ + parent->child->prev = item->prev; + } + + /* make sure the detached item doesn't point anywhere anymore */ + item->prev = NULL; + item->next = NULL; + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromArray(cJSON *array, int which) +{ + if (which < 0) + { + return NULL; + } + + return cJSON_DetachItemViaPointer(array, get_array_item(array, (size_t)which)); +} + +CJSON_PUBLIC(void) cJSON_DeleteItemFromArray(cJSON *array, int which) +{ + cJSON_Delete(cJSON_DetachItemFromArray(array, which)); +} + +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObject(cJSON *object, const char *string) +{ + cJSON *to_detach = cJSON_GetObjectItem(object, string); + + return cJSON_DetachItemViaPointer(object, to_detach); +} + +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObjectCaseSensitive(cJSON *object, const char *string) +{ + cJSON *to_detach = cJSON_GetObjectItemCaseSensitive(object, string); + + return cJSON_DetachItemViaPointer(object, to_detach); +} + +CJSON_PUBLIC(void) cJSON_DeleteItemFromObject(cJSON *object, const char *string) +{ + cJSON_Delete(cJSON_DetachItemFromObject(object, string)); +} + +CJSON_PUBLIC(void) cJSON_DeleteItemFromObjectCaseSensitive(cJSON *object, const char *string) +{ + cJSON_Delete(cJSON_DetachItemFromObjectCaseSensitive(object, string)); +} + +/* Replace array/object items with new ones. */ +CJSON_PUBLIC(cJSON_bool) cJSON_InsertItemInArray(cJSON *array, int which, cJSON *newitem) +{ + cJSON *after_inserted = NULL; + + if (which < 0 || newitem == NULL) + { + return false; + } + + after_inserted = get_array_item(array, (size_t)which); + if (after_inserted == NULL) + { + return add_item_to_array(array, newitem); + } + + if (after_inserted != array->child && after_inserted->prev == NULL) { + /* return false if after_inserted is a corrupted array item */ + return false; + } + + newitem->next = after_inserted; + newitem->prev = after_inserted->prev; + after_inserted->prev = newitem; + if (after_inserted == array->child) + { + array->child = newitem; + } + else + { + newitem->prev->next = newitem; + } + return true; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemViaPointer(cJSON * const parent, cJSON * const item, cJSON * replacement) +{ + if ((parent == NULL) || (parent->child == NULL) || (replacement == NULL) || (item == NULL)) + { + return false; + } + + if (replacement == item) + { + return true; + } + + replacement->next = item->next; + replacement->prev = item->prev; + + if (replacement->next != NULL) + { + replacement->next->prev = replacement; + } + if (parent->child == item) + { + if (parent->child->prev == parent->child) + { + replacement->prev = replacement; + } + parent->child = replacement; + } + else + { /* + * To find the last item in array quickly, we use prev in array. + * We can't modify the last item's next pointer where this item was the parent's child + */ + if (replacement->prev != NULL) + { + replacement->prev->next = replacement; + } + if (replacement->next == NULL) + { + parent->child->prev = replacement; + } + } + + item->next = NULL; + item->prev = NULL; + cJSON_Delete(item); + + return true; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInArray(cJSON *array, int which, cJSON *newitem) +{ + if (which < 0) + { + return false; + } + + return cJSON_ReplaceItemViaPointer(array, get_array_item(array, (size_t)which), newitem); +} + +static cJSON_bool replace_item_in_object(cJSON *object, const char *string, cJSON *replacement, cJSON_bool case_sensitive) +{ + if ((replacement == NULL) || (string == NULL)) + { + return false; + } + + /* replace the name in the replacement */ + if (!(replacement->type & cJSON_StringIsConst) && (replacement->string != NULL)) + { + cJSON_free(replacement->string); + } + replacement->string = (char*)cJSON_strdup((const unsigned char*)string, &global_hooks); + if (replacement->string == NULL) + { + return false; + } + + replacement->type &= ~cJSON_StringIsConst; + + return cJSON_ReplaceItemViaPointer(object, get_object_item(object, string, case_sensitive), replacement); +} + +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObject(cJSON *object, const char *string, cJSON *newitem) +{ + return replace_item_in_object(object, string, newitem, false); +} + +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObjectCaseSensitive(cJSON *object, const char *string, cJSON *newitem) +{ + return replace_item_in_object(object, string, newitem, true); +} + +/* Create basic types: */ +CJSON_PUBLIC(cJSON *) cJSON_CreateNull(void) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_NULL; + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateTrue(void) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_True; + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateFalse(void) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_False; + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateBool(cJSON_bool boolean) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = boolean ? cJSON_True : cJSON_False; + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateNumber(double num) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_Number; + item->valuedouble = num; + + /* use saturation in case of overflow */ + if (num >= INT_MAX) + { + item->valueint = INT_MAX; + } + else if (num <= (double)INT_MIN) + { + item->valueint = INT_MIN; + } + else + { + item->valueint = (int)num; + } + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateString(const char *string) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_String; + item->valuestring = (char*)cJSON_strdup((const unsigned char*)string, &global_hooks); + if(!item->valuestring) + { + cJSON_Delete(item); + return NULL; + } + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateStringReference(const char *string) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if (item != NULL) + { + item->type = cJSON_String | cJSON_IsReference; + item->valuestring = (char*)cast_away_const(string); + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateObjectReference(const cJSON *child) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if (item != NULL) { + item->type = cJSON_Object | cJSON_IsReference; + item->child = (cJSON*)cast_away_const(child); + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateArrayReference(const cJSON *child) { + cJSON *item = cJSON_New_Item(&global_hooks); + if (item != NULL) { + item->type = cJSON_Array | cJSON_IsReference; + item->child = (cJSON*)cast_away_const(child); + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateRaw(const char *raw) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_Raw; + item->valuestring = (char*)cJSON_strdup((const unsigned char*)raw, &global_hooks); + if(!item->valuestring) + { + cJSON_Delete(item); + return NULL; + } + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateArray(void) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type=cJSON_Array; + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateObject(void) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if (item) + { + item->type = cJSON_Object; + } + + return item; +} + +/* Create Arrays: */ +CJSON_PUBLIC(cJSON *) cJSON_CreateIntArray(const int *numbers, int count) +{ + size_t i = 0; + cJSON *n = NULL; + cJSON *p = NULL; + cJSON *a = NULL; + + if ((count < 0) || (numbers == NULL)) + { + return NULL; + } + + a = cJSON_CreateArray(); + + for(i = 0; a && (i < (size_t)count); i++) + { + n = cJSON_CreateNumber(numbers[i]); + if (!n) + { + cJSON_Delete(a); + return NULL; + } + if(!i) + { + a->child = n; + } + else + { + suffix_object(p, n); + } + p = n; + } + + if (a && a->child) { + a->child->prev = n; + } + + return a; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateFloatArray(const float *numbers, int count) +{ + size_t i = 0; + cJSON *n = NULL; + cJSON *p = NULL; + cJSON *a = NULL; + + if ((count < 0) || (numbers == NULL)) + { + return NULL; + } + + a = cJSON_CreateArray(); + + for(i = 0; a && (i < (size_t)count); i++) + { + n = cJSON_CreateNumber((double)numbers[i]); + if(!n) + { + cJSON_Delete(a); + return NULL; + } + if(!i) + { + a->child = n; + } + else + { + suffix_object(p, n); + } + p = n; + } + + if (a && a->child) { + a->child->prev = n; + } + + return a; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateDoubleArray(const double *numbers, int count) +{ + size_t i = 0; + cJSON *n = NULL; + cJSON *p = NULL; + cJSON *a = NULL; + + if ((count < 0) || (numbers == NULL)) + { + return NULL; + } + + a = cJSON_CreateArray(); + + for(i = 0; a && (i < (size_t)count); i++) + { + n = cJSON_CreateNumber(numbers[i]); + if(!n) + { + cJSON_Delete(a); + return NULL; + } + if(!i) + { + a->child = n; + } + else + { + suffix_object(p, n); + } + p = n; + } + + if (a && a->child) { + a->child->prev = n; + } + + return a; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateStringArray(const char *const *strings, int count) +{ + size_t i = 0; + cJSON *n = NULL; + cJSON *p = NULL; + cJSON *a = NULL; + + if ((count < 0) || (strings == NULL)) + { + return NULL; + } + + a = cJSON_CreateArray(); + + for (i = 0; a && (i < (size_t)count); i++) + { + n = cJSON_CreateString(strings[i]); + if(!n) + { + cJSON_Delete(a); + return NULL; + } + if(!i) + { + a->child = n; + } + else + { + suffix_object(p,n); + } + p = n; + } + + if (a && a->child) { + a->child->prev = n; + } + + return a; +} + +/* Duplication */ +CJSON_PUBLIC(cJSON *) cJSON_Duplicate(const cJSON *item, cJSON_bool recurse) +{ + cJSON *newitem = NULL; + cJSON *child = NULL; + cJSON *next = NULL; + cJSON *newchild = NULL; + + /* Bail on bad ptr */ + if (!item) + { + goto fail; + } + /* Create new item */ + newitem = cJSON_New_Item(&global_hooks); + if (!newitem) + { + goto fail; + } + /* Copy over all vars */ + newitem->type = item->type & (~cJSON_IsReference); + newitem->valueint = item->valueint; + newitem->valuedouble = item->valuedouble; + if (item->valuestring) + { + newitem->valuestring = (char*)cJSON_strdup((unsigned char*)item->valuestring, &global_hooks); + if (!newitem->valuestring) + { + goto fail; + } + } + if (item->string) + { + newitem->string = (item->type&cJSON_StringIsConst) ? item->string : (char*)cJSON_strdup((unsigned char*)item->string, &global_hooks); + if (!newitem->string) + { + goto fail; + } + } + /* If non-recursive, then we're done! */ + if (!recurse) + { + return newitem; + } + /* Walk the ->next chain for the child. */ + child = item->child; + while (child != NULL) + { + newchild = cJSON_Duplicate(child, true); /* Duplicate (with recurse) each item in the ->next chain */ + if (!newchild) + { + goto fail; + } + if (next != NULL) + { + /* If newitem->child already set, then crosswire ->prev and ->next and move on */ + next->next = newchild; + newchild->prev = next; + next = newchild; + } + else + { + /* Set newitem->child and move to it */ + newitem->child = newchild; + next = newchild; + } + child = child->next; + } + if (newitem && newitem->child) + { + newitem->child->prev = newchild; + } + + return newitem; + +fail: + if (newitem != NULL) + { + cJSON_Delete(newitem); + } + + return NULL; +} + +static void skip_oneline_comment(char **input) +{ + *input += static_strlen("//"); + + for (; (*input)[0] != '\0'; ++(*input)) + { + if ((*input)[0] == '\n') { + *input += static_strlen("\n"); + return; + } + } +} + +static void skip_multiline_comment(char **input) +{ + *input += static_strlen("/*"); + + for (; (*input)[0] != '\0'; ++(*input)) + { + if (((*input)[0] == '*') && ((*input)[1] == '/')) + { + *input += static_strlen("*/"); + return; + } + } +} + +static void minify_string(char **input, char **output) { + (*output)[0] = (*input)[0]; + *input += static_strlen("\""); + *output += static_strlen("\""); + + + for (; (*input)[0] != '\0'; (void)++(*input), ++(*output)) { + (*output)[0] = (*input)[0]; + + if ((*input)[0] == '\"') { + (*output)[0] = '\"'; + *input += static_strlen("\""); + *output += static_strlen("\""); + return; + } else if (((*input)[0] == '\\') && ((*input)[1] == '\"')) { + (*output)[1] = (*input)[1]; + *input += static_strlen("\""); + *output += static_strlen("\""); + } + } +} + +CJSON_PUBLIC(void) cJSON_Minify(char *json) +{ + char *into = json; + + if (json == NULL) + { + return; + } + + while (json[0] != '\0') + { + switch (json[0]) + { + case ' ': + case '\t': + case '\r': + case '\n': + json++; + break; + + case '/': + if (json[1] == '/') + { + skip_oneline_comment(&json); + } + else if (json[1] == '*') + { + skip_multiline_comment(&json); + } else { + json++; + } + break; + + case '\"': + minify_string(&json, (char**)&into); + break; + + default: + into[0] = json[0]; + json++; + into++; + } + } + + /* and null-terminate. */ + *into = '\0'; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsInvalid(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_Invalid; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsFalse(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_False; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsTrue(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xff) == cJSON_True; +} + + +CJSON_PUBLIC(cJSON_bool) cJSON_IsBool(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & (cJSON_True | cJSON_False)) != 0; +} +CJSON_PUBLIC(cJSON_bool) cJSON_IsNull(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_NULL; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsNumber(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_Number; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsString(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_String; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsArray(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_Array; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsObject(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_Object; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsRaw(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_Raw; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_Compare(const cJSON * const a, const cJSON * const b, const cJSON_bool case_sensitive) +{ + if ((a == NULL) || (b == NULL) || ((a->type & 0xFF) != (b->type & 0xFF))) + { + return false; + } + + /* check if type is valid */ + switch (a->type & 0xFF) + { + case cJSON_False: + case cJSON_True: + case cJSON_NULL: + case cJSON_Number: + case cJSON_String: + case cJSON_Raw: + case cJSON_Array: + case cJSON_Object: + break; + + default: + return false; + } + + /* identical objects are equal */ + if (a == b) + { + return true; + } + + switch (a->type & 0xFF) + { + /* in these cases and equal type is enough */ + case cJSON_False: + case cJSON_True: + case cJSON_NULL: + return true; + + case cJSON_Number: + if (compare_double(a->valuedouble, b->valuedouble)) + { + return true; + } + return false; + + case cJSON_String: + case cJSON_Raw: + if ((a->valuestring == NULL) || (b->valuestring == NULL)) + { + return false; + } + if (strcmp(a->valuestring, b->valuestring) == 0) + { + return true; + } + + return false; + + case cJSON_Array: + { + cJSON *a_element = a->child; + cJSON *b_element = b->child; + + for (; (a_element != NULL) && (b_element != NULL);) + { + if (!cJSON_Compare(a_element, b_element, case_sensitive)) + { + return false; + } + + a_element = a_element->next; + b_element = b_element->next; + } + + /* one of the arrays is longer than the other */ + if (a_element != b_element) { + return false; + } + + return true; + } + + case cJSON_Object: + { + cJSON *a_element = NULL; + cJSON *b_element = NULL; + cJSON_ArrayForEach(a_element, a) + { + /* TODO This has O(n^2) runtime, which is horrible! */ + b_element = get_object_item(b, a_element->string, case_sensitive); + if (b_element == NULL) + { + return false; + } + + if (!cJSON_Compare(a_element, b_element, case_sensitive)) + { + return false; + } + } + + /* doing this twice, once on a and b to prevent true comparison if a subset of b + * TODO: Do this the proper way, this is just a fix for now */ + cJSON_ArrayForEach(b_element, b) + { + a_element = get_object_item(a, b_element->string, case_sensitive); + if (a_element == NULL) + { + return false; + } + + if (!cJSON_Compare(b_element, a_element, case_sensitive)) + { + return false; + } + } + + return true; + } + + default: + return false; + } +} + +CJSON_PUBLIC(void *) cJSON_malloc(size_t size) +{ + return global_hooks.allocate(size); +} + +CJSON_PUBLIC(void) cJSON_free(void *object) +{ + global_hooks.deallocate(object); + object = NULL; +} diff --git a/core/src/drivers/plugins/native/s7comm/cjson/cJSON.h b/core/src/drivers/plugins/native/s7comm/cjson/cJSON.h new file mode 100644 index 00000000..88cf0bcf --- /dev/null +++ b/core/src/drivers/plugins/native/s7comm/cjson/cJSON.h @@ -0,0 +1,300 @@ +/* + Copyright (c) 2009-2017 Dave Gamble and cJSON contributors + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +#ifndef cJSON__h +#define cJSON__h + +#ifdef __cplusplus +extern "C" +{ +#endif + +#if !defined(__WINDOWS__) && (defined(WIN32) || defined(WIN64) || defined(_MSC_VER) || defined(_WIN32)) +#define __WINDOWS__ +#endif + +#ifdef __WINDOWS__ + +/* When compiling for windows, we specify a specific calling convention to avoid issues where we are being called from a project with a different default calling convention. For windows you have 3 define options: + +CJSON_HIDE_SYMBOLS - Define this in the case where you don't want to ever dllexport symbols +CJSON_EXPORT_SYMBOLS - Define this on library build when you want to dllexport symbols (default) +CJSON_IMPORT_SYMBOLS - Define this if you want to dllimport symbol + +For *nix builds that support visibility attribute, you can define similar behavior by + +setting default visibility to hidden by adding +-fvisibility=hidden (for gcc) +or +-xldscope=hidden (for sun cc) +to CFLAGS + +then using the CJSON_API_VISIBILITY flag to "export" the same symbols the way CJSON_EXPORT_SYMBOLS does + +*/ + +#define CJSON_CDECL __cdecl +#define CJSON_STDCALL __stdcall + +/* export symbols by default, this is necessary for copy pasting the C and header file */ +#if !defined(CJSON_HIDE_SYMBOLS) && !defined(CJSON_IMPORT_SYMBOLS) && !defined(CJSON_EXPORT_SYMBOLS) +#define CJSON_EXPORT_SYMBOLS +#endif + +#if defined(CJSON_HIDE_SYMBOLS) +#define CJSON_PUBLIC(type) type CJSON_STDCALL +#elif defined(CJSON_EXPORT_SYMBOLS) +#define CJSON_PUBLIC(type) __declspec(dllexport) type CJSON_STDCALL +#elif defined(CJSON_IMPORT_SYMBOLS) +#define CJSON_PUBLIC(type) __declspec(dllimport) type CJSON_STDCALL +#endif +#else /* !__WINDOWS__ */ +#define CJSON_CDECL +#define CJSON_STDCALL + +#if (defined(__GNUC__) || defined(__SUNPRO_CC) || defined (__SUNPRO_C)) && defined(CJSON_API_VISIBILITY) +#define CJSON_PUBLIC(type) __attribute__((visibility("default"))) type +#else +#define CJSON_PUBLIC(type) type +#endif +#endif + +/* project version */ +#define CJSON_VERSION_MAJOR 1 +#define CJSON_VERSION_MINOR 7 +#define CJSON_VERSION_PATCH 18 + +#include + +/* cJSON Types: */ +#define cJSON_Invalid (0) +#define cJSON_False (1 << 0) +#define cJSON_True (1 << 1) +#define cJSON_NULL (1 << 2) +#define cJSON_Number (1 << 3) +#define cJSON_String (1 << 4) +#define cJSON_Array (1 << 5) +#define cJSON_Object (1 << 6) +#define cJSON_Raw (1 << 7) /* raw json */ + +#define cJSON_IsReference 256 +#define cJSON_StringIsConst 512 + +/* The cJSON structure: */ +typedef struct cJSON +{ + /* next/prev allow you to walk array/object chains. Alternatively, use GetArraySize/GetArrayItem/GetObjectItem */ + struct cJSON *next; + struct cJSON *prev; + /* An array or object item will have a child pointer pointing to a chain of the items in the array/object. */ + struct cJSON *child; + + /* The type of the item, as above. */ + int type; + + /* The item's string, if type==cJSON_String and type == cJSON_Raw */ + char *valuestring; + /* writing to valueint is DEPRECATED, use cJSON_SetNumberValue instead */ + int valueint; + /* The item's number, if type==cJSON_Number */ + double valuedouble; + + /* The item's name string, if this item is the child of, or is in the list of subitems of an object. */ + char *string; +} cJSON; + +typedef struct cJSON_Hooks +{ + /* malloc/free are CDECL on Windows regardless of the default calling convention of the compiler, so ensure the hooks allow passing those functions directly. */ + void *(CJSON_CDECL *malloc_fn)(size_t sz); + void (CJSON_CDECL *free_fn)(void *ptr); +} cJSON_Hooks; + +typedef int cJSON_bool; + +/* Limits how deeply nested arrays/objects can be before cJSON rejects to parse them. + * This is to prevent stack overflows. */ +#ifndef CJSON_NESTING_LIMIT +#define CJSON_NESTING_LIMIT 1000 +#endif + +/* returns the version of cJSON as a string */ +CJSON_PUBLIC(const char*) cJSON_Version(void); + +/* Supply malloc, realloc and free functions to cJSON */ +CJSON_PUBLIC(void) cJSON_InitHooks(cJSON_Hooks* hooks); + +/* Memory Management: the caller is always responsible to free the results from all variants of cJSON_Parse (with cJSON_Delete) and cJSON_Print (with stdlib free, cJSON_Hooks.free_fn, or cJSON_free as appropriate). The exception is cJSON_PrintPreallocated, where the caller has full responsibility of the buffer. */ +/* Supply a block of JSON, and this returns a cJSON object you can interrogate. */ +CJSON_PUBLIC(cJSON *) cJSON_Parse(const char *value); +CJSON_PUBLIC(cJSON *) cJSON_ParseWithLength(const char *value, size_t buffer_length); +/* ParseWithOpts allows you to require (and check) that the JSON is null terminated, and to retrieve the pointer to the final byte parsed. */ +/* If you supply a ptr in return_parse_end and parsing fails, then return_parse_end will contain a pointer to the error so will match cJSON_GetErrorPtr(). */ +CJSON_PUBLIC(cJSON *) cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cJSON_bool require_null_terminated); +CJSON_PUBLIC(cJSON *) cJSON_ParseWithLengthOpts(const char *value, size_t buffer_length, const char **return_parse_end, cJSON_bool require_null_terminated); + +/* Render a cJSON entity to text for transfer/storage. */ +CJSON_PUBLIC(char *) cJSON_Print(const cJSON *item); +/* Render a cJSON entity to text for transfer/storage without any formatting. */ +CJSON_PUBLIC(char *) cJSON_PrintUnformatted(const cJSON *item); +/* Render a cJSON entity to text using a buffered strategy. prebuffer is a guess at the final size. guessing well reduces reallocation. fmt=0 gives unformatted, =1 gives formatted */ +CJSON_PUBLIC(char *) cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON_bool fmt); +/* Render a cJSON entity to text using a buffer already allocated in memory with given length. Returns 1 on success and 0 on failure. */ +/* NOTE: cJSON is not always 100% accurate in estimating how much memory it will use, so to be safe allocate 5 bytes more than you actually need */ +CJSON_PUBLIC(cJSON_bool) cJSON_PrintPreallocated(cJSON *item, char *buffer, const int length, const cJSON_bool format); +/* Delete a cJSON entity and all subentities. */ +CJSON_PUBLIC(void) cJSON_Delete(cJSON *item); + +/* Returns the number of items in an array (or object). */ +CJSON_PUBLIC(int) cJSON_GetArraySize(const cJSON *array); +/* Retrieve item number "index" from array "array". Returns NULL if unsuccessful. */ +CJSON_PUBLIC(cJSON *) cJSON_GetArrayItem(const cJSON *array, int index); +/* Get item "string" from object. Case insensitive. */ +CJSON_PUBLIC(cJSON *) cJSON_GetObjectItem(const cJSON * const object, const char * const string); +CJSON_PUBLIC(cJSON *) cJSON_GetObjectItemCaseSensitive(const cJSON * const object, const char * const string); +CJSON_PUBLIC(cJSON_bool) cJSON_HasObjectItem(const cJSON *object, const char *string); +/* For analysing failed parses. This returns a pointer to the parse error. You'll probably need to look a few chars back to make sense of it. Defined when cJSON_Parse() returns 0. 0 when cJSON_Parse() succeeds. */ +CJSON_PUBLIC(const char *) cJSON_GetErrorPtr(void); + +/* Check item type and return its value */ +CJSON_PUBLIC(char *) cJSON_GetStringValue(const cJSON * const item); +CJSON_PUBLIC(double) cJSON_GetNumberValue(const cJSON * const item); + +/* These functions check the type of an item */ +CJSON_PUBLIC(cJSON_bool) cJSON_IsInvalid(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsFalse(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsTrue(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsBool(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsNull(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsNumber(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsString(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsArray(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsObject(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsRaw(const cJSON * const item); + +/* These calls create a cJSON item of the appropriate type. */ +CJSON_PUBLIC(cJSON *) cJSON_CreateNull(void); +CJSON_PUBLIC(cJSON *) cJSON_CreateTrue(void); +CJSON_PUBLIC(cJSON *) cJSON_CreateFalse(void); +CJSON_PUBLIC(cJSON *) cJSON_CreateBool(cJSON_bool boolean); +CJSON_PUBLIC(cJSON *) cJSON_CreateNumber(double num); +CJSON_PUBLIC(cJSON *) cJSON_CreateString(const char *string); +/* raw json */ +CJSON_PUBLIC(cJSON *) cJSON_CreateRaw(const char *raw); +CJSON_PUBLIC(cJSON *) cJSON_CreateArray(void); +CJSON_PUBLIC(cJSON *) cJSON_CreateObject(void); + +/* Create a string where valuestring references a string so + * it will not be freed by cJSON_Delete */ +CJSON_PUBLIC(cJSON *) cJSON_CreateStringReference(const char *string); +/* Create an object/array that only references it's elements so + * they will not be freed by cJSON_Delete */ +CJSON_PUBLIC(cJSON *) cJSON_CreateObjectReference(const cJSON *child); +CJSON_PUBLIC(cJSON *) cJSON_CreateArrayReference(const cJSON *child); + +/* These utilities create an Array of count items. + * The parameter count cannot be greater than the number of elements in the number array, otherwise array access will be out of bounds.*/ +CJSON_PUBLIC(cJSON *) cJSON_CreateIntArray(const int *numbers, int count); +CJSON_PUBLIC(cJSON *) cJSON_CreateFloatArray(const float *numbers, int count); +CJSON_PUBLIC(cJSON *) cJSON_CreateDoubleArray(const double *numbers, int count); +CJSON_PUBLIC(cJSON *) cJSON_CreateStringArray(const char *const *strings, int count); + +/* Append item to the specified array/object. */ +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToArray(cJSON *array, cJSON *item); +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item); +/* Use this when string is definitely const (i.e. a literal, or as good as), and will definitely survive the cJSON object. + * WARNING: When this function was used, make sure to always check that (item->type & cJSON_StringIsConst) is zero before + * writing to `item->string` */ +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObjectCS(cJSON *object, const char *string, cJSON *item); +/* Append reference to item to the specified array/object. Use this when you want to add an existing cJSON to a new cJSON, but don't want to corrupt your existing cJSON. */ +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item); +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToObject(cJSON *object, const char *string, cJSON *item); + +/* Remove/Detach items from Arrays/Objects. */ +CJSON_PUBLIC(cJSON *) cJSON_DetachItemViaPointer(cJSON *parent, cJSON * const item); +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromArray(cJSON *array, int which); +CJSON_PUBLIC(void) cJSON_DeleteItemFromArray(cJSON *array, int which); +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObject(cJSON *object, const char *string); +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObjectCaseSensitive(cJSON *object, const char *string); +CJSON_PUBLIC(void) cJSON_DeleteItemFromObject(cJSON *object, const char *string); +CJSON_PUBLIC(void) cJSON_DeleteItemFromObjectCaseSensitive(cJSON *object, const char *string); + +/* Update array items. */ +CJSON_PUBLIC(cJSON_bool) cJSON_InsertItemInArray(cJSON *array, int which, cJSON *newitem); /* Shifts pre-existing items to the right. */ +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemViaPointer(cJSON * const parent, cJSON * const item, cJSON * replacement); +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInArray(cJSON *array, int which, cJSON *newitem); +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObject(cJSON *object,const char *string,cJSON *newitem); +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObjectCaseSensitive(cJSON *object,const char *string,cJSON *newitem); + +/* Duplicate a cJSON item */ +CJSON_PUBLIC(cJSON *) cJSON_Duplicate(const cJSON *item, cJSON_bool recurse); +/* Duplicate will create a new, identical cJSON item to the one you pass, in new memory that will + * need to be released. With recurse!=0, it will duplicate any children connected to the item. + * The item->next and ->prev pointers are always zero on return from Duplicate. */ +/* Recursively compare two cJSON items for equality. If either a or b is NULL or invalid, they will be considered unequal. + * case_sensitive determines if object keys are treated case sensitive (1) or case insensitive (0) */ +CJSON_PUBLIC(cJSON_bool) cJSON_Compare(const cJSON * const a, const cJSON * const b, const cJSON_bool case_sensitive); + +/* Minify a strings, remove blank characters(such as ' ', '\t', '\r', '\n') from strings. + * The input pointer json cannot point to a read-only address area, such as a string constant, + * but should point to a readable and writable address area. */ +CJSON_PUBLIC(void) cJSON_Minify(char *json); + +/* Helper functions for creating and adding items to an object at the same time. + * They return the added item or NULL on failure. */ +CJSON_PUBLIC(cJSON*) cJSON_AddNullToObject(cJSON * const object, const char * const name); +CJSON_PUBLIC(cJSON*) cJSON_AddTrueToObject(cJSON * const object, const char * const name); +CJSON_PUBLIC(cJSON*) cJSON_AddFalseToObject(cJSON * const object, const char * const name); +CJSON_PUBLIC(cJSON*) cJSON_AddBoolToObject(cJSON * const object, const char * const name, const cJSON_bool boolean); +CJSON_PUBLIC(cJSON*) cJSON_AddNumberToObject(cJSON * const object, const char * const name, const double number); +CJSON_PUBLIC(cJSON*) cJSON_AddStringToObject(cJSON * const object, const char * const name, const char * const string); +CJSON_PUBLIC(cJSON*) cJSON_AddRawToObject(cJSON * const object, const char * const name, const char * const raw); +CJSON_PUBLIC(cJSON*) cJSON_AddObjectToObject(cJSON * const object, const char * const name); +CJSON_PUBLIC(cJSON*) cJSON_AddArrayToObject(cJSON * const object, const char * const name); + +/* When assigning an integer value, it needs to be propagated to valuedouble too. */ +#define cJSON_SetIntValue(object, number) ((object) ? (object)->valueint = (object)->valuedouble = (number) : (number)) +/* helper for the cJSON_SetNumberValue macro */ +CJSON_PUBLIC(double) cJSON_SetNumberHelper(cJSON *object, double number); +#define cJSON_SetNumberValue(object, number) ((object != NULL) ? cJSON_SetNumberHelper(object, (double)number) : (number)) +/* Change the valuestring of a cJSON_String object, only takes effect when type of object is cJSON_String */ +CJSON_PUBLIC(char*) cJSON_SetValuestring(cJSON *object, const char *valuestring); + +/* If the object is not a boolean type this does nothing and returns cJSON_Invalid else it returns the new type*/ +#define cJSON_SetBoolValue(object, boolValue) ( \ + (object != NULL && ((object)->type & (cJSON_False|cJSON_True))) ? \ + (object)->type=((object)->type &(~(cJSON_False|cJSON_True)))|((boolValue)?cJSON_True:cJSON_False) : \ + cJSON_Invalid\ +) + +/* Macro for iterating over an array or object */ +#define cJSON_ArrayForEach(element, array) for(element = (array != NULL) ? (array)->child : NULL; element != NULL; element = element->next) + +/* malloc/free objects using the malloc/free functions that have been set with cJSON_InitHooks */ +CJSON_PUBLIC(void *) cJSON_malloc(size_t size); +CJSON_PUBLIC(void) cJSON_free(void *object); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/core/src/drivers/plugins/native/s7comm/docs/IMPLEMENTATION_PLAN.md b/core/src/drivers/plugins/native/s7comm/docs/IMPLEMENTATION_PLAN.md new file mode 100644 index 00000000..f7574db9 --- /dev/null +++ b/core/src/drivers/plugins/native/s7comm/docs/IMPLEMENTATION_PLAN.md @@ -0,0 +1,1385 @@ +# S7Comm Plugin Implementation Plan for OpenPLC Runtime v4 + +## Table of Contents + +1. [Executive Summary](#executive-summary) +2. [Background and Motivation](#background-and-motivation) +3. [Analysis of Existing Implementation](#analysis-of-existing-implementation) + - [OpenPLC v3 S7Comm Architecture](#openplc-v3-s7comm-architecture) + - [Snap7 Library API](#snap7-library-api) + - [OpenPLC Runtime v4 Plugin Architecture](#openplc-runtime-v4-plugin-architecture) +4. [Design Decisions](#design-decisions) +5. [JSON Configuration Schema](#json-configuration-schema) +6. [Plugin Structure](#plugin-structure) +7. [Implementation Details](#implementation-details) +8. [Phased Implementation Plan](#phased-implementation-plan) +9. [Testing Strategy](#testing-strategy) +10. [References](#references) + +--- + +## Executive Summary + +This document outlines the plan to port S7Comm functionality from OpenPLC Runtime v3 to the new v4 plugin architecture. The implementation leverages the Snap7 open-source library to provide Siemens S7 protocol server capabilities, allowing OpenPLC to communicate with S7-compatible HMIs, SCADA systems, and other industrial equipment. + +Key improvements over v3: +- Full JSON-based configuration +- Flexible data block mapping to any OpenPLC buffer +- Native C/C++ plugin for optimal performance +- Thread-safe design with PLC cycle synchronization +- Configurable PLC identity for S7 client compatibility +- Comprehensive logging and diagnostics + +--- + +## Background and Motivation + +### What is S7Comm? + +S7Comm (S7 Communication) is the proprietary protocol used by Siemens S7 PLCs (S7-300, S7-400, S7-1200, S7-1500). It runs over ISO-on-TCP (RFC 1006) on port 102 and is widely supported by: + +- Siemens WinCC and TIA Portal +- Many third-party HMI/SCADA systems +- Industrial data acquisition tools +- OPC servers + +### Why Add S7Comm to OpenPLC v4? + +1. **Industry Compatibility**: Many existing installations use S7Comm-compatible equipment +2. **User Request**: Feature was present in v3 and users expect it in v4 +3. **Protocol Maturity**: S7Comm is well-documented and stable +4. **Snap7 Library**: High-quality open-source implementation available + +### Snap7 Library + +Snap7 is an open-source, multi-platform Ethernet communication suite for interfacing with Siemens S7 PLCs. It provides: + +- S7 Server (emulate a PLC) +- S7 Client (connect to PLCs) +- S7 Partner (peer-to-peer communication) + +Repository: https://github.com/davenardella/snap7 + +--- + +## Analysis of Existing Implementation + +### OpenPLC v3 S7Comm Architecture + +The v3 implementation is located at `OpenPLC_v3/utils/snap7_src/wrapper/`: + +#### Key Files + +| File | Purpose | +|------|---------| +| `oplc_snap7.cpp` | Main implementation - server lifecycle, callbacks, buffer mapping | +| `oplc_snap7.h` | Header with S7 protocol constants and function declarations | + +#### Data Area Mapping (Hardcoded in v3) + +| S7 Area | DB Number | OpenPLC Buffer | Description | +|---------|-----------|----------------|-------------| +| PE (0x81) | N/A | `bool_input[][]` | Process inputs (digital) | +| PA (0x82) | N/A | `bool_output[][]` | Process outputs (digital) | +| MK (0x83) | N/A | Internal `MK[]` | Marker/flag memory | +| DB | 2 | `int_input[]` | Input words (%IW) | +| DB | 102 | `int_output[]` | Output words (%QW) | +| DB | 1002 | `int_memory[]` | Memory words (%MW) | +| DB | 1004 | `dint_memory[]` | Memory double-words (%MD) | + +#### Server Lifecycle (v3) + +```c +// Initialization +void initializeSnap7() { + Server = new TS7Server; + Server->SetEventsMask(0x3ff); + Server->SetEventsCallback(EventCallBack, NULL); + Server->SetRWAreaCallback(RWAreaCallBack, NULL); +} + +// Start server +void startSnap7() { + Server->StartTo("0.0.0.0"); // Port 102 +} + +// Stop server +void stopSnap7() { + Server->Stop(); +} + +// Cleanup +void finalizeSnap7() { + Server->Stop(); + delete Server; +} +``` + +#### RW Callback Pattern (v3) + +```c +int S7API RWAreaCallBack(void* usrPtr, int Sender, int Operation, + PS7Tag PTag, void* pUsrData) { + pthread_mutex_lock(&bufferLock); // Thread safety + + switch (PTag->Area) { + case S7AreaPE: // Process inputs + case S7AreaPA: // Process outputs + // Handle bit/byte access + break; + case S7AreaMK: // Markers + // Handle marker access + break; + case S7AreaDB: // Data blocks + switch (PTag->DBNumber) { + case 2: // int_input + case 102: // int_output + case 1002: // int_memory + case 1004: // dint_memory + } + break; + } + + pthread_mutex_unlock(&bufferLock); + return result; +} +``` + +#### Limitations of v3 Implementation + +1. **Hardcoded DB Numbers**: Cannot change DB2, DB102, DB1002, DB1004 +2. **Fixed Buffer Sizes**: Limited to BUFFER_SIZE constant +3. **No Configuration File**: All settings compiled in +4. **Tight Coupling**: Integrated directly into main runtime +5. **Limited Logging**: Minimal diagnostic output +6. **No PLC Identity Config**: Fixed SZL responses + +--- + +### Snap7 Library API + +#### Server Creation and Lifecycle + +```c +// C API +S7Object Srv_Create(); +void Srv_Destroy(S7Object *Server); +int Srv_Start(S7Object Server); +int Srv_StartTo(S7Object Server, const char *Address); +int Srv_Stop(S7Object Server); + +// C++ API +class TS7Server { +public: + TS7Server(); + ~TS7Server(); + int Start(); + int StartTo(const char *Address); + int Stop(); +}; +``` + +#### Configuration Parameters + +| Parameter ID | Constant | Type | Default | Description | +|--------------|----------|------|---------|-------------| +| 1 | `p_u16_LocalPort` | uint16 | 102 | TCP listening port | +| 3 | `p_i32_PingTimeout` | int32 | 10000 | Keep-alive timeout (ms) | +| 4 | `p_i32_SendTimeout` | int32 | 3000 | Socket send timeout (ms) | +| 5 | `p_i32_RecvTimeout` | int32 | 3000 | Socket receive timeout (ms) | +| 6 | `p_i32_WorkInterval` | int32 | 100 | Worker thread interval (ms) | +| 10 | `p_i32_PDURequest` | int32 | 480 | PDU size (240-960) | +| 11 | `p_i32_MaxClients` | int32 | 32 | Max concurrent connections | + +```c +int Srv_GetParam(S7Object Server, int ParamNumber, void *pValue); +int Srv_SetParam(S7Object Server, int ParamNumber, void *pValue); +``` + +#### Data Area Registration + +```c +// Area codes +const int srvAreaPE = 0; // Process inputs (I) +const int srvAreaPA = 1; // Process outputs (Q) +const int srvAreaMK = 2; // Markers (M) +const int srvAreaCT = 3; // Counters (C) +const int srvAreaTM = 4; // Timers (T) +const int srvAreaDB = 5; // Data blocks (DB) + +// Registration functions +int Srv_RegisterArea(S7Object Server, int AreaCode, word Index, + void *pUsrData, int Size); +int Srv_UnregisterArea(S7Object Server, int AreaCode, word Index); + +// Thread-safe access +int Srv_LockArea(S7Object Server, int AreaCode, word Index); +int Srv_UnlockArea(S7Object Server, int AreaCode, word Index); +``` + +#### Callback Mechanisms + +```c +// Tag structure for RW operations +typedef struct { + int Area; // Area code (S7AreaXX) + int DBNumber; // DB number (for S7AreaDB) + int Start; // Starting byte offset + int Size; // Data size in bytes + int WordLen; // Word length code +} TS7Tag, *PS7Tag; + +// Word length codes +const int S7WLBit = 0x01; // 1 bit +const int S7WLByte = 0x02; // 8 bits +const int S7WLChar = 0x03; // 8 bits +const int S7WLWord = 0x04; // 16 bits +const int S7WLDWord = 0x06; // 32 bits +const int S7WLInt = 0x05; // 16 bits signed +const int S7WLDInt = 0x07; // 32 bits signed +const int S7WLReal = 0x08; // 32 bits float + +// Callback types +typedef int (S7API *pfn_RWAreaCallBack)(void *usrPtr, int Sender, + int Operation, PS7Tag PTag, + void *pUsrData); +typedef void (S7API *pfn_SrvCallBack)(void *usrPtr, PSrvEvent PEvent, + int Size); + +// Operation codes +const int OperationRead = 0; +const int OperationWrite = 1; + +// Set callbacks +int Srv_SetRWAreaCallback(S7Object Server, pfn_RWAreaCallBack pCallback, + void *usrPtr); +int Srv_SetEventsCallback(S7Object Server, pfn_SrvCallBack pCallback, + void *usrPtr); +``` + +#### Event Codes + +```c +// Server events +const longword evcServerStarted = 0x00000001; +const longword evcServerStopped = 0x00000002; +const longword evcListenerCannotStart = 0x00000004; +const longword evcClientAdded = 0x00000008; +const longword evcClientRejected = 0x00000010; +const longword evcClientNoRoom = 0x00000020; +const longword evcClientException = 0x00000040; +const longword evcClientDisconnected = 0x00000080; + +// Protocol events +const longword evcPDUincoming = 0x00010000; +const longword evcDataRead = 0x00020000; +const longword evcDataWrite = 0x00040000; +const longword evcNegotiatePDU = 0x00080000; +const longword evcReadSZL = 0x00100000; +const longword evcClock = 0x00200000; +const longword evcUpload = 0x00400000; +const longword evcDownload = 0x00800000; +``` + +--- + +### OpenPLC Runtime v4 Plugin Architecture + +#### Plugin Configuration File (`plugins.conf`) + +``` +name,path,enabled,type,config_path,venv_path +``` + +| Field | Description | +|-------|-------------| +| `name` | Unique plugin identifier (max 64 chars) | +| `path` | Path to `.py` or `.so` file | +| `enabled` | 1=enabled, 0=disabled | +| `type` | 0=Python, 1=Native C/C++ | +| `config_path` | Path to JSON config file | +| `venv_path` | Python venv path (Python plugins only) | + +#### Plugin Lifecycle Functions + +| Function | Required | Description | +|----------|----------|-------------| +| `init(void *args)` | Yes | Initialize plugin, copy runtime args | +| `start_loop()` | Yes | Start background operations | +| `stop_loop()` | Yes | Stop background operations | +| `cleanup()` | No | Release resources | +| `cycle_start()` | No | Called at PLC scan cycle start (native only) | +| `cycle_end()` | No | Called at PLC scan cycle end (native only) | + +#### Runtime Arguments Structure + +```c +typedef struct { + // Boolean buffers [1024][8] + IEC_BOOL *(*bool_input)[8]; + IEC_BOOL *(*bool_output)[8]; + IEC_BOOL *(*bool_memory)[8]; + + // Byte buffers [1024] + IEC_BYTE **byte_input; + IEC_BYTE **byte_output; + + // Integer buffers [1024] + IEC_UINT **int_input; + IEC_UINT **int_output; + IEC_UINT **int_memory; + + // Double integer buffers [1024] + IEC_UDINT **dint_input; + IEC_UDINT **dint_output; + IEC_UDINT **dint_memory; + + // Long integer buffers [1024] + IEC_ULINT **lint_input; + IEC_ULINT **lint_output; + IEC_ULINT **lint_memory; + + // Synchronization + int (*mutex_take)(pthread_mutex_t *mutex); + int (*mutex_give)(pthread_mutex_t *mutex); + pthread_mutex_t *buffer_mutex; + + // Logging + void (*log_info)(const char *format, ...); + void (*log_debug)(const char *format, ...); + void (*log_warn)(const char *format, ...); + void (*log_error)(const char *format, ...); + + // Configuration + const char *config_file_path; + + // Buffer dimensions + int buffer_size; // 1024 + int bits_per_buffer; // 8 +} plugin_runtime_args_t; +``` + +#### Buffer Access Pattern + +```c +// Thread-safe buffer access +args->mutex_take(args->buffer_mutex); + +// Read from bool_input[buffer_idx][bit_idx] +IEC_BOOL *ptr = args->bool_input[buffer_idx][bit_idx]; +if (ptr != NULL) { + value = *ptr; +} + +// Write to int_output[buffer_idx] +IEC_UINT *ptr = args->int_output[buffer_idx]; +if (ptr != NULL) { + *ptr = value; +} + +args->mutex_give(args->buffer_mutex); +``` + +--- + +## Design Decisions + +### 1. Native C/C++ Plugin (Not Python) + +**Rationale:** +- Snap7 is a C/C++ library with complex threading +- Low-latency buffer access required for industrial protocols +- No GIL contention with PLC cycle +- Direct memory operations for endianness conversion + +### 2. Callback-Based Data Access with Cycle Hooks + +**Approach:** +- Use Snap7 RW callback for immediate data access +- Implement `cycle_start()` and `cycle_end()` for optional buffering +- Mutex protection during callback execution + +**Benefits:** +- Real-time data access for S7 clients +- Option to batch updates with PLC cycle +- Flexible synchronization strategy + +### 3. Configurable Data Block Mapping + +**Approach:** +- JSON configuration defines DB numbers and buffer mappings +- Each DB maps to a specific OpenPLC buffer type +- Support for custom start offsets + +**Benefits:** +- Users can match existing HMI configurations +- No recompilation needed to change mappings +- Multiple configurations for different deployments + +### 4. Event-Driven Logging + +**Approach:** +- Configurable event mask for Snap7 +- Log levels controlled via JSON config +- Integration with OpenPLC logging system + +**Benefits:** +- Minimal performance impact when disabled +- Detailed diagnostics when needed +- Consistent log format with rest of runtime + +--- + +## JSON Configuration Schema + +### Complete Configuration Example + +```json +{ + "server": { + "enabled": true, + "bind_address": "0.0.0.0", + "port": 102, + "max_clients": 32, + "work_interval_ms": 100, + "send_timeout_ms": 3000, + "recv_timeout_ms": 3000, + "ping_timeout_ms": 10000, + "pdu_size": 480 + }, + "plc_identity": { + "name": "OpenPLC Runtime", + "module_type": "CPU 315-2 PN/DP", + "serial_number": "S C-XXXXXXXXX", + "copyright": "OpenPLC Project", + "module_name": "OpenPLC" + }, + "data_blocks": [ + { + "db_number": 1, + "description": "Digital Inputs (%IX)", + "size_bytes": 128, + "mapping": { + "type": "bool_input", + "start_buffer": 0, + "bit_addressing": true + } + }, + { + "db_number": 2, + "description": "Digital Outputs (%QX)", + "size_bytes": 128, + "mapping": { + "type": "bool_output", + "start_buffer": 0, + "bit_addressing": true + } + }, + { + "db_number": 10, + "description": "Analog Inputs (%IW)", + "size_bytes": 2048, + "mapping": { + "type": "int_input", + "start_buffer": 0, + "bit_addressing": false + } + }, + { + "db_number": 20, + "description": "Analog Outputs (%QW)", + "size_bytes": 2048, + "mapping": { + "type": "int_output", + "start_buffer": 0, + "bit_addressing": false + } + }, + { + "db_number": 100, + "description": "Memory Words (%MW)", + "size_bytes": 2048, + "mapping": { + "type": "int_memory", + "start_buffer": 0, + "bit_addressing": false + } + }, + { + "db_number": 200, + "description": "Memory DWords (%MD)", + "size_bytes": 4096, + "mapping": { + "type": "dint_memory", + "start_buffer": 0, + "bit_addressing": false + } + } + ], + "system_areas": { + "pe_area": { + "enabled": true, + "size_bytes": 128, + "mapping": { + "type": "bool_input", + "start_buffer": 0 + } + }, + "pa_area": { + "enabled": true, + "size_bytes": 128, + "mapping": { + "type": "bool_output", + "start_buffer": 0 + } + }, + "mk_area": { + "enabled": true, + "size_bytes": 256, + "mapping": { + "type": "bool_memory", + "start_buffer": 0 + } + } + }, + "logging": { + "log_connections": true, + "log_data_access": false, + "log_errors": true + } +} +``` + +### Configuration Field Reference + +#### Server Section + +| Field | Type | Default | Range | Description | +|-------|------|---------|-------|-------------| +| `enabled` | bool | true | - | Enable/disable the S7 server | +| `bind_address` | string | "0.0.0.0" | Valid IP | Network interface to bind | +| `port` | int | 102 | 1-65535 | S7Comm TCP port | +| `max_clients` | int | 32 | 1-1024 | Maximum simultaneous connections | +| `work_interval_ms` | int | 100 | 1-10000 | Worker thread polling interval | +| `send_timeout_ms` | int | 3000 | 100-60000 | Socket send timeout | +| `recv_timeout_ms` | int | 3000 | 100-60000 | Socket receive timeout | +| `ping_timeout_ms` | int | 10000 | 1000-300000 | Keep-alive timeout | +| `pdu_size` | int | 480 | 240-960 | Maximum PDU size | + +#### PLC Identity Section + +These values are returned in S7 SZL (System State List) queries: + +| Field | S7 SZL | Description | +|-------|--------|-------------| +| `name` | 0x001C Index 1 | PLC name (max 32 chars) | +| `module_type` | 0x001C Index 2 | CPU type string | +| `serial_number` | 0x001C Index 3 | Serial number | +| `copyright` | 0x001C Index 4 | Copyright string | +| `module_name` | 0x001C Index 5 | Module name | + +#### Data Blocks Section + +| Field | Type | Description | +|-------|------|-------------| +| `db_number` | int | S7 DB number (1-65535) | +| `description` | string | Human-readable description | +| `size_bytes` | int | DB size in bytes | +| `mapping.type` | enum | OpenPLC buffer type (see below) | +| `mapping.start_buffer` | int | Starting buffer index | +| `mapping.bit_addressing` | bool | Enable bit-level access | + +#### Supported Buffer Types + +| Type | IEC Type | Element Size | Max Elements | +|------|----------|--------------|--------------| +| `bool_input` | BOOL | 1 bit | 8192 (1024 * 8) | +| `bool_output` | BOOL | 1 bit | 8192 | +| `bool_memory` | BOOL | 1 bit | 8192 | +| `byte_input` | BYTE | 1 byte | 1024 | +| `byte_output` | BYTE | 1 byte | 1024 | +| `int_input` | UINT/INT | 2 bytes | 1024 | +| `int_output` | UINT/INT | 2 bytes | 1024 | +| `int_memory` | UINT/INT | 2 bytes | 1024 | +| `dint_input` | UDINT/DINT | 4 bytes | 1024 | +| `dint_output` | UDINT/DINT | 4 bytes | 1024 | +| `dint_memory` | UDINT/DINT | 4 bytes | 1024 | +| `lint_input` | ULINT/LINT | 8 bytes | 1024 | +| `lint_output` | ULINT/LINT | 8 bytes | 1024 | +| `lint_memory` | ULINT/LINT | 8 bytes | 1024 | + +#### System Areas Section + +| Area | S7 Code | Description | +|------|---------|-------------| +| `pe_area` | 0x81 | Process inputs (I area) | +| `pa_area` | 0x82 | Process outputs (Q area) | +| `mk_area` | 0x83 | Markers (M area) | + +#### Logging Section + +| Field | Type | Default | Description | +|-------|------|---------|-------------| +| `log_connections` | bool | true | Log client connect/disconnect | +| `log_data_access` | bool | false | Log read/write operations | +| `log_errors` | bool | true | Log errors and warnings | + +--- + +## Plugin Structure + +### Directory Layout + +``` +core/src/drivers/plugins/native/s7comm/ +├── CMakeLists.txt # Build configuration +├── s7comm_plugin.c # Main plugin implementation +├── s7comm_plugin.h # Plugin header +├── s7comm_config.c # JSON configuration parser +├── s7comm_config.h # Configuration structures +├── s7comm_buffer_mapping.c # OpenPLC buffer mapping logic +├── s7comm_buffer_mapping.h # Buffer mapping header +├── s7comm_callbacks.c # Snap7 RW and event callbacks +├── s7comm_callbacks.h # Callback function declarations +├── s7comm_config.json # Default configuration file +├── docs/ +│ ├── IMPLEMENTATION_PLAN.md # This document +│ └── USER_GUIDE.md # User documentation +├── snap7/ # Snap7 library +│ ├── snap7.h # Snap7 header +│ ├── snap7.cpp # Snap7 C++ wrapper +│ └── src/ # Snap7 source files +│ ├── core/ +│ │ ├── s7_server.h +│ │ ├── s7_server.cpp +│ │ ├── s7_types.h +│ │ └── s7_isotcp.h/cpp +│ └── sys/ +│ ├── snap_tcpsrvr.h/cpp +│ └── snap_threads.h +└── tests/ + ├── test_config.c # Configuration parsing tests + ├── test_buffer_mapping.c # Buffer mapping tests + └── test_integration.py # Integration tests with python-snap7 +``` + +### Core Data Structures + +```c +// s7comm_config.h + +#ifndef S7COMM_CONFIG_H +#define S7COMM_CONFIG_H + +#include +#include +#include + +#define MAX_DATA_BLOCKS 64 +#define MAX_STRING_LEN 64 +#define MAX_DESCRIPTION_LEN 128 + +// Buffer type enumeration +typedef enum { + BUFFER_TYPE_NONE = 0, + BUFFER_TYPE_BOOL_INPUT, + BUFFER_TYPE_BOOL_OUTPUT, + BUFFER_TYPE_BOOL_MEMORY, + BUFFER_TYPE_BYTE_INPUT, + BUFFER_TYPE_BYTE_OUTPUT, + BUFFER_TYPE_INT_INPUT, + BUFFER_TYPE_INT_OUTPUT, + BUFFER_TYPE_INT_MEMORY, + BUFFER_TYPE_DINT_INPUT, + BUFFER_TYPE_DINT_OUTPUT, + BUFFER_TYPE_DINT_MEMORY, + BUFFER_TYPE_LINT_INPUT, + BUFFER_TYPE_LINT_OUTPUT, + BUFFER_TYPE_LINT_MEMORY +} buffer_type_t; + +// Buffer mapping configuration +typedef struct { + buffer_type_t type; + int start_buffer; + bool bit_addressing; +} buffer_mapping_t; + +// Data block configuration +typedef struct { + int db_number; + char description[MAX_DESCRIPTION_LEN]; + int size_bytes; + buffer_mapping_t mapping; + uint8_t *data_buffer; // Allocated S7 data buffer + pthread_mutex_t *lock; // Per-DB lock for thread safety +} data_block_config_t; + +// System area configuration +typedef struct { + bool enabled; + int size_bytes; + buffer_mapping_t mapping; + uint8_t *data_buffer; +} system_area_config_t; + +// PLC identity configuration +typedef struct { + char name[MAX_STRING_LEN]; + char module_type[MAX_STRING_LEN]; + char serial_number[MAX_STRING_LEN]; + char copyright[MAX_STRING_LEN]; + char module_name[MAX_STRING_LEN]; +} plc_identity_t; + +// Logging configuration +typedef struct { + bool log_connections; + bool log_data_access; + bool log_errors; +} logging_config_t; + +// Complete S7Comm configuration +typedef struct { + // Server settings + bool enabled; + char bind_address[MAX_STRING_LEN]; + uint16_t port; + int max_clients; + int work_interval_ms; + int send_timeout_ms; + int recv_timeout_ms; + int ping_timeout_ms; + int pdu_size; + + // PLC identity + plc_identity_t identity; + + // Data blocks + int num_data_blocks; + data_block_config_t data_blocks[MAX_DATA_BLOCKS]; + + // System areas + system_area_config_t pe_area; + system_area_config_t pa_area; + system_area_config_t mk_area; + + // Logging + logging_config_t logging; +} s7comm_config_t; + +// Configuration functions +int s7comm_parse_config(const char *config_path, s7comm_config_t *config); +int s7comm_validate_config(const s7comm_config_t *config); +void s7comm_free_config(s7comm_config_t *config); +void s7comm_print_config(const s7comm_config_t *config); + +#endif // S7COMM_CONFIG_H +``` + +### Plugin Interface + +```c +// s7comm_plugin.h + +#ifndef S7COMM_PLUGIN_H +#define S7COMM_PLUGIN_H + +#include "plugin_types.h" + +// Required plugin lifecycle functions +int init(void *args); +void start_loop(void); +void stop_loop(void); +void cleanup(void); + +// Optional cycle hooks (native plugins only) +void cycle_start(void); +void cycle_end(void); + +#endif // S7COMM_PLUGIN_H +``` + +--- + +## Implementation Details + +### Endianness Handling + +S7Comm uses big-endian (network byte order). OpenPLC uses native endianness. + +```c +// s7comm_buffer_mapping.c + +#include + +// Convert host to S7 (big-endian) +static inline uint16_t host_to_s7_16(uint16_t val) { + return htobe16(val); +} + +static inline uint32_t host_to_s7_32(uint32_t val) { + return htobe32(val); +} + +static inline uint64_t host_to_s7_64(uint64_t val) { + return htobe64(val); +} + +// Convert S7 to host +static inline uint16_t s7_to_host_16(uint16_t val) { + return be16toh(val); +} + +static inline uint32_t s7_to_host_32(uint32_t val) { + return be32toh(val); +} + +static inline uint64_t s7_to_host_64(uint64_t val) { + return be64toh(val); +} +``` + +### Bit Addressing + +S7 uses byte.bit addressing (e.g., DBX10.3 = byte 10, bit 3): + +```c +// Convert S7 bit address to OpenPLC buffer indices +static void s7_bit_to_openplc(int s7_bit_address, int start_buffer, + int *buffer_idx, int *bit_idx) { + int s7_byte = s7_bit_address / 8; + int s7_bit = s7_bit_address % 8; + + *buffer_idx = start_buffer + s7_byte; + *bit_idx = s7_bit; +} + +// Read single bit from OpenPLC buffer +static int read_bool_bit(plugin_runtime_args_t *args, buffer_type_t type, + int buffer_idx, int bit_idx, uint8_t *value) { + IEC_BOOL *(*buffer)[8] = NULL; + + switch (type) { + case BUFFER_TYPE_BOOL_INPUT: + buffer = args->bool_input; + break; + case BUFFER_TYPE_BOOL_OUTPUT: + buffer = args->bool_output; + break; + case BUFFER_TYPE_BOOL_MEMORY: + buffer = args->bool_memory; + break; + default: + return -1; + } + + if (buffer_idx >= args->buffer_size || bit_idx >= args->bits_per_buffer) { + return -1; + } + + IEC_BOOL *ptr = buffer[buffer_idx][bit_idx]; + *value = (ptr != NULL) ? *ptr : 0; + return 0; +} +``` + +### Thread Safety Strategy + +```c +// s7comm_callbacks.c + +int S7API s7comm_rw_callback(void *usrPtr, int Sender, int Operation, + PS7Tag PTag, void *pUsrData) { + s7comm_context_t *ctx = (s7comm_context_t *)usrPtr; + plugin_runtime_args_t *args = ctx->runtime_args; + + // Acquire OpenPLC buffer mutex + args->mutex_take(args->buffer_mutex); + + int result = 0; + bool is_read = (Operation == OperationRead); + + switch (PTag->Area) { + case S7AreaPE: + result = handle_pe_access(ctx, PTag, pUsrData, is_read); + break; + case S7AreaPA: + result = handle_pa_access(ctx, PTag, pUsrData, is_read); + break; + case S7AreaMK: + result = handle_mk_access(ctx, PTag, pUsrData, is_read); + break; + case S7AreaDB: + result = handle_db_access(ctx, PTag, pUsrData, is_read); + break; + default: + result = errSrvUnknownArea; + break; + } + + // Release mutex + args->mutex_give(args->buffer_mutex); + + return result; +} +``` + +### DB Access Handler + +```c +static int handle_db_access(s7comm_context_t *ctx, PS7Tag PTag, + void *pUsrData, bool is_read) { + // Find matching DB configuration + data_block_config_t *db = find_db_config(ctx->config, PTag->DBNumber); + if (db == NULL) { + return errSrvUnknownArea; + } + + // Validate access bounds + if (PTag->Start + PTag->Size > db->size_bytes) { + return errSrvInvalidParams; + } + + // Handle based on word length + switch (PTag->WordLen) { + case S7WLBit: + return handle_bit_access(ctx, db, PTag, pUsrData, is_read); + case S7WLByte: + case S7WLChar: + return handle_byte_access(ctx, db, PTag, pUsrData, is_read); + case S7WLWord: + case S7WLInt: + return handle_word_access(ctx, db, PTag, pUsrData, is_read); + case S7WLDWord: + case S7WLDInt: + case S7WLReal: + return handle_dword_access(ctx, db, PTag, pUsrData, is_read); + default: + return errSrvInvalidParams; + } +} +``` + +### Configuration Validation + +```c +// s7comm_config.c + +int s7comm_validate_config(const s7comm_config_t *config) { + // Validate port + if (config->port == 0) { + return -1; + } + + // Validate timeouts + if (config->send_timeout_ms < 100 || config->recv_timeout_ms < 100) { + return -1; + } + + // Validate PDU size + if (config->pdu_size < 240 || config->pdu_size > 960) { + return -1; + } + + // Check for duplicate DB numbers + for (int i = 0; i < config->num_data_blocks; i++) { + for (int j = i + 1; j < config->num_data_blocks; j++) { + if (config->data_blocks[i].db_number == + config->data_blocks[j].db_number) { + return -1; + } + } + } + + // Validate buffer mappings don't exceed limits + for (int i = 0; i < config->num_data_blocks; i++) { + const data_block_config_t *db = &config->data_blocks[i]; + int max_offset = get_buffer_max_offset(db->mapping.type); + + int required = db->mapping.start_buffer + + (db->size_bytes / get_element_size(db->mapping.type)); + + if (required > max_offset) { + return -1; + } + } + + return 0; +} +``` + +--- + +## Phased Implementation Plan + +### Phase 1: Foundation (1-2 weeks) + +**Objectives:** +- Project structure setup +- Snap7 library integration +- Basic server lifecycle + +**Tasks:** +1. Create plugin directory structure +2. Copy/integrate Snap7 source files +3. Create CMakeLists.txt +4. Implement plugin lifecycle stubs (`init`, `start_loop`, `stop_loop`, `cleanup`) +5. Hardcode minimal configuration for testing +6. Test server starts and accepts connections +7. Add entry to `plugins.conf` + +**Deliverables:** +- Compilable `libs7comm.so` plugin +- Server accepts connections on port 102 +- Basic logging output + +**Verification:** +```bash +# Build plugin +cd build && cmake .. && make s7comm_plugin + +# Test with python-snap7 +python3 -c " +import snap7 +client = snap7.client.Client() +client.connect('127.0.0.1', 0, 0) +print('Connected:', client.get_connected()) +client.disconnect() +" +``` + +### Phase 2: Configuration System (1 week) + +**Objectives:** +- JSON configuration parsing +- All parameters configurable + +**Tasks:** +1. Add cJSON library (or similar) for JSON parsing +2. Implement `s7comm_parse_config()` function +3. Create default `s7comm_config.json` +4. Implement configuration validation +5. Apply server parameters from config +6. Add PLC identity configuration + +**Deliverables:** +- Plugin fully configurable via JSON +- Configuration validation with clear error messages +- Default configuration file + +**Verification:** +- Modify port in config, verify server binds to new port +- Invalid config rejected with clear error message + +### Phase 3: Buffer Mapping (1-2 weeks) + +**Objectives:** +- Dynamic buffer allocation +- Complete S7 area mapping + +**Tasks:** +1. Implement buffer allocation based on config +2. Register S7 areas (PE, PA, MK, DBs) with Snap7 +3. Implement RW callback handler +4. Implement PE/PA area handlers (bool_input/bool_output) +5. Implement MK area handler (bool_memory) +6. Implement DB handlers for all buffer types +7. Add endianness conversion +8. Support bit-level addressing + +**Deliverables:** +- All configured areas accessible via S7 +- Correct data mapping to OpenPLC buffers +- Proper byte order handling + +**Verification:** +```python +# Test DB read/write +import snap7 +client = snap7.client.Client() +client.connect('127.0.0.1', 0, 0) + +# Write to DB10 (int_input mapping) +data = bytearray([0x12, 0x34]) +client.db_write(10, 0, data) + +# Read back +result = client.db_read(10, 0, 2) +assert result == data +``` + +### Phase 4: Thread Safety (1 week) + +**Objectives:** +- Ensure thread-safe buffer access +- Implement cycle hooks + +**Tasks:** +1. Integrate with plugin mutex (buffer_mutex) +2. Test concurrent access from multiple clients +3. Implement `cycle_start()` hook +4. Implement `cycle_end()` hook +5. Add stress testing +6. Profile and optimize hot paths + +**Deliverables:** +- No race conditions under load +- Deterministic buffer updates +- Performance metrics + +**Verification:** +- Run multiple S7 clients simultaneously +- Verify PLC cycle timing unaffected +- Memory sanitizer clean run + +### Phase 5: Logging and Diagnostics (0.5 weeks) + +**Objectives:** +- Comprehensive logging +- Event handling + +**Tasks:** +1. Implement Snap7 event callback +2. Add connection logging (configurable) +3. Add data access logging (configurable) +4. Add error logging with context +5. Implement server status reporting + +**Deliverables:** +- Configurable logging output +- Clear diagnostic messages +- Status queryable via logs + +### Phase 6: Testing and Documentation (1 week) + +**Objectives:** +- Comprehensive test coverage +- User documentation + +**Tasks:** +1. Unit tests for configuration parsing +2. Unit tests for buffer mapping +3. Integration tests with python-snap7 +4. Performance benchmarks +5. Write USER_GUIDE.md +6. Add configuration examples +7. Update main plugin README + +**Deliverables:** +- Test suite with >80% coverage +- Complete user documentation +- Example configurations + +### Phase 7: Advanced Features (Optional, Future) + +**Potential enhancements:** +1. SZL data responses (system queries) +2. Password protection for areas +3. Block upload/download simulation +4. Counter/Timer area support +5. WebSocket monitoring interface +6. Multiple server instances +7. Connection rate limiting + +--- + +## Testing Strategy + +### Unit Tests + +```c +// test_config.c + +void test_parse_valid_config(void) { + s7comm_config_t config; + int result = s7comm_parse_config("test_config.json", &config); + + assert(result == 0); + assert(config.port == 102); + assert(config.num_data_blocks == 6); + assert(config.data_blocks[0].db_number == 1); +} + +void test_parse_invalid_config(void) { + s7comm_config_t config; + int result = s7comm_parse_config("invalid_config.json", &config); + + assert(result != 0); +} + +void test_validate_duplicate_db(void) { + s7comm_config_t config = {0}; + config.num_data_blocks = 2; + config.data_blocks[0].db_number = 1; + config.data_blocks[1].db_number = 1; // Duplicate + + int result = s7comm_validate_config(&config); + assert(result != 0); +} +``` + +### Integration Tests + +```python +# test_integration.py + +import snap7 +import pytest + +@pytest.fixture +def s7_client(): + client = snap7.client.Client() + client.connect('127.0.0.1', 0, 0, 102) + yield client + client.disconnect() + +def test_read_db(s7_client): + """Test reading from configured DB""" + data = s7_client.db_read(10, 0, 10) + assert len(data) == 10 + +def test_write_db(s7_client): + """Test writing to configured DB""" + data = bytearray([0x01, 0x02, 0x03, 0x04]) + s7_client.db_write(20, 0, data) + result = s7_client.db_read(20, 0, 4) + assert result == data + +def test_read_inputs(s7_client): + """Test reading process inputs (PE area)""" + data = s7_client.read_area(snap7.types.Areas.PE, 0, 0, 10) + assert len(data) == 10 + +def test_write_outputs(s7_client): + """Test writing process outputs (PA area)""" + data = bytearray([0xFF, 0x00]) + s7_client.write_area(snap7.types.Areas.PA, 0, 0, data) + +def test_concurrent_access(s7_client): + """Test multiple concurrent clients""" + import threading + + def client_task(): + c = snap7.client.Client() + c.connect('127.0.0.1', 0, 0, 102) + for _ in range(100): + c.db_read(10, 0, 10) + c.disconnect() + + threads = [threading.Thread(target=client_task) for _ in range(10)] + for t in threads: + t.start() + for t in threads: + t.join() +``` + +### Performance Benchmarks + +```python +# benchmark.py + +import snap7 +import time + +def benchmark_read(client, iterations=10000): + start = time.time() + for _ in range(iterations): + client.db_read(10, 0, 100) + elapsed = time.time() - start + print(f"Read: {iterations/elapsed:.0f} ops/sec") + +def benchmark_write(client, iterations=10000): + data = bytearray(100) + start = time.time() + for _ in range(iterations): + client.db_write(20, 0, data) + elapsed = time.time() - start + print(f"Write: {iterations/elapsed:.0f} ops/sec") +``` + +--- + +## References + +### External Documentation + +1. **Snap7 Project**: https://github.com/davenardella/snap7 +2. **Snap7 Documentation**: http://snap7.sourceforge.net/ +3. **S7Comm Protocol Analysis**: https://github.com/orange-cyberdefense/S7Comm-Wireshark-Dissector +4. **RFC 1006 (ISO-on-TCP)**: https://tools.ietf.org/html/rfc1006 + +### Internal Documentation + +1. **OpenPLC v4 Plugin Guide**: `core/src/drivers/README.md` +2. **Plugin Types Header**: `core/src/drivers/plugin_types.h` +3. **Image Tables**: `core/src/plc_app/image_tables.h` +4. **Architecture Overview**: `docs/ARCHITECTURE.md` + +### Related Code + +1. **OpenPLC v3 S7Comm**: `OpenPLC_v3/utils/snap7_src/wrapper/oplc_snap7.cpp` +2. **Modbus Slave Plugin**: `core/src/drivers/plugins/python/modbus_slave/simple_modbus.py` +3. **Native Plugin Example**: `core/src/drivers/plugins/native/examples/test_plugin.c` + +--- + +## Appendix A: S7 Address Reference + +### S7 Area Codes + +| Area | Code | Description | Access | +|------|------|-------------|--------| +| PE | 0x81 | Process inputs | Read | +| PA | 0x82 | Process outputs | Read/Write | +| MK | 0x83 | Markers/Flags | Read/Write | +| DB | 0x84 | Data blocks | Read/Write | +| CT | 0x1C | Counters | Read/Write | +| TM | 0x1D | Timers | Read/Write | + +### S7 Word Length Codes + +| Code | Name | Size | Description | +|------|------|------|-------------| +| 0x01 | Bit | 1 bit | Single bit | +| 0x02 | Byte | 1 byte | 8 bits | +| 0x03 | Char | 1 byte | ASCII character | +| 0x04 | Word | 2 bytes | 16-bit unsigned | +| 0x05 | Int | 2 bytes | 16-bit signed | +| 0x06 | DWord | 4 bytes | 32-bit unsigned | +| 0x07 | DInt | 4 bytes | 32-bit signed | +| 0x08 | Real | 4 bytes | IEEE 754 float | + +### S7 Address Examples + +| S7 Address | Area | DB | Offset | Bit | Description | +|------------|------|-----|--------|-----|-------------| +| I0.0 | PE | - | 0 | 0 | Input byte 0, bit 0 | +| Q10.5 | PA | - | 10 | 5 | Output byte 10, bit 5 | +| M100.0 | MK | - | 100 | 0 | Marker byte 100, bit 0 | +| DB1.DBX0.0 | DB | 1 | 0 | 0 | DB1, byte 0, bit 0 | +| DB10.DBW0 | DB | 10 | 0 | - | DB10, word at byte 0 | +| DB10.DBD4 | DB | 10 | 4 | - | DB10, dword at byte 4 | + +--- + +## Appendix B: Error Codes + +### Snap7 Server Errors + +| Code | Name | Description | +|------|------|-------------| +| 0x00100000 | errSrvCannotStart | Server failed to start | +| 0x00200000 | errSrvDBNullPointer | NULL pointer for DB registration | +| 0x00300000 | errSrvAreaAlreadyExists | Area already registered | +| 0x00400000 | errSrvUnknownArea | Unknown area requested | +| 0x00500000 | errSrvInvalidParams | Invalid parameters | +| 0x00600000 | errSrvTooManyDB | Too many DBs registered | +| 0x00700000 | errSrvInvalidParamNumber | Invalid parameter number | +| 0x00800000 | errSrvCannotChangeParam | Cannot change parameter | + +--- + +*Document Version: 1.0* +*Last Updated: 2026-01-12* +*Author: OpenPLC Development Team* diff --git a/core/src/drivers/plugins/native/s7comm/docs/JSON_SCHEMA.md b/core/src/drivers/plugins/native/s7comm/docs/JSON_SCHEMA.md new file mode 100644 index 00000000..e90aa28f --- /dev/null +++ b/core/src/drivers/plugins/native/s7comm/docs/JSON_SCHEMA.md @@ -0,0 +1,460 @@ +# S7Comm Plugin JSON Configuration Schema + +This document defines the JSON configuration schema for the S7Comm plugin. +It is intended for OpenPLC Editor developers implementing the configuration UI. + +## JSON Structure Overview + +```json +{ + "server": { ... }, + "plc_identity": { ... }, + "data_blocks": [ ... ], + "system_areas": { ... }, + "logging": { ... } +} +``` + +All sections are optional. Missing sections use default values. + +## Complete Schema Reference + +### Root Object + +| Field | Type | Required | Default | Description | +|-------|------|----------|---------|-------------| +| `server` | object | No | See below | Server configuration | +| `plc_identity` | object | No | See below | PLC identity for S7 clients | +| `data_blocks` | array | No | `[]` | Data block mappings | +| `system_areas` | object | No | All disabled | S7 system area mappings | +| `logging` | object | No | See below | Logging configuration | + +--- + +### server Object + +```json +{ + "server": { + "enabled": true, + "bind_address": "0.0.0.0", + "port": 102, + "max_clients": 32, + "work_interval_ms": 100, + "send_timeout_ms": 3000, + "recv_timeout_ms": 3000, + "ping_timeout_ms": 10000, + "pdu_size": 480 + } +} +``` + +| Field | Type | Required | Default | Constraints | Description | +|-------|------|----------|---------|-------------|-------------| +| `enabled` | boolean | No | `true` | - | Enable/disable the S7 server | +| `bind_address` | string | No | `"0.0.0.0"` | Valid IP or `"0.0.0.0"` | Network interface to bind | +| `port` | integer | No | `102` | 1-65535 | TCP port (102 = standard S7) | +| `max_clients` | integer | No | `32` | 1-128 | Max simultaneous connections | +| `work_interval_ms` | integer | No | `100` | 10-1000 | Internal polling interval (ms) | +| `send_timeout_ms` | integer | No | `3000` | 100-30000 | Socket send timeout (ms) | +| `recv_timeout_ms` | integer | No | `3000` | 100-30000 | Socket receive timeout (ms) | +| `ping_timeout_ms` | integer | No | `10000` | 1000-60000 | Keep-alive timeout (ms) | +| `pdu_size` | integer | No | `480` | 240-960 | Maximum PDU size | + +**Notes:** +- Port 102 requires root/administrator privileges +- Use port > 1024 to avoid privilege requirements + +--- + +### plc_identity Object + +```json +{ + "plc_identity": { + "name": "OpenPLC Runtime", + "module_type": "CPU 315-2 PN/DP", + "serial_number": "S C-XXXXXXXXX", + "copyright": "OpenPLC Project", + "module_name": "OpenPLC" + } +} +``` + +| Field | Type | Required | Default | Constraints | Description | +|-------|------|----------|---------|-------------|-------------| +| `name` | string | No | `"OpenPLC Runtime"` | Max 64 chars | PLC name | +| `module_type` | string | No | `"CPU 315-2 PN/DP"` | Max 64 chars | CPU type string | +| `serial_number` | string | No | `"S C-XXXXXXXXX"` | Max 64 chars | Serial number | +| `copyright` | string | No | `"OpenPLC Project"` | Max 64 chars | Copyright string | +| `module_name` | string | No | `"OpenPLC"` | Max 64 chars | Module name | + +**Notes:** +- These values are returned in S7 SZL (System Status List) queries +- `module_type` should match a real Siemens CPU for best HMI compatibility + +--- + +### data_blocks Array + +```json +{ + "data_blocks": [ + { + "db_number": 1, + "description": "Digital Inputs", + "size_bytes": 128, + "mapping": { + "type": "bool_input", + "start_buffer": 0, + "bit_addressing": true + } + } + ] +} +``` + +Each data block object: + +| Field | Type | Required | Default | Constraints | Description | +|-------|------|----------|---------|-------------|-------------| +| `db_number` | integer | **Yes** | - | 1-65535 | S7 DB number | +| `description` | string | No | `""` | Max 128 chars | Human-readable description | +| `size_bytes` | integer | **Yes** | - | 1-65536 | DB size in bytes | +| `mapping` | object | **Yes** | - | See below | Buffer mapping configuration | + +**mapping Object:** + +| Field | Type | Required | Default | Constraints | Description | +|-------|------|----------|---------|-------------|-------------| +| `type` | string | **Yes** | - | See valid types | OpenPLC buffer type | +| `start_buffer` | integer | No | `0` | 0-1023 | Starting index in OpenPLC buffer | +| `bit_addressing` | boolean | No | `false` | - | Enable bit-level access (BOOL types) | + +**Valid `type` values:** + +| Type | IEC Address | Element Size | Direction | Description | +|------|-------------|--------------|-----------|-------------| +| `"bool_input"` | %IX | 1 bit | Read | Digital inputs | +| `"bool_output"` | %QX | 1 bit | Read/Write | Digital outputs | +| `"bool_memory"` | %MX | 1 bit | Read/Write | Internal markers | +| `"byte_input"` | %IB | 1 byte | Read | Byte inputs | +| `"byte_output"` | %QB | 1 byte | Read/Write | Byte outputs | +| `"int_input"` | %IW | 2 bytes | Read | Word inputs (UINT) | +| `"int_output"` | %QW | 2 bytes | Read/Write | Word outputs (UINT) | +| `"int_memory"` | %MW | 2 bytes | Read/Write | Word memory (UINT) | +| `"dint_input"` | %ID | 4 bytes | Read | Double word inputs (UDINT) | +| `"dint_output"` | %QD | 4 bytes | Read/Write | Double word outputs (UDINT) | +| `"dint_memory"` | %MD | 4 bytes | Read/Write | Double word memory (UDINT) | +| `"lint_input"` | %IL | 8 bytes | Read | Long word inputs (ULINT) | +| `"lint_output"` | %QL | 8 bytes | Read/Write | Long word outputs (ULINT) | +| `"lint_memory"` | %ML | 8 bytes | Read/Write | Long word memory (ULINT) | + +**Notes:** +- Maximum 64 data blocks supported +- Input types are read-only from S7 client perspective +- Output and memory types are read/write + +--- + +### system_areas Object + +```json +{ + "system_areas": { + "pe_area": { + "enabled": true, + "size_bytes": 128, + "mapping": { + "type": "bool_input", + "start_buffer": 0 + } + }, + "pa_area": { + "enabled": true, + "size_bytes": 128, + "mapping": { + "type": "bool_output", + "start_buffer": 0 + } + }, + "mk_area": { + "enabled": true, + "size_bytes": 256, + "mapping": { + "type": "bool_memory", + "start_buffer": 0 + } + } + } +} +``` + +Each system area (`pe_area`, `pa_area`, `mk_area`): + +| Field | Type | Required | Default | Constraints | Description | +|-------|------|----------|---------|-------------|-------------| +| `enabled` | boolean | No | `false` | - | Enable this area | +| `size_bytes` | integer | No | `0` | 1-65536 | Area size in bytes | +| `mapping` | object | No | - | See above | Buffer mapping (same as data_blocks) | + +**Area Descriptions:** + +| Area | S7 Area Code | S7 Address | Typical Use | +|------|--------------|------------|-------------| +| `pe_area` | PE (Process Inputs) | I (e.g., I0.0) | Digital/analog inputs | +| `pa_area` | PA (Process Outputs) | Q (e.g., Q0.0) | Digital/analog outputs | +| `mk_area` | MK (Markers) | M (e.g., M0.0) | Internal flags/memory | + +--- + +### logging Object + +```json +{ + "logging": { + "log_connections": true, + "log_data_access": false, + "log_errors": true + } +} +``` + +| Field | Type | Required | Default | Description | +|-------|------|----------|---------|-------------| +| `log_connections` | boolean | No | `true` | Log client connect/disconnect | +| `log_data_access` | boolean | No | `false` | Log read/write operations (verbose) | +| `log_errors` | boolean | No | `true` | Log errors and warnings | + +**Notes:** +- `log_data_access` generates high log volume - use for debugging only +- All logs go through the centralized logging system (visible in Editor) + +--- + +## Complete Example Configurations + +### Minimal Configuration + +```json +{ + "server": { + "enabled": true, + "port": 102 + } +} +``` + +### Digital I/O Only + +```json +{ + "server": { + "enabled": true, + "port": 102 + }, + "data_blocks": [ + { + "db_number": 1, + "description": "Digital Inputs", + "size_bytes": 128, + "mapping": { + "type": "bool_input", + "start_buffer": 0, + "bit_addressing": true + } + }, + { + "db_number": 2, + "description": "Digital Outputs", + "size_bytes": 128, + "mapping": { + "type": "bool_output", + "start_buffer": 0, + "bit_addressing": true + } + } + ] +} +``` + +### Mixed I/O with System Areas + +```json +{ + "server": { + "enabled": true, + "bind_address": "0.0.0.0", + "port": 102, + "max_clients": 16 + }, + "plc_identity": { + "name": "Production PLC", + "module_type": "CPU 315-2 PN/DP" + }, + "data_blocks": [ + { + "db_number": 10, + "description": "Analog Inputs", + "size_bytes": 256, + "mapping": { + "type": "int_input", + "start_buffer": 0 + } + }, + { + "db_number": 20, + "description": "Analog Outputs", + "size_bytes": 256, + "mapping": { + "type": "int_output", + "start_buffer": 0 + } + }, + { + "db_number": 100, + "description": "Setpoints", + "size_bytes": 512, + "mapping": { + "type": "int_memory", + "start_buffer": 0 + } + } + ], + "system_areas": { + "pe_area": { + "enabled": true, + "size_bytes": 128, + "mapping": { + "type": "bool_input", + "start_buffer": 0 + } + }, + "pa_area": { + "enabled": true, + "size_bytes": 128, + "mapping": { + "type": "bool_output", + "start_buffer": 0 + } + }, + "mk_area": { + "enabled": true, + "size_bytes": 256, + "mapping": { + "type": "bool_memory", + "start_buffer": 0 + } + } + }, + "logging": { + "log_connections": true, + "log_data_access": false, + "log_errors": true + } +} +``` + +### Non-Privileged Port + +For running without root privileges: + +```json +{ + "server": { + "enabled": true, + "port": 1102, + "max_clients": 8 + }, + "data_blocks": [ + { + "db_number": 1, + "size_bytes": 1024, + "mapping": { + "type": "int_memory", + "start_buffer": 0 + } + } + ] +} +``` + +--- + +## Editor UI Recommendations + +### Required Fields +- `server.enabled` - Toggle to enable/disable plugin +- `server.port` - Port number input (validate 1-65535) +- At least one data_block or system_area for meaningful operation + +### Suggested UI Layout + +1. **Server Settings Panel** + - Enable/Disable toggle + - Port number (default 102, warn if < 1024) + - Max clients (slider 1-128) + - Advanced: timeouts, PDU size (collapsible) + +2. **PLC Identity Panel** (collapsible, optional) + - Text fields for identity strings + - Module type dropdown with common CPU types + +3. **Data Blocks Panel** + - Add/Remove data block buttons + - For each block: + - DB Number (required, unique) + - Description (optional) + - Size in bytes (required) + - Mapping type dropdown + - Start buffer index + - Bit addressing checkbox (only for bool types) + +4. **System Areas Panel** (collapsible) + - PE Area (Inputs) - enable + size + mapping + - PA Area (Outputs) - enable + size + mapping + - MK Area (Markers) - enable + size + mapping + +5. **Logging Panel** (collapsible) + - Checkboxes for each log type + +### Validation Rules + +1. **DB Numbers**: Must be unique, 1-65535 +2. **Sizes**: Must be > 0, <= 65536 +3. **Port**: Warn if 102 (requires root), error if out of range +4. **Start Buffer**: Validate against buffer_size (typically 1024) +5. **Type Consistency**: Warn if mixing input types with write-heavy use cases + +--- + +## Runtime Behavior + +### Double-Buffering Architecture + +The plugin uses double-buffering for thread safety: +- **S7 Buffer**: What Snap7 clients read/write (asynchronous access) +- **Shadow Buffer**: Used for synchronization with OpenPLC + +Sync occurs at the end of each PLC scan cycle: +1. If no clients connected: skip sync entirely (optimization) +2. Copy S7 buffers -> shadow buffers (brief mutex lock) +3. Sync shadow <-> OpenPLC buffers +4. Copy shadow buffers -> S7 buffers (brief mutex lock) + +### Data Direction + +| Buffer Type | S7 Client Can Read | S7 Client Can Write | +|-------------|-------------------|---------------------| +| `*_input` | Yes | No (ignored) | +| `*_output` | Yes | Yes | +| `*_memory` | Yes | Yes | + +### Byte Order + +S7 protocol uses **big-endian** (network byte order). The plugin automatically converts between big-endian (S7) and little-endian (OpenPLC/x86). + +--- + +*Schema Version: 1.0* +*Last Updated: 2026-01-12* diff --git a/core/src/drivers/plugins/native/s7comm/docs/USER_GUIDE.md b/core/src/drivers/plugins/native/s7comm/docs/USER_GUIDE.md new file mode 100644 index 00000000..05e08cf7 --- /dev/null +++ b/core/src/drivers/plugins/native/s7comm/docs/USER_GUIDE.md @@ -0,0 +1,554 @@ +# S7Comm Plugin User Guide + +## Overview + +The S7Comm plugin enables OpenPLC Runtime v4 to communicate using the Siemens S7 protocol. This allows S7-compatible HMIs, SCADA systems, and other industrial equipment to read and write data from/to the OpenPLC runtime. + +## Features + +- Full S7Comm protocol support via Snap7 library +- Configurable data block mappings +- Support for all OpenPLC buffer types (BOOL, BYTE, UINT, UDINT, ULINT) +- Multiple concurrent client connections +- Configurable PLC identity for S7 client compatibility +- Double-buffered, thread-safe operation (S7 clients run asynchronously from PLC cycle) +- Automatic optimization: no overhead when no clients are connected +- Centralized logging integrated with OpenPLC Editor + +## Quick Start + +### 1. Enable the Plugin + +Add or uncomment the S7Comm entry in `plugins.conf`: + +``` +s7comm,./core/src/drivers/plugins/native/s7comm/libs7comm.so,1,1,./core/src/drivers/plugins/native/s7comm/s7comm_config.json, +``` + +### 2. Configure the Plugin + +Edit `s7comm_config.json` to match your requirements: + +```json +{ + "server": { + "enabled": true, + "port": 102 + }, + "data_blocks": [ + { + "db_number": 1, + "size_bytes": 128, + "mapping": { + "type": "bool_input", + "start_buffer": 0 + } + } + ] +} +``` + +### 3. Start the Runtime + +```bash +sudo ./start_openplc.sh +``` + +### 4. Connect with S7 Client + +The plugin listens on port 102 (default S7 port). Connect using any S7-compatible client. + +## Configuration Reference + +### Server Settings + +```json +{ + "server": { + "enabled": true, + "bind_address": "0.0.0.0", + "port": 102, + "max_clients": 32, + "work_interval_ms": 100, + "send_timeout_ms": 3000, + "recv_timeout_ms": 3000, + "ping_timeout_ms": 10000, + "pdu_size": 480 + } +} +``` + +| Setting | Default | Description | +|---------|---------|-------------| +| `enabled` | true | Enable/disable the S7 server | +| `bind_address` | "0.0.0.0" | Network interface to bind (0.0.0.0 = all) | +| `port` | 102 | TCP port (102 is standard S7 port) | +| `max_clients` | 32 | Maximum simultaneous connections | +| `work_interval_ms` | 100 | Internal polling interval | +| `send_timeout_ms` | 3000 | Socket send timeout | +| `recv_timeout_ms` | 3000 | Socket receive timeout | +| `ping_timeout_ms` | 10000 | Keep-alive timeout | +| `pdu_size` | 480 | Maximum PDU size (240-960) | + +**Note:** Port 102 requires root privileges on Linux. Use `sudo` or configure capabilities. + +### PLC Identity + +Configure how the PLC identifies itself to S7 clients: + +```json +{ + "plc_identity": { + "name": "OpenPLC Runtime", + "module_type": "CPU 315-2 PN/DP", + "serial_number": "S C-XXXXXXXXX", + "copyright": "OpenPLC Project", + "module_name": "OpenPLC" + } +} +``` + +These values are returned when clients query the PLC's system state list (SZL). + +### Data Block Mappings + +Data blocks map S7 DB areas to OpenPLC memory buffers: + +```json +{ + "data_blocks": [ + { + "db_number": 10, + "description": "Analog Inputs", + "size_bytes": 2048, + "mapping": { + "type": "int_input", + "start_buffer": 0, + "bit_addressing": false + } + } + ] +} +``` + +| Field | Description | +|-------|-------------| +| `db_number` | S7 DB number (1-65535) | +| `description` | Human-readable description (optional) | +| `size_bytes` | Size of the data block in bytes | +| `mapping.type` | OpenPLC buffer type to map to | +| `mapping.start_buffer` | Starting index in OpenPLC buffer | +| `mapping.bit_addressing` | Enable bit-level access (for BOOL types) | + +### Supported Buffer Types + +| Type | IEC Address | Element Size | Max Count | +|------|-------------|--------------|-----------| +| `bool_input` | %IX | 1 bit | 8192 | +| `bool_output` | %QX | 1 bit | 8192 | +| `bool_memory` | %MX | 1 bit | 8192 | +| `byte_input` | %IB | 1 byte | 1024 | +| `byte_output` | %QB | 1 byte | 1024 | +| `int_input` | %IW | 2 bytes | 1024 | +| `int_output` | %QW | 2 bytes | 1024 | +| `int_memory` | %MW | 2 bytes | 1024 | +| `dint_input` | %ID | 4 bytes | 1024 | +| `dint_output` | %QD | 4 bytes | 1024 | +| `dint_memory` | %MD | 4 bytes | 1024 | +| `lint_input` | %IL | 8 bytes | 1024 | +| `lint_output` | %QL | 8 bytes | 1024 | +| `lint_memory` | %ML | 8 bytes | 1024 | + +### System Areas + +Configure S7 system areas (PE, PA, MK): + +```json +{ + "system_areas": { + "pe_area": { + "enabled": true, + "size_bytes": 128, + "mapping": { + "type": "bool_input", + "start_buffer": 0 + } + }, + "pa_area": { + "enabled": true, + "size_bytes": 128, + "mapping": { + "type": "bool_output", + "start_buffer": 0 + } + }, + "mk_area": { + "enabled": true, + "size_bytes": 256, + "mapping": { + "type": "bool_memory", + "start_buffer": 0 + } + } + } +} +``` + +| Area | S7 Address | Description | +|------|------------|-------------| +| `pe_area` | I (Inputs) | Process inputs | +| `pa_area` | Q (Outputs) | Process outputs | +| `mk_area` | M (Markers) | Internal flags/markers | + +### Logging + +Configure logging verbosity: + +```json +{ + "logging": { + "log_connections": true, + "log_data_access": false, + "log_errors": true + } +} +``` + +| Setting | Description | +|---------|-------------| +| `log_connections` | Log client connect/disconnect events | +| `log_data_access` | Log read/write operations (verbose) | +| `log_errors` | Log errors and warnings | + +## Example Configurations + +### Minimal Configuration + +Basic setup with just digital I/O: + +```json +{ + "server": { + "enabled": true, + "port": 102 + }, + "data_blocks": [ + { + "db_number": 1, + "size_bytes": 128, + "mapping": { "type": "bool_input", "start_buffer": 0 } + }, + { + "db_number": 2, + "size_bytes": 128, + "mapping": { "type": "bool_output", "start_buffer": 0 } + } + ] +} +``` + +### Full Industrial Setup + +Complete configuration with analog and digital I/O: + +```json +{ + "server": { + "enabled": true, + "bind_address": "0.0.0.0", + "port": 102, + "max_clients": 16, + "pdu_size": 480 + }, + "plc_identity": { + "name": "Production Line PLC", + "module_type": "CPU 315-2 PN/DP" + }, + "data_blocks": [ + { + "db_number": 1, + "description": "Digital Inputs", + "size_bytes": 128, + "mapping": { "type": "bool_input", "start_buffer": 0, "bit_addressing": true } + }, + { + "db_number": 2, + "description": "Digital Outputs", + "size_bytes": 128, + "mapping": { "type": "bool_output", "start_buffer": 0, "bit_addressing": true } + }, + { + "db_number": 10, + "description": "Analog Inputs (4-20mA)", + "size_bytes": 256, + "mapping": { "type": "int_input", "start_buffer": 0 } + }, + { + "db_number": 20, + "description": "Analog Outputs", + "size_bytes": 256, + "mapping": { "type": "int_output", "start_buffer": 0 } + }, + { + "db_number": 100, + "description": "Setpoints", + "size_bytes": 512, + "mapping": { "type": "int_memory", "start_buffer": 0 } + }, + { + "db_number": 200, + "description": "Counters/Timers", + "size_bytes": 1024, + "mapping": { "type": "dint_memory", "start_buffer": 0 } + } + ], + "system_areas": { + "pe_area": { "enabled": true, "size_bytes": 128, "mapping": { "type": "bool_input", "start_buffer": 0 } }, + "pa_area": { "enabled": true, "size_bytes": 128, "mapping": { "type": "bool_output", "start_buffer": 0 } }, + "mk_area": { "enabled": true, "size_bytes": 256, "mapping": { "type": "bool_memory", "start_buffer": 0 } } + }, + "logging": { + "log_connections": true, + "log_data_access": false, + "log_errors": true + } +} +``` + +### WinCC-Compatible Configuration + +Configuration matching typical WinCC tag addressing: + +```json +{ + "server": { + "enabled": true, + "port": 102 + }, + "plc_identity": { + "name": "OpenPLC", + "module_type": "CPU 315-2 PN/DP" + }, + "data_blocks": [ + { + "db_number": 1, + "description": "Process Data", + "size_bytes": 4096, + "mapping": { "type": "int_memory", "start_buffer": 0 } + } + ], + "system_areas": { + "pe_area": { "enabled": true, "size_bytes": 256, "mapping": { "type": "bool_input", "start_buffer": 0 } }, + "pa_area": { "enabled": true, "size_bytes": 256, "mapping": { "type": "bool_output", "start_buffer": 0 } }, + "mk_area": { "enabled": true, "size_bytes": 256, "mapping": { "type": "bool_memory", "start_buffer": 0 } } + } +} +``` + +## S7 Address Mapping + +### Understanding S7 Addresses + +| S7 Address | Area | Description | OpenPLC Equivalent | +|------------|------|-------------|-------------------| +| I0.0 | PE | Input byte 0, bit 0 | %IX0.0 | +| Q10.5 | PA | Output byte 10, bit 5 | %QX10.5 | +| M100.0 | MK | Marker byte 100, bit 0 | %MX100.0 | +| DB1.DBX0.0 | DB | DB1, byte 0, bit 0 | Configured mapping | +| DB10.DBW0 | DB | DB10, word at byte 0 | Configured mapping | +| DB10.DBD4 | DB | DB10, dword at byte 4 | Configured mapping | + +### Address Calculation + +For data blocks with word mappings: + +``` +OpenPLC Buffer Index = start_buffer + (S7_byte_offset / element_size) + +Example: +- DB10 mapped to int_input, start_buffer = 0 +- S7 access: DB10.DBW10 (word at byte 10) +- OpenPLC: int_input[0 + (10/2)] = int_input[5] +``` + +For data blocks with bit mappings: + +``` +OpenPLC Buffer Index = start_buffer + S7_byte_offset +OpenPLC Bit Index = S7_bit_offset + +Example: +- DB1 mapped to bool_output, start_buffer = 0 +- S7 access: DB1.DBX5.3 (byte 5, bit 3) +- OpenPLC: bool_output[0 + 5][3] = bool_output[5][3] +``` + +## Connecting S7 Clients + +### Connection Parameters + +| Parameter | Value | +|-----------|-------| +| IP Address | OpenPLC host IP | +| Rack | 0 | +| Slot | 0 (or 1, 2) | +| Port | 102 (default) | + +### Python (python-snap7) + +```python +import snap7 + +client = snap7.client.Client() +client.connect('192.168.1.100', 0, 0) # IP, rack, slot + +# Read DB10, 10 bytes starting at byte 0 +data = client.db_read(10, 0, 10) + +# Write to DB20 +client.db_write(20, 0, bytearray([0x01, 0x02, 0x03])) + +# Read inputs (PE area) +inputs = client.read_area(snap7.types.Areas.PE, 0, 0, 10) + +# Write outputs (PA area) +client.write_area(snap7.types.Areas.PA, 0, 0, bytearray([0xFF])) + +client.disconnect() +``` + +### Node.js (node-snap7) + +```javascript +const snap7 = require('node-snap7'); + +const client = new snap7.S7Client(); +client.ConnectTo('192.168.1.100', 0, 0, (err) => { + if (err) throw err; + + // Read DB10 + client.DBRead(10, 0, 10, (err, data) => { + console.log(data); + }); + + // Write to DB20 + client.DBWrite(20, 0, Buffer.from([0x01, 0x02]), (err) => { + if (!err) console.log('Written'); + }); +}); +``` + +### WinCC / TIA Portal + +1. Add a new connection to the PLC +2. Set connection type to "S7 connection" +3. Enter OpenPLC IP address +4. Set Rack=0, Slot=0 +5. Configure tags using DB addresses matching your configuration + +## Troubleshooting + +### Server Won't Start + +**Symptom:** Plugin fails to initialize + +**Solutions:** +1. Check port availability: `netstat -an | grep 102` +2. Verify root privileges for port 102 +3. Check configuration file syntax +4. Review logs for specific errors + +### Connection Refused + +**Symptom:** Clients cannot connect + +**Solutions:** +1. Verify server is running (check logs) +2. Check firewall rules for port 102 +3. Verify bind_address allows external connections +4. Check max_clients limit + +### Data Not Updating + +**Symptom:** Values don't change in HMI + +**Solutions:** +1. Verify DB number matches configuration +2. Check address offsets are within size_bytes +3. Verify PLC program is writing to correct addresses +4. Enable log_data_access for debugging + +### Wrong Values + +**Symptom:** Data appears corrupted + +**Solutions:** +1. Check byte order (S7 uses big-endian) +2. Verify data type matches (Word vs DWord) +3. Check buffer mapping configuration +4. Verify bit_addressing setting for BOOL types + +### Performance Issues + +**Symptom:** Slow response times + +**Solutions:** +1. Reduce log_data_access (very verbose) +2. Increase work_interval_ms +3. Reduce number of concurrent clients +4. Check network latency + +## Limitations + +1. **Counter/Timer Areas**: CT and TM areas are not currently supported +2. **Block Upload/Download**: Block transfer operations are not supported +3. **Password Protection**: S7 password protection is not implemented +4. **Multiple Instances**: Only one S7 server instance per runtime + +## Security Considerations + +1. **No Authentication**: S7Comm has no built-in authentication +2. **Network Segmentation**: Isolate S7 traffic on industrial network +3. **Firewall Rules**: Restrict access to port 102 +4. **Read-Only Mode**: Consider mapping sensitive areas as read-only + +## Architecture + +### Double-Buffering + +The plugin uses a double-buffering architecture for thread safety: + +``` +S7 Clients <---> [S7 Buffer] <---> [Shadow Buffer] <---> OpenPLC Buffers + ^ ^ + | | + Snap7 server PLC cycle sync + (asynchronous) (at cycle_end) +``` + +- **S7 Buffer**: Registered with Snap7, accessed asynchronously by S7 clients +- **Shadow Buffer**: Used for synchronization with OpenPLC at cycle end +- **Sync**: Happens only at cycle_end with brief mutex locks (minimal contention) +- **Optimization**: If no clients are connected, sync is skipped entirely + +### Data Flow + +1. S7 clients read/write to S7 buffers at any time (lock-free) +2. At end of PLC scan cycle: + - S7 buffer -> Shadow buffer (capture client writes) + - Shadow buffer <-> OpenPLC buffers (apply writes, get new values) + - Shadow buffer -> S7 buffer (publish to clients) + +## Related Documentation + +- [JSON Configuration Schema](JSON_SCHEMA.md) - Detailed schema reference for Editor developers + +## Support + +For issues and feature requests: +- GitHub Issues: https://github.com/Autonomy-Logic/openplc-runtime/issues + +--- + +*Document Version: 1.1* +*Last Updated: 2026-01-12* diff --git a/core/src/drivers/plugins/native/s7comm/s7comm_config.c b/core/src/drivers/plugins/native/s7comm/s7comm_config.c new file mode 100644 index 00000000..5b57feae --- /dev/null +++ b/core/src/drivers/plugins/native/s7comm/s7comm_config.c @@ -0,0 +1,477 @@ +/** + * @file s7comm_config.c + * @brief S7Comm Plugin Configuration Parser Implementation + * + * Parses JSON configuration files using cJSON library. + */ + +#include "s7comm_config.h" +#include "cJSON.h" + +#include +#include +#include + +/* Error codes */ +#define S7COMM_CONFIG_OK 0 +#define S7COMM_CONFIG_ERR_FILE -1 +#define S7COMM_CONFIG_ERR_PARSE -2 +#define S7COMM_CONFIG_ERR_MEMORY -3 +#define S7COMM_CONFIG_ERR_INVALID -4 +#define S7COMM_CONFIG_ERR_MISSING -5 + +/* Buffer type string mappings */ +static const struct { + const char *name; + s7comm_buffer_type_t type; +} buffer_type_map[] = { + {"bool_input", BUFFER_TYPE_BOOL_INPUT}, + {"bool_output", BUFFER_TYPE_BOOL_OUTPUT}, + {"bool_memory", BUFFER_TYPE_BOOL_MEMORY}, + {"byte_input", BUFFER_TYPE_BYTE_INPUT}, + {"byte_output", BUFFER_TYPE_BYTE_OUTPUT}, + {"int_input", BUFFER_TYPE_INT_INPUT}, + {"int_output", BUFFER_TYPE_INT_OUTPUT}, + {"int_memory", BUFFER_TYPE_INT_MEMORY}, + {"dint_input", BUFFER_TYPE_DINT_INPUT}, + {"dint_output", BUFFER_TYPE_DINT_OUTPUT}, + {"dint_memory", BUFFER_TYPE_DINT_MEMORY}, + {"lint_input", BUFFER_TYPE_LINT_INPUT}, + {"lint_output", BUFFER_TYPE_LINT_OUTPUT}, + {"lint_memory", BUFFER_TYPE_LINT_MEMORY}, + {NULL, BUFFER_TYPE_NONE} +}; + +/** + * @brief Read entire file into a string + */ +static char *read_file(const char *path) +{ + FILE *fp = fopen(path, "rb"); + if (fp == NULL) { + return NULL; + } + + fseek(fp, 0, SEEK_END); + long size = ftell(fp); + fseek(fp, 0, SEEK_SET); + + if (size <= 0 || size > 1024 * 1024) { /* Max 1MB config file */ + fclose(fp); + return NULL; + } + + char *buffer = (char *)malloc(size + 1); + if (buffer == NULL) { + fclose(fp); + return NULL; + } + + size_t read_size = fread(buffer, 1, size, fp); + fclose(fp); + + if ((long)read_size != size) { + free(buffer); + return NULL; + } + + buffer[size] = '\0'; + return buffer; +} + +/** + * @brief Parse buffer type from string + */ +static s7comm_buffer_type_t parse_buffer_type(const char *type_str) +{ + if (type_str == NULL) { + return BUFFER_TYPE_NONE; + } + + for (int i = 0; buffer_type_map[i].name != NULL; i++) { + if (strcmp(type_str, buffer_type_map[i].name) == 0) { + return buffer_type_map[i].type; + } + } + + return BUFFER_TYPE_NONE; +} + +/** + * @brief Safely copy string with length limit + */ +static void safe_strcpy(char *dest, const char *src, size_t max_len) +{ + if (src == NULL) { + dest[0] = '\0'; + return; + } + strncpy(dest, src, max_len - 1); + dest[max_len - 1] = '\0'; +} + +/** + * @brief Get string value from JSON object + */ +static const char *get_string(const cJSON *obj, const char *key, const char *default_val) +{ + const cJSON *item = cJSON_GetObjectItemCaseSensitive(obj, key); + if (cJSON_IsString(item) && item->valuestring != NULL) { + return item->valuestring; + } + return default_val; +} + +/** + * @brief Get integer value from JSON object + */ +static int get_int(const cJSON *obj, const char *key, int default_val) +{ + const cJSON *item = cJSON_GetObjectItemCaseSensitive(obj, key); + if (cJSON_IsNumber(item)) { + return item->valueint; + } + return default_val; +} + +/** + * @brief Get boolean value from JSON object + */ +static bool get_bool(const cJSON *obj, const char *key, bool default_val) +{ + const cJSON *item = cJSON_GetObjectItemCaseSensitive(obj, key); + if (cJSON_IsBool(item)) { + return cJSON_IsTrue(item); + } + return default_val; +} + +/** + * @brief Parse buffer mapping from JSON object + */ +static void parse_buffer_mapping(const cJSON *obj, s7comm_buffer_mapping_t *mapping) +{ + if (obj == NULL || mapping == NULL) { + return; + } + + const char *type_str = get_string(obj, "type", NULL); + mapping->type = parse_buffer_type(type_str); + mapping->start_buffer = get_int(obj, "start_buffer", 0); + mapping->bit_addressing = get_bool(obj, "bit_addressing", false); +} + +/** + * @brief Parse server section from JSON + */ +static void parse_server_section(const cJSON *server, s7comm_config_t *config) +{ + if (server == NULL) { + return; + } + + config->enabled = get_bool(server, "enabled", true); + safe_strcpy(config->bind_address, get_string(server, "bind_address", "0.0.0.0"), + S7COMM_MAX_STRING_LEN); + config->port = (uint16_t)get_int(server, "port", S7COMM_DEFAULT_PORT); + config->max_clients = get_int(server, "max_clients", S7COMM_DEFAULT_MAX_CLIENTS); + config->work_interval_ms = get_int(server, "work_interval_ms", S7COMM_DEFAULT_WORK_INTERVAL); + config->send_timeout_ms = get_int(server, "send_timeout_ms", S7COMM_DEFAULT_SEND_TIMEOUT); + config->recv_timeout_ms = get_int(server, "recv_timeout_ms", S7COMM_DEFAULT_RECV_TIMEOUT); + config->ping_timeout_ms = get_int(server, "ping_timeout_ms", S7COMM_DEFAULT_PING_TIMEOUT); + config->pdu_size = get_int(server, "pdu_size", S7COMM_DEFAULT_PDU_SIZE); +} + +/** + * @brief Parse PLC identity section from JSON + */ +static void parse_identity_section(const cJSON *identity, s7comm_plc_identity_t *id) +{ + if (identity == NULL) { + return; + } + + safe_strcpy(id->name, get_string(identity, "name", "OpenPLC Runtime"), + S7COMM_MAX_STRING_LEN); + safe_strcpy(id->module_type, get_string(identity, "module_type", "CPU 315-2 PN/DP"), + S7COMM_MAX_STRING_LEN); + safe_strcpy(id->serial_number, get_string(identity, "serial_number", "S C-XXXXXXXXX"), + S7COMM_MAX_STRING_LEN); + safe_strcpy(id->copyright, get_string(identity, "copyright", "OpenPLC Project"), + S7COMM_MAX_STRING_LEN); + safe_strcpy(id->module_name, get_string(identity, "module_name", "OpenPLC"), + S7COMM_MAX_STRING_LEN); +} + +/** + * @brief Parse a single data block from JSON + */ +static int parse_data_block(const cJSON *db_json, s7comm_data_block_t *db) +{ + if (db_json == NULL || db == NULL) { + return S7COMM_CONFIG_ERR_INVALID; + } + + db->db_number = get_int(db_json, "db_number", 0); + if (db->db_number <= 0 || db->db_number > 65535) { + return S7COMM_CONFIG_ERR_INVALID; + } + + safe_strcpy(db->description, get_string(db_json, "description", ""), + S7COMM_MAX_DESCRIPTION_LEN); + db->size_bytes = get_int(db_json, "size_bytes", 0); + if (db->size_bytes <= 0) { + return S7COMM_CONFIG_ERR_INVALID; + } + + const cJSON *mapping = cJSON_GetObjectItemCaseSensitive(db_json, "mapping"); + if (mapping != NULL) { + parse_buffer_mapping(mapping, &db->mapping); + } + + return S7COMM_CONFIG_OK; +} + +/** + * @brief Parse data_blocks array from JSON + */ +static int parse_data_blocks_section(const cJSON *data_blocks, s7comm_config_t *config) +{ + if (data_blocks == NULL || !cJSON_IsArray(data_blocks)) { + config->num_data_blocks = 0; + return S7COMM_CONFIG_OK; + } + + int count = cJSON_GetArraySize(data_blocks); + if (count > S7COMM_MAX_DATA_BLOCKS) { + count = S7COMM_MAX_DATA_BLOCKS; + } + + config->num_data_blocks = 0; + const cJSON *db_json; + cJSON_ArrayForEach(db_json, data_blocks) { + if (config->num_data_blocks >= S7COMM_MAX_DATA_BLOCKS) { + break; + } + + int result = parse_data_block(db_json, &config->data_blocks[config->num_data_blocks]); + if (result == S7COMM_CONFIG_OK) { + config->num_data_blocks++; + } + } + + return S7COMM_CONFIG_OK; +} + +/** + * @brief Parse a system area (PE, PA, MK) from JSON + */ +static void parse_system_area(const cJSON *area_json, s7comm_system_area_t *area) +{ + if (area_json == NULL) { + area->enabled = false; + return; + } + + area->enabled = get_bool(area_json, "enabled", false); + area->size_bytes = get_int(area_json, "size_bytes", 128); + + const cJSON *mapping = cJSON_GetObjectItemCaseSensitive(area_json, "mapping"); + if (mapping != NULL) { + parse_buffer_mapping(mapping, &area->mapping); + } +} + +/** + * @brief Parse system_areas section from JSON + */ +static void parse_system_areas_section(const cJSON *system_areas, s7comm_config_t *config) +{ + if (system_areas == NULL) { + return; + } + + parse_system_area(cJSON_GetObjectItemCaseSensitive(system_areas, "pe_area"), + &config->pe_area); + parse_system_area(cJSON_GetObjectItemCaseSensitive(system_areas, "pa_area"), + &config->pa_area); + parse_system_area(cJSON_GetObjectItemCaseSensitive(system_areas, "mk_area"), + &config->mk_area); +} + +/** + * @brief Parse logging section from JSON + */ +static void parse_logging_section(const cJSON *logging, s7comm_logging_t *log_config) +{ + if (logging == NULL) { + return; + } + + log_config->log_connections = get_bool(logging, "log_connections", true); + log_config->log_data_access = get_bool(logging, "log_data_access", false); + log_config->log_errors = get_bool(logging, "log_errors", true); +} + +void s7comm_config_init_defaults(s7comm_config_t *config) +{ + if (config == NULL) { + return; + } + + memset(config, 0, sizeof(s7comm_config_t)); + + /* Server defaults */ + config->enabled = true; + safe_strcpy(config->bind_address, "0.0.0.0", S7COMM_MAX_STRING_LEN); + config->port = S7COMM_DEFAULT_PORT; + config->max_clients = S7COMM_DEFAULT_MAX_CLIENTS; + config->work_interval_ms = S7COMM_DEFAULT_WORK_INTERVAL; + config->send_timeout_ms = S7COMM_DEFAULT_SEND_TIMEOUT; + config->recv_timeout_ms = S7COMM_DEFAULT_RECV_TIMEOUT; + config->ping_timeout_ms = S7COMM_DEFAULT_PING_TIMEOUT; + config->pdu_size = S7COMM_DEFAULT_PDU_SIZE; + + /* Identity defaults */ + safe_strcpy(config->identity.name, "OpenPLC Runtime", S7COMM_MAX_STRING_LEN); + safe_strcpy(config->identity.module_type, "CPU 315-2 PN/DP", S7COMM_MAX_STRING_LEN); + safe_strcpy(config->identity.serial_number, "S C-XXXXXXXXX", S7COMM_MAX_STRING_LEN); + safe_strcpy(config->identity.copyright, "OpenPLC Project", S7COMM_MAX_STRING_LEN); + safe_strcpy(config->identity.module_name, "OpenPLC", S7COMM_MAX_STRING_LEN); + + /* Logging defaults */ + config->logging.log_connections = true; + config->logging.log_data_access = false; + config->logging.log_errors = true; +} + +int s7comm_config_parse(const char *config_path, s7comm_config_t *config) +{ + if (config_path == NULL || config == NULL) { + return S7COMM_CONFIG_ERR_INVALID; + } + + /* Initialize with defaults */ + s7comm_config_init_defaults(config); + + /* Read file contents */ + char *json_str = read_file(config_path); + if (json_str == NULL) { + return S7COMM_CONFIG_ERR_FILE; + } + + /* Parse JSON */ + cJSON *root = cJSON_Parse(json_str); + free(json_str); + + if (root == NULL) { + return S7COMM_CONFIG_ERR_PARSE; + } + + /* Parse each section */ + parse_server_section(cJSON_GetObjectItemCaseSensitive(root, "server"), config); + parse_identity_section(cJSON_GetObjectItemCaseSensitive(root, "plc_identity"), &config->identity); + parse_data_blocks_section(cJSON_GetObjectItemCaseSensitive(root, "data_blocks"), config); + parse_system_areas_section(cJSON_GetObjectItemCaseSensitive(root, "system_areas"), config); + parse_logging_section(cJSON_GetObjectItemCaseSensitive(root, "logging"), &config->logging); + + cJSON_Delete(root); + + /* Validate the parsed configuration */ + return s7comm_config_validate(config); +} + +int s7comm_config_validate(const s7comm_config_t *config) +{ + if (config == NULL) { + return S7COMM_CONFIG_ERR_INVALID; + } + + /* Validate port */ + if (config->port == 0) { + return S7COMM_CONFIG_ERR_INVALID; + } + + /* Validate timeouts */ + if (config->send_timeout_ms < 100 || config->recv_timeout_ms < 100) { + return S7COMM_CONFIG_ERR_INVALID; + } + + /* Validate PDU size (S7 spec: 240-960) */ + if (config->pdu_size < 240 || config->pdu_size > 960) { + return S7COMM_CONFIG_ERR_INVALID; + } + + /* Validate max_clients */ + if (config->max_clients < 1 || config->max_clients > 1024) { + return S7COMM_CONFIG_ERR_INVALID; + } + + /* Check for duplicate DB numbers */ + for (int i = 0; i < config->num_data_blocks; i++) { + for (int j = i + 1; j < config->num_data_blocks; j++) { + if (config->data_blocks[i].db_number == config->data_blocks[j].db_number) { + return S7COMM_CONFIG_ERR_INVALID; + } + } + } + + /* Validate data block mappings */ + for (int i = 0; i < config->num_data_blocks; i++) { + const s7comm_data_block_t *db = &config->data_blocks[i]; + + if (db->size_bytes <= 0 || db->size_bytes > 65535) { + return S7COMM_CONFIG_ERR_INVALID; + } + + if (db->mapping.type == BUFFER_TYPE_NONE) { + return S7COMM_CONFIG_ERR_INVALID; + } + + if (db->mapping.start_buffer < 0) { + return S7COMM_CONFIG_ERR_INVALID; + } + } + + return S7COMM_CONFIG_OK; +} + +const char *s7comm_buffer_type_name(s7comm_buffer_type_t type) +{ + for (int i = 0; buffer_type_map[i].name != NULL; i++) { + if (buffer_type_map[i].type == type) { + return buffer_type_map[i].name; + } + } + return "none"; +} + +int s7comm_buffer_type_size(s7comm_buffer_type_t type) +{ + switch (type) { + case BUFFER_TYPE_BOOL_INPUT: + case BUFFER_TYPE_BOOL_OUTPUT: + case BUFFER_TYPE_BOOL_MEMORY: + case BUFFER_TYPE_BYTE_INPUT: + case BUFFER_TYPE_BYTE_OUTPUT: + return 1; + + case BUFFER_TYPE_INT_INPUT: + case BUFFER_TYPE_INT_OUTPUT: + case BUFFER_TYPE_INT_MEMORY: + return 2; + + case BUFFER_TYPE_DINT_INPUT: + case BUFFER_TYPE_DINT_OUTPUT: + case BUFFER_TYPE_DINT_MEMORY: + return 4; + + case BUFFER_TYPE_LINT_INPUT: + case BUFFER_TYPE_LINT_OUTPUT: + case BUFFER_TYPE_LINT_MEMORY: + return 8; + + default: + return 0; + } +} diff --git a/core/src/drivers/plugins/native/s7comm/s7comm_config.h b/core/src/drivers/plugins/native/s7comm/s7comm_config.h new file mode 100644 index 00000000..66d5b1a3 --- /dev/null +++ b/core/src/drivers/plugins/native/s7comm/s7comm_config.h @@ -0,0 +1,177 @@ +/** + * @file s7comm_config.h + * @brief S7Comm Plugin Configuration Structures and Parser + * + * Defines data structures for JSON configuration and functions + * for parsing, validating, and managing configuration. + */ + +#ifndef S7COMM_CONFIG_H +#define S7COMM_CONFIG_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* Configuration limits */ +#define S7COMM_MAX_DATA_BLOCKS 64 +#define S7COMM_MAX_STRING_LEN 64 +#define S7COMM_MAX_DESCRIPTION_LEN 128 + +/* Default values */ +#define S7COMM_DEFAULT_PORT 102 +#define S7COMM_DEFAULT_MAX_CLIENTS 32 +#define S7COMM_DEFAULT_WORK_INTERVAL 100 +#define S7COMM_DEFAULT_SEND_TIMEOUT 3000 +#define S7COMM_DEFAULT_RECV_TIMEOUT 3000 +#define S7COMM_DEFAULT_PING_TIMEOUT 10000 +#define S7COMM_DEFAULT_PDU_SIZE 480 + +/** + * @brief Buffer type enumeration for mapping S7 areas to OpenPLC buffers + */ +typedef enum { + BUFFER_TYPE_NONE = 0, + BUFFER_TYPE_BOOL_INPUT, + BUFFER_TYPE_BOOL_OUTPUT, + BUFFER_TYPE_BOOL_MEMORY, + BUFFER_TYPE_BYTE_INPUT, + BUFFER_TYPE_BYTE_OUTPUT, + BUFFER_TYPE_INT_INPUT, + BUFFER_TYPE_INT_OUTPUT, + BUFFER_TYPE_INT_MEMORY, + BUFFER_TYPE_DINT_INPUT, + BUFFER_TYPE_DINT_OUTPUT, + BUFFER_TYPE_DINT_MEMORY, + BUFFER_TYPE_LINT_INPUT, + BUFFER_TYPE_LINT_OUTPUT, + BUFFER_TYPE_LINT_MEMORY +} s7comm_buffer_type_t; + +/** + * @brief Buffer mapping configuration + */ +typedef struct { + s7comm_buffer_type_t type; /* OpenPLC buffer type */ + int start_buffer; /* Starting buffer index */ + bool bit_addressing; /* Enable bit-level access */ +} s7comm_buffer_mapping_t; + +/** + * @brief Data block configuration + */ +typedef struct { + int db_number; /* S7 DB number (1-65535) */ + char description[S7COMM_MAX_DESCRIPTION_LEN]; /* Human-readable description */ + int size_bytes; /* DB size in bytes */ + s7comm_buffer_mapping_t mapping; /* Buffer mapping */ +} s7comm_data_block_t; + +/** + * @brief System area configuration (PE, PA, MK) + */ +typedef struct { + bool enabled; /* Enable this area */ + int size_bytes; /* Area size in bytes */ + s7comm_buffer_mapping_t mapping; /* Buffer mapping */ +} s7comm_system_area_t; + +/** + * @brief PLC identity configuration for SZL responses + */ +typedef struct { + char name[S7COMM_MAX_STRING_LEN]; /* PLC name */ + char module_type[S7COMM_MAX_STRING_LEN]; /* CPU type string */ + char serial_number[S7COMM_MAX_STRING_LEN]; /* Serial number */ + char copyright[S7COMM_MAX_STRING_LEN]; /* Copyright string */ + char module_name[S7COMM_MAX_STRING_LEN]; /* Module name */ +} s7comm_plc_identity_t; + +/** + * @brief Logging configuration + */ +typedef struct { + bool log_connections; /* Log client connect/disconnect */ + bool log_data_access; /* Log read/write operations */ + bool log_errors; /* Log errors and warnings */ +} s7comm_logging_t; + +/** + * @brief Complete S7Comm configuration + */ +typedef struct { + /* Server settings */ + bool enabled; /* Enable/disable the S7 server */ + char bind_address[S7COMM_MAX_STRING_LEN]; /* Network interface to bind */ + uint16_t port; /* S7Comm TCP port */ + int max_clients; /* Maximum simultaneous connections */ + int work_interval_ms; /* Worker thread polling interval */ + int send_timeout_ms; /* Socket send timeout */ + int recv_timeout_ms; /* Socket receive timeout */ + int ping_timeout_ms; /* Keep-alive timeout */ + int pdu_size; /* Maximum PDU size */ + + /* PLC identity */ + s7comm_plc_identity_t identity; + + /* Data blocks */ + int num_data_blocks; + s7comm_data_block_t data_blocks[S7COMM_MAX_DATA_BLOCKS]; + + /* System areas */ + s7comm_system_area_t pe_area; /* Process inputs (I area) */ + s7comm_system_area_t pa_area; /* Process outputs (Q area) */ + s7comm_system_area_t mk_area; /* Markers (M area) */ + + /* Logging */ + s7comm_logging_t logging; +} s7comm_config_t; + +/** + * @brief Parse configuration from JSON file + * + * @param config_path Path to the JSON configuration file + * @param config Pointer to configuration structure to populate + * @return 0 on success, negative error code on failure + */ +int s7comm_config_parse(const char *config_path, s7comm_config_t *config); + +/** + * @brief Validate configuration values + * + * @param config Pointer to configuration structure to validate + * @return 0 if valid, negative error code indicating the issue + */ +int s7comm_config_validate(const s7comm_config_t *config); + +/** + * @brief Initialize configuration with default values + * + * @param config Pointer to configuration structure to initialize + */ +void s7comm_config_init_defaults(s7comm_config_t *config); + +/** + * @brief Get human-readable name for a buffer type + * + * @param type Buffer type enumeration value + * @return String name of the buffer type + */ +const char *s7comm_buffer_type_name(s7comm_buffer_type_t type); + +/** + * @brief Get element size in bytes for a buffer type + * + * @param type Buffer type enumeration value + * @return Size in bytes (1, 2, 4, or 8), or 0 for invalid type + */ +int s7comm_buffer_type_size(s7comm_buffer_type_t type); + +#ifdef __cplusplus +} +#endif + +#endif /* S7COMM_CONFIG_H */ diff --git a/core/src/drivers/plugins/native/s7comm/s7comm_config.json b/core/src/drivers/plugins/native/s7comm/s7comm_config.json new file mode 100644 index 00000000..faac7d8e --- /dev/null +++ b/core/src/drivers/plugins/native/s7comm/s7comm_config.json @@ -0,0 +1,133 @@ +{ + "server": { + "enabled": true, + "bind_address": "0.0.0.0", + "port": 102, + "max_clients": 32, + "work_interval_ms": 100, + "send_timeout_ms": 3000, + "recv_timeout_ms": 3000, + "ping_timeout_ms": 10000, + "pdu_size": 480 + }, + "plc_identity": { + "name": "OpenPLC Runtime", + "module_type": "CPU 315-2 PN/DP", + "serial_number": "S C-OPENPLC01", + "copyright": "OpenPLC Project", + "module_name": "OpenPLC" + }, + "data_blocks": [ + { + "db_number": 1, + "description": "Digital Inputs (%IX)", + "size_bytes": 128, + "mapping": { + "type": "bool_input", + "start_buffer": 0, + "bit_addressing": true + } + }, + { + "db_number": 2, + "description": "Digital Outputs (%QX)", + "size_bytes": 128, + "mapping": { + "type": "bool_output", + "start_buffer": 0, + "bit_addressing": true + } + }, + { + "db_number": 3, + "description": "Digital Memory (%MX)", + "size_bytes": 128, + "mapping": { + "type": "bool_memory", + "start_buffer": 0, + "bit_addressing": true + } + }, + { + "db_number": 10, + "description": "Analog Inputs (%IW)", + "size_bytes": 2048, + "mapping": { + "type": "int_input", + "start_buffer": 0, + "bit_addressing": false + } + }, + { + "db_number": 20, + "description": "Analog Outputs (%QW)", + "size_bytes": 2048, + "mapping": { + "type": "int_output", + "start_buffer": 0, + "bit_addressing": false + } + }, + { + "db_number": 100, + "description": "Memory Words (%MW)", + "size_bytes": 2048, + "mapping": { + "type": "int_memory", + "start_buffer": 0, + "bit_addressing": false + } + }, + { + "db_number": 200, + "description": "Memory DWords (%MD)", + "size_bytes": 4096, + "mapping": { + "type": "dint_memory", + "start_buffer": 0, + "bit_addressing": false + } + }, + { + "db_number": 300, + "description": "Memory LWords (%ML)", + "size_bytes": 8192, + "mapping": { + "type": "lint_memory", + "start_buffer": 0, + "bit_addressing": false + } + } + ], + "system_areas": { + "pe_area": { + "enabled": true, + "size_bytes": 128, + "mapping": { + "type": "bool_input", + "start_buffer": 0 + } + }, + "pa_area": { + "enabled": true, + "size_bytes": 128, + "mapping": { + "type": "bool_output", + "start_buffer": 0 + } + }, + "mk_area": { + "enabled": true, + "size_bytes": 256, + "mapping": { + "type": "bool_memory", + "start_buffer": 0 + } + } + }, + "logging": { + "log_connections": true, + "log_data_access": false, + "log_errors": true + } +} diff --git a/core/src/drivers/plugins/native/s7comm/s7comm_plugin.cpp b/core/src/drivers/plugins/native/s7comm/s7comm_plugin.cpp new file mode 100644 index 00000000..ff5554c0 --- /dev/null +++ b/core/src/drivers/plugins/native/s7comm/s7comm_plugin.cpp @@ -0,0 +1,1128 @@ +/** + * @file s7comm_plugin.cpp + * @brief S7Comm Plugin Implementation for OpenPLC Runtime v4 + * + * This plugin implements a Siemens S7 communication server using the Snap7 library. + * It allows S7-compatible HMIs and SCADA systems to read/write OpenPLC I/O buffers. + * + * Buffer Sync Strategy: + * - S7 buffers: What Snap7 clients read/write (accessed asynchronously) + * - Shadow buffers: Used for sync with OpenPLC (minimizes mutex hold time) + * - S7 mutex: Protects S7 buffers during brief memcpy operations + * + * Data flow (full bidirectional sync for ALL types): + * - cycle_start: Copy ENTIRE S7 buffer -> OpenPLC buffer + * S7 client writes become visible to the PLC program + * + * - cycle_end: Copy ENTIRE OpenPLC buffer -> S7 buffer + * PLC program outputs become visible to S7 clients + * + * This approach allows S7 clients to write to any location. Values that are + * actively driven by the PLC program or I/O drivers will be overwritten + * during the scan cycle, but S7 writes to other locations will persist. + */ + +#include +#include +#include +#include + +/* Snap7 includes */ +#include "snap7_libmain.h" +#include "s7_types.h" + +/* Plugin includes */ +extern "C" { +#include "plugin_logger.h" +#include "plugin_types.h" +#include "s7comm_plugin.h" +#include "s7comm_config.h" +} + +/* + * ============================================================================= + * Constants + * ============================================================================= + */ +#define S7COMM_MAX_DB_SIZE 65536 /* Maximum size for a single DB buffer */ + +/* + * ============================================================================= + * Data Block Runtime Structure (with double-buffering) + * ============================================================================= + */ +typedef struct { + int db_number; /* S7 DB number */ + s7comm_buffer_type_t type; /* Mapping type */ + int start_buffer; /* Starting buffer index */ + int size_bytes; /* Size in bytes */ + bool bit_addressing; /* Bit-level access enabled */ + uint8_t *s7_buffer; /* S7 buffer (registered with Snap7) */ + uint8_t *shadow_buffer; /* Shadow buffer for sync with OpenPLC */ +} s7comm_db_runtime_t; + +/* + * ============================================================================= + * System Area Runtime Structure (with double-buffering) + * ============================================================================= + */ +typedef struct { + bool enabled; + int size_bytes; + s7comm_buffer_type_t type; + int start_buffer; + uint8_t *s7_buffer; /* S7 buffer (registered with Snap7) */ + uint8_t *shadow_buffer; /* Shadow buffer for sync with OpenPLC */ +} s7comm_area_runtime_t; + +/* + * ============================================================================= + * Plugin State + * ============================================================================= + */ +static plugin_logger_t g_logger; +static plugin_runtime_args_t g_runtime_args; +static s7comm_config_t g_config; +static bool g_initialized = false; +static bool g_running = false; +static bool g_config_loaded = false; + +/* Snap7 server handle (S7Object is uintptr_t, use 0 for null) */ +static S7Object g_server = 0; + +/* S7 buffer mutex - protects S7 buffers during sync */ +static pthread_mutex_t g_s7_mutex = PTHREAD_MUTEX_INITIALIZER; + +/* Runtime data blocks (dynamically allocated based on config) */ +static s7comm_db_runtime_t g_db_runtime[S7COMM_MAX_DATA_BLOCKS]; +static int g_num_db_runtime = 0; + +/* System area runtime (with double-buffering) */ +static s7comm_area_runtime_t g_pe_runtime; +static s7comm_area_runtime_t g_pa_runtime; +static s7comm_area_runtime_t g_mk_runtime; + +/* + * ============================================================================= + * Forward Declarations + * ============================================================================= + */ +static void s7comm_event_callback(void *usrPtr, PSrvEvent PEvent, int Size); +static int allocate_buffers(void); +static void free_buffers(void); +static int register_all_areas(void); +static void sync_shadow_to_openplc_by_type(uint8_t *shadow, int size, s7comm_buffer_type_t type, int start_buffer); +static void sync_openplc_to_shadow_by_type(uint8_t *shadow, int size, s7comm_buffer_type_t type, int start_buffer); + +/* + * ============================================================================= + * Endianness Conversion Helpers + * S7 protocol uses big-endian (network byte order) + * ============================================================================= + */ +static inline uint16_t swap16(uint16_t val) +{ + return ((val & 0xFF00) >> 8) | ((val & 0x00FF) << 8); +} + +static inline uint32_t swap32(uint32_t val) +{ + return ((val & 0xFF000000) >> 24) | + ((val & 0x00FF0000) >> 8) | + ((val & 0x0000FF00) << 8) | + ((val & 0x000000FF) << 24); +} + +static inline uint64_t swap64(uint64_t val) +{ + return ((val & 0xFF00000000000000ULL) >> 56) | + ((val & 0x00FF000000000000ULL) >> 40) | + ((val & 0x0000FF0000000000ULL) >> 24) | + ((val & 0x000000FF00000000ULL) >> 8) | + ((val & 0x00000000FF000000ULL) << 8) | + ((val & 0x0000000000FF0000ULL) << 24) | + ((val & 0x000000000000FF00ULL) << 40) | + ((val & 0x00000000000000FFULL) << 56); +} + +/* + * ============================================================================= + * Memory Management + * ============================================================================= + */ + +/** + * @brief Allocate a system area with double-buffering + */ +static int allocate_area(s7comm_area_runtime_t *area, const s7comm_system_area_t *config) +{ + memset(area, 0, sizeof(s7comm_area_runtime_t)); + + if (!config->enabled || config->size_bytes <= 0) { + area->enabled = false; + return 0; + } + + area->enabled = true; + area->size_bytes = config->size_bytes; + area->type = config->mapping.type; + area->start_buffer = config->mapping.start_buffer; + + /* Allocate S7 buffer (what Snap7 clients see) */ + area->s7_buffer = (uint8_t *)calloc(1, config->size_bytes); + if (area->s7_buffer == NULL) { + return -1; + } + + /* Allocate shadow buffer (for sync with OpenPLC) */ + area->shadow_buffer = (uint8_t *)calloc(1, config->size_bytes); + if (area->shadow_buffer == NULL) { + free(area->s7_buffer); + area->s7_buffer = NULL; + return -1; + } + + return 0; +} + +/** + * @brief Free a system area's buffers + */ +static void free_area(s7comm_area_runtime_t *area) +{ + if (area->s7_buffer != NULL) { + free(area->s7_buffer); + area->s7_buffer = NULL; + } + if (area->shadow_buffer != NULL) { + free(area->shadow_buffer); + area->shadow_buffer = NULL; + } + area->enabled = false; +} + +/** + * @brief Allocate all buffers (S7 + shadow) based on configuration + */ +static int allocate_buffers(void) +{ + g_num_db_runtime = 0; + + /* Allocate system areas with double-buffering */ + if (allocate_area(&g_pe_runtime, &g_config.pe_area) != 0) { + plugin_logger_error(&g_logger, "Failed to allocate PE area buffers"); + return -1; + } + + if (allocate_area(&g_pa_runtime, &g_config.pa_area) != 0) { + plugin_logger_error(&g_logger, "Failed to allocate PA area buffers"); + return -1; + } + + if (allocate_area(&g_mk_runtime, &g_config.mk_area) != 0) { + plugin_logger_error(&g_logger, "Failed to allocate MK area buffers"); + return -1; + } + + /* Allocate data blocks with double-buffering */ + for (int i = 0; i < g_config.num_data_blocks; i++) { + const s7comm_data_block_t *db_cfg = &g_config.data_blocks[i]; + + if (db_cfg->size_bytes <= 0 || db_cfg->size_bytes > S7COMM_MAX_DB_SIZE) { + plugin_logger_warn(&g_logger, "DB%d: invalid size %d, skipping", + db_cfg->db_number, db_cfg->size_bytes); + continue; + } + + s7comm_db_runtime_t *db_rt = &g_db_runtime[g_num_db_runtime]; + db_rt->db_number = db_cfg->db_number; + db_rt->type = db_cfg->mapping.type; + db_rt->start_buffer = db_cfg->mapping.start_buffer; + db_rt->size_bytes = db_cfg->size_bytes; + db_rt->bit_addressing = db_cfg->mapping.bit_addressing; + + /* Allocate S7 buffer */ + db_rt->s7_buffer = (uint8_t *)calloc(1, db_cfg->size_bytes); + if (db_rt->s7_buffer == NULL) { + plugin_logger_error(&g_logger, "Failed to allocate DB%d S7 buffer", db_cfg->db_number); + return -1; + } + + /* Allocate shadow buffer */ + db_rt->shadow_buffer = (uint8_t *)calloc(1, db_cfg->size_bytes); + if (db_rt->shadow_buffer == NULL) { + plugin_logger_error(&g_logger, "Failed to allocate DB%d shadow buffer", db_cfg->db_number); + free(db_rt->s7_buffer); + db_rt->s7_buffer = NULL; + return -1; + } + + g_num_db_runtime++; + plugin_logger_debug(&g_logger, "Allocated DB%d: %d bytes (double-buffered), type=%s", + db_cfg->db_number, db_cfg->size_bytes, + s7comm_buffer_type_name(db_cfg->mapping.type)); + } + + return 0; +} + +/** + * @brief Free all allocated buffers + */ +static void free_buffers(void) +{ + /* Free system areas */ + free_area(&g_pe_runtime); + free_area(&g_pa_runtime); + free_area(&g_mk_runtime); + + /* Free data blocks */ + for (int i = 0; i < g_num_db_runtime; i++) { + if (g_db_runtime[i].s7_buffer != NULL) { + free(g_db_runtime[i].s7_buffer); + g_db_runtime[i].s7_buffer = NULL; + } + if (g_db_runtime[i].shadow_buffer != NULL) { + free(g_db_runtime[i].shadow_buffer); + g_db_runtime[i].shadow_buffer = NULL; + } + } + g_num_db_runtime = 0; +} + +/** + * @brief Register all S7 areas with the Snap7 server + */ +static int register_all_areas(void) +{ + int result; + + /* Register system areas (using S7 buffers, not shadow) */ + if (g_pe_runtime.enabled && g_pe_runtime.s7_buffer != NULL) { + result = Srv_RegisterArea(g_server, srvAreaPE, 0, g_pe_runtime.s7_buffer, g_pe_runtime.size_bytes); + if (result != 0) { + plugin_logger_warn(&g_logger, "Failed to register PE area: 0x%08X", result); + } else { + plugin_logger_debug(&g_logger, "Registered PE area: %d bytes", g_pe_runtime.size_bytes); + } + } + + if (g_pa_runtime.enabled && g_pa_runtime.s7_buffer != NULL) { + result = Srv_RegisterArea(g_server, srvAreaPA, 0, g_pa_runtime.s7_buffer, g_pa_runtime.size_bytes); + if (result != 0) { + plugin_logger_warn(&g_logger, "Failed to register PA area: 0x%08X", result); + } else { + plugin_logger_debug(&g_logger, "Registered PA area: %d bytes", g_pa_runtime.size_bytes); + } + } + + if (g_mk_runtime.enabled && g_mk_runtime.s7_buffer != NULL) { + result = Srv_RegisterArea(g_server, srvAreaMK, 0, g_mk_runtime.s7_buffer, g_mk_runtime.size_bytes); + if (result != 0) { + plugin_logger_warn(&g_logger, "Failed to register MK area: 0x%08X", result); + } else { + plugin_logger_debug(&g_logger, "Registered MK area: %d bytes", g_mk_runtime.size_bytes); + } + } + + /* Register data blocks (using S7 buffers) */ + for (int i = 0; i < g_num_db_runtime; i++) { + s7comm_db_runtime_t *db = &g_db_runtime[i]; + result = Srv_RegisterArea(g_server, srvAreaDB, db->db_number, db->s7_buffer, db->size_bytes); + if (result != 0) { + plugin_logger_warn(&g_logger, "Failed to register DB%d: 0x%08X", db->db_number, result); + } else { + plugin_logger_debug(&g_logger, "Registered DB%d: %d bytes", db->db_number, db->size_bytes); + } + } + + return 0; +} + +/* + * ============================================================================= + * Plugin Lifecycle Functions + * ============================================================================= + */ + +/** + * @brief Initialize the S7Comm plugin + */ +extern "C" int init(void *args) +{ + /* Initialize logger first (before we have runtime_args) */ + plugin_logger_init(&g_logger, "S7COMM", NULL); + plugin_logger_info(&g_logger, "Initializing S7Comm plugin (double-buffered)..."); + + if (!args) { + plugin_logger_error(&g_logger, "init args is NULL"); + return -1; + } + + /* Copy runtime args (critical - pointer is freed after init returns) */ + memcpy(&g_runtime_args, args, sizeof(plugin_runtime_args_t)); + + /* Re-initialize logger with runtime_args for central logging */ + plugin_logger_init(&g_logger, "S7COMM", args); + + plugin_logger_info(&g_logger, "Buffer size: %d", g_runtime_args.buffer_size); + + /* Initialize S7 buffer mutex */ + pthread_mutex_init(&g_s7_mutex, NULL); + + /* Parse configuration file */ + const char *config_path = g_runtime_args.plugin_specific_config_file_path; + if (config_path == NULL || config_path[0] == '\0') { + plugin_logger_warn(&g_logger, "No config file specified, using defaults"); + s7comm_config_init_defaults(&g_config); + } else { + plugin_logger_info(&g_logger, "Loading config: %s", config_path); + int result = s7comm_config_parse(config_path, &g_config); + if (result != 0) { + plugin_logger_error(&g_logger, "Failed to parse config file (error %d)", result); + plugin_logger_warn(&g_logger, "Using default configuration"); + s7comm_config_init_defaults(&g_config); + } else { + plugin_logger_info(&g_logger, "Configuration loaded successfully"); + g_config_loaded = true; + } + } + + /* Check if server is enabled */ + if (!g_config.enabled) { + plugin_logger_info(&g_logger, "S7Comm server is disabled in configuration"); + g_initialized = true; + return 0; + } + + /* Log configuration summary */ + plugin_logger_info(&g_logger, "Server config: port=%d, max_clients=%d, pdu_size=%d", + g_config.port, g_config.max_clients, g_config.pdu_size); + plugin_logger_info(&g_logger, "PLC identity: %s (%s)", g_config.identity.name, g_config.identity.module_type); + plugin_logger_info(&g_logger, "Data blocks configured: %d", g_config.num_data_blocks); + + /* Allocate all buffers (S7 + shadow for double-buffering) */ + if (allocate_buffers() != 0) { + plugin_logger_error(&g_logger, "Failed to allocate buffers"); + free_buffers(); + return -1; + } + + /* Create Snap7 server */ + g_server = Srv_Create(); + if (g_server == 0) { + plugin_logger_error(&g_logger, "Failed to create Snap7 server"); + free_buffers(); + return -1; + } + + /* Configure server parameters from config */ + uint16_t port = g_config.port; + int max_clients = g_config.max_clients; + int work_interval = g_config.work_interval_ms; + int send_timeout = g_config.send_timeout_ms; + int recv_timeout = g_config.recv_timeout_ms; + int ping_timeout = g_config.ping_timeout_ms; + int pdu_size = g_config.pdu_size; + + Srv_SetParam(g_server, p_u16_LocalPort, &port); + Srv_SetParam(g_server, p_i32_MaxClients, &max_clients); + Srv_SetParam(g_server, p_i32_WorkInterval, &work_interval); + Srv_SetParam(g_server, p_i32_SendTimeout, &send_timeout); + Srv_SetParam(g_server, p_i32_RecvTimeout, &recv_timeout); + Srv_SetParam(g_server, p_i32_PingTimeout, &ping_timeout); + Srv_SetParam(g_server, p_i32_PDURequest, &pdu_size); + + /* Set event mask based on logging configuration */ + longword event_mask = 0; + if (g_config.logging.log_connections) { + event_mask |= evcServerStarted | evcServerStopped | + evcClientAdded | evcClientDisconnected | evcClientRejected; + } + if (g_config.logging.log_errors) { + event_mask |= evcListenerCannotStart | evcClientException; + } + if (g_config.logging.log_data_access) { + event_mask |= evcDataRead | evcDataWrite; + } + Srv_SetMask(g_server, mkEvent, event_mask); + + /* Set event callback for logging */ + Srv_SetEventsCallback(g_server, s7comm_event_callback, NULL); + + /* Register all S7 areas with the server */ + register_all_areas(); + + g_initialized = true; + plugin_logger_info(&g_logger, "S7Comm plugin initialized successfully (double-buffered mode)"); + + /* Log registered areas summary */ + if (g_pe_runtime.enabled) { + plugin_logger_info(&g_logger, "PE area: %d bytes -> %s[%d]", + g_pe_runtime.size_bytes, + s7comm_buffer_type_name(g_pe_runtime.type), + g_pe_runtime.start_buffer); + } + if (g_pa_runtime.enabled) { + plugin_logger_info(&g_logger, "PA area: %d bytes -> %s[%d]", + g_pa_runtime.size_bytes, + s7comm_buffer_type_name(g_pa_runtime.type), + g_pa_runtime.start_buffer); + } + if (g_mk_runtime.enabled) { + plugin_logger_info(&g_logger, "MK area: %d bytes -> %s[%d]", + g_mk_runtime.size_bytes, + s7comm_buffer_type_name(g_mk_runtime.type), + g_mk_runtime.start_buffer); + } + for (int i = 0; i < g_num_db_runtime; i++) { + plugin_logger_info(&g_logger, "DB%d: %d bytes -> %s[%d]", + g_db_runtime[i].db_number, + g_db_runtime[i].size_bytes, + s7comm_buffer_type_name(g_db_runtime[i].type), + g_db_runtime[i].start_buffer); + } + + return 0; +} + +/** + * @brief Start the S7 server + */ +extern "C" void start_loop(void) +{ + if (!g_initialized) { + plugin_logger_error(&g_logger, "Cannot start - plugin not initialized"); + return; + } + + if (!g_config.enabled) { + plugin_logger_info(&g_logger, "S7 server disabled in configuration"); + return; + } + + if (g_running) { + plugin_logger_warn(&g_logger, "Server already running"); + return; + } + + plugin_logger_info(&g_logger, "Starting S7 server on %s:%d...", + g_config.bind_address, g_config.port); + + /* Start the server */ + int result; + if (strcmp(g_config.bind_address, "0.0.0.0") == 0) { + result = Srv_Start(g_server); + } else { + result = Srv_StartTo(g_server, g_config.bind_address); + } + + if (result != 0) { + plugin_logger_error(&g_logger, "Failed to start S7 server: 0x%08X", result); + if (g_config.port < 1024) { + plugin_logger_error(&g_logger, "Note: Port %d requires root privileges on Linux", g_config.port); + } + return; + } + + g_running = true; + plugin_logger_info(&g_logger, "S7 server started successfully"); +} + +/** + * @brief Stop the S7 server + */ +extern "C" void stop_loop(void) +{ + if (!g_running) { + plugin_logger_debug(&g_logger, "Server already stopped"); + return; + } + + plugin_logger_info(&g_logger, "Stopping S7 server..."); + + Srv_Stop(g_server); + g_running = false; + + plugin_logger_info(&g_logger, "S7 server stopped"); +} + +/** + * @brief Cleanup plugin resources + */ +extern "C" void cleanup(void) +{ + plugin_logger_info(&g_logger, "Cleaning up S7Comm plugin..."); + + if (g_running) { + stop_loop(); + } + + if (g_server != 0) { + Srv_Destroy(g_server); + g_server = 0; + } + + free_buffers(); + pthread_mutex_destroy(&g_s7_mutex); + + g_initialized = false; + g_config_loaded = false; + plugin_logger_info(&g_logger, "S7Comm plugin cleanup complete"); +} + +/** + * @brief Called at the start of each PLC scan cycle + * + * Copy ENTIRE S7 buffer -> OpenPLC buffer for ALL types. + * This allows S7 clients to write to any location. Values that are actively + * driven by the PLC program or I/O drivers will be overwritten during the + * scan cycle, but S7 writes to other locations will persist. + */ +extern "C" void cycle_start(void) +{ + if (!g_initialized || !g_running || !g_config.enabled) { + return; + } + + /* + * Lock S7 mutex and copy S7 -> shadow for ALL areas + * This captures everything S7 clients have written + */ + pthread_mutex_lock(&g_s7_mutex); + + /* Copy all system areas S7 -> shadow */ + if (g_pe_runtime.enabled) { + memcpy(g_pe_runtime.shadow_buffer, g_pe_runtime.s7_buffer, g_pe_runtime.size_bytes); + } + if (g_pa_runtime.enabled) { + memcpy(g_pa_runtime.shadow_buffer, g_pa_runtime.s7_buffer, g_pa_runtime.size_bytes); + } + if (g_mk_runtime.enabled) { + memcpy(g_mk_runtime.shadow_buffer, g_mk_runtime.s7_buffer, g_mk_runtime.size_bytes); + } + + /* Copy all data blocks S7 -> shadow */ + for (int i = 0; i < g_num_db_runtime; i++) { + s7comm_db_runtime_t *db = &g_db_runtime[i]; + memcpy(db->shadow_buffer, db->s7_buffer, db->size_bytes); + } + + pthread_mutex_unlock(&g_s7_mutex); + + /* + * Sync shadow -> OpenPLC for ALL areas + * S7 client writes become visible to the PLC program + */ + + /* Sync all system areas shadow -> OpenPLC */ + if (g_pe_runtime.enabled) { + sync_shadow_to_openplc_by_type(g_pe_runtime.shadow_buffer, g_pe_runtime.size_bytes, + g_pe_runtime.type, g_pe_runtime.start_buffer); + } + if (g_pa_runtime.enabled) { + sync_shadow_to_openplc_by_type(g_pa_runtime.shadow_buffer, g_pa_runtime.size_bytes, + g_pa_runtime.type, g_pa_runtime.start_buffer); + } + if (g_mk_runtime.enabled) { + sync_shadow_to_openplc_by_type(g_mk_runtime.shadow_buffer, g_mk_runtime.size_bytes, + g_mk_runtime.type, g_mk_runtime.start_buffer); + } + + /* Sync all data blocks shadow -> OpenPLC */ + for (int i = 0; i < g_num_db_runtime; i++) { + s7comm_db_runtime_t *db = &g_db_runtime[i]; + sync_shadow_to_openplc_by_type(db->shadow_buffer, db->size_bytes, db->type, db->start_buffer); + } +} + +/** + * @brief Called at the end of each PLC scan cycle + * + * Copy ENTIRE OpenPLC buffer -> S7 buffer for ALL types. + * This makes all PLC values visible to S7 clients. Any values driven by + * the PLC program or I/O drivers during the scan cycle will overwrite + * whatever S7 clients wrote at cycle_start. + */ +extern "C" void cycle_end(void) +{ + if (!g_initialized || !g_running || !g_config.enabled) { + return; + } + + /* + * Sync OpenPLC -> shadow for ALL areas + * This captures the final state after PLC execution + */ + + /* Sync all system areas OpenPLC -> shadow */ + if (g_pe_runtime.enabled) { + sync_openplc_to_shadow_by_type(g_pe_runtime.shadow_buffer, g_pe_runtime.size_bytes, + g_pe_runtime.type, g_pe_runtime.start_buffer); + } + if (g_pa_runtime.enabled) { + sync_openplc_to_shadow_by_type(g_pa_runtime.shadow_buffer, g_pa_runtime.size_bytes, + g_pa_runtime.type, g_pa_runtime.start_buffer); + } + if (g_mk_runtime.enabled) { + sync_openplc_to_shadow_by_type(g_mk_runtime.shadow_buffer, g_mk_runtime.size_bytes, + g_mk_runtime.type, g_mk_runtime.start_buffer); + } + + /* Sync all data blocks OpenPLC -> shadow */ + for (int i = 0; i < g_num_db_runtime; i++) { + s7comm_db_runtime_t *db = &g_db_runtime[i]; + sync_openplc_to_shadow_by_type(db->shadow_buffer, db->size_bytes, db->type, db->start_buffer); + } + + /* + * Lock S7 mutex and copy shadow -> S7 for ALL areas + * This makes PLC values visible to S7 clients + */ + pthread_mutex_lock(&g_s7_mutex); + + /* Copy all system areas shadow -> S7 */ + if (g_pe_runtime.enabled) { + memcpy(g_pe_runtime.s7_buffer, g_pe_runtime.shadow_buffer, g_pe_runtime.size_bytes); + } + if (g_pa_runtime.enabled) { + memcpy(g_pa_runtime.s7_buffer, g_pa_runtime.shadow_buffer, g_pa_runtime.size_bytes); + } + if (g_mk_runtime.enabled) { + memcpy(g_mk_runtime.s7_buffer, g_mk_runtime.shadow_buffer, g_mk_runtime.size_bytes); + } + + /* Copy all data blocks shadow -> S7 */ + for (int i = 0; i < g_num_db_runtime; i++) { + s7comm_db_runtime_t *db = &g_db_runtime[i]; + memcpy(db->s7_buffer, db->shadow_buffer, db->size_bytes); + } + + pthread_mutex_unlock(&g_s7_mutex); +} + +/* + * ============================================================================= + * Snap7 Callbacks + * ============================================================================= + */ + +/** + * @brief Snap7 event callback for logging connections and errors + */ +static void s7comm_event_callback(void *usrPtr, PSrvEvent PEvent, int Size) +{ + (void)usrPtr; + (void)Size; + + switch (PEvent->EvtCode) { + case evcServerStarted: + plugin_logger_info(&g_logger, "S7 server started"); + break; + case evcServerStopped: + plugin_logger_info(&g_logger, "S7 server stopped"); + break; + case evcClientAdded: + if (g_config.logging.log_connections) { + plugin_logger_info(&g_logger, "Client connected (ID: %d)", PEvent->EvtSender); + } + break; + case evcClientDisconnected: + if (g_config.logging.log_connections) { + plugin_logger_info(&g_logger, "Client disconnected (ID: %d)", PEvent->EvtSender); + } + break; + case evcClientRejected: + plugin_logger_warn(&g_logger, "Client rejected (ID: %d)", PEvent->EvtSender); + break; + case evcListenerCannotStart: + plugin_logger_error(&g_logger, "Listener cannot start - port may be in use or requires root"); + break; + case evcClientException: + if (g_config.logging.log_errors) { + plugin_logger_warn(&g_logger, "Client exception (ID: %d)", PEvent->EvtSender); + } + break; + case evcDataRead: + if (g_config.logging.log_data_access) { + plugin_logger_debug(&g_logger, "Data read by client %d", PEvent->EvtSender); + } + break; + case evcDataWrite: + if (g_config.logging.log_data_access) { + plugin_logger_debug(&g_logger, "Data write by client %d", PEvent->EvtSender); + } + break; + default: + /* Ignore other events */ + break; + } +} + +/* + * ============================================================================= + * Buffer Synchronization Functions (Shadow <-> OpenPLC) + * ============================================================================= + */ + +/** + * @brief Sync a bool buffer from shadow to OpenPLC + * + * Copies S7 client writes to OpenPLC buffers. Used at cycle_start for ALL types. + * The PLC program and I/O drivers will overwrite values they actively control. + */ +static void sync_shadow_bool_to_openplc(uint8_t *shadow, int size, s7comm_buffer_type_t type, int start_buffer) +{ + IEC_BOOL *(*buffer)[8] = NULL; + + switch (type) { + case BUFFER_TYPE_BOOL_INPUT: + buffer = g_runtime_args.bool_input; + break; + case BUFFER_TYPE_BOOL_OUTPUT: + buffer = g_runtime_args.bool_output; + break; + case BUFFER_TYPE_BOOL_MEMORY: + buffer = g_runtime_args.bool_memory; + break; + default: + return; + } + + int max_bytes = g_runtime_args.buffer_size - start_buffer; + if (max_bytes > size) max_bytes = size; + + for (int byte_idx = 0; byte_idx < max_bytes; byte_idx++) { + uint8_t byte_val = shadow[byte_idx]; + int plc_idx = start_buffer + byte_idx; + for (int bit_idx = 0; bit_idx < 8; bit_idx++) { + IEC_BOOL *ptr = buffer[plc_idx][bit_idx]; + if (ptr != NULL) { + *ptr = (byte_val >> bit_idx) & 0x01; + } + } + } +} + +/** + * @brief Sync OpenPLC bool buffer to shadow + */ +static void sync_openplc_bool_to_shadow(uint8_t *shadow, int size, s7comm_buffer_type_t type, int start_buffer) +{ + IEC_BOOL *(*buffer)[8] = NULL; + + switch (type) { + case BUFFER_TYPE_BOOL_INPUT: + buffer = g_runtime_args.bool_input; + break; + case BUFFER_TYPE_BOOL_OUTPUT: + buffer = g_runtime_args.bool_output; + break; + case BUFFER_TYPE_BOOL_MEMORY: + buffer = g_runtime_args.bool_memory; + break; + default: + return; + } + + int max_bytes = g_runtime_args.buffer_size - start_buffer; + if (max_bytes > size) max_bytes = size; + + for (int byte_idx = 0; byte_idx < max_bytes; byte_idx++) { + uint8_t byte_val = 0; + int plc_idx = start_buffer + byte_idx; + for (int bit_idx = 0; bit_idx < 8; bit_idx++) { + IEC_BOOL *ptr = buffer[plc_idx][bit_idx]; + if (ptr != NULL && *ptr) { + byte_val |= (1 << bit_idx); + } + } + shadow[byte_idx] = byte_val; + } +} + +/** + * @brief Sync shadow int buffer to OpenPLC (with endian conversion) + * + * Copies S7 client writes to OpenPLC buffers. Used at cycle_start for ALL types. + * The PLC program and I/O drivers will overwrite values they actively control. + */ +static void sync_shadow_int_to_openplc(uint8_t *shadow, int size, s7comm_buffer_type_t type, int start_buffer) +{ + IEC_UINT **buffer = NULL; + + switch (type) { + case BUFFER_TYPE_INT_INPUT: + buffer = g_runtime_args.int_input; + break; + case BUFFER_TYPE_INT_OUTPUT: + buffer = g_runtime_args.int_output; + break; + case BUFFER_TYPE_INT_MEMORY: + buffer = g_runtime_args.int_memory; + break; + default: + return; + } + + uint16_t *shadow_words = (uint16_t *)shadow; + int num_words = size / 2; + int max_words = g_runtime_args.buffer_size - start_buffer; + if (max_words > num_words) max_words = num_words; + + for (int i = 0; i < max_words; i++) { + IEC_UINT *ptr = buffer[start_buffer + i]; + if (ptr != NULL) { + *ptr = swap16(shadow_words[i]); + } + } +} + +/** + * @brief Sync OpenPLC int buffer to shadow (with endian conversion) + */ +static void sync_openplc_int_to_shadow(uint8_t *shadow, int size, s7comm_buffer_type_t type, int start_buffer) +{ + IEC_UINT **buffer = NULL; + + switch (type) { + case BUFFER_TYPE_INT_INPUT: + buffer = g_runtime_args.int_input; + break; + case BUFFER_TYPE_INT_OUTPUT: + buffer = g_runtime_args.int_output; + break; + case BUFFER_TYPE_INT_MEMORY: + buffer = g_runtime_args.int_memory; + break; + default: + return; + } + + uint16_t *shadow_words = (uint16_t *)shadow; + int num_words = size / 2; + int max_words = g_runtime_args.buffer_size - start_buffer; + if (max_words > num_words) max_words = num_words; + + for (int i = 0; i < max_words; i++) { + IEC_UINT *ptr = buffer[start_buffer + i]; + if (ptr != NULL) { + shadow_words[i] = swap16(*ptr); + } + } +} + +/** + * @brief Sync shadow dint buffer to OpenPLC (with endian conversion) + * + * Copies S7 client writes to OpenPLC buffers. Used at cycle_start for ALL types. + * The PLC program and I/O drivers will overwrite values they actively control. + */ +static void sync_shadow_dint_to_openplc(uint8_t *shadow, int size, s7comm_buffer_type_t type, int start_buffer) +{ + IEC_UDINT **buffer = NULL; + + switch (type) { + case BUFFER_TYPE_DINT_INPUT: + buffer = g_runtime_args.dint_input; + break; + case BUFFER_TYPE_DINT_OUTPUT: + buffer = g_runtime_args.dint_output; + break; + case BUFFER_TYPE_DINT_MEMORY: + buffer = g_runtime_args.dint_memory; + break; + default: + return; + } + + uint32_t *shadow_dwords = (uint32_t *)shadow; + int num_dwords = size / 4; + int max_dwords = g_runtime_args.buffer_size - start_buffer; + if (max_dwords > num_dwords) max_dwords = num_dwords; + + for (int i = 0; i < max_dwords; i++) { + IEC_UDINT *ptr = buffer[start_buffer + i]; + if (ptr != NULL) { + *ptr = swap32(shadow_dwords[i]); + } + } +} + +/** + * @brief Sync OpenPLC dint buffer to shadow (with endian conversion) + */ +static void sync_openplc_dint_to_shadow(uint8_t *shadow, int size, s7comm_buffer_type_t type, int start_buffer) +{ + IEC_UDINT **buffer = NULL; + + switch (type) { + case BUFFER_TYPE_DINT_INPUT: + buffer = g_runtime_args.dint_input; + break; + case BUFFER_TYPE_DINT_OUTPUT: + buffer = g_runtime_args.dint_output; + break; + case BUFFER_TYPE_DINT_MEMORY: + buffer = g_runtime_args.dint_memory; + break; + default: + return; + } + + uint32_t *shadow_dwords = (uint32_t *)shadow; + int num_dwords = size / 4; + int max_dwords = g_runtime_args.buffer_size - start_buffer; + if (max_dwords > num_dwords) max_dwords = num_dwords; + + for (int i = 0; i < max_dwords; i++) { + IEC_UDINT *ptr = buffer[start_buffer + i]; + if (ptr != NULL) { + shadow_dwords[i] = swap32(*ptr); + } + } +} + +/** + * @brief Sync shadow lint buffer to OpenPLC (with endian conversion) + * + * Copies S7 client writes to OpenPLC buffers. Used at cycle_start for ALL types. + * The PLC program and I/O drivers will overwrite values they actively control. + */ +static void sync_shadow_lint_to_openplc(uint8_t *shadow, int size, s7comm_buffer_type_t type, int start_buffer) +{ + IEC_ULINT **buffer = NULL; + + switch (type) { + case BUFFER_TYPE_LINT_INPUT: + buffer = g_runtime_args.lint_input; + break; + case BUFFER_TYPE_LINT_OUTPUT: + buffer = g_runtime_args.lint_output; + break; + case BUFFER_TYPE_LINT_MEMORY: + buffer = g_runtime_args.lint_memory; + break; + default: + return; + } + + uint64_t *shadow_lwords = (uint64_t *)shadow; + int num_lwords = size / 8; + int max_lwords = g_runtime_args.buffer_size - start_buffer; + if (max_lwords > num_lwords) max_lwords = num_lwords; + + for (int i = 0; i < max_lwords; i++) { + IEC_ULINT *ptr = buffer[start_buffer + i]; + if (ptr != NULL) { + *ptr = swap64(shadow_lwords[i]); + } + } +} + +/** + * @brief Sync OpenPLC lint buffer to shadow (with endian conversion) + */ +static void sync_openplc_lint_to_shadow(uint8_t *shadow, int size, s7comm_buffer_type_t type, int start_buffer) +{ + IEC_ULINT **buffer = NULL; + + switch (type) { + case BUFFER_TYPE_LINT_INPUT: + buffer = g_runtime_args.lint_input; + break; + case BUFFER_TYPE_LINT_OUTPUT: + buffer = g_runtime_args.lint_output; + break; + case BUFFER_TYPE_LINT_MEMORY: + buffer = g_runtime_args.lint_memory; + break; + default: + return; + } + + uint64_t *shadow_lwords = (uint64_t *)shadow; + int num_lwords = size / 8; + int max_lwords = g_runtime_args.buffer_size - start_buffer; + if (max_lwords > num_lwords) max_lwords = num_lwords; + + for (int i = 0; i < max_lwords; i++) { + IEC_ULINT *ptr = buffer[start_buffer + i]; + if (ptr != NULL) { + shadow_lwords[i] = swap64(*ptr); + } + } +} + +/** + * @brief Dispatch sync from shadow to OpenPLC based on buffer type + * + * Used by cycle_start() for ALL types. Copies S7 client writes to OpenPLC buffers. + * Values driven by the PLC program or I/O drivers will be overwritten during the scan cycle. + */ +static void sync_shadow_to_openplc_by_type(uint8_t *shadow, int size, s7comm_buffer_type_t type, int start_buffer) +{ + switch (type) { + case BUFFER_TYPE_BOOL_INPUT: + case BUFFER_TYPE_BOOL_OUTPUT: + case BUFFER_TYPE_BOOL_MEMORY: + sync_shadow_bool_to_openplc(shadow, size, type, start_buffer); + break; + + case BUFFER_TYPE_INT_INPUT: + case BUFFER_TYPE_INT_OUTPUT: + case BUFFER_TYPE_INT_MEMORY: + sync_shadow_int_to_openplc(shadow, size, type, start_buffer); + break; + + case BUFFER_TYPE_DINT_INPUT: + case BUFFER_TYPE_DINT_OUTPUT: + case BUFFER_TYPE_DINT_MEMORY: + sync_shadow_dint_to_openplc(shadow, size, type, start_buffer); + break; + + case BUFFER_TYPE_LINT_INPUT: + case BUFFER_TYPE_LINT_OUTPUT: + case BUFFER_TYPE_LINT_MEMORY: + sync_shadow_lint_to_openplc(shadow, size, type, start_buffer); + break; + + default: + break; + } +} + +/** + * @brief Dispatch sync from OpenPLC to shadow based on buffer type + */ +static void sync_openplc_to_shadow_by_type(uint8_t *shadow, int size, s7comm_buffer_type_t type, int start_buffer) +{ + switch (type) { + case BUFFER_TYPE_BOOL_INPUT: + case BUFFER_TYPE_BOOL_OUTPUT: + case BUFFER_TYPE_BOOL_MEMORY: + sync_openplc_bool_to_shadow(shadow, size, type, start_buffer); + break; + + case BUFFER_TYPE_INT_INPUT: + case BUFFER_TYPE_INT_OUTPUT: + case BUFFER_TYPE_INT_MEMORY: + sync_openplc_int_to_shadow(shadow, size, type, start_buffer); + break; + + case BUFFER_TYPE_DINT_INPUT: + case BUFFER_TYPE_DINT_OUTPUT: + case BUFFER_TYPE_DINT_MEMORY: + sync_openplc_dint_to_shadow(shadow, size, type, start_buffer); + break; + + case BUFFER_TYPE_LINT_INPUT: + case BUFFER_TYPE_LINT_OUTPUT: + case BUFFER_TYPE_LINT_MEMORY: + sync_openplc_lint_to_shadow(shadow, size, type, start_buffer); + break; + + default: + break; + } +} diff --git a/core/src/drivers/plugins/native/s7comm/s7comm_plugin.h b/core/src/drivers/plugins/native/s7comm/s7comm_plugin.h new file mode 100644 index 00000000..f86c93a7 --- /dev/null +++ b/core/src/drivers/plugins/native/s7comm/s7comm_plugin.h @@ -0,0 +1,74 @@ +/** + * @file s7comm_plugin.h + * @brief S7Comm Plugin for OpenPLC Runtime v4 + * + * This plugin implements a Siemens S7 communication server using the Snap7 library, + * allowing S7-compatible HMIs and SCADA systems to communicate with OpenPLC. + * + * The plugin is self-contained with Snap7 sources compiled directly into the + * shared library, requiring no external dependencies. + */ + +#ifndef S7COMM_PLUGIN_H +#define S7COMM_PLUGIN_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Initialize the S7Comm plugin + * + * Called when the plugin is loaded. Initializes the Snap7 server and + * registers data areas for S7 client access. + * + * @param args Pointer to plugin_runtime_args_t containing runtime buffers, + * mutex functions, and logging function pointers + * @return 0 on success, -1 on failure + */ +int init(void *args); + +/** + * @brief Start the S7 server + * + * Starts the Snap7 server and begins accepting client connections + * on the configured port (default: 102). + */ +void start_loop(void); + +/** + * @brief Stop the S7 server + * + * Stops the Snap7 server and disconnects all clients gracefully. + */ +void stop_loop(void); + +/** + * @brief Cleanup plugin resources + * + * Releases all resources allocated by the plugin including + * the Snap7 server instance and data buffers. + */ +void cleanup(void); + +/** + * @brief Called at the start of each PLC scan cycle + * + * Synchronizes OpenPLC input buffers to S7 data areas. + * Called with buffer mutex already held. + */ +void cycle_start(void); + +/** + * @brief Called at the end of each PLC scan cycle + * + * Synchronizes S7 data areas to OpenPLC output buffers. + * Called with buffer mutex already held. + */ +void cycle_end(void); + +#ifdef __cplusplus +} +#endif + +#endif /* S7COMM_PLUGIN_H */ diff --git a/core/src/drivers/plugins/native/s7comm/snap7/core/s7_client.cpp b/core/src/drivers/plugins/native/s7comm/snap7/core/s7_client.cpp new file mode 100644 index 00000000..af115b96 --- /dev/null +++ b/core/src/drivers/plugins/native/s7comm/snap7/core/s7_client.cpp @@ -0,0 +1,503 @@ +/*=============================================================================| +| PROJECT SNAP7 1.4.3 | +|==============================================================================| +| Copyright (C) 2013, 2025 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/ | +|=============================================================================*/ +#include "s7_client.h" + +//--------------------------------------------------------------------------- +TSnap7Client::TSnap7Client() +{ + FThread = 0; + CliCompletion = 0; + EvtJob = NULL; + EvtComplete = NULL; + FThread=NULL; + ThreadCreated = false; +} +//--------------------------------------------------------------------------- +TSnap7Client::~TSnap7Client() +{ + Destroying=true; + Disconnect(); + CliCompletion=NULL; + if (ThreadCreated) + { + CloseThread(); + delete EvtComplete; + delete EvtJob; + ThreadCreated=false; + } +} +//--------------------------------------------------------------------------- +void TSnap7Client::CloseThread() +{ + int Timeout; + + if (FThread) + { + FThread->Terminate(); + if (Job.Pending) + Timeout=3000; + else + Timeout=1000; + EvtJob->Set(); + if (FThread->WaitFor(Timeout)!=WAIT_OBJECT_0) + FThread->Kill(); + try { + delete FThread; + } + catch (...){ + } + + FThread=0; + } +} +//--------------------------------------------------------------------------- +void TSnap7Client::OpenThread() +{ + FThread = new TClientThread(this); + FThread->Start(); +} +//--------------------------------------------------------------------------- +int TSnap7Client::Reset(bool DoReconnect) +{ + bool WasConnected = Connected; + if (ThreadCreated) + { + CloseThread(); + Disconnect(); + OpenThread(); + } + else + Disconnect(); + + if (DoReconnect || WasConnected) + return Connect(); + else + return 0; +} +//--------------------------------------------------------------------------- +void TSnap7Client::DoCompletion() +{ + if ((CliCompletion!=NULL) && !Destroying) + { + try{ + CliCompletion(FUsrPtr, Job.Op, Job.Result); + }catch (...) + { + } + } +} +//--------------------------------------------------------------------------- +int TSnap7Client::SetAsCallback(pfn_CliCompletion pCompletion, void * usrPtr) +{ + CliCompletion=pCompletion; + FUsrPtr=usrPtr; + return 0; +} +//--------------------------------------------------------------------------- +int TSnap7Client::GetParam(int ParamNumber, void * pValue) +{ + // Actually there are no specific client params, maybe in future... + return TSnap7MicroClient::GetParam(ParamNumber, pValue); +} +//--------------------------------------------------------------------------- +int TSnap7Client::SetParam(int ParamNumber, void * pValue) +{ + // Actually there are no specific client params, maybe in future... + return TSnap7MicroClient::SetParam(ParamNumber, pValue); +} +//--------------------------------------------------------------------------- +bool TSnap7Client::CheckAsCompletion(int &opResult) +{ + if (!Job.Pending) + opResult=Job.Result; + else + if (!Destroying) + opResult=errCliJobPending; // don't set LastError here + else + { + opResult=errCliDestroying; + return true; + } + + return !Job.Pending; +} +//--------------------------------------------------------------------------- +int TSnap7Client::AsReadArea(int Area, int DBNumber, int Start, int Amount, int WordLen, void * pUsrData) +{ + if (!Job.Pending) + { + Job.Pending = true; + Job.Op = s7opReadArea; + Job.Area = Area; + Job.Number = DBNumber; + Job.Start = Start; + Job.Amount = Amount; + Job.WordLen = WordLen; + Job.pData = pUsrData; + JobStart = SysGetTick(); + StartAsyncJob(); + return 0; + } + else + return SetError(errCliJobPending); +} +//--------------------------------------------------------------------------- +int TSnap7Client::AsWriteArea(int Area, int DBNumber, int Start, int Amount, int WordLen, void * pUsrData) +{ + int ByteSize, TotalSize; + + if (!Job.Pending) + { + Job.Pending =true; + Job.Op =s7opWriteArea; + Job.Area =Area; + Job.Number =DBNumber; + Job.Start =Start; + // Performs some check first to copy the data + ByteSize=DataSizeByte(WordLen); + TotalSize=ByteSize*Amount; // Total size in bytes + if (ByteSize==0) + return SetError(errCliInvalidWordLen); + if ((TotalSize < 1) || (TotalSize > int(sizeof(opData)))) + return SetError(errCliInvalidParams); + Job.Amount =Amount; + Job.WordLen =WordLen; + // Doublebuffering + memcpy(&opData, pUsrData, TotalSize); + Job.pData =&opData; + JobStart =SysGetTick(); + StartAsyncJob(); + return 0; + } + else + return SetError(errCliJobPending); +} +//--------------------------------------------------------------------------- +int TSnap7Client::AsListBlocksOfType(int BlockType, PS7BlocksOfType pUsrData, int & ItemsCount) +{ + if (!Job.Pending) + { + Job.Pending =true; + Job.Op =s7opListBlocksOfType; + Job.Area =BlockType; + Job.pData =pUsrData; + Job.pAmount =&ItemsCount; + JobStart =SysGetTick(); + StartAsyncJob(); + return 0; + } + else + return SetError(errCliJobPending); +} +//--------------------------------------------------------------------------- +int TSnap7Client::AsReadSZL(int ID, int Index, PS7SZL pUsrData, int & Size) +{ + if (!Job.Pending) + { + Job.Pending =true; + Job.Op =s7opReadSZL; + Job.ID =ID; + Job.Index =Index; + Job.pData =pUsrData; + Job.pAmount =&Size; + Job.Amount =Size; + Job.IParam =1; // Data has to be copied into user buffer + JobStart =SysGetTick(); + StartAsyncJob(); + return 0; + } + else + return SetError(errCliJobPending); +} +//--------------------------------------------------------------------------- +int TSnap7Client::AsReadSZLList(PS7SZLList pUsrData, int &ItemsCount) +{ + if (!Job.Pending) + { + Job.Pending =true; + Job.Op =s7opReadSzlList; + Job.pData =pUsrData; + Job.pAmount =&ItemsCount; + Job.Amount =ItemsCount; + JobStart =SysGetTick(); + StartAsyncJob(); + return 0; + } + else + return SetError(errCliJobPending); +} +//--------------------------------------------------------------------------- +int TSnap7Client::AsUpload(int BlockType, int BlockNum, void * pUsrData, int & Size) +{ + if (!Job.Pending) + { + Job.Pending =true; + Job.Op =s7opUpload; + Job.Area =BlockType; + Job.pData =pUsrData; + Job.pAmount =&Size; + Job.Amount =Size; + Job.Number =BlockNum; + Job.IParam =0; // not full upload, only data + JobStart =SysGetTick(); + StartAsyncJob(); + return 0; + } + else + return SetError(errCliJobPending); +} +//--------------------------------------------------------------------------- +int TSnap7Client::AsFullUpload(int BlockType, int BlockNum, void * pUsrData, int & Size) +{ + if (!Job.Pending) + { + Job.Pending =true; + Job.Op =s7opUpload; + Job.Area =BlockType; + Job.pData =pUsrData; + Job.pAmount =&Size; + Job.Amount =Size; + Job.Number =BlockNum; + Job.IParam =1; // full upload + JobStart =SysGetTick(); + StartAsyncJob(); + return 0; + } + else + return SetError(errCliJobPending); +} +//--------------------------------------------------------------------------- +int TSnap7Client::AsDownload(int BlockNum, void * pUsrData, int Size) +{ + if (!Job.Pending) + { + // Checks the size : here we only need a size>0 to avoid problems during + // doublebuffering, the real test of the block size will be done in + // Checkblock. + if (Size<1) + return SetError(errCliInvalidBlockSize); + Job.Pending =true; + Job.Op =s7opDownload; + // Doublebuffering + memcpy(&opData, pUsrData, Size); + Job.Number =BlockNum; + Job.Amount =Size; + JobStart =SysGetTick(); + StartAsyncJob(); + return 0; + } + else + return SetError(errCliJobPending); +} +//--------------------------------------------------------------------------- +int TSnap7Client::AsCopyRamToRom(int Timeout) +{ + if (!Job.Pending) + { + Job.Pending =true; + Job.Op =s7opCopyRamToRom; + if (Timeout>0) + { + Job.IParam =Timeout; + JobStart =SysGetTick(); + StartAsyncJob(); + return 0; + } + else + return SetError(errCliInvalidParams); + } + else + return SetError(errCliJobPending); +} +//--------------------------------------------------------------------------- +int TSnap7Client::AsCompress(int Timeout) +{ + if (!Job.Pending) + { + Job.Pending =true; + Job.Op =s7opCompress; + if (Timeout>0) + { + Job.IParam =Timeout; + JobStart =SysGetTick(); + StartAsyncJob(); + return 0; + } + else + return SetError(errCliInvalidParams); + } + else + return SetError(errCliJobPending); +} +//--------------------------------------------------------------------------- +int TSnap7Client::AsDBRead(int DBNumber, int Start, int Size, void * pUsrData) +{ + return AsReadArea(S7AreaDB, DBNumber, Start, Size, S7WLByte, pUsrData); +} +//--------------------------------------------------------------------------- +int TSnap7Client::AsDBWrite(int DBNumber, int Start, int Size, void * pUsrData) +{ + return AsWriteArea(S7AreaDB, DBNumber, Start, Size, S7WLByte, pUsrData); +} +//--------------------------------------------------------------------------- +int TSnap7Client::AsMBRead(int Start, int Size, void * pUsrData) +{ + return AsReadArea(S7AreaMK, 0, Start, Size, S7WLByte, pUsrData); +} +//--------------------------------------------------------------------------- +int TSnap7Client::AsMBWrite(int Start, int Size, void * pUsrData) +{ + return AsWriteArea(S7AreaMK, 0, Start, Size, S7WLByte, pUsrData); +} +//--------------------------------------------------------------------------- +int TSnap7Client::AsEBRead(int Start, int Size, void * pUsrData) +{ + return AsReadArea(S7AreaPE, 0, Start, Size, S7WLByte, pUsrData); +} +//--------------------------------------------------------------------------- +int TSnap7Client::AsEBWrite(int Start, int Size, void * pUsrData) +{ + return AsWriteArea(S7AreaPE, 0, Start, Size, S7WLByte, pUsrData); +} +//--------------------------------------------------------------------------- +int TSnap7Client::AsABRead(int Start, int Size, void * pUsrData) +{ + return AsReadArea(S7AreaPA, 0, Start, Size, S7WLByte, pUsrData); +} +//--------------------------------------------------------------------------- +int TSnap7Client::AsABWrite(int Start, int Size, void * pUsrData) +{ + return AsWriteArea(S7AreaPA, 0, Start, Size, S7WLByte, pUsrData); +} +//--------------------------------------------------------------------------- +int TSnap7Client::AsTMRead(int Start, int Amount, void * pUsrData) +{ + return AsReadArea(S7AreaTM, 0, Start, Amount, S7WLTimer, pUsrData); +} +//--------------------------------------------------------------------------- +int TSnap7Client::AsTMWrite(int Start, int Amount, void * pUsrData) +{ + return AsWriteArea(S7AreaTM, 0, Start, Amount, S7WLTimer, pUsrData); +} +//--------------------------------------------------------------------------- +int TSnap7Client::AsCTRead(int Start, int Amount, void * pUsrData) +{ + return AsReadArea(S7AreaCT, 0, Start, Amount, S7WLCounter, pUsrData); +} +//--------------------------------------------------------------------------- +int TSnap7Client::AsCTWrite(int Start, int Amount, void * pUsrData) +{ + return AsWriteArea(S7AreaCT, 0, Start, Amount, S7WLCounter, pUsrData); +} +//--------------------------------------------------------------------------- +int TSnap7Client::AsDBGet(int DBNumber, void * pUsrData, int &Size) +{ + if (!Job.Pending) + { + if (Size<=0) + return SetError(errCliInvalidBlockSize); + Job.Pending =true; + Job.Op =s7opDBGet; + Job.Number =DBNumber; + Job.pData =pUsrData; + Job.pAmount =&Size; + Job.Amount =Size; + JobStart =SysGetTick(); + StartAsyncJob(); + return 0; + } + else + return SetError(errCliJobPending); +} +//--------------------------------------------------------------------------- +int TSnap7Client::AsDBFill(int DBNumber, int FillChar) +{ + if (!Job.Pending) + { + Job.Pending =true; + Job.Op =s7opDBFill; + Job.Number =DBNumber; + Job.IParam =FillChar; + JobStart =SysGetTick(); + StartAsyncJob(); + return 0; + } + else + return SetError(errCliJobPending); +} +//--------------------------------------------------------------------------- +void TSnap7Client::StartAsyncJob() +{ + ClrError(); + if (!ThreadCreated) + { + EvtJob = new TSnapEvent(false); + EvtComplete = new TSnapEvent(false); + OpenThread(); + ThreadCreated=true; + } + EvtComplete->Reset(); // reset if previously was not called WaitAsCompletion + EvtJob->Set(); +} +//--------------------------------------------------------------------------- +int TSnap7Client::WaitAsCompletion(unsigned long Timeout) +{ + if (Job.Pending) + { + if (ThreadCreated) + { + if (EvtComplete->WaitFor(Timeout)==WAIT_OBJECT_0) + return Job.Result; + else + { + if (Destroying) + return errCliDestroying; + else + return SetError(errCliJobTimeout); + } + } + else + return SetError(errCliJobTimeout); + } + else + return Job.Result; +} +//--------------------------------------------------------------------------- +void TClientThread::Execute() +{ + while (!Terminated) + { + FClient->EvtJob->WaitForever(); + if (!Terminated) + { + FClient->PerformOperation(); + FClient->EvtComplete->Set(); + // Notify the caller the end of job (if callback is set) + FClient->DoCompletion(); + } + }; +} + diff --git a/core/src/drivers/plugins/native/s7comm/snap7/core/s7_client.h b/core/src/drivers/plugins/native/s7comm/snap7/core/s7_client.h new file mode 100644 index 00000000..560949e2 --- /dev/null +++ b/core/src/drivers/plugins/native/s7comm/snap7/core/s7_client.h @@ -0,0 +1,104 @@ +/*=============================================================================| +| PROJECT SNAP7 1.4.3 | +|==============================================================================| +| Copyright (C) 2013, 2025 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/ | +|=============================================================================*/ +#ifndef s7_client_h +#define s7_client_h +//--------------------------------------------------------------------------- +#include "snap_threads.h" +#include "s7_micro_client.h" +//--------------------------------------------------------------------------- + +extern "C" { +typedef void (S7API *pfn_CliCompletion) (void * usrPtr, int opCode, int opResult); +} +class TSnap7Client; + +class TClientThread: public TSnapThread +{ +private: + TSnap7Client * FClient; +public: + TClientThread(TSnap7Client *Client) + { + FClient = Client; + } + void Execute(); +}; +//--------------------------------------------------------------------------- +class TSnap7Client: public TSnap7MicroClient +{ +private: + TClientThread *FThread; + bool ThreadCreated; + void CloseThread(); + void OpenThread(); + void StartAsyncJob(); +protected: + PSnapEvent EvtJob; + PSnapEvent EvtComplete; + pfn_CliCompletion CliCompletion; + void *FUsrPtr; + void DoCompletion(); +public: + friend class TClientThread; + TSnap7Client(); + ~TSnap7Client(); + int Reset(bool DoReconnect); + int SetAsCallback(pfn_CliCompletion pCompletion, void * usrPtr); + int GetParam(int ParamNumber, void *pValue); + int SetParam(int ParamNumber, void *pValue); + // Async functions + bool CheckAsCompletion( int & opResult); + int WaitAsCompletion(unsigned long Timeout); + int AsReadArea(int Area, int DBNumber, int Start, int Amount, int WordLen, void * pUsrData); + int AsWriteArea(int Area, int DBNumber, int Start, int Amount, int WordLen, void * pUsrData); + int AsListBlocksOfType(int BlockType, PS7BlocksOfType pUsrData, int & ItemsCount); + int AsReadSZL(int ID, int Index, PS7SZL pUsrData, int & Size); + int AsReadSZLList(PS7SZLList pUsrData, int &ItemsCount); + int AsUpload(int BlockType, int BlockNum, void * pUsrData, int & Size); + int AsFullUpload(int BlockType, int BlockNum, void * pUsrData, int & Size); + int AsDownload(int BlockNum, void * pUsrData, int Size); + int AsCopyRamToRom(int Timeout); + int AsCompress(int Timeout); + int AsDBRead(int DBNumber, int Start, int Size, void * pUsrData); + int AsDBWrite(int DBNumber, int Start, int Size, void * pUsrData); + int AsMBRead(int Start, int Size, void * pUsrData); + int AsMBWrite(int Start, int Size, void * pUsrData); + int AsEBRead(int Start, int Size, void * pUsrData); + int AsEBWrite(int Start, int Size, void * pUsrData); + int AsABRead(int Start, int Size, void * pUsrData); + int AsABWrite(int Start, int Size, void * pUsrData); + int AsTMRead(int Start, int Amount, void * pUsrData); + int AsTMWrite(int Start, int Amount, void * pUsrData); + int AsCTRead(int Start, int Amount, void * pUsrData); + int AsCTWrite(int Start, int Amount, void * pUsrData); + int AsDBGet(int DBNumber, void * pUsrData, int & Size); + int AsDBFill(int DBNumber, int FillChar); +}; + +typedef TSnap7Client *PSnap7Client; + +//--------------------------------------------------------------------------- +#endif // s7_client_h diff --git a/core/src/drivers/plugins/native/s7comm/snap7/core/s7_firmware.h b/core/src/drivers/plugins/native/s7comm/snap7/core/s7_firmware.h new file mode 100644 index 00000000..28102a13 --- /dev/null +++ b/core/src/drivers/plugins/native/s7comm/snap7/core/s7_firmware.h @@ -0,0 +1,1256 @@ +/*=============================================================================| +| PROJECT SNAP7 1.4.3 | +|==============================================================================| +| Copyright (C) 2013, 2025 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/ | +|=============================================================================*/ +#ifndef s7_firmware_h +#define s7_firmware_h +//--------------------------------------------------------------------------- + +#include "snap_platform.h" + +//****************************************************************************** +// CPU DATABANK +//****************************************************************************** + + byte SZLNotAvail[4] = { + 0x0A,0x00,0x00,0x00 + }; + + byte SZLSysState[6] = { + 0xFF,0x09,0x00,0x02,0x02,0x00 + }; + + byte SZL_ID_0000_IDX_XXXX[236] = { + 0xFF,0x09,0x00,0xE8,0x00,0x00,0x00,0x00,0x00,0x02,0x00,0x70,0x00,0x00,0x0F,0x00,0x00,0x02,0x00, + 0x11,0x01,0x11,0x0F,0x11,0x00,0x12,0x01,0x12,0x0F,0x12,0x00,0x13,0x01,0x13,0x00,0x14,0x0F,0x14, + 0x00,0x15,0x01,0x15,0x00,0x17,0x01,0x17,0x0F,0x17,0x00,0x18,0x01,0x18,0x0F,0x18,0x00,0x19,0x0F, + 0x19,0x00,0x1A,0x0F,0x1A,0x00,0x1B,0x0F,0x1B,0x00,0x1C,0x01,0x1C,0x0F,0x1C,0x00,0x21,0x0A,0x21, + 0x0F,0x21,0x02,0x22,0x00,0x23,0x0F,0x23,0x00,0x24,0x01,0x24,0x04,0x24,0x05,0x24,0x00,0x25,0x01, + 0x25,0x02,0x25,0x0F,0x25,0x01,0x31,0x01,0x32,0x02,0x32,0x00,0x36,0x01,0x36,0x0F,0x36,0x00,0x37, + 0x01,0x37,0x0F,0x37,0x00,0x38,0x01,0x38,0x02,0x38,0x0F,0x38,0x01,0x39,0x00,0x3A,0x0F,0x3A,0x00, + 0x74,0x01,0x74,0x0F,0x74,0x05,0x91,0x0A,0x91,0x0C,0x91,0x0D,0x91,0x00,0x92,0x02,0x92,0x06,0x92, + 0x0F,0x92,0x00,0x94,0x01,0x94,0x02,0x94,0x06,0x94,0x07,0x94,0x0F,0x94,0x00,0x95,0x01,0x95,0x0F, + 0x95,0x06,0x96,0x0C,0x96,0x0C,0x97,0x0D,0x97,0x01,0x9A,0x02,0x9A,0x0F,0x9A,0x0C,0x9B,0x00,0x9C, + 0x01,0x9C,0x02,0x9C,0x03,0x9C,0x0F,0x9C,0x00,0xA0,0x01,0xA0,0x0F,0xA0,0x00,0xB1,0x00,0xB2,0x00, + 0xB3,0x00,0xB4,0x01,0xB5,0x02,0xB5,0x03,0xB5,0x04,0xB5,0x05,0xB5,0x06,0xB5,0x07,0xB5,0x08,0xB5, + 0x01,0xB6,0x02,0xB6,0x03,0xB6,0x04,0xB6 + }; + + byte SZL_ID_0F00_IDX_XXXX[12] = { + 0xFF,0x09,0x00,0x08,0x0F,0x00,0x00,0x00,0x00,0x02,0x00,0x70 + }; + + byte SZL_ID_0002_IDX_XXXX[458] = { // <--Wrapped to 458 bytes + 0xFF,0x09,0x01,0xC6,0x00,0x02,0x00,0x00,0x00,0x08,0x00,0x71,0x01,0xAC,0x00,0x01,0x00,0x28,0x00, + 0x1C,0x01,0xAC,0x24,0x00,0x00,0x24,0x00,0x00,0x01,0xAC,0x23,0x00,0x00,0x06,0x04,0xB0,0x01,0xAC, + 0x22,0x00,0x00,0x08,0x00,0x01,0x01,0xAC,0x31,0x00,0x04,0x00,0x00,0x01,0x01,0xAC,0x12,0xFF,0x00, + 0x08,0x00,0x01,0x01,0xAC,0x12,0x31,0x00,0x08,0x00,0x01,0x01,0xAD,0x00,0x00,0x80,0x00,0x00,0x01, + 0x01,0xAD,0x01,0x00,0x80,0x00,0x00,0x01,0x01,0xAD,0x02,0x00,0x80,0x00,0x00,0x01,0x01,0xAD,0x03, + 0x00,0x80,0x00,0x00,0x01,0x01,0xAD,0x04,0x00,0x80,0x00,0x00,0x01,0x01,0xAD,0x05,0x00,0x80,0x00, + 0x00,0x01,0x01,0xAD,0x06,0x00,0x80,0x00,0x00,0x01,0x01,0xAD,0x07,0x00,0x80,0x00,0x00,0x01,0x01, + 0xAD,0x00,0x01,0x80,0x00,0x00,0x01,0x01,0xAD,0x01,0x01,0x80,0x00,0x00,0x01,0x01,0xAD,0x02,0x01, + 0x80,0x00,0x00,0x01,0x01,0xAD,0x03,0x01,0x80,0x00,0x00,0x01,0x01,0xAD,0x04,0x01,0x80,0x00,0x00, + 0x01,0x01,0xAD,0x05,0x01,0x80,0x00,0x00,0x01,0x01,0xAD,0x06,0x01,0x80,0x00,0x00,0x01,0x01,0xAD, + 0x07,0x01,0x80,0x00,0x00,0x01,0x01,0xAD,0x00,0x03,0x80,0x00,0x00,0x01,0x01,0xAD,0x01,0x03,0x80, + 0x00,0x00,0x01,0x01,0xAD,0x02,0x03,0x80,0x00,0x00,0x01,0x01,0xAD,0x03,0x03,0x80,0x00,0x00,0x01, + 0x01,0xAD,0x04,0x03,0x80,0x00,0x00,0x01,0x01,0xAD,0x05,0x03,0x80,0x00,0x00,0x01,0x01,0xAD,0x06, + 0x03,0x80,0x00,0x00,0x01,0x01,0xAD,0x07,0x03,0x80,0x00,0x00,0x01,0x01,0xAD,0x00,0x04,0x80,0x00, + 0x00,0x01,0x01,0xAD,0x01,0x04,0x80,0x00,0x00,0x01,0x01,0xAD,0x02,0x04,0x80,0x00,0x00,0x01,0x01, + 0xAD,0x03,0x04,0x80,0x00,0x00,0x01,0x01,0xAD,0x04,0x04,0x80,0x00,0x00,0x01,0x01,0xAD,0x05,0x04, + 0x80,0x00,0x00,0x01,0x01,0xAD,0x06,0x04,0x80,0x00,0x00,0x01,0x01,0xAD,0x07,0x04,0x80,0x00,0x00, + 0x01,0x01,0xAD,0x00,0x05,0x80,0x00,0x00,0x01,0x01,0xAD,0x01,0x05,0x80,0x00,0x00,0x01,0x01,0xAD, + 0x02,0x05,0x80,0x00,0x00,0x01,0x01,0xAD,0x03,0x05,0x80,0x00,0x00,0x01,0x01,0xAD,0x04,0x05,0x80, + 0x00,0x00,0x01,0x01,0xAD,0x05,0x05,0x80,0x00,0x00,0x01,0x01,0xAD,0x06,0x05,0x80,0x00,0x00,0x01, + 0x01,0xAD,0x07,0x05,0x80,0x00,0x00,0x01,0x01,0xAD,0x00,0x06,0x80,0x00,0x00,0x01,0x01,0xAD,0x01, + 0x06,0x80,0x00,0x00,0x01,0x01,0xAD,0x02,0x06,0x80,0x00,0x00,0x01,0x01,0xAD,0x03,0x06,0x80,0x00, + 0x00,0x01,0x01,0xAD,0x04,0x06,0x80,0x00,0x00,0x01,0x01,0xAD,0x05,0x06,0x80,0x00,0x00,0x01,0x01, + 0xAD,0x06,0x06,0x80,0x00,0x00,0x01,0x01,0xAD,0x07,0x06,0x80,0x00,0x00,0x01,0x01,0xAD,0x00,0x07, + 0x80,0x00 + }; + + byte SZL_ID_0011_IDX_XXXX[124] = { + 0xFF,0x09,0x00,0x78,0x00,0x11,0x00,0x00,0x00,0x1C,0x00,0x04,0x00,0x01,0x36,0x45,0x53,0x37,0x20, + 0x33,0x31,0x35,0x2D,0x32,0x45,0x48,0x31,0x34,0x2D,0x30,0x41,0x42,0x30,0x20,0x00,0xC0,0x00,0x04, + 0x00,0x01,0x00,0x06,0x36,0x45,0x53,0x37,0x20,0x33,0x31,0x35,0x2D,0x32,0x45,0x48,0x31,0x34,0x2D, + 0x30,0x41,0x42,0x30,0x20,0x00,0xC0,0x00,0x04,0x00,0x01,0x00,0x07,0x20,0x20,0x20,0x20,0x20,0x20, + 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x00,0xC0,0x56,0x03,0x02, + 0x06,0x00,0x81,0x42,0x6F,0x6F,0x74,0x20,0x4C,0x6F,0x61,0x64,0x65,0x72,0x20,0x20,0x20,0x20,0x20, + 0x20,0x20,0x20,0x20,0x00,0x00,0x41,0x20,0x09,0x09 + }; + + byte SZL_ID_0012_IDX_XXXX[58] = { + 0xFF,0x09,0x00,0x36,0x00,0x12,0x00,0x00,0x00,0x02,0x00,0x17,0x00,0x01,0x01,0x01,0x01,0x04,0x03, + 0x02,0x03,0x03,0x03,0x04,0x03,0x06,0x03,0x07,0x03,0x08,0x03,0x09,0x03,0x0A,0x03,0x0B,0x03,0x0C, + 0x03,0x0D,0x03,0x0E,0x03,0x0F,0x03,0x10,0x03,0x11,0x03,0x12,0x03,0x13,0x03,0x14,0x03,0x15,0x03, + 0x17 + }; + + byte SZL_ID_0013_IDX_XXXX[192] = { + 0xFF,0x09,0x00,0xBC,0x00,0x13,0x00,0x00,0x00,0x24,0x00,0x05,0x00,0x01,0x00,0x01,0x00,0x06,0x00, + 0x00,0x00,0x11,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x00,0x00,0x26,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x00,0x01,0x00,0x00,0x00,0x00,0x00, + 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x02,0x00,0x08,0x00,0x00,0x00,0x02,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x08,0x00,0x00,0x00,0x00, + 0x0C,0x06,0x00,0x00,0x00,0x00,0x00,0x04,0x00,0x02,0x00,0x80,0x00,0x00,0x00,0x02,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x05,0x00,0x02,0x00,0x00,0x0C,0x00,0x00,0x02,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0C,0x00,0x00,0x00,0x00,0x20,0x00,0x00, + 0x00,0x00 + }; + + byte SZL_ID_0014_IDX_XXXX[84] = { + 0xFF,0x09,0x00,0x50,0x00,0x14,0x00,0x00,0x00,0x08,0x00,0x09,0x00,0x01,0x00,0x01,0x08,0x00,0x00, + 0x00,0x00,0x02,0x00,0x01,0x08,0x00,0x00,0x00,0x00,0x03,0x00,0x01,0x40,0x00,0x00,0x80,0x00,0x04, + 0x00,0x01,0x01,0x00,0x00,0x00,0x00,0x05,0x00,0x01,0x01,0x00,0x00,0x08,0x00,0x06,0x00,0x01,0x08, + 0x00,0x00,0x00,0x00,0x07,0x00,0x01,0x80,0x00,0x00,0x00,0x00,0x08,0x00,0x01,0x08,0x00,0x00,0x10, + 0x00,0x09,0x00,0x01,0x00,0x20,0x00,0x00 + }; + + byte SZL_ID_0015_IDX_XXXX[62] = { + 0xFF,0x09,0x00,0x3A,0x00,0x15,0x00,0x00,0x00,0x0A,0x00,0x05,0x08,0x00,0x00,0x16,0x03,0xD1,0x00, + 0x00,0xFF,0xFE,0x0A,0x00,0x3E,0x81,0x03,0xD1,0x00,0x00,0xFF,0xFE,0x0B,0x00,0x04,0x22,0x03,0xD1, + 0x00,0x00,0xFF,0xFE,0x0C,0x00,0x1F,0x40,0x03,0xD1,0x00,0x00,0xFF,0xFE,0x0E,0x00,0x1F,0x40,0x03, + 0xD1,0x00,0x00,0xFF,0xFE + }; + + byte SZL_ID_0F14_IDX_XXXX[12] = { + 0xFF,0x09,0x00,0x08,0x0F,0x14,0x00,0x00,0x00,0x08,0x00,0x09 + }; + + byte SZL_ID_0019_IDX_XXXX[40] = { + 0xFF,0x09,0x00,0x24,0x00,0x19,0x00,0x00,0x00,0x04,0x00,0x07,0x00,0x01,0x00,0x00,0x00,0x04,0x01, + 0x00,0x00,0x05,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x0B,0x00,0x00,0x00,0x0C,0x00,0x00,0x00,0x15, + 0x00,0x00 + }; + + byte SZL_ID_0F19_IDX_XXXX[12] = { + 0xFF,0x09,0x00,0x08,0x0F,0x19,0x00,0x00,0x00,0x04,0x00,0x07 + }; + + byte SZL_ID_001C_IDX_XXXX[352] = { + 0xFF,0x09,0x01,0x5C,0x00,0x1C,0x00,0x00,0x00,0x22,0x00,0x0A,0x00,0x01,0x53,0x4E,0x41,0x50,0x37, + 0x2D,0x53,0x45,0x52,0x56,0x45,0x52,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x43,0x50,0x55,0x20,0x33,0x31,0x35,0x2D,0x32, + 0x20,0x50,0x4E,0x2F,0x44,0x50,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x04,0x4F,0x72,0x69,0x67,0x69,0x6E,0x61,0x6C,0x20,0x53,0x69,0x65,0x6D,0x65,0x6E,0x73,0x20, + 0x45,0x71,0x75,0x69,0x70,0x6D,0x65,0x6E,0x74,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x05,0x53,0x20, + 0x43,0x2D,0x43,0x32,0x55,0x52,0x32,0x38,0x39,0x32,0x32,0x30,0x31,0x32,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x07,0x43,0x50,0x55,0x20,0x33,0x31, + 0x35,0x2D,0x32,0x20,0x50,0x4E,0x2F,0x44,0x50,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x08,0x4D,0x4D,0x43,0x20,0x32,0x36,0x37,0x46,0x46,0x31, + 0x31,0x46,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x09,0x00,0x2A,0xF6,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x0A,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0B,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + }; + + byte SZL_ID_0F1C_IDX_XXXX[12] = { + 0xFF,0x09,0x00,0x08,0x0F,0x1C,0x00,0x00,0x00,0x22,0x00,0x0A + }; + + byte SZL_ID_0036_IDX_XXXX[12] = { + 0xFF,0x09,0x00,0x08,0x00,0x36,0x00,0x00,0x00,0x00,0x00,0x00 + }; + + byte SZL_ID_0F36_IDX_XXXX[12] = { + 0xFF,0x09,0x00,0x08,0x0F,0x36,0x00,0x00,0x00,0x00,0x00,0x00 + }; + + byte SZL_ID_0025_IDX_XXXX[16] = { + 0xFF,0x09,0x00,0x0C,0x00,0x25,0x00,0x00,0x00,0x04,0x00,0x01,0x01,0x0C,0x3D,0x00 + }; + + byte SZL_ID_0F25_IDX_XXXX[12] = { + 0xFF,0x09,0x00,0x08,0x0F,0x25,0x00,0x00,0x00,0x04,0x00,0x01 + }; + + byte SZL_ID_0037_IDX_XXXX[60] = { + 0xFF,0x09,0x00,0x38,0x00,0x37,0x00,0x00,0x00,0x30,0x00,0x01,0x07,0xFE,0xC0,0xA8,0x01,0x0A,0xFF, + 0xFF,0xFF,0x00,0xC0,0xA8,0x01,0x0A,0x00,0x1B,0x1B,0x1D,0x1A,0x2D,0x01,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x8F,0x88,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00 + }; + + byte SZL_ID_0F37_IDX_XXXX[12] = { + 0xFF,0x09,0x00,0x08,0x0F,0x37,0x00,0x00,0x00,0x30,0x00,0x01 + }; + + byte SZL_ID_0074_IDX_XXXX[40] = { + 0xFF,0x09,0x00,0x24,0x00,0x74,0x00,0x00,0x00,0x04,0x00,0x07,0x00,0x01,0x00,0x00,0x00,0x04,0x01, + 0x00,0x00,0x05,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x0B,0x00,0x00,0x00,0x0C,0x00,0x00,0x00,0x15, + 0x00,0x00 + }; + + byte SZL_ID_0F74_IDX_XXXX[12] = { + 0xFF,0x09,0x00,0x08,0x0F,0x74,0x00,0x00,0x00,0x04,0x00,0x07 + }; + + byte SZL_ID_0591_IDX_XXXX[76] = { + 0xFF,0x09,0x00,0x48,0x05,0x91,0x00,0x00,0x00,0x10,0x00,0x04,0x00,0x00,0x02,0x01,0x07,0xFF,0xC4, + 0xC0,0xC4,0xC0,0x00,0x00,0xB4,0x02,0x00,0x11,0x00,0x00,0x02,0x02,0x07,0xFE,0xA7,0xC4,0xA7,0xC4, + 0x00,0x00,0xB4,0x02,0x00,0x11,0x00,0x00,0x02,0x03,0x07,0xFD,0x97,0xC5,0x97,0xC5,0x00,0x00,0xB4, + 0x02,0x00,0x11,0x00,0x00,0x02,0x04,0x07,0xFC,0x97,0xC5,0x97,0xC5,0x00,0x00,0xB4,0x02,0x00,0x11 + }; + + byte SZL_ID_0A91_IDX_XXXX[44] = { + 0xFF,0x09,0x00,0x28,0x0A,0x91,0x00,0x00,0x00,0x10,0x00,0x02,0x01,0x00,0x02,0x01,0x07,0xFF,0xC4, + 0xC0,0xC4,0xC0,0x00,0x01,0xFE,0x02,0x00,0x11,0x80,0x00,0x00,0x00,0x07,0xFB,0xA7,0xC4,0xA7,0xC4, + 0x00,0x01,0xFE,0x02,0x00,0x11 + }; + + byte SZL_ID_0F92_IDX_XXXX[12] = { + 0xFF,0x09,0x00,0x08,0x0F,0x92,0x00,0x00,0x00,0x10,0x00,0x01 + }; + + byte SZL_ID_0294_IDX_XXXX[270] = { + 0xFF,0x09,0x01,0x0A,0x02,0x94,0x00,0x00,0x01,0x02,0x00,0x01,0x00,0x00,0x03,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00 + }; + + byte SZL_ID_0794_IDX_XXXX[270] = { + 0xFF,0x09,0x01,0x0A,0x07,0x94,0x00,0x00,0x01,0x02,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00 + }; + + byte SZL_ID_0F94_IDX_XXXX[12] = { + 0xFF,0x09,0x00,0x08,0x0F,0x94,0x00,0x00,0x01,0x02,0x00,0x01 + }; + + byte SZL_ID_0095_IDX_XXXX[52] = { + 0xFF,0x09,0x00,0x30,0x00,0x95,0x00,0x00,0x00,0x28,0x00,0x01,0x64,0x00,0x02,0x02,0x07,0xFE,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + }; + + byte SZL_ID_0F95_IDX_XXXX[12] = { + 0xFF,0x09,0x00,0x08,0x0F,0x95,0x00,0x00,0x00,0x28,0x00,0x01 + }; + + byte SZL_ID_00A0_IDX_XXXX[212] = { + 0xFF,0x09,0x00,0xD0,0x00,0xA0,0x00,0x00,0x00,0x14,0x00,0x0A,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00, + 0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x38,0x86,0x13,0x81,0xFE,0x64,0xC7,0x72, + 0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x38,0x76,0x43,0x01,0xFF,0x46,0xC7, + 0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x29,0x66,0x43,0x04,0xFF,0x84, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x94,0x02,0x04,0x23,0x52,0x13,0x90,0x16,0x43,0x02,0xFF, + 0x68,0xC7,0x00,0x00,0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x88,0x56,0x13,0x81, + 0xFE,0x64,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x88,0x46,0x43, + 0x01,0xFF,0x46,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x79,0x46, + 0x43,0x04,0xFF,0x84,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x94,0x02,0x04,0x23,0x51,0x19,0x48, + 0x26,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00,0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x50,0x45, + 0x09,0x96,0x13,0x81,0xFE,0x64,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x50, + 0x45,0x09,0x86 + }; + + byte SZL_ID_0FA0_IDX_XXXX[12] = { + 0xFF,0x09,0x00,0x08,0x0F,0xA0,0x00,0x00,0x00,0x14,0x01,0xF4 + }; + + byte SZL_ID_0017_IDX_XXXX[458] = { // <--Wrapped to 458 bytes + 0xFF,0x09,0x01,0xC6,0x00,0x17,0x00,0x00,0x00,0x04,0x00,0x73,0x00,0x00,0x00,0x01,0x00,0x01,0x00, + 0x03,0x00,0x02,0x00,0x02,0x00,0x03,0x00,0x01,0x00,0x04,0x00,0x01,0x00,0x07,0x00,0x01,0x00,0x16, + 0x00,0x01,0x00,0x64,0x00,0x01,0x00,0x65,0x00,0x01,0x00,0x66,0x00,0x01,0x00,0x67,0x00,0x01,0x00, + 0x7A,0x00,0x01,0x00,0xC8,0x00,0x01,0x00,0xD2,0x00,0x01,0x02,0xBC,0x00,0x01,0x02,0xBD,0x00,0x01, + 0x02,0xBE,0x00,0x01,0x02,0xBF,0x00,0x01,0x02,0xC0,0x00,0x01,0x02,0xC1,0x00,0x01,0x02,0xC2,0x00, + 0x01,0x02,0xC3,0x00,0x01,0x02,0xC4,0x00,0x01,0x02,0xC5,0x00,0x01,0x02,0xC6,0x00,0x01,0x02,0xC7, + 0x00,0x01,0x02,0xC8,0x00,0x01,0x02,0xC9,0x00,0x01,0x02,0xCA,0x00,0x01,0x02,0xCB,0x00,0x01,0x02, + 0xCC,0x00,0x01,0x02,0xCD,0x00,0x01,0x02,0xCE,0x00,0x01,0x02,0xCF,0x00,0x01,0x02,0xD0,0x00,0x01, + 0x02,0xD1,0x00,0x01,0x02,0xD2,0x00,0x01,0x02,0xD3,0x00,0x01,0x02,0xD4,0x00,0x01,0x02,0xD5,0x00, + 0x01,0x02,0xD6,0x00,0x01,0x02,0xD7,0x00,0x01,0x02,0xD8,0x00,0x01,0x02,0xD9,0x00,0x01,0x02,0xDA, + 0x00,0x01,0x02,0xDB,0x00,0x01,0x02,0xDC,0x00,0x01,0x02,0xDD,0x00,0x01,0x02,0xDE,0x00,0x01,0x02, + 0xDF,0x00,0x01,0x02,0xE0,0x00,0x01,0x02,0xE1,0x00,0x01,0x02,0xE2,0x00,0x01,0x02,0xE3,0x00,0x01, + 0x02,0xE4,0x00,0x01,0x02,0xE5,0x00,0x01,0x02,0xE6,0x00,0x01,0x02,0xE7,0x00,0x01,0x02,0xE8,0x00, + 0x01,0x02,0xE9,0x00,0x01,0x02,0xEA,0x00,0x01,0x02,0xEB,0x00,0x01,0x02,0xEC,0x00,0x01,0x02,0xED, + 0x00,0x01,0x02,0xEE,0x00,0x01,0x02,0xEF,0x00,0x01,0x02,0xF0,0x00,0x01,0x02,0xF1,0x00,0x01,0x02, + 0xF2,0x00,0x01,0x02,0xF3,0x00,0x01,0x02,0xF4,0x00,0x01,0x02,0xF5,0x00,0x01,0x02,0xF6,0x00,0x01, + 0x02,0xF7,0x00,0x01,0x02,0xF8,0x00,0x01,0x02,0xF9,0x00,0x01,0x02,0xFA,0x00,0x01,0x02,0xFB,0x00, + 0x01,0x02,0xFC,0x00,0x01,0x02,0xFD,0x00,0x01,0x02,0xFE,0x00,0x01,0x02,0xFF,0x00,0x01,0x03,0x00, + 0x00,0x01,0x03,0x01,0x00,0x01,0x03,0x02,0x00,0x01,0x03,0x03,0x00,0x01,0x03,0x04,0x00,0x01,0x03, + 0x05,0x00,0x01,0x03,0x06,0x00,0x01,0x03,0x07,0x00,0x01,0x03,0x08,0x00,0x01,0x03,0x09,0x00,0x01, + 0x03,0x0A,0x00,0x01,0x03,0x0B,0x00,0x01,0x03,0x0C,0x00,0x01,0x03,0x0D,0x00,0x01,0x03,0x0E,0x00, + 0x01,0x03,0x0F,0x00,0x01,0x03,0x10,0x00,0x01,0x03,0x11,0x00,0x01,0x03,0x12,0x00,0x01,0x03,0x13, + 0x00,0x01,0x03,0x14,0x00,0x01,0x03,0x15,0x00,0x01,0x03,0x16,0x00,0x01,0x03,0x17,0x00,0x01,0x03, + 0x18,0x00,0x01,0x03,0x19,0x00,0x01,0x03,0x1A,0x00,0x01,0x03,0x1B,0x00,0x01,0x03,0x1C,0x00,0x01, + 0x03,0x1D + }; + + byte SZL_ID_0F17_IDX_XXXX[12] = { + 0xFF,0x09,0x00,0x08,0x0F,0x17,0x00,0x00,0x00,0x04,0x00,0x73 + }; + + byte SZL_ID_0018_IDX_XXXX[28] = { + 0xFF,0x09,0x00,0x18,0x00,0x18,0x00,0x00,0x00,0x04,0x00,0x04,0x00,0x00,0x00,0x08,0x00,0x01,0x00, + 0x08,0x00,0x02,0x00,0x08,0x00,0x03,0x00,0x08 + }; + + byte SZL_ID_0F18_IDX_XXXX[12] = { + 0xFF,0x09,0x00,0x08,0x0F,0x18,0x00,0x00,0x00,0x04,0x00,0x04 + }; + + byte SZL_ID_001A_IDX_XXXX[48] = { + 0xFF,0x09,0x00,0x2C,0x00,0x1A,0x00,0x00,0x00,0x0C,0x00,0x03,0x09,0x01,0x01,0x05,0x00,0x08,0x00, + 0x00,0x00,0x80,0x00,0x00,0x12,0x01,0x00,0x02,0x00,0x06,0x00,0x00,0x00,0x06,0x00,0x00,0x18,0x01, + 0x00,0x02,0x00,0x02,0x00,0x00,0x00,0x02,0x00,0x00 + }; + + byte SZL_ID_0F1A_IDX_XXXX[12] = { + 0xFF,0x09,0x00,0x08,0x0F,0x1A,0x00,0x00,0x00,0x0C,0x00,0x03 + }; + + byte SZL_ID_001B_IDX_XXXX[132] = { + 0xFF,0x09,0x00,0x80,0x00,0x1B,0x00,0x00,0x00,0x14,0x00,0x06,0x09,0x00,0x00,0x00,0x00,0x02,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0C,0x06,0x00,0x00,0x00,0x00,0x09,0x00,0x00,0x70,0x00,0x02, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x07,0xF3,0xFA,0x00,0x00,0x00,0x00,0x12,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x26,0x00,0x00,0x00,0x00,0x12,0x00,0x00,0x70, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x05,0xFF,0xDA,0x00,0x00,0x00,0x00,0x18,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x00, + 0x00,0x70,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00 + }; + + byte SZL_ID_0F1B_IDX_XXXX[12] = { + 0xFF,0x09,0x00,0x08,0x0F,0x1B,0x00,0x00,0x00,0x14,0x00,0x06 + }; + + byte SZL_ID_0021_IDX_XXXX[100] = { + 0xFF,0x09,0x00,0x60,0x00,0x21,0x00,0x00,0x00,0x04,0x00,0x16,0x01,0x01,0x01,0x01,0x01,0x11,0xFE, + 0x0A,0x01,0x21,0xFE,0x14,0x01,0x22,0xFE,0x15,0x01,0x33,0xFE,0x20,0x01,0x34,0xFE,0x21,0x01,0x35, + 0xFE,0x22,0x01,0x36,0xFE,0x23,0x01,0x41,0xFE,0x28,0x01,0x55,0xFE,0x37,0x01,0x56,0xFE,0x38,0x01, + 0x57,0xFE,0x39,0x01,0x64,0xFE,0x3D,0x00,0x01,0xFE,0x50,0x00,0x42,0xFE,0x52,0x00,0x61,0xFE,0x53, + 0x00,0xA1,0xFE,0x55,0x00,0xC1,0xFE,0x56,0x00,0xD2,0xFE,0x57,0x00,0x81,0xFE,0x64,0x00,0x21,0xFE, + 0x79,0x00,0x42,0xFE,0x7A + }; + + byte SZL_ID_0A21_IDX_XXXX[16] = { + 0xFF,0x09,0x00,0x0C,0x0A,0x21,0x00,0x00,0x00,0x04,0x00,0x01,0x01,0x01,0x01,0x01 + }; + + byte SZL_ID_0F21_IDX_XXXX[12] = { + 0xFF,0x09,0x00,0x08,0x0F,0x21,0x00,0x00,0x00,0x04,0x00,0x16 + }; + + byte SZL_ID_0023_IDX_XXXX[228] = { + 0xFF,0x09,0x00,0xE0,0x00,0x23,0x00,0x00,0x00,0x12,0x00,0x0C,0x1A,0x00,0x00,0x00,0x10,0xBE,0x00, + 0x00,0x08,0x00,0x0B,0xC0,0xFC,0x01,0xFF,0xFF,0xFF,0xF3,0x19,0x00,0x00,0x00,0x10,0x01,0x00,0x00, + 0x08,0x00,0x0B,0xC0,0xFC,0x01,0xFF,0xFF,0xFF,0xF3,0x10,0x00,0x00,0x00,0x10,0x26,0x00,0x00,0x08, + 0x00,0x0B,0xC0,0xFC,0x01,0xFF,0xFF,0xFF,0xF3,0x0C,0x00,0x00,0x00,0x10,0x01,0x00,0x00,0x08,0x00, + 0x0B,0xC0,0xFC,0x01,0xFF,0xFF,0xFF,0xF3,0x0B,0x00,0x00,0x00,0x10,0x01,0x00,0x00,0x08,0x00,0x0B, + 0xC0,0xFC,0x01,0xFF,0xFF,0xFF,0xF3,0x0A,0x00,0x00,0x00,0x10,0x01,0x00,0x00,0x08,0x00,0x0B,0xC0, + 0xFC,0x01,0xFF,0xFF,0xFF,0xF3,0x09,0x00,0x00,0x00,0x10,0x01,0x00,0x00,0x08,0x00,0x0B,0xC0,0xFC, + 0x01,0xFF,0xFF,0xFF,0xF3,0x04,0x00,0x00,0x00,0x10,0x01,0x00,0x00,0x08,0x00,0x0B,0xC0,0xFC,0x01, + 0xFF,0xFF,0xFF,0xF3,0x03,0x00,0x00,0x00,0x10,0x01,0x00,0x00,0x08,0x00,0x0B,0xC0,0xFC,0x01,0xFF, + 0xFF,0xFF,0xF3,0x02,0x00,0x00,0x00,0x10,0x16,0x00,0x00,0x08,0x00,0x0B,0xC0,0xFC,0x01,0xFF,0xFF, + 0xFF,0xF3,0x01,0x00,0x00,0x00,0x10,0x01,0x00,0x00,0x08,0x00,0x0B,0xC0,0xFC,0x01,0xFF,0xFF,0xFF, + 0xF3,0x1B,0x00,0x00,0x00,0x10,0x01,0x00,0x00,0x08,0x00,0x0B,0xC0,0xFC,0x01,0xFF,0xFF,0xFF,0xF3 + }; + + byte SZL_ID_0F23_IDX_XXXX[12] = { + 0xFF,0x09,0x00,0x08,0x0F,0x23,0x00,0x00,0x00,0x12,0x00,0x0C + }; + + byte SZL_ID_0024_IDX_XXXX[92] = { + 0xFF,0x09,0x00,0x58,0x00,0x24,0x00,0x00,0x00,0x14,0x00,0x04,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00, + 0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x38,0x86,0x43,0x01,0xFF,0x46,0xC7,0x72, + 0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x29,0x66,0x43,0x04,0xFF,0x84,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x94,0x02,0x04,0x23,0x52,0x13,0x90,0x16,0x43,0x02,0xFF,0x68, + 0xC7,0x00,0x00,0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x88,0x56 + }; + + byte SZL_ID_0124_IDX_XXXX[32] = { + 0xFF,0x09,0x00,0x1C,0x01,0x24,0x00,0x00,0x00,0x14,0x00,0x01,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00, + 0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x38,0x86 + }; + + byte SZL_ID_0424_IDX_XXXX[32] = { + 0xFF,0x09,0x00,0x1C,0x04,0x24,0x00,0x00,0x00,0x14,0x00,0x01,0x51,0x44,0xFF, + 0x08, // <-- CPU Status + 0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x94,0x02,0x05,0x02,0x01,0x55,0x90,0x67 + }; + + byte SZL_ID_0038_IDX_XXXX[78] = { + 0xFF,0x09,0x00,0x4A,0x00,0x38,0x00,0x00,0x00,0x42,0x00,0x01,0x00,0x01,0x07,0xFE,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xAB,0xE3,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0xEE,0x49,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00 + }; + + byte SZL_ID_0F38_IDX_XXXX[12] = { + 0xFF,0x09,0x00,0x08,0x0F,0x38,0x00,0x00,0x00,0x42,0x00,0x01 + }; + + byte SZL_ID_003A_IDX_XXXX[12] = { + 0xFF,0x09,0x00,0x08,0x00,0x3A,0x00,0x00,0x00,0x94,0x00,0x00 + }; + + byte SZL_ID_0F3A_IDX_XXXX[12] = { + 0xFF,0x09,0x00,0x08,0x0F,0x3A,0x00,0x00,0x00,0x94,0x00,0x08 + }; + + byte SZL_ID_0F9A_IDX_XXXX[12] = { + 0xFF,0x09,0x00,0x08,0x0F,0x9A,0x00,0x00,0x01,0x1C,0x00,0x01 + }; + + byte SZL_ID_0D91_IDX_0000[28] = { + 0xFF,0x09,0x00,0x18,0x0D,0x91,0x00,0x00,0x00,0x10,0x00,0x01,0x00,0x00,0x02,0x00,0x7F,0xFF,0x00, + 0xC0,0x00,0xC0,0x00,0x00,0xB4,0x02,0x00,0x11 + }; + + byte SZL_ID_0092_IDX_0000[28] = { + 0xFF,0x09,0x00,0x18,0x00,0x92,0x00,0x00,0x00,0x10,0x00,0x01,0x01,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + }; + + byte SZL_ID_0292_IDX_0000[28] = { + 0xFF,0x09,0x00,0x18,0x02,0x92,0x00,0x00,0x00,0x10,0x00,0x01,0x01,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + }; + + byte SZL_ID_0692_IDX_0000[28] = { + 0xFF,0x09,0x00,0x18,0x06,0x92,0x00,0x00,0x00,0x10,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + }; + + byte SZL_ID_0094_IDX_0000[270] = { + 0xFF,0x09,0x01,0x0A,0x00,0x94,0x00,0x00,0x01,0x02,0x00,0x01,0x00,0x00,0x03,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00 + }; + + byte SZL_ID_0D97_IDX_0000[60] = { + 0xFF,0x09,0x00,0x38,0x0D,0x97,0x00,0x00,0x00,0x30,0x00,0x01,0x7F,0xFF,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x02,0x00,0x04,0x00,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xC0,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x00,0x11,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00 + }; + + byte SZL_ID_0111_IDX_0001[40] = { + 0xFF,0x09,0x00,0x24,0x01,0x11,0x00,0x01,0x00,0x1C,0x00,0x01,0x00,0x01,0x36,0x45,0x53,0x37,0x20, + 0x33,0x31,0x35,0x2D,0x32,0x45,0x48,0x31,0x34,0x2D,0x30,0x41,0x42,0x30,0x20,0x00,0xC0,0x00,0x04, + 0x00,0x01 + }; + + byte SZL_ID_0111_IDX_0006[40] = { + 0xFF,0x09,0x00,0x24,0x01,0x11,0x00,0x06,0x00,0x1C,0x00,0x01,0x00,0x06,0x36,0x45,0x53,0x37,0x20, + 0x33,0x31,0x35,0x2D,0x32,0x45,0x48,0x31,0x34,0x2D,0x30,0x41,0x42,0x30,0x20,0x00,0xC0,0x00,0x04, + 0x00,0x01 + }; + + byte SZL_ID_0111_IDX_0007[40] = { + 0xFF,0x09,0x00,0x24,0x01,0x11,0x00,0x07,0x00,0x1C,0x00,0x01,0x00,0x07,0x20,0x20,0x20,0x20,0x20, + 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x00,0xC0,0x56,0x03, + 0x02,0x06 + }; + + byte SZL_ID_0F11_IDX_0001[12] = { + 0xFF,0x09,0x00,0x08,0x0F,0x11,0x00,0x00,0x00,0x1C,0x00,0x04 + }; + + byte SZL_ID_0F11_IDX_0006[12] = { + 0xFF,0x09,0x00,0x08,0x0F,0x11,0x00,0x00,0x00,0x1C,0x00,0x04 + }; + + byte SZL_ID_0F11_IDX_0007[12] = { + 0xFF,0x09,0x00,0x08,0x0F,0x11,0x00,0x00,0x00,0x1C,0x00,0x04 + }; + + byte SZL_ID_0112_IDX_0000[14] = { + 0xFF,0x09,0x00,0x0A,0x01,0x12,0x00,0x00,0x00,0x02,0x00,0x01,0x00,0x01 + }; + + byte SZL_ID_0112_IDX_0100[16] = { + 0xFF,0x09,0x00,0x0C,0x01,0x12,0x01,0x00,0x00,0x02,0x00,0x02,0x01,0x01,0x01,0x04 + }; + + byte SZL_ID_0112_IDX_0200[12] = { + 0xFF,0x09,0x00,0x08,0x01,0x12,0x02,0x00,0x00,0x00,0x00,0x00 + }; + + byte SZL_ID_0112_IDX_0400[12] = { + 0xFF,0x09,0x00,0x08,0x01,0x12,0x04,0x00,0x00,0x00,0x00,0x00 + }; + + byte SZL_ID_0F12_IDX_0000[12] = { + 0xFF,0x09,0x00,0x08,0x0F,0x12,0x00,0x00,0x00,0x02,0x00,0x17 + }; + + byte SZL_ID_0F12_IDX_0100[12] = { + 0xFF,0x09,0x00,0x08,0x0F,0x12,0x00,0x00,0x00,0x02,0x00,0x17 + }; + + byte SZL_ID_0F12_IDX_0200[12] = { + 0xFF,0x09,0x00,0x08,0x0F,0x12,0x00,0x00,0x00,0x02,0x00,0x17 + }; + + byte SZL_ID_0F12_IDX_0400[12] = { + 0xFF,0x09,0x00,0x08,0x0F,0x12,0x00,0x00,0x00,0x02,0x00,0x17 + }; + + byte SZL_ID_0113_IDX_0001[48] = { + 0xFF,0x09,0x00,0x2C,0x01,0x13,0x00,0x01,0x00,0x24,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x06,0x00, + 0x00,0x00,0x11,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x00,0x00,0x26,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + }; + + byte SZL_ID_0115_IDX_0800[22] = { + 0xFF,0x09,0x00,0x12,0x01,0x15,0x08,0x00,0x00,0x0A,0x00,0x01,0x08,0x00,0x00,0x16,0x03,0xD1,0x00, + 0x00,0xFF,0xFE + }; + + byte SZL_ID_011C_IDX_0001[46] = { + 0xFF,0x09,0x00,0x2A,0x01,0x1C,0x00,0x01,0x00,0x22,0x00,0x01,0x00,0x01,0x53,0x4D,0x41,0x52,0x54, + 0x37,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + }; + + byte SZL_ID_011C_IDX_0002[46] = { + 0xFF,0x09,0x00,0x2A,0x01,0x1C,0x00,0x02,0x00,0x22,0x00,0x01,0x00,0x02,0x43,0x50,0x55,0x20,0x33, + 0x31,0x35,0x2D,0x32,0x20,0x50,0x4E,0x2F,0x44,0x50,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + }; + + byte SZL_ID_011C_IDX_0003[46] = { + 0xFF,0x09,0x00,0x2A,0x01,0x1C,0x00,0x03,0x00,0x22,0x00,0x01,0x00,0x03,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + }; + + byte SZL_ID_011C_IDX_0004[46] = { + 0xFF,0x09,0x00,0x2A,0x01,0x1C,0x00,0x04,0x00,0x22,0x00,0x01,0x00,0x04,0x4F,0x72,0x69,0x67,0x69, + 0x6E,0x61,0x6C,0x20,0x53,0x69,0x65,0x6D,0x65,0x6E,0x73,0x20,0x45,0x71,0x75,0x69,0x70,0x6D,0x65, + 0x6E,0x74,0x00,0x00,0x00,0x00,0x00,0x00 + }; + + byte SZL_ID_011C_IDX_0005[46] = { + 0xFF,0x09,0x00,0x2A,0x01,0x1C,0x00,0x05,0x00,0x22,0x00,0x01,0x00,0x05,0x53,0x20,0x43,0x2D,0x43, + 0x32,0x55,0x52,0x32,0x38,0x39,0x32,0x32,0x30,0x31,0x32,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + }; + + byte SZL_ID_011C_IDX_0007[46] = { + 0xFF,0x09,0x00,0x2A,0x01,0x1C,0x00,0x07,0x00,0x22,0x00,0x01,0x00,0x07,0x43,0x50,0x55,0x20,0x33, + 0x31,0x35,0x2D,0x32,0x20,0x50,0x4E,0x2F,0x44,0x50,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + }; + + byte SZL_ID_011C_IDX_0008[46] = { + 0xFF,0x09,0x00,0x2A,0x01,0x1C,0x00,0x08,0x00,0x22,0x00,0x01,0x00,0x08,0x4D,0x4D,0x43,0x20,0x32, + 0x36,0x37,0x46,0x46,0x31,0x31,0x46,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + }; + + byte SZL_ID_011C_IDX_0009[46] = { + 0xFF,0x09,0x00,0x2A,0x01,0x1C,0x00,0x09,0x00,0x22,0x00,0x01,0x00,0x09,0x00,0x2A,0xF6,0x00,0x00, + 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + }; + + byte SZL_ID_011C_IDX_000A[46] = { + 0xFF,0x09,0x00,0x2A,0x01,0x1C,0x00,0x0A,0x00,0x22,0x00,0x01,0x00,0x0A,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + }; + + byte SZL_ID_011C_IDX_000B[46] = { + 0xFF,0x09,0x00,0x2A,0x01,0x1C,0x00,0x0B,0x00,0x22,0x00,0x01,0x00,0x0B,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + }; + + byte SZL_ID_0222_IDX_0001[40] = { + 0xFF,0x09,0x00,0x24,0x02,0x22,0x00,0x01,0x00,0x1C,0x00,0x01,0x11,0x03,0x01,0x01,0xC8,0x58,0x00, + 0x00,0x00,0x00,0x00,0x01,0x94,0x02,0x05,0x02,0x01,0x56,0x64,0x77,0x00,0x10,0x00,0x08,0x00,0x00, + 0x00,0x00 + }; + + byte SZL_ID_0222_IDX_000A[40] = { + 0xFF,0x09,0x00,0x24,0x02,0x22,0x00,0x0A,0x00,0x1C,0x00,0x01,0x10,0x11,0x02,0x0A,0x00,0x50,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00 + }; + + byte SZL_ID_0222_IDX_0014[40] = { + 0xFF,0x09,0x00,0x24,0x02,0x22,0x00,0x14,0x00,0x1C,0x00,0x01,0x10,0x21,0x03,0x14,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00 + }; + + byte SZL_ID_0222_IDX_0028[40] = { + 0xFF,0x09,0x00,0x24,0x02,0x22,0x00,0x28,0x00,0x1C,0x00,0x01,0x10,0x41,0x10,0x28,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00 + }; + + byte SZL_ID_0222_IDX_0050[40] = { + 0xFF,0x09,0x00,0x24,0x02,0x22,0x00,0x50,0x00,0x1C,0x00,0x01,0x35,0x01,0xFE,0x50,0xC8,0x58,0x00, + 0x00,0x00,0x00,0x00,0x96,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x08,0x00,0x00, + 0x00,0x00 + }; + + byte SZL_ID_0222_IDX_0064[40] = { + 0xFF,0x09,0x00,0x24,0x02,0x22,0x00,0x64,0x00,0x1C,0x00,0x01,0x13,0x81,0xFE,0x64,0xC7,0x72,0x43, + 0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x38,0x76,0x00,0x00,0x00,0x08,0x00,0x00, + 0x00,0x00 + }; + + byte SZL_ID_0125_IDX_0000[16] = { + 0xFF,0x09,0x00,0x0C,0x01,0x25,0x00,0x00,0x00,0x04,0x00,0x01,0x00,0x03,0x01,0x00 + }; + + byte SZL_ID_0125_IDX_0001[16] = { + 0xFF,0x09,0x00,0x0C,0x01,0x25,0x00,0x01,0x00,0x04,0x00,0x01,0x01,0x0C,0x3D,0x00 + }; + + byte SZL_ID_0225_IDX_0001[16] = { + 0xFF,0x09,0x00,0x0C,0x02,0x25,0x00,0x01,0x00,0x04,0x00,0x01,0x00,0x03,0x01,0x00 + }; + + byte SZL_ID_0132_IDX_0001[52] = { + 0xFF,0x09,0x00,0x30,0x01,0x32,0x00,0x01,0x00,0x28,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00, + 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0E,0x00,0x00,0x00,0x14,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03 + }; + + byte SZL_ID_0132_IDX_0002[52] = { + 0xFF,0x09,0x00,0x30,0x01,0x32,0x00,0x02,0x00,0x28,0x00,0x01,0x00,0x02,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x0E,0x00,0x00,0x00,0x00,0x06,0x01,0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + }; + + byte SZL_ID_0132_IDX_0003[52] = { + 0xFF,0x09,0x00,0x30,0x01,0x32,0x00,0x03,0x00,0x28,0x00,0x01,0x00,0x03,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + }; + + byte SZL_ID_0132_IDX_0004[52] = { + 0xFF,0x09,0x00,0x30,0x01,0x32,0x00,0x04,0x00,0x28,0x00,0x01,0x00,0x04,0x00,0x01,0x00,0x00,0x00, + 0x01,0x00,0x02,0x00,0x00,0x00,0x00,0x56,0x56,0x10,0x01,0x33,0x7B,0x02,0x00,0x75,0xF4,0x02,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + }; + + byte SZL_ID_0132_IDX_0005[52] = { + 0xFF,0x09,0x00,0x30,0x01,0x32,0x00,0x05,0x00,0x28,0x00,0x01,0x00,0x05,0x00,0x00,0x00,0x01,0x00, + 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + }; + + byte SZL_ID_0132_IDX_0006[52] = { + 0xFF,0x09,0x00,0x30,0x01,0x32,0x00,0x06,0x00,0x28,0x00,0x01,0x00,0x06,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + }; + + byte SZL_ID_0132_IDX_0007[52] = { + 0xFF,0x09,0x00,0x30,0x01,0x32,0x00,0x07,0x00,0x28,0x00,0x01,0x00,0x07,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + }; + + byte SZL_ID_0132_IDX_0008[52] = { + 0xFF,0x09,0x00,0x30,0x01,0x32,0x00,0x08,0x00,0x28,0x00,0x01,0x00,0x08,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x94,0x02,0x05,0x02, + 0x01,0x56,0x94,0x57,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + }; + + byte SZL_ID_0132_IDX_0009[52] = { + 0xFF,0x09,0x00,0x30,0x01,0x32,0x00,0x09,0x00,0x28,0x00,0x01,0x00,0x09,0x00,0x02,0xDC,0x6C,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + }; + + byte SZL_ID_0132_IDX_000A[52] = { + 0xFF,0x09,0x00,0x30,0x01,0x32,0x00,0x0A,0x00,0x28,0x00,0x01,0x00,0x0A,0x00,0x02,0xDC,0x6C,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + }; + + byte SZL_ID_0132_IDX_000B[52] = { + 0xFF,0x09,0x00,0x30,0x01,0x32,0x00,0x0B,0x00,0x28,0x00,0x01,0x00,0x0B,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + }; + + byte SZL_ID_0132_IDX_000C[52] = { + 0xFF,0x09,0x00,0x30,0x01,0x32,0x00,0x0C,0x00,0x28,0x00,0x01,0x00,0x0C,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + }; + + byte SZL_ID_0232_IDX_0001[52] = { + 0xFF,0x09,0x00,0x30,0x02,0x32,0x00,0x01,0x00,0x28,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00, + 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0E,0x00,0x00,0x00,0x14,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03 + }; + + byte SZL_ID_0232_IDX_0004[52] = { + 0xFF,0x09,0x00,0x30,0x02,0x32,0x00,0x04,0x00,0x28,0x00,0x01,0x00,0x04,0x00,0x01,0x00,0x00,0x00, + 0x01,0x00,0x02,0x00,0x00,0x00,0x00,0x56,0x56,0x10,0x01,0x33,0x7B,0x02,0x00,0x75,0xF4,0x02,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + }; + + byte SZL_ID_0137_IDX_07FE[60] = { + 0xFF,0x09,0x00,0x38,0x01,0x37,0x07,0xFE,0x00,0x30,0x00,0x01,0x07,0xFE,0xC0,0xA8,0x01,0x0A,0xFF, + 0xFF,0xFF,0x00,0xC0,0xA8,0x01,0x0A,0x00,0x1B,0x1B,0x1D,0x1A,0x2D,0x01,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x8F,0x88,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00 + }; + + byte SZL_ID_0174_IDX_0001[16] = { + 0xFF,0x09,0x00,0x0C,0x01,0x74,0x00,0x01,0x00,0x04,0x00,0x01,0x00,0x01,0x00,0x00 + }; + + byte SZL_ID_0174_IDX_0004[16] = { + 0xFF,0x09,0x00,0x0C,0x01,0x74,0x00,0x04,0x00,0x04,0x00,0x01,0x00,0x04,0x01,0x00 + }; + + byte SZL_ID_0174_IDX_0005[16] = { + 0xFF,0x09,0x00,0x0C,0x01,0x74,0x00,0x05,0x00,0x04,0x00,0x01,0x00,0x05,0x00,0x00 + }; + + byte SZL_ID_0174_IDX_0006[16] = { + 0xFF,0x09,0x00,0x0C,0x01,0x74,0x00,0x06,0x00,0x04,0x00,0x01,0x00,0x06,0x00,0x00 + }; + + byte SZL_ID_0174_IDX_000B[16] = { + 0xFF,0x09,0x00,0x0C,0x01,0x74,0x00,0x0B,0x00,0x04,0x00,0x01,0x00,0x0B,0x00,0x00 + }; + + byte SZL_ID_0174_IDX_000C[16] = { + 0xFF,0x09,0x00,0x0C,0x01,0x74,0x00,0x0C,0x00,0x04,0x00,0x01,0x00,0x0C,0x00,0x00 + }; + + byte SZL_ID_0194_IDX_0064[270] = { + 0xFF,0x09,0x01,0x0A,0x01,0x94,0x00,0x64,0x01,0x02,0x00,0x01,0x00,0x64,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00 + }; + + byte SZL_ID_0694_IDX_0064[270] = { + 0xFF,0x09,0x01,0x0A,0x06,0x94,0x00,0x64,0x01,0x02,0x00,0x01,0x00,0x64,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00 + }; + + byte SZL_ID_01A0_IDX_0000[12] = { + 0xFF,0x09,0x00,0x08,0x01,0xA0,0x00,0x00,0x00,0x14,0x00,0x00 + }; + + byte SZL_ID_01A0_IDX_0001[32] = { + 0xFF,0x09,0x00,0x1C,0x01,0xA0,0x00,0x00,0x00,0x14,0x00,0x01,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00, + 0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x38,0x86 + }; + + byte SZL_ID_01A0_IDX_0002[52] = { + 0xFF,0x09,0x00,0x30,0x01,0xA0,0x00,0x00,0x00,0x14,0x00,0x02,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00, + 0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x38,0x86,0x13,0x81,0xFE,0x64,0xC7,0x72, + 0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x38,0x76 + }; + + byte SZL_ID_01A0_IDX_0003[72] = { + 0xFF,0x09,0x00,0x44,0x01,0xA0,0x00,0x00,0x00,0x14,0x00,0x03,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00, + 0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x38,0x86,0x13,0x81,0xFE,0x64,0xC7,0x72, + 0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x38,0x76,0x43,0x01,0xFF,0x46,0xC7, + 0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x29,0x66 + }; + + byte SZL_ID_01A0_IDX_0004[92] = { + 0xFF,0x09,0x00,0x58,0x01,0xA0,0x00,0x00,0x00,0x14,0x00,0x04,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00, + 0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x38,0x86,0x13,0x81,0xFE,0x64,0xC7,0x72, + 0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x38,0x76,0x43,0x01,0xFF,0x46,0xC7, + 0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x29,0x66,0x43,0x04,0xFF,0x84, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x94,0x02,0x04,0x23,0x52,0x13,0x90,0x16 + }; + + byte SZL_ID_01A0_IDX_0005[112] = { + 0xFF,0x09,0x00,0x6C,0x01,0xA0,0x00,0x00,0x00,0x14,0x00,0x05,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00, + 0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x38,0x86,0x13,0x81,0xFE,0x64,0xC7,0x72, + 0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x38,0x76,0x43,0x01,0xFF,0x46,0xC7, + 0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x29,0x66,0x43,0x04,0xFF,0x84, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x94,0x02,0x04,0x23,0x52,0x13,0x90,0x16,0x43,0x02,0xFF, + 0x68,0xC7,0x00,0x00,0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x88,0x56 + }; + + byte SZL_ID_01A0_IDX_0006[132] = { + 0xFF,0x09,0x00,0x80,0x01,0xA0,0x00,0x00,0x00,0x14,0x00,0x06,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00, + 0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x38,0x86,0x13,0x81,0xFE,0x64,0xC7,0x72, + 0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x38,0x76,0x43,0x01,0xFF,0x46,0xC7, + 0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x29,0x66,0x43,0x04,0xFF,0x84, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x94,0x02,0x04,0x23,0x52,0x13,0x90,0x16,0x43,0x02,0xFF, + 0x68,0xC7,0x00,0x00,0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x88,0x56,0x13,0x81, + 0xFE,0x64,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x88,0x46 + }; + + byte SZL_ID_01A0_IDX_0007[152] = { + 0xFF,0x09,0x00,0x94,0x01,0xA0,0x00,0x00,0x00,0x14,0x00,0x07,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00, + 0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x38,0x86,0x13,0x81,0xFE,0x64,0xC7,0x72, + 0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x38,0x76,0x43,0x01,0xFF,0x46,0xC7, + 0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x29,0x66,0x43,0x04,0xFF,0x84, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x94,0x02,0x04,0x23,0x52,0x13,0x90,0x16,0x43,0x02,0xFF, + 0x68,0xC7,0x00,0x00,0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x88,0x56,0x13,0x81, + 0xFE,0x64,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x88,0x46,0x43, + 0x01,0xFF,0x46,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x79,0x46 + }; + + byte SZL_ID_01A0_IDX_0008[172] = { + 0xFF,0x09,0x00,0xA8,0x01,0xA0,0x00,0x00,0x00,0x14,0x00,0x08,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00, + 0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x38,0x86,0x13,0x81,0xFE,0x64,0xC7,0x72, + 0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x38,0x76,0x43,0x01,0xFF,0x46,0xC7, + 0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x29,0x66,0x43,0x04,0xFF,0x84, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x94,0x02,0x04,0x23,0x52,0x13,0x90,0x16,0x43,0x02,0xFF, + 0x68,0xC7,0x00,0x00,0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x88,0x56,0x13,0x81, + 0xFE,0x64,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x88,0x46,0x43, + 0x01,0xFF,0x46,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x79,0x46, + 0x43,0x04,0xFF,0x84,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x94,0x02,0x04,0x23,0x51,0x19,0x48, + 0x26 + }; + + byte SZL_ID_01A0_IDX_0009[192] = { + 0xFF,0x09,0x00,0xBC,0x01,0xA0,0x00,0x00,0x00,0x14,0x00,0x09,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00, + 0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x38,0x86,0x13,0x81,0xFE,0x64,0xC7,0x72, + 0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x38,0x76,0x43,0x01,0xFF,0x46,0xC7, + 0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x29,0x66,0x43,0x04,0xFF,0x84, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x94,0x02,0x04,0x23,0x52,0x13,0x90,0x16,0x43,0x02,0xFF, + 0x68,0xC7,0x00,0x00,0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x88,0x56,0x13,0x81, + 0xFE,0x64,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x88,0x46,0x43, + 0x01,0xFF,0x46,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x79,0x46, + 0x43,0x04,0xFF,0x84,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x94,0x02,0x04,0x23,0x51,0x19,0x48, + 0x26,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00,0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x50,0x45, + 0x09,0x96 + }; + + byte SZL_ID_01A0_IDX_000A[212] = { + 0xFF,0x09,0x00,0xD0,0x01,0xA0,0x00,0x00,0x00,0x14,0x00,0x0A,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00, + 0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x38,0x86,0x13,0x81,0xFE,0x64,0xC7,0x72, + 0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x38,0x76,0x43,0x01,0xFF,0x46,0xC7, + 0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x29,0x66,0x43,0x04,0xFF,0x84, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x94,0x02,0x04,0x23,0x52,0x13,0x90,0x16,0x43,0x02,0xFF, + 0x68,0xC7,0x00,0x00,0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x88,0x56,0x13,0x81, + 0xFE,0x64,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x88,0x46,0x43, + 0x01,0xFF,0x46,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x79,0x46, + 0x43,0x04,0xFF,0x84,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x94,0x02,0x04,0x23,0x51,0x19,0x48, + 0x26,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00,0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x50,0x45, + 0x09,0x96,0x13,0x81,0xFE,0x64,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x50, + 0x45,0x09,0x86 + }; + + byte SZL_ID_01A0_IDX_000B[232] = { + 0xFF,0x09,0x00,0xE4,0x01,0xA0,0x00,0x00,0x00,0x14,0x00,0x0B,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00, + 0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x38,0x86,0x13,0x81,0xFE,0x64,0xC7,0x72, + 0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x38,0x76,0x43,0x01,0xFF,0x46,0xC7, + 0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x29,0x66,0x43,0x04,0xFF,0x84, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x94,0x02,0x04,0x23,0x52,0x13,0x90,0x16,0x43,0x02,0xFF, + 0x68,0xC7,0x00,0x00,0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x88,0x56,0x13,0x81, + 0xFE,0x64,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x88,0x46,0x43, + 0x01,0xFF,0x46,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x79,0x46, + 0x43,0x04,0xFF,0x84,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x94,0x02,0x04,0x23,0x51,0x19,0x48, + 0x26,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00,0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x50,0x45, + 0x09,0x96,0x13,0x81,0xFE,0x64,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x50, + 0x45,0x09,0x86,0x43,0x01,0xFF,0x46,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23, + 0x50,0x45,0x00,0x96 + }; + + byte SZL_ID_01A0_IDX_000C[252] = { + 0xFF,0x09,0x00,0xF8,0x01,0xA0,0x00,0x00,0x00,0x14,0x00,0x0C,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00, + 0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x38,0x86,0x13,0x81,0xFE,0x64,0xC7,0x72, + 0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x38,0x76,0x43,0x01,0xFF,0x46,0xC7, + 0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x29,0x66,0x43,0x04,0xFF,0x84, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x94,0x02,0x04,0x23,0x52,0x13,0x90,0x16,0x43,0x02,0xFF, + 0x68,0xC7,0x00,0x00,0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x88,0x56,0x13,0x81, + 0xFE,0x64,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x88,0x46,0x43, + 0x01,0xFF,0x46,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x79,0x46, + 0x43,0x04,0xFF,0x84,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x94,0x02,0x04,0x23,0x51,0x19,0x48, + 0x26,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00,0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x50,0x45, + 0x09,0x96,0x13,0x81,0xFE,0x64,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x50, + 0x45,0x09,0x86,0x43,0x01,0xFF,0x46,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23, + 0x50,0x45,0x00,0x96,0x43,0x04,0xFF,0x84,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x94,0x02,0x04, + 0x23,0x50,0x42,0x54,0x66 + }; + + byte SZL_ID_01A0_IDX_000D[272] = { + 0xFF,0x09,0x01,0x0C,0x01,0xA0,0x00,0x00,0x00,0x14,0x00,0x0D,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00, + 0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x38,0x86,0x13,0x81,0xFE,0x64,0xC7,0x72, + 0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x38,0x76,0x43,0x01,0xFF,0x46,0xC7, + 0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x29,0x66,0x43,0x04,0xFF,0x84, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x94,0x02,0x04,0x23,0x52,0x13,0x90,0x16,0x43,0x02,0xFF, + 0x68,0xC7,0x00,0x00,0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x88,0x56,0x13,0x81, + 0xFE,0x64,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x88,0x46,0x43, + 0x01,0xFF,0x46,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x79,0x46, + 0x43,0x04,0xFF,0x84,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x94,0x02,0x04,0x23,0x51,0x19,0x48, + 0x26,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00,0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x50,0x45, + 0x09,0x96,0x13,0x81,0xFE,0x64,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x50, + 0x45,0x09,0x86,0x43,0x01,0xFF,0x46,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23, + 0x50,0x45,0x00,0x96,0x43,0x04,0xFF,0x84,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x94,0x02,0x04, + 0x23,0x50,0x42,0x54,0x66,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00,0x00,0x08,0x14,0x77,0x14,0x94,0x02, + 0x04,0x23,0x50,0x32,0x40,0x46 + }; + + byte SZL_ID_01A0_IDX_000E[292] = { + 0xFF,0x09,0x01,0x20,0x01,0xA0,0x00,0x00,0x00,0x14,0x00,0x0E,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00, + 0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x38,0x86,0x13,0x81,0xFE,0x64,0xC7,0x72, + 0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x38,0x76,0x43,0x01,0xFF,0x46,0xC7, + 0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x29,0x66,0x43,0x04,0xFF,0x84, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x94,0x02,0x04,0x23,0x52,0x13,0x90,0x16,0x43,0x02,0xFF, + 0x68,0xC7,0x00,0x00,0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x88,0x56,0x13,0x81, + 0xFE,0x64,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x88,0x46,0x43, + 0x01,0xFF,0x46,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x79,0x46, + 0x43,0x04,0xFF,0x84,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x94,0x02,0x04,0x23,0x51,0x19,0x48, + 0x26,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00,0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x50,0x45, + 0x09,0x96,0x13,0x81,0xFE,0x64,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x50, + 0x45,0x09,0x86,0x43,0x01,0xFF,0x46,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23, + 0x50,0x45,0x00,0x96,0x43,0x04,0xFF,0x84,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x94,0x02,0x04, + 0x23,0x50,0x42,0x54,0x66,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00,0x00,0x08,0x14,0x77,0x14,0x94,0x02, + 0x04,0x23,0x50,0x32,0x40,0x46,0x13,0x81,0xFE,0x64,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94, + 0x02,0x04,0x23,0x50,0x32,0x40,0x36 + }; + + byte SZL_ID_01A0_IDX_000F[312] = { + 0xFF,0x09,0x01,0x34,0x01,0xA0,0x00,0x00,0x00,0x14,0x00,0x0F,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00, + 0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x38,0x86,0x13,0x81,0xFE,0x64,0xC7,0x72, + 0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x38,0x76,0x43,0x01,0xFF,0x46,0xC7, + 0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x29,0x66,0x43,0x04,0xFF,0x84, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x94,0x02,0x04,0x23,0x52,0x13,0x90,0x16,0x43,0x02,0xFF, + 0x68,0xC7,0x00,0x00,0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x88,0x56,0x13,0x81, + 0xFE,0x64,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x88,0x46,0x43, + 0x01,0xFF,0x46,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x79,0x46, + 0x43,0x04,0xFF,0x84,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x94,0x02,0x04,0x23,0x51,0x19,0x48, + 0x26,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00,0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x50,0x45, + 0x09,0x96,0x13,0x81,0xFE,0x64,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x50, + 0x45,0x09,0x86,0x43,0x01,0xFF,0x46,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23, + 0x50,0x45,0x00,0x96,0x43,0x04,0xFF,0x84,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x94,0x02,0x04, + 0x23,0x50,0x42,0x54,0x66,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00,0x00,0x08,0x14,0x77,0x14,0x94,0x02, + 0x04,0x23,0x50,0x32,0x40,0x46,0x13,0x81,0xFE,0x64,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94, + 0x02,0x04,0x23,0x50,0x32,0x40,0x36,0x43,0x01,0xFF,0x46,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14, + 0x94,0x02,0x04,0x23,0x50,0x32,0x31,0x46 + }; + + byte SZL_ID_01A0_IDX_0010[332] = { + 0xFF,0x09,0x01,0x48,0x01,0xA0,0x00,0x00,0x00,0x14,0x00,0x10,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00, + 0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x38,0x86,0x13,0x81,0xFE,0x64,0xC7,0x72, + 0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x38,0x76,0x43,0x01,0xFF,0x46,0xC7, + 0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x29,0x66,0x43,0x04,0xFF,0x84, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x94,0x02,0x04,0x23,0x52,0x13,0x90,0x16,0x43,0x02,0xFF, + 0x68,0xC7,0x00,0x00,0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x88,0x56,0x13,0x81, + 0xFE,0x64,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x88,0x46,0x43, + 0x01,0xFF,0x46,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x79,0x46, + 0x43,0x04,0xFF,0x84,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x94,0x02,0x04,0x23,0x51,0x19,0x48, + 0x26,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00,0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x50,0x45, + 0x09,0x96,0x13,0x81,0xFE,0x64,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x50, + 0x45,0x09,0x86,0x43,0x01,0xFF,0x46,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23, + 0x50,0x45,0x00,0x96,0x43,0x04,0xFF,0x84,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x94,0x02,0x04, + 0x23,0x50,0x42,0x54,0x66,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00,0x00,0x08,0x14,0x77,0x14,0x94,0x02, + 0x04,0x23,0x50,0x32,0x40,0x46,0x13,0x81,0xFE,0x64,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94, + 0x02,0x04,0x23,0x50,0x32,0x40,0x36,0x43,0x01,0xFF,0x46,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14, + 0x94,0x02,0x04,0x23,0x50,0x32,0x31,0x46,0x43,0x04,0xFF,0x84,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x94,0x02,0x04,0x23,0x50,0x29,0x92,0x26 + }; + + byte SZL_ID_01A0_IDX_0011[352] = { + 0xFF,0x09,0x01,0x5C,0x01,0xA0,0x00,0x00,0x00,0x14,0x00,0x11,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00, + 0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x38,0x86,0x13,0x81,0xFE,0x64,0xC7,0x72, + 0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x38,0x76,0x43,0x01,0xFF,0x46,0xC7, + 0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x29,0x66,0x43,0x04,0xFF,0x84, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x94,0x02,0x04,0x23,0x52,0x13,0x90,0x16,0x43,0x02,0xFF, + 0x68,0xC7,0x00,0x00,0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x88,0x56,0x13,0x81, + 0xFE,0x64,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x88,0x46,0x43, + 0x01,0xFF,0x46,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x79,0x46, + 0x43,0x04,0xFF,0x84,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x94,0x02,0x04,0x23,0x51,0x19,0x48, + 0x26,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00,0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x50,0x45, + 0x09,0x96,0x13,0x81,0xFE,0x64,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x50, + 0x45,0x09,0x86,0x43,0x01,0xFF,0x46,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23, + 0x50,0x45,0x00,0x96,0x43,0x04,0xFF,0x84,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x94,0x02,0x04, + 0x23,0x50,0x42,0x54,0x66,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00,0x00,0x08,0x14,0x77,0x14,0x94,0x02, + 0x04,0x23,0x50,0x32,0x40,0x46,0x13,0x81,0xFE,0x64,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94, + 0x02,0x04,0x23,0x50,0x32,0x40,0x36,0x43,0x01,0xFF,0x46,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14, + 0x94,0x02,0x04,0x23,0x50,0x32,0x31,0x46,0x43,0x04,0xFF,0x84,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x94,0x02,0x04,0x23,0x50,0x29,0x92,0x26,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00,0x00,0x08,0x14, + 0x77,0x14,0x94,0x02,0x04,0x23,0x49,0x22,0x26,0x66 + }; + + byte SZL_ID_01A0_IDX_0012[372] = { + 0xFF,0x09,0x01,0x70,0x01,0xA0,0x00,0x00,0x00,0x14,0x00,0x12,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00, + 0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x38,0x86,0x13,0x81,0xFE,0x64,0xC7,0x72, + 0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x38,0x76,0x43,0x01,0xFF,0x46,0xC7, + 0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x29,0x66,0x43,0x04,0xFF,0x84, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x94,0x02,0x04,0x23,0x52,0x13,0x90,0x16,0x43,0x02,0xFF, + 0x68,0xC7,0x00,0x00,0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x88,0x56,0x13,0x81, + 0xFE,0x64,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x88,0x46,0x43, + 0x01,0xFF,0x46,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x79,0x46, + 0x43,0x04,0xFF,0x84,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x94,0x02,0x04,0x23,0x51,0x19,0x48, + 0x26,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00,0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x50,0x45, + 0x09,0x96,0x13,0x81,0xFE,0x64,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x50, + 0x45,0x09,0x86,0x43,0x01,0xFF,0x46,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23, + 0x50,0x45,0x00,0x96,0x43,0x04,0xFF,0x84,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x94,0x02,0x04, + 0x23,0x50,0x42,0x54,0x66,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00,0x00,0x08,0x14,0x77,0x14,0x94,0x02, + 0x04,0x23,0x50,0x32,0x40,0x46,0x13,0x81,0xFE,0x64,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94, + 0x02,0x04,0x23,0x50,0x32,0x40,0x36,0x43,0x01,0xFF,0x46,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14, + 0x94,0x02,0x04,0x23,0x50,0x32,0x31,0x46,0x43,0x04,0xFF,0x84,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x94,0x02,0x04,0x23,0x50,0x29,0x92,0x26,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00,0x00,0x08,0x14, + 0x77,0x14,0x94,0x02,0x04,0x23,0x49,0x22,0x26,0x66,0x13,0x81,0xFE,0x64,0xC7,0x72,0x43,0x04,0x08, + 0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x49,0x22,0x26,0x56 + }; + + byte SZL_ID_01A0_IDX_0013[392] = { + 0xFF,0x09,0x01,0x84,0x01,0xA0,0x00,0x00,0x00,0x14,0x00,0x13,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00, + 0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x38,0x86,0x13,0x81,0xFE,0x64,0xC7,0x72, + 0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x38,0x76,0x43,0x01,0xFF,0x46,0xC7, + 0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x29,0x66,0x43,0x04,0xFF,0x84, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x94,0x02,0x04,0x23,0x52,0x13,0x90,0x16,0x43,0x02,0xFF, + 0x68,0xC7,0x00,0x00,0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x88,0x56,0x13,0x81, + 0xFE,0x64,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x88,0x46,0x43, + 0x01,0xFF,0x46,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x79,0x46, + 0x43,0x04,0xFF,0x84,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x94,0x02,0x04,0x23,0x51,0x19,0x48, + 0x26,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00,0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x50,0x45, + 0x09,0x96,0x13,0x81,0xFE,0x64,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x50, + 0x45,0x09,0x86,0x43,0x01,0xFF,0x46,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23, + 0x50,0x45,0x00,0x96,0x43,0x04,0xFF,0x84,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x94,0x02,0x04, + 0x23,0x50,0x42,0x54,0x66,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00,0x00,0x08,0x14,0x77,0x14,0x94,0x02, + 0x04,0x23,0x50,0x32,0x40,0x46,0x13,0x81,0xFE,0x64,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94, + 0x02,0x04,0x23,0x50,0x32,0x40,0x36,0x43,0x01,0xFF,0x46,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14, + 0x94,0x02,0x04,0x23,0x50,0x32,0x31,0x46,0x43,0x04,0xFF,0x84,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x94,0x02,0x04,0x23,0x50,0x29,0x92,0x26,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00,0x00,0x08,0x14, + 0x77,0x14,0x94,0x02,0x04,0x23,0x49,0x22,0x26,0x66,0x13,0x81,0xFE,0x64,0xC7,0x72,0x43,0x04,0x08, + 0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x49,0x22,0x26,0x56,0x43,0x01,0xFF,0x46,0xC7,0x72,0x43,0x04, + 0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x49,0x22,0x17,0x66 + }; + + byte SZL_ID_01A0_IDX_0014[412] = { + 0xFF,0x09,0x01,0x98,0x01,0xA0,0x00,0x00,0x00,0x14,0x00,0x14,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00, + 0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x38,0x86,0x13,0x81,0xFE,0x64,0xC7,0x72, + 0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x38,0x76,0x43,0x01,0xFF,0x46,0xC7, + 0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x29,0x66,0x43,0x04,0xFF,0x84, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x94,0x02,0x04,0x23,0x52,0x13,0x90,0x16,0x43,0x02,0xFF, + 0x68,0xC7,0x00,0x00,0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x88,0x56,0x13,0x81, + 0xFE,0x64,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x88,0x46,0x43, + 0x01,0xFF,0x46,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x79,0x46, + 0x43,0x04,0xFF,0x84,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x94,0x02,0x04,0x23,0x51,0x19,0x48, + 0x26,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00,0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x50,0x45, + 0x09,0x96,0x13,0x81,0xFE,0x64,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x50, + 0x45,0x09,0x86,0x43,0x01,0xFF,0x46,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23, + 0x50,0x45,0x00,0x96,0x43,0x04,0xFF,0x84,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x94,0x02,0x04, + 0x23,0x50,0x42,0x54,0x66,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00,0x00,0x08,0x14,0x77,0x14,0x94,0x02, + 0x04,0x23,0x50,0x32,0x40,0x46,0x13,0x81,0xFE,0x64,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94, + 0x02,0x04,0x23,0x50,0x32,0x40,0x36,0x43,0x01,0xFF,0x46,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14, + 0x94,0x02,0x04,0x23,0x50,0x32,0x31,0x46,0x43,0x04,0xFF,0x84,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x94,0x02,0x04,0x23,0x50,0x29,0x92,0x26,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00,0x00,0x08,0x14, + 0x77,0x14,0x94,0x02,0x04,0x23,0x49,0x22,0x26,0x66,0x13,0x81,0xFE,0x64,0xC7,0x72,0x43,0x04,0x08, + 0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x49,0x22,0x26,0x56,0x43,0x01,0xFF,0x46,0xC7,0x72,0x43,0x04, + 0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x49,0x22,0x17,0x66,0x43,0x04,0xFF,0x84,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x94,0x02,0x04,0x23,0x49,0x19,0x75,0x46 + }; + + byte SZL_ID_01A0_IDX_0015[432] = { + 0xFF,0x09,0x01,0xAC,0x01,0xA0,0x00,0x00,0x00,0x14,0x00,0x15,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00, + 0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x38,0x86,0x13,0x81,0xFE,0x64,0xC7,0x72, + 0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x38,0x76,0x43,0x01,0xFF,0x46,0xC7, + 0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x29,0x66,0x43,0x04,0xFF,0x84, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x94,0x02,0x04,0x23,0x52,0x13,0x90,0x16,0x43,0x02,0xFF, + 0x68,0xC7,0x00,0x00,0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x88,0x56,0x13,0x81, + 0xFE,0x64,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x88,0x46,0x43, + 0x01,0xFF,0x46,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x79,0x46, + 0x43,0x04,0xFF,0x84,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x94,0x02,0x04,0x23,0x51,0x19,0x48, + 0x26,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00,0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x50,0x45, + 0x09,0x96,0x13,0x81,0xFE,0x64,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x50, + 0x45,0x09,0x86,0x43,0x01,0xFF,0x46,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23, + 0x50,0x45,0x00,0x96,0x43,0x04,0xFF,0x84,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x94,0x02,0x04, + 0x23,0x50,0x42,0x54,0x66,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00,0x00,0x08,0x14,0x77,0x14,0x94,0x02, + 0x04,0x23,0x50,0x32,0x40,0x46,0x13,0x81,0xFE,0x64,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94, + 0x02,0x04,0x23,0x50,0x32,0x40,0x36,0x43,0x01,0xFF,0x46,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14, + 0x94,0x02,0x04,0x23,0x50,0x32,0x31,0x46,0x43,0x04,0xFF,0x84,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x94,0x02,0x04,0x23,0x50,0x29,0x92,0x26,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00,0x00,0x08,0x14, + 0x77,0x14,0x94,0x02,0x04,0x23,0x49,0x22,0x26,0x66,0x13,0x81,0xFE,0x64,0xC7,0x72,0x43,0x04,0x08, + 0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x49,0x22,0x26,0x56,0x43,0x01,0xFF,0x46,0xC7,0x72,0x43,0x04, + 0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x49,0x22,0x17,0x66,0x43,0x04,0xFF,0x84,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x94,0x02,0x04,0x23,0x49,0x19,0x75,0x46,0x43,0x02,0xFF,0x68,0xC7,0x00, + 0x00,0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x48,0x32,0x21,0x16 + }; + + byte SZL_ID_0117_IDX_0000[16] = { + 0xFF,0x09,0x00,0x0C,0x01,0x17,0x00,0x00,0x00,0x04,0x00,0x01,0x00,0x00,0x00,0x01 + }; + + byte SZL_ID_0117_IDX_0001[16] = { + 0xFF,0x09,0x00,0x0C,0x01,0x17,0x00,0x01,0x00,0x04,0x00,0x01,0x00,0x01,0x00,0x03 + }; + + byte SZL_ID_0117_IDX_0002[16] = { + 0xFF,0x09,0x00,0x0C,0x01,0x17,0x00,0x02,0x00,0x04,0x00,0x01,0x00,0x02,0x00,0x02 + }; + + byte SZL_ID_0117_IDX_0003[16] = { + 0xFF,0x09,0x00,0x0C,0x01,0x17,0x00,0x03,0x00,0x04,0x00,0x01,0x00,0x03,0x00,0x01 + }; + + byte SZL_ID_0117_IDX_0004[16] = { + 0xFF,0x09,0x00,0x0C,0x01,0x17,0x00,0x04,0x00,0x04,0x00,0x01,0x00,0x04,0x00,0x01 + }; + + byte SZL_ID_0118_IDX_0000[16] = { + 0xFF,0x09,0x00,0x0C,0x01,0x18,0x00,0x00,0x00,0x04,0x00,0x01,0x00,0x00,0x00,0x08 + }; + + byte SZL_ID_0118_IDX_0001[16] = { + 0xFF,0x09,0x00,0x0C,0x01,0x18,0x00,0x01,0x00,0x04,0x00,0x01,0x00,0x01,0x00,0x08 + }; + + byte SZL_ID_0118_IDX_0002[16] = { + 0xFF,0x09,0x00,0x0C,0x01,0x18,0x00,0x02,0x00,0x04,0x00,0x01,0x00,0x02,0x00,0x08 + }; + + byte SZL_ID_0118_IDX_0003[16] = { + 0xFF,0x09,0x00,0x0C,0x01,0x18,0x00,0x03,0x00,0x04,0x00,0x01,0x00,0x03,0x00,0x08 + }; + + byte SZL_ID_0131_IDX_0001[52] = { + 0xFF,0x09,0x00,0x30,0x01,0x31,0x00,0x01,0x00,0x28,0x00,0x01,0x00,0x01, + 0x08,0x00, // PDU SIZE : We expose 2048 00F0 + 0x04,0x00, // Max Commections : We expose 1024 0010 + 0x00,0xB7,0x1B,0x00,0x00,0x02,0xDC,0x6C,0x05,0xF5,0xE1,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + }; + + byte SZL_ID_0131_IDX_0002[52] = { + 0xFF,0x09,0x00,0x30,0x01,0x31,0x00,0x02,0x00,0x28,0x00,0x01,0x00,0x02,0xBE,0xFD,0x4F,0x00,0x00, + 0x00,0x00,0x00,0x08,0x00,0x00,0x00,0x3C,0x01,0x08,0x00,0x00,0x00,0x7D,0x00,0x00,0x05,0x03,0x0F, + 0x00,0x00,0x08,0x00,0x00,0x0C,0x00,0x0A,0x00,0x00,0x00,0x01,0x00,0x00 + }; + + byte SZL_ID_0131_IDX_0003[52] = { + 0xFF,0x09,0x00,0x30,0x01,0x31,0x00,0x03,0x00,0x28,0x00,0x01,0x00,0x03,0x7F,0xFC,0x83,0x01, + 0x00,0xF0, // Max size of consistently readable data (will be set = PDU size) + 0x00,0x10,0x00,0x01,0x02,0x09,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + }; + + byte SZL_ID_0131_IDX_0004[52] = { + 0xFF,0x09,0x00,0x30,0x01,0x31,0x00,0x04,0x00,0x28,0x00,0x01,0x00,0x04,0xFE,0x01,0x62,0x41,0x63, + 0x00,0x1E,0x00,0x10,0x10,0x10,0x04,0x02,0x00,0x00,0x02,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + }; + + byte SZL_ID_0131_IDX_0005[52] = { + 0xFF,0x09,0x00,0x30,0x01,0x31,0x00,0x05,0x00,0x28,0x00,0x01,0x00,0x05,0x3E,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x10,0x01,0xF4,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + }; + + byte SZL_ID_0131_IDX_0006[52] = { + 0xFF,0x09,0x00,0x30,0x01,0x31,0x00,0x06,0x00,0x28,0x00,0x01,0x00,0x06,0xF3,0x00,0x00,0xF8,0x01, + 0xF0,0xFF,0x4E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x00,0x00,0x00,0x20, + 0x01,0x00,0x00,0x0E,0x00,0x4C,0xFF,0xFF,0x05,0xC0,0x00,0x00,0x00,0x00 + }; + + byte SZL_ID_0131_IDX_0007[52] = { + 0xFF,0x09,0x00,0x30,0x01,0x31,0x00,0x07,0x00,0x28,0x00,0x01,0x00,0x07,0x01,0x00,0x3F,0x00,0x20, + 0x01,0x01,0x00,0x00,0x00,0x01,0x08,0x01,0x08,0x01,0x08,0x20,0x08,0x02,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + }; + + byte SZL_ID_0131_IDX_0008[52] = { + 0xFF,0x09,0x00,0x30,0x01,0x31,0x00,0x08,0x00,0x28,0x00,0x01,0x00,0x08,0x70,0x03,0x70,0x02,0x70, + 0x02,0x40,0x32,0x40,0x32,0x40,0x32,0x40,0x64,0x40,0x32,0x40,0x32,0x40,0x64,0x40,0x64,0x40,0x64, + 0x40,0x64,0x40,0x64,0x40,0x64,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + }; + + byte SZL_ID_0131_IDX_0009[52] = { + 0xFF,0x09,0x00,0x30,0x01,0x31,0x00,0x09,0x00,0x28,0x00,0x01,0x00,0x09,0x04,0x06,0x01,0x00,0x01, + 0xF7,0x01,0xF7,0x00,0x01,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + }; + + byte SZL_ID_0C91_IDX_07FE[28] = { + 0xFF,0x09,0x00,0x18,0x0C,0x91,0x07,0xFE,0x00,0x10,0x00,0x01,0x00,0x00,0x02,0x02,0x07,0xFE,0xA7, + 0xC4,0xA7,0xC4,0x00,0x00,0xB4,0x02,0x00,0x11 + }; + + +#endif + diff --git a/core/src/drivers/plugins/native/s7comm/snap7/core/s7_isotcp.cpp b/core/src/drivers/plugins/native/s7comm/snap7/core/s7_isotcp.cpp new file mode 100644 index 00000000..70b6af45 --- /dev/null +++ b/core/src/drivers/plugins/native/s7comm/snap7/core/s7_isotcp.cpp @@ -0,0 +1,541 @@ +/*=============================================================================| +| PROJECT SNAP7 1.4.3 | +|==============================================================================| +| Copyright (C) 2013, 2025 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/ | +|=============================================================================*/ +#include "s7_isotcp.h" +//--------------------------------------------------------------------------- +TIsoTcpSocket::TIsoTcpSocket() +{ + RecvTimeout = 3000; // Some old equipments are a bit slow to answer.... + RemotePort = isoTcpPort; + // These fields should be $0000 and in any case RFC says that they are not considered. + // But some equipment...need a non zero value for the source reference. + DstRef = 0x0000; + SrcRef = 0x0100; + // PDU size requested + IsoPDUSize =1024; + IsoMaxFragments=MaxIsoFragments; + LastIsoError=0; +} +//--------------------------------------------------------------------------- +TIsoTcpSocket::~TIsoTcpSocket() +{ +} +//--------------------------------------------------------------------------- +int TIsoTcpSocket::CheckPDU(void *pPDU, u_char PduTypeExpected) +{ + PIsoHeaderInfo Info; + int Size; + ClrIsoError(); + if (pPDU!=0) + { + Info = PIsoHeaderInfo(pPDU); + Size = PDUSize(pPDU); + // Performs check + if (( Size<7 ) || ( Size>IsoPayload_Size ) || // Checks RFC 1006 header length + ( Info->HLengthPDUType!=PduTypeExpected)) // Checks PDU Type + return SetIsoError(errIsoInvalidPDU); + else + return noError; + } + else + return SetIsoError(errIsoNullPointer); +} +//--------------------------------------------------------------------------- +int TIsoTcpSocket::SetIsoError(int Error) +{ + LastIsoError = Error | LastTcpError; + return LastIsoError; +} +//--------------------------------------------------------------------------- +void TIsoTcpSocket::ClrIsoError() +{ + LastIsoError=0; + LastTcpError=0; +} +//--------------------------------------------------------------------------- +int TIsoTcpSocket::BuildControlPDU() +{ + int ParLen, IsoLen; + + ClrIsoError(); + FControlPDU.COTP.Params.PduSizeCode=0xC0; // code that identifies TPDU size + FControlPDU.COTP.Params.PduSizeLen =0x01; // 1 byte this field + switch(IsoPDUSize) + { + case 128: + FControlPDU.COTP.Params.PduSizeVal =0x07; + break; + case 256: + FControlPDU.COTP.Params.PduSizeVal =0x08; + break; + case 512: + FControlPDU.COTP.Params.PduSizeVal =0x09; + break; + case 1024: + FControlPDU.COTP.Params.PduSizeVal =0x0A; + break; + case 2048: + FControlPDU.COTP.Params.PduSizeVal =0x0B; + break; + case 4096: + FControlPDU.COTP.Params.PduSizeVal =0x0C; + break; + case 8192: + FControlPDU.COTP.Params.PduSizeVal =0x0D; + break; + default: + FControlPDU.COTP.Params.PduSizeVal =0x0B; // Our Default + }; + // Build TSAPs + FControlPDU.COTP.Params.TSAP[0]=0xC1; // code that identifies source TSAP + FControlPDU.COTP.Params.TSAP[1]=2; // source TSAP Len + FControlPDU.COTP.Params.TSAP[2]=(SrcTSap>>8) & 0xFF; // HI part + FControlPDU.COTP.Params.TSAP[3]=SrcTSap & 0xFF; // LO part + + FControlPDU.COTP.Params.TSAP[4]=0xC2; // code that identifies dest TSAP + FControlPDU.COTP.Params.TSAP[5]=2; // dest TSAP Len + FControlPDU.COTP.Params.TSAP[6]=(DstTSap>>8) & 0xFF; // HI part + FControlPDU.COTP.Params.TSAP[7]=DstTSap & 0xFF; // LO part + + // Params length + ParLen=11; // 2 Src TSAP (Code+field Len) + + // 2 Src TSAP len + + // 2 Dst TSAP (Code+field Len) + + // 2 Src TSAP len + + // 3 PDU size (Code+field Len+Val) = 11 + // Telegram length + IsoLen=sizeof(TTPKT)+ // TPKT Header + 7 + // COTP Header Size without params + ParLen; // COTP params + + FControlPDU.TPKT.Version =isoTcpVersion; + FControlPDU.TPKT.Reserved =0; + FControlPDU.TPKT.HI_Lenght=0; // Connection Telegram size cannot exced 255 bytes, so + // this field is always 0 + FControlPDU.TPKT.LO_Lenght=IsoLen; + + FControlPDU.COTP.HLength =ParLen + 6; // <-- 6 = 7 - 1 (COTP Header size - 1) + FControlPDU.COTP.PDUType =pdu_type_CR; // Connection Request + FControlPDU.COTP.DstRef =DstRef; // Destination reference + FControlPDU.COTP.SrcRef =SrcRef; // Source reference + FControlPDU.COTP.CO_R =0x00; // Class + Option : RFC0983 states that it must be always 0x40 + // but for some equipment (S7) must be 0 in disaccord of specifications !!! + return noError; +} +//--------------------------------------------------------------------------- +int TIsoTcpSocket::PDUSize(void *pPDU) +{ + return PIsoHeaderInfo(pPDU)->TPKT.HI_Lenght*256+PIsoHeaderInfo( pPDU )->TPKT.LO_Lenght; +} +//--------------------------------------------------------------------------- +void TIsoTcpSocket::IsoParsePDU(TIsoControlPDU pdu) +{ +// Currently we accept a connection with any kind of src/dst tsap +// Override to implement special filters. +} +//--------------------------------------------------------------------------- +int TIsoTcpSocket::IsoConfirmConnection(u_char PDUType) +{ + PIsoControlPDU CPDU = PIsoControlPDU(&PDU); + u_short TempRef; + + ClrIsoError(); + PDU.COTP.PDUType=PDUType; + // Exchange SrcRef<->DstRef, not strictly needed by COTP 8073 but S7PLC as client needs it. + TempRef=CPDU->COTP.DstRef; + CPDU->COTP.DstRef=CPDU->COTP.SrcRef; + CPDU->COTP.SrcRef=0x0100;//TempRef; + + return SendPacket(&PDU,PDUSize(&PDU)); +} +//--------------------------------------------------------------------------- +void TIsoTcpSocket::FragmentSkipped(int Size) +{ +// override for log purpose +} +//--------------------------------------------------------------------------- +int TIsoTcpSocket::isoConnect() +{ + pbyte TmpControlPDU; + PIsoControlPDU ControlPDU; + u_int Length; + int Result; + + // Build the default connection telegram + BuildControlPDU(); + ControlPDU =&FControlPDU; + + // Checks the format + Result =CheckPDU(ControlPDU, pdu_type_CR); + if (Result!=0) + return Result; + + Result =SckConnect(); + if (Result==noError) + { + // Calcs the length + Length =PDUSize(ControlPDU); + // Send connection telegram + SendPacket(ControlPDU, Length); + if (LastTcpError==0) + { + TmpControlPDU = pbyte(ControlPDU); + // Receives TPKT header (4 bytes) + RecvPacket(TmpControlPDU, sizeof(TTPKT)); + if (LastTcpError==0) + { + // Calc the packet length + Length =PDUSize(TmpControlPDU); + // Check if it fits in the buffer and if it's greater then TTPKT size + if ((Length<=sizeof(TIsoControlPDU)) && (Length>sizeof(TTPKT))) + { + // Points to COTP + TmpControlPDU+=sizeof(TTPKT); + Length -= sizeof(TTPKT); + // Receives remainin bytes 4 bytes after + RecvPacket(TmpControlPDU, Length); + if (LastTcpError==0) + { + // Finally checks the Connection Confirm telegram + Result =CheckPDU(ControlPDU, pdu_type_CC); + if (Result!=0) + LastIsoError=Result; + } + else + Result =SetIsoError(errIsoRecvPacket); + } + else + Result =SetIsoError(errIsoInvalidPDU); + } + else + Result =SetIsoError(errIsoRecvPacket); + // Flush buffer + if (Result!=0) + Purge(); + } + else + Result =SetIsoError(errIsoSendPacket); + + if (Result!=0) + SckDisconnect(); + } + return Result; +} +//--------------------------------------------------------------------------- +int TIsoTcpSocket::isoSendBuffer(void *Data, int Size) +{ + int Result; + u_int IsoSize; + + ClrIsoError(); + // Total Size = Size + Header Size + IsoSize =Size+DataHeaderSize; + // Checks the length + if ((IsoSize>0) && (IsoSize<=IsoFrameSize)) + { + // Builds the header + Result =0; + // TPKT + PDU.TPKT.Version = isoTcpVersion; + PDU.TPKT.Reserved = 0; + PDU.TPKT.HI_Lenght= (u_short(IsoSize)>> 8) & 0xFF; + PDU.TPKT.LO_Lenght= u_short(IsoSize) & 0xFF; + // COPT + PDU.COTP.HLength =sizeof(TCOTP_DT)-1; + PDU.COTP.PDUType =pdu_type_DT; + PDU.COTP.EoT_Num =pdu_EoT; + // Fill payload + if (Data!=0) // Data=null ==> use internal buffer PDU.Payload + memcpy(&PDU.Payload, Data, Size); + // Send over TCP/IP + SendPacket(&PDU, IsoSize); + + if (LastTcpError!=0) + Result =SetIsoError(errIsoSendPacket); + } + else + Result =SetIsoError(errIsoInvalidDataSize ); + return Result; +} +//--------------------------------------------------------------------------- +int TIsoTcpSocket::isoRecvBuffer(void *Data, int & Size) +{ + int Result; + + ClrIsoError(); + Size =0; + Result =isoRecvPDU(&PDU); + if (Result==0) + { + Size =PDUSize( &PDU )-DataHeaderSize; + if (Data!=0) // Data=NULL ==> a child will consume directly PDY.Payload + memcpy(Data, &PDU.Payload, Size); + } + return Result; +} +//--------------------------------------------------------------------------- +int TIsoTcpSocket::isoExchangeBuffer(void *Data, int &Size) +{ + int Result; + + ClrIsoError(); + Result =isoSendBuffer(Data, Size); + if (Result==0) + Result =isoRecvBuffer(Data, Size); + return Result; +} +//--------------------------------------------------------------------------- +bool TIsoTcpSocket::IsoPDUReady() +{ + ClrIsoError(); + return PacketReady(sizeof(TCOTP_DT)); +} +//--------------------------------------------------------------------------- +int TIsoTcpSocket::isoDisconnect(bool OnlyTCP) +{ + int Result; + + ClrIsoError(); + if (Connected) + Purge(); // Flush pending + LastIsoError=0; + // OnlyTCP true -> Disconnect Request telegram is not required : only TCP disconnection + if (!OnlyTCP) + { + // if we are connected -> we have a valid connection telegram + if (Connected) + FControlPDU.COTP.PDUType =pdu_type_DR; + // Checks the format + Result =CheckPDU(&FControlPDU, pdu_type_DR); + if (Result!=0) + return Result; + // Sends Disconnect request + SendPacket(&FControlPDU, PDUSize(&FControlPDU)); + if (LastTcpError!=0) + { + Result =SetIsoError(errIsoSendPacket); + return Result; + } + } + // TCP disconnect + SckDisconnect(); + if (LastTcpError!=0) + Result =SetIsoError(errIsoDisconnect); + else + Result =0; + + return Result; +} +//--------------------------------------------------------------------------- +int TIsoTcpSocket::isoSendPDU(PIsoDataPDU Data) +{ + int Result; + + ClrIsoError(); + Result=CheckPDU(Data,pdu_type_DT); + if (Result==0) + { + SendPacket(Data,PDUSize(Data)); + if (LastTcpError!=0) + Result=SetIsoError(errIsoSendPacket); + } + return Result; +} +//------------------------------------------------------------------------------ +int TIsoTcpSocket::isoRecvFragment(void *From, int Max, int &Size, bool &EoT) +{ + int DataLength; + + Size =0; + EoT =false; + byte PDUType; + ClrIsoError(); + // header is received always from beginning + RecvPacket(&PDU, DataHeaderSize); // TPKT + COPT_DT + if (LastTcpError==0) + { + PDUType=PDU.COTP.PDUType; + switch (PDUType) + { + case pdu_type_CR: + case pdu_type_DR: + EoT=true; + break; + case pdu_type_DT: + EoT = (PDU.COTP.EoT_Num & 0x80) == 0x80; // EoT flag + break; + default: + return SetIsoError(errIsoInvalidPDU); + } + + DataLength = PDUSize(&PDU) - DataHeaderSize; + if (CheckPDU(&PDU, PDUType)!=0) + return LastIsoError; + // Checks for data presence + if (DataLength>0) // payload present + { + // Check if the data fits in the buffer + if(DataLength<=Max) + { + RecvPacket(From, DataLength); + if (LastTcpError!=0) + return SetIsoError(errIsoRecvPacket); + else + Size =DataLength; + } + else + return SetIsoError(errIsoPduOverflow); + } + } + else + return SetIsoError(errIsoRecvPacket); + + return LastIsoError; +} +//--------------------------------------------------------------------------- +// Fragments Recv schema +//------------------------------------------------------------------------------ +// +// packet 1 packet 2 packet 3 +// +--------+------------+ +--------+------------+ +--------+------------+ +// | HEADER | FRAGMENT 1 | | HEADER | FRAGMENT 2 | | HEADER | FRAGMENT 3 | +// +--------+------------+ +--------+------------+ +--------+------------+ +// | | | +// | +-----------+ | +// | | | +// | | +------------------------+ +// | | | (Packet 3 has EoT Flag set) +// V V V +// +--------+------------+------------+------------+ +// | HEADER | FRAGMENT 1 : FRAGMENT 2 : FRAGMENT 3 | +// +--------+------------+------------+------------+ +// ^ +// | +// +-- A new header is built with updated info +// +//------------------------------------------------------------------------------ +int TIsoTcpSocket::isoRecvPDU(PIsoDataPDU Data) +{ + int Result; + int Size; + pbyte pData; + int max; + int Offset; + int Received; + int NumParts; + bool Complete; + + NumParts =1; + Offset =0; + Complete =false; + ClrIsoError(); + pData = pbyte(&PDU.Payload); + do { + pData=pData+Offset; + max =IsoPayload_Size-Offset; // Maximum packet allowed + if (max>0) + { + Result =isoRecvFragment(pData, max, Received, Complete); + if((Result==0) && !Complete) + { + ++NumParts; + Offset += Received; + if (NumParts>IsoMaxFragments) + Result =SetIsoError(errIsoTooManyFragments); + } + } + else + Result =SetIsoError(errIsoTooManyFragments); + } while ((!Complete) && (Result==0)); + + + if (Result==0) + { + // Add to offset the header size + Size =Offset+Received+DataHeaderSize; + // Adjust header + PDU.TPKT.HI_Lenght =(u_short(Size)>>8) & 0xFF; + PDU.TPKT.LO_Lenght =u_short(Size) & 0xFF; + // Copies data if target is not the local PDU + if (Data!=&PDU) + memcpy(Data, &PDU, Size); + } + else + if (LastTcpError!=WSAECONNRESET) + Purge(); + return Result; +} +//--------------------------------------------------------------------------- +int TIsoTcpSocket::isoExchangePDU(PIsoDataPDU Data) +{ + int Result; + ClrIsoError(); + Result=isoSendPDU(Data); + if (Result==0) + Result=isoRecvPDU(Data); + return Result; +} +//--------------------------------------------------------------------------- +void TIsoTcpSocket::IsoPeek(void *pPDU, TPDUKind &PduKind) +{ + PIsoHeaderInfo Info; + u_int IsoLen; + + Info=PIsoHeaderInfo(pPDU); + IsoLen=PDUSize(Info); + + // Check for empty fragment : size of PDU = size of header and nothing else + if (IsoLen==DataHeaderSize ) + { + // We don't need to check the EoT flag since the PDU is empty.... + PduKind=pkEmptyFragment; + return; + }; + // Check for invalid packet : size of PDU < size of header + if (IsoLenDataHeaderSize : check the PDUType + switch (Info->PDUType) + { + case pdu_type_CR: + PduKind=pkConnectionRequest; + break; + case pdu_type_DR: + PduKind=pkDisconnectRequest; + break; + case pdu_type_DT: + PduKind=pkValidData; + break; + default: + PduKind=pkUnrecognizedType; + }; +} + + + diff --git a/core/src/drivers/plugins/native/s7comm/snap7/core/s7_isotcp.h b/core/src/drivers/plugins/native/s7comm/snap7/core/s7_isotcp.h new file mode 100644 index 00000000..86f2a9a6 --- /dev/null +++ b/core/src/drivers/plugins/native/s7comm/snap7/core/s7_isotcp.h @@ -0,0 +1,271 @@ +/*=============================================================================| +| PROJECT SNAP7 1.4.3 | +|==============================================================================| +| Copyright (C) 2013, 2025 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/ | +|=============================================================================*/ +#ifndef s7_isotcp_h +#define s7_isotcp_h +//--------------------------------------------------------------------------- +#include "snap_msgsock.h" +//--------------------------------------------------------------------------- +#pragma pack(1) + +#define isoTcpVersion 3 // RFC 1006 +#define isoTcpPort 102 // RFC 1006 +#define isoInvalidHandle 0 +#define MaxTSAPLength 16 // Max Lenght for Src and Dst TSAP +#define MaxIsoFragments 64 // Max fragments +#define IsoPayload_Size 4096 // Iso telegram Buffer size + +#define noError 0 + +const longword errIsoMask = 0x000F0000; +const longword errIsoBase = 0x0000FFFF; + +const longword errIsoConnect = 0x00010000; // Connection error +const longword errIsoDisconnect = 0x00020000; // Disconnect error +const longword errIsoInvalidPDU = 0x00030000; // Bad format +const longword errIsoInvalidDataSize = 0x00040000; // Bad Datasize passed to send/recv : buffer is invalid +const longword errIsoNullPointer = 0x00050000; // Null passed as pointer +const longword errIsoShortPacket = 0x00060000; // A short packet received +const longword errIsoTooManyFragments = 0x00070000; // Too many packets without EoT flag +const longword errIsoPduOverflow = 0x00080000; // The sum of fragments data exceded maximum packet size +const longword errIsoSendPacket = 0x00090000; // An error occurred during send +const longword errIsoRecvPacket = 0x000A0000; // An error occurred during recv +const longword errIsoInvalidParams = 0x000B0000; // Invalid TSAP params +const longword errIsoResvd_1 = 0x000C0000; // Unassigned +const longword errIsoResvd_2 = 0x000D0000; // Unassigned +const longword errIsoResvd_3 = 0x000E0000; // Unassigned +const longword errIsoResvd_4 = 0x000F0000; // Unassigned + +const longword ISO_OPT_TCP_NODELAY = 0x00000001; // Disable Nagle algorithm +const longword ISO_OPT_INSIDE_MTU = 0x00000002; // Max packet size < MTU ethernet card + +// TPKT Header - ISO on TCP - RFC 1006 (4 bytes) +typedef struct{ + u_char Version; // Always 3 for RFC 1006 + u_char Reserved; // 0 + u_char HI_Lenght; // High part of packet lenght (entire frame, payload and TPDU included) + u_char LO_Lenght; // Low part of packet lenght (entire frame, payload and TPDU included) +} TTPKT; // Packet length : min 7 max 65535 + +typedef struct { + u_char PduSizeCode; + u_char PduSizeLen; + u_char PduSizeVal; + u_char TSAP[245]; // We don't know in advance these fields.... +} TCOPT_Params ; + +// PDU Type constants - ISO 8073, not all are mentioned in RFC 1006 +// For our purposes we use only those labeled with ** +// These constants contains 4 low bit order 0 (credit nibble) +// +// $10 ED : Expedited Data +// $20 EA : Expedited Data Ack +// $40 UD : CLTP UD +// $50 RJ : Reject +// $70 AK : Ack data +// ** $80 DR : Disconnect request (note : S7 doesn't use it) +// ** $C0 DC : Disconnect confirm (note : S7 doesn't use it) +// ** $D0 CC : Connection confirm +// ** $E0 CR : Connection request +// ** $F0 DT : Data +// + +// COTP Header for CONNECTION REQUEST/CONFIRM - DISCONNECT REQUEST/CONFIRM +typedef struct { + u_char HLength; // Header length : initialized to 6 (length without params - 1) + // descending classes that add values in params field must update it. + u_char PDUType; // 0xE0 Connection request + // 0xD0 Connection confirm + // 0x80 Disconnect request + // 0xDC Disconnect confirm + u_short DstRef; // Destination reference : Always 0x0000 + u_short SrcRef; // Source reference : Always 0x0000 + u_char CO_R; // If the telegram is used for Connection request/Confirm, + // the meaning of this field is CLASS+OPTION : + // Class (High 4 bits) + Option (Low 4 bits) + // Class : Always 4 (0100) but is ignored in input (RFC States this) + // Option : Always 0, also this in ignored. + // If the telegram is used for Disconnect request, + // the meaning of this field is REASON : + // 1 Congestion at TSAP + // 2 Session entity not attached to TSAP + // 3 Address unknown (at TCP connect time) + // 128+0 Normal disconnect initiated by the session + // entity. + // 128+1 Remote transport entity congestion at connect + // request time + // 128+3 Connection negotiation failed + // 128+5 Protocol Error + // 128+8 Connection request refused on this network + // connection + // Parameter data : depending on the protocol implementation. + // ISO 8073 define several type of parameters, but RFC 1006 recognizes only + // TSAP related parameters and PDU size. See RFC 0983 for more details. + TCOPT_Params Params; + /* Other params not used here, list only for completeness + ACK_TIME = 0x85, 1000 0101 Acknowledge Time + RES_ERROR = 0x86, 1000 0110 Residual Error Rate + PRIORITY = 0x87, 1000 0111 Priority + TRANSIT_DEL = 0x88, 1000 1000 Transit Delay + THROUGHPUT = 0x89, 1000 1001 Throughput + SEQ_NR = 0x8A, 1000 1010 Subsequence Number (in AK) + REASSIGNMENT = 0x8B, 1000 1011 Reassignment Time + FLOW_CNTL = 0x8C, 1000 1100 Flow Control Confirmation (in AK) + TPDU_SIZE = 0xC0, 1100 0000 TPDU Size + SRC_TSAP = 0xC1, 1100 0001 TSAP-ID / calling TSAP ( in CR/CC ) + DST_TSAP = 0xC2, 1100 0010 TSAP-ID / called TSAP + CHECKSUM = 0xC3, 1100 0011 Checksum + VERSION_NR = 0xC4, 1100 0100 Version Number + PROTECTION = 0xC5, 1100 0101 Protection Parameters (user defined) + OPT_SEL = 0xC6, 1100 0110 Additional Option Selection + PROTO_CLASS = 0xC7, 1100 0111 Alternative Protocol Classes + PREF_MAX_TPDU_SIZE = 0xF0, 1111 0000 + INACTIVITY_TIMER = 0xF2, 1111 0010 + ADDICC = 0xe0 1110 0000 Additional Information on Connection Clearing + */ +} TCOTP_CO ; +typedef TCOTP_CO *PCOTP_CO; + +// COTP Header for DATA EXCHANGE +typedef struct { + u_char HLength; // Header length : 3 for this header + u_char PDUType; // 0xF0 for this header + u_char EoT_Num; // EOT (bit 7) + PDU Number (bits 0..6) + // EOT = 1 -> End of Trasmission Packet (This packet is complete) + // PDU Number : Always 0 +} TCOTP_DT; +typedef TCOTP_DT *PCOTP_DT; + +// Info part of a PDU, only common parts. We use it to check the consistence +// of a telegram regardless of it's nature (control or data). +typedef struct { + TTPKT TPKT; // TPKT Header + // Common part of any COTP + u_char HLength; // Header length : 3 for this header + u_char PDUType; // Pdu type +} TIsoHeaderInfo ; +typedef TIsoHeaderInfo *PIsoHeaderInfo; + +// PDU Type consts (Code + Credit) +const byte pdu_type_CR = 0xE0; // Connection request +const byte pdu_type_CC = 0xD0; // Connection confirm +const byte pdu_type_DR = 0x80; // Disconnect request +const byte pdu_type_DC = 0xC0; // Disconnect confirm +const byte pdu_type_DT = 0xF0; // Data transfer + +const byte pdu_EoT = 0x80; // End of Trasmission Packet (This packet is complete) + +const longword DataHeaderSize = sizeof(TTPKT)+sizeof(TCOTP_DT); +const longword IsoFrameSize = IsoPayload_Size+DataHeaderSize; + +typedef struct { + TTPKT TPKT; // TPKT Header + TCOTP_CO COTP; // COPT Header for CONNECTION stuffs +} TIsoControlPDU; +typedef TIsoControlPDU *PIsoControlPDU; + +typedef u_char TIsoPayload[IsoPayload_Size]; + +typedef struct { + TTPKT TPKT; // TPKT Header + TCOTP_DT COTP; // COPT Header for DATA EXCHANGE + TIsoPayload Payload; // Payload +} TIsoDataPDU ; + +typedef TIsoDataPDU *PIsoDataPDU; +typedef TIsoPayload *PIsoPayload; + +typedef enum { + pkConnectionRequest, + pkDisconnectRequest, + pkEmptyFragment, + pkInvalidPDU, + pkUnrecognizedType, + pkValidData +} TPDUKind ; + +#pragma pack() + +void ErrIsoText(int Error, char *Msg, int len); + +class TIsoTcpSocket : public TMsgSocket +{ +private: + + TIsoControlPDU FControlPDU; + int IsoMaxFragments; // max fragments allowed for an ISO telegram + // Checks the PDU format + int CheckPDU(void *pPDU, u_char PduTypeExpected); + // Receives the next fragment + int isoRecvFragment(void *From, int Max, int &Size, bool &EoT); +protected: + TIsoDataPDU PDU; + int SetIsoError(int Error); + // Builds the control PDU starting from address properties + virtual int BuildControlPDU(); + // Calcs the PDU size + int PDUSize(void *pPDU); + // Parses the connection request PDU to extract TSAP and PDU size info + virtual void IsoParsePDU(TIsoControlPDU PDU); + // Confirms the connection, override this method for special pourpose + // By default it checks the PDU format and resend it changing the pdu type + int IsoConfirmConnection(u_char PDUType); + void ClrIsoError(); + virtual void FragmentSkipped(int Size); +public: + word SrcTSap; // Source TSAP + word DstTSap; // Destination TSAP + word SrcRef; // Source Reference + word DstRef; // Destination Reference + int IsoPDUSize; + int LastIsoError; + //-------------------------------------------------------------------------- + TIsoTcpSocket(); + ~TIsoTcpSocket(); + // HIGH Level functions (work on payload hiding the underlying protocol) + // Connects with a peer, the connection PDU is automatically built starting from address scheme (see below) + int isoConnect(); + // Disconnects from a peer, if OnlyTCP = true, only a TCP disconnect is performed, + // otherwise a disconnect PDU is built and send. + int isoDisconnect(bool OnlyTCP); + // Sends a buffer, a valid header is created + int isoSendBuffer(void *Data, int Size); + // Receives a buffer + int isoRecvBuffer(void *Data, int & Size); + // Exchange cycle send->receive + int isoExchangeBuffer(void *Data, int & Size); + // A PDU is ready (at least its header) to be read + bool IsoPDUReady(); + // Same as isoSendBuffer, but the entire PDU has to be provided (in any case a check is performed) + int isoSendPDU(PIsoDataPDU Data); + // Same as isoRecvBuffer, but it returns the entire PDU, automatically enques the fragments + int isoRecvPDU(PIsoDataPDU Data); + // Same as isoExchangeBuffer, but the entire PDU has to be provided (in any case a check is performed) + int isoExchangePDU(PIsoDataPDU Data); + // Peeks an header info to know which kind of telegram is incoming + void IsoPeek(void *pPDU, TPDUKind &PduKind); +}; + +#endif // s7_isotcp_h diff --git a/core/src/drivers/plugins/native/s7comm/snap7/core/s7_micro_client.cpp b/core/src/drivers/plugins/native/s7comm/snap7/core/s7_micro_client.cpp new file mode 100644 index 00000000..b5c727c3 --- /dev/null +++ b/core/src/drivers/plugins/native/s7comm/snap7/core/s7_micro_client.cpp @@ -0,0 +1,3328 @@ +/*=============================================================================| +| PROJECT SNAP7 1.4.3 | +|==============================================================================| +| Copyright (C) 2013, 2025 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/ | +|=============================================================================*/ +#include "s7_micro_client.h" +//--------------------------------------------------------------------------- + +TSnap7MicroClient::TSnap7MicroClient() +{ + SrcRef =0x0100; // RFC0983 states that SrcRef and DetRef should be 0 + // and, in any case, they are ignored. + // S7 instead requires a number != 0 + // Libnodave uses 0x0100 + // S7Manager uses 0x0D00 + // TIA Portal V12 uses 0x1D00 + // WinCC uses 0x0300 + // Seems that every non zero value is good enough... + DstRef =0x0000; + SrcTSap =0x0100; + DstTSap =0x0000; // It's filled by connection functions + ConnectionType = CONNTYPE_PG; // Default connection type + memset(&Job,0,sizeof(TSnap7Job)); +} +//--------------------------------------------------------------------------- +TSnap7MicroClient::~TSnap7MicroClient() +{ + Destroying = true; +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::opReadArea() +{ + PReqFunReadParams ReqParams; + PResFunReadParams ResParams; + PS7ResHeader23 Answer; + PResFunReadItem ResData; + word RPSize; // ReqParams size + int WordSize; + uintptr_t Offset; + pbyte Target; + int Address; + int IsoSize; + int Start; + int MaxElements; // Max elements that we can transfer in a PDU + word NumElements; // Num of elements that we are asking for this telegram + int TotElements; // Total elements requested + int Size; + int Result; + + WordSize=DataSizeByte(Job.WordLen); // The size in bytes of an element that we are asking for + if (WordSize==0) + return errCliInvalidWordLen; + // First check : params bounds + if ((Job.Number<0) || (Job.Number>65535) || (Job.Start<0) || (Job.Amount<1)) + return errCliInvalidParams; + // Second check : transport size + if ((Job.WordLen==S7WLBit) && (Job.Amount>1)) + return errCliInvalidTransportSize; + // Request Params size + RPSize =sizeof(TReqFunReadItem)+2; // 1 item + FunRead + ItemsCount + // Setup pointers (note : PDUH_out and PDU.Payload are the same pointer) + ReqParams =PReqFunReadParams(pbyte(PDUH_out)+sizeof(TS7ReqHeader)); + Answer =PS7ResHeader23(&PDU.Payload); + ResParams =PResFunReadParams(pbyte(Answer)+ResHeaderSize23); + ResData =PResFunReadItem(pbyte(ResParams)+sizeof(TResFunReadParams)); + // Each packet cannot exceed the PDU length (in bytes) negotiated, and moreover + // we must ensure to transfer a "finite" number of item per PDU + MaxElements=(PDULength-sizeof(TS7ResHeader23)-sizeof(TResFunReadParams)-4) / WordSize; + TotElements=Job.Amount; + Start =Job.Start; + Offset =0; + Result =0; + while ((TotElements>0) && (Result==0)) + { + NumElements=TotElements; + if (NumElements>MaxElements) + NumElements=MaxElements; + + Target=pbyte(Job.pData)+Offset; + //----------------------------------------------- Read next slice----- + PDUH_out->P = 0x32; // Always 0x32 + PDUH_out->PDUType = PduType_request; // 0x01 + PDUH_out->AB_EX = 0x0000; // Always 0x0000 + PDUH_out->Sequence = GetNextWord(); // AutoInc + PDUH_out->ParLen = SwapWord(RPSize); // 14 bytes params + PDUH_out->DataLen = 0x0000; // No data + + ReqParams->FunRead = pduFuncRead; // 0x04 + ReqParams->ItemsCount = 1; + ReqParams->Items[0].ItemHead[0] = 0x12; + ReqParams->Items[0].ItemHead[1] = 0x0A; + ReqParams->Items[0].ItemHead[2] = 0x10; + ReqParams->Items[0].TransportSize = Job.WordLen; + ReqParams->Items[0].Length = SwapWord(NumElements); + ReqParams->Items[0].Area = Job.Area; + if (Job.Area==S7AreaDB) + ReqParams->Items[0].DBNumber = SwapWord(Job.Number); + else + ReqParams->Items[0].DBNumber = 0x0000; + // Adjusts the offset + if ((Job.WordLen==S7WLBit) || (Job.WordLen==S7WLCounter) || (Job.WordLen==S7WLTimer)) + Address = Start; + else + Address = Start*8; + + ReqParams->Items[0].Address[2] = Address & 0x000000FF; + Address = Address >> 8; + ReqParams->Items[0].Address[1] = Address & 0x000000FF; + Address = Address >> 8; + ReqParams->Items[0].Address[0] = Address & 0x000000FF; + + IsoSize = sizeof(TS7ReqHeader)+RPSize; + Result = isoExchangeBuffer(0,IsoSize); + // Get Data + if (Result==0) // 1St level Iso + { + Size = 0; + // Item level error + if (ResData->ReturnCode==0xFF) // <-- 0xFF means Result OK + { + // Calcs data size in bytes + Size = SwapWord(ResData->DataLength); + // Adjust Size in accord of TransportSize + if ((ResData->TransportSize != TS_ResOctet) && (ResData->TransportSize != TS_ResReal) && (ResData->TransportSize != TS_ResBit)) + Size = Size >> 3; + memcpy(Target, &ResData->Data[0], Size); + } + else + Result = CpuError(ResData->ReturnCode); + Offset+=Size; + }; + //-------------------------------------------------------------------- + TotElements -= NumElements; + Start += NumElements*WordSize; + } + return Result; +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::opWriteArea() +{ + PReqFunWriteParams ReqParams; + PReqFunWriteDataItem ReqData; // only 1 item for WriteArea Function + PResFunWrite ResParams; + PS7ResHeader23 Answer; + word RPSize; // ReqParams size + word RHSize; // Request headers size + bool First = true; + pbyte Source; + pbyte Target; + int Address; + int IsoSize; + int WordSize; + word Size; + uintptr_t Offset = 0; + int Start; // where we are starting from for this telegram + int MaxElements; // Max elements that we can transfer in a PDU + word NumElements; // Num of elements that we are asking for this telegram + int TotElements; // Total elements requested + int Result = 0; + + WordSize=DataSizeByte(Job.WordLen); // The size in bytes of an element that we are pushing + if (WordSize==0) + return errCliInvalidWordLen; + // First check : params bounds + if ((Job.Number<0) || (Job.Number>65535) || (Job.Start<0) || (Job.Amount<1)) + return errCliInvalidParams; + // Second check : transport size + if ((Job.WordLen==S7WLBit) && (Job.Amount>1)) + return errCliInvalidTransportSize; + + RHSize =sizeof(TS7ReqHeader)+ // Request header + 2+ // FunWrite+ItemCount (of TReqFunWriteParams) + sizeof(TReqFunWriteItem)+// 1 item reference + 4; // ReturnCode+TransportSize+DataLength + RPSize =sizeof(TReqFunWriteItem)+2; + + // Setup pointers (note : PDUH_out and PDU.Payload are the same pointer) + ReqParams=PReqFunWriteParams(pbyte(PDUH_out)+sizeof(TS7ReqHeader)); + ReqData =PReqFunWriteDataItem(pbyte(ReqParams)+sizeof(TReqFunWriteItem)+2); // 2 = FunWrite+ItemsCount + Target =pbyte(ReqData)+4; // 4 = ReturnCode+TransportSize+DataLength + Answer =PS7ResHeader23(&PDU.Payload); + ResParams=PResFunWrite(pbyte(Answer)+ResHeaderSize23); + + // Each packet cannot exceed the PDU length (in bytes) negotiated, and moreover + // we must ensure to transfer a "finite" number of item per PDU + MaxElements=(PDULength-RHSize) / WordSize; + TotElements=Job.Amount; + Start =Job.Start; + while ((TotElements>0) && (Result==0)) + { + NumElements=TotElements; + if (NumElements>MaxElements) + NumElements=MaxElements; + Source=pbyte(Job.pData)+Offset; + + Size=NumElements * WordSize; + PDUH_out->P=0x32; // Always 0x32 + PDUH_out->PDUType=PduType_request; // 0x01 + PDUH_out->AB_EX=0x0000; // Always 0x0000 + PDUH_out->Sequence=GetNextWord(); // AutoInc + PDUH_out->ParLen =SwapWord(RPSize); // 14 bytes params + PDUH_out->DataLen =SwapWord(Size+4); + + ReqParams->FunWrite=pduFuncWrite; // 0x05 + ReqParams->ItemsCount=1; + ReqParams->Items[0].ItemHead[0]=0x12; + ReqParams->Items[0].ItemHead[1]=0x0A; + ReqParams->Items[0].ItemHead[2]=0x10; + ReqParams->Items[0].TransportSize=Job.WordLen; + ReqParams->Items[0].Length=SwapWord(NumElements); + ReqParams->Items[0].Area=Job.Area; + if (Job.Area==S7AreaDB) + ReqParams->Items[0].DBNumber=SwapWord(Job.Number); + else + ReqParams->Items[0].DBNumber=0x0000; + + // Adjusts the offset + if ((Job.WordLen==S7WLBit) || (Job.WordLen==S7WLCounter) || (Job.WordLen==S7WLTimer)) + Address=Start; + else + Address=Start*8; + + ReqParams->Items[0].Address[2]=Address & 0x000000FF; + Address=Address >> 8; + ReqParams->Items[0].Address[1]=Address & 0x000000FF; + Address=Address >> 8; + ReqParams->Items[0].Address[0]=Address & 0x000000FF; + + ReqData->ReturnCode=0x00; + + switch(Job.WordLen) + { + case S7WLBit: + ReqData->TransportSize=TS_ResBit; + break; + case S7WLInt: + case S7WLDInt: + ReqData->TransportSize=TS_ResInt; + break; + case S7WLReal: + ReqData->TransportSize=TS_ResReal; + break; + case S7WLChar : + case S7WLCounter: + case S7WLTimer: + ReqData->TransportSize=TS_ResOctet; + break; + default: + ReqData->TransportSize=TS_ResByte; + break; + }; + + if ((ReqData->TransportSize!=TS_ResOctet) && (ReqData->TransportSize!=TS_ResReal) && (ReqData->TransportSize!=TS_ResBit)) + ReqData->DataLength=SwapWord(Size*8); + else + ReqData->DataLength=SwapWord(Size); + + memcpy(Target, Source, Size); + IsoSize=RHSize + Size; + Result=isoExchangeBuffer(0,IsoSize); + + if (Result==0) // 1St check : Iso result + { + Result=CpuError(SwapWord(Answer->Error)); // 2nd level global error + if (Result==0) + { // 2th check : item error + if (ResParams->Data[0] == 0xFF) // <-- 0xFF means Result OK + Result=0; + else + // Now we check the error : if it's the first part we report the cpu error + // otherwise we warn that the function failed but some data were written + if (First) + Result=CpuError(ResParams->Data[0]); + else + Result=errCliPartialDataWritten; + }; + Offset+=Size; + }; + First=false; + + TotElements-=NumElements; + Start+=(NumElements*WordSize); + } + return Result; +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::opReadMultiVars() +{ + PS7DataItem Item; + PReqFunReadParams ReqParams; + PS7ResHeader23 Answer; + PResFunReadParams ResParams; + TResFunReadData ResData; + + word RPSize; // ReqParams size + uintptr_t Offset =0 ; + word Slice; + longword Address; + int IsoSize; + pbyte P; + int ItemsCount, c, Result; + + Item = PS7DataItem(Job.pData); + ItemsCount = Job.Amount; + + // Some useful initial check to detail the errors (Since S7 CPU always answers + // with $05 if (something is wrong in params) + if (ItemsCount>MaxVars) + return errCliTooManyItems; + + // Adjusts Word Length in case of timers and counters and clears results + for (c = 0; c < ItemsCount; c++) + { + Item->Result=0; + if (Item->Area==S7AreaCT) + Item->WordLen=S7WLCounter; + if (Item->Area==S7AreaTM) + Item->WordLen=S7WLTimer; + Item++; + }; + + // Let's build the PDU + RPSize = word(2 + ItemsCount * sizeof(TReqFunReadItem)); + ReqParams = PReqFunReadParams(pbyte(PDUH_out)+sizeof(TS7ReqHeader)); + Answer = PS7ResHeader23(&PDU.Payload); + ResParams = PResFunReadParams(pbyte(Answer)+ResHeaderSize23); + // Fill Header + PDUH_out->P=0x32; // Always 0x32 + PDUH_out->PDUType=PduType_request; // 0x01 + PDUH_out->AB_EX=0x0000; // Always 0x0000 + PDUH_out->Sequence=GetNextWord(); // AutoInc + PDUH_out->ParLen=SwapWord(RPSize); // Request params size + PDUH_out->DataLen=0x0000; // No data in output + + // Fill Params + ReqParams->FunRead=pduFuncRead; // 0x04 + ReqParams->ItemsCount=ItemsCount; + + Item = PS7DataItem(Job.pData); + for (c = 0; c < ItemsCount; c++) + { + ReqParams->Items[c].ItemHead[0]=0x12; + ReqParams->Items[c].ItemHead[1]=0x0A; + ReqParams->Items[c].ItemHead[2]=0x10; + + ReqParams->Items[c].TransportSize=Item->WordLen; + ReqParams->Items[c].Length=SwapWord(Item->Amount); + ReqParams->Items[c].Area=Item->Area; + // Automatically drops DBNumber if (Area is not DB + if (Item->Area==S7AreaDB) + ReqParams->Items[c].DBNumber=SwapWord(Item->DBNumber); + else + ReqParams->Items[c].DBNumber=0x0000; + // Adjusts the offset + if ((Item->WordLen==S7WLBit) || (Item->WordLen==S7WLCounter) || (Item->WordLen==S7WLTimer)) + Address=Item->Start; + else + Address=Item->Start*8; + // Builds the offset + ReqParams->Items[c].Address[2]=Address & 0x000000FF; + Address=Address >> 8; + ReqParams->Items[c].Address[1]=Address & 0x000000FF; + Address=Address >> 8; + ReqParams->Items[c].Address[0]=Address & 0x000000FF; + Item++; + }; + + IsoSize=RPSize+sizeof(TS7ReqHeader); + if (IsoSize>PDULength) + return errCliSizeOverPDU; + Result=isoExchangeBuffer(0,IsoSize); + + if (Result!=0) + return Result; + + // Function level error + if (Answer->Error!=0) + return CpuError(SwapWord(Answer->Error)); + + if (ResParams->ItemCount!=ItemsCount) + return errCliInvalidPlcAnswer; + + P=pbyte(ResParams)+sizeof(TResFunReadParams); + Item = PS7DataItem(Job.pData); + for (c = 0; c < ItemsCount; c++) + { + ResData[c] =PResFunReadItem(pbyte(P)+Offset); + Slice=0; + // Item level error + if (ResData[c]->ReturnCode==0xFF) // <-- 0xFF means Result OK + { + // Calcs data size in bytes + Slice=SwapWord(ResData[c]->DataLength); + // Adjust Size in accord of TransportSize + if ((ResData[c]->TransportSize != TS_ResOctet) && (ResData[c]->TransportSize != TS_ResReal) && (ResData[c]->TransportSize != TS_ResBit)) + Slice=Slice >> 3; + + memcpy(Item->pdata, ResData[c]->Data, Slice); + Item->Result=0; + } + else + Item->Result=CpuError(ResData[c]->ReturnCode); + + if ((Slice % 2)!=0) + Slice++; // Skip fill byte for Odd frame + + Offset+=(4+Slice); + Item++; + }; + return Result; +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::opWriteMultiVars() +{ + PS7DataItem Item; + PReqFunWriteParams ReqParams; + PResFunWrite ResParams; + TReqFunWriteData ReqData; + PS7ResHeader23 Answer; + pbyte P; + uintptr_t Offset; + longword Address; + int ItemsCount, c, IsoSize; + word RPSize; // ReqParams size + word Size; // Write data size + int WordSize, Result; + + Item = PS7DataItem(Job.pData); + ItemsCount = Job.Amount; + + // Some useful initial check to detail the errors (Since S7 CPU always answers + // with $05 if (something is wrong in params) + if (ItemsCount>MaxVars) + return errCliTooManyItems; + + // Adjusts Word Length in case of timers and counters and clears results + for (c = 0; c < ItemsCount; c++) + { + Item->Result=0; + if (Item->Area==S7AreaCT) + Item->WordLen=S7WLCounter; + if (Item->Area==S7AreaTM) + Item->WordLen=S7WLTimer; + Item++; + }; + + // Let's build the PDU : setup pointers + RPSize = word(2 + ItemsCount * sizeof(TReqFunWriteItem)); + ReqParams = PReqFunWriteParams(pbyte(PDUH_out)+sizeof(TS7ReqHeader)); + Answer = PS7ResHeader23(&PDU.Payload); + ResParams = PResFunWrite(pbyte(Answer)+ResHeaderSize23); + P=pbyte(ReqParams)+RPSize; + + // Fill Header + PDUH_out->P=0x32; // Always 0x32 + PDUH_out->PDUType=PduType_request; // 0x01 + PDUH_out->AB_EX=0x0000; // Always 0x0000 + PDUH_out->Sequence=GetNextWord(); // AutoInc + PDUH_out->ParLen=SwapWord(RPSize); // Request params size + + // Fill Params + ReqParams->FunWrite=pduFuncWrite; // 0x05 + ReqParams->ItemsCount=ItemsCount; + + Offset=0; + Item = PS7DataItem(Job.pData); + for (c = 0; c < ItemsCount; c++) + { + // Items Params + ReqParams->Items[c].ItemHead[0]=0x12; + ReqParams->Items[c].ItemHead[1]=0x0A; + ReqParams->Items[c].ItemHead[2]=0x10; + + ReqParams->Items[c].TransportSize=Item->WordLen; + ReqParams->Items[c].Length=SwapWord(Item->Amount); + ReqParams->Items[c].Area=Item->Area; + + if (Item->Area==S7AreaDB) + ReqParams->Items[c].DBNumber=SwapWord(Item->DBNumber); + else + ReqParams->Items[c].DBNumber=0x0000; + + // Adjusts the offset + if ((Item->WordLen==S7WLBit) || (Item->WordLen==S7WLCounter) || (Item->WordLen==S7WLTimer)) + Address=Item->Start; + else + Address=Item->Start*8; + // Builds the offset + ReqParams->Items[c].Address[2]=Address & 0x000000FF; + Address=Address >> 8; + ReqParams->Items[c].Address[1]=Address & 0x000000FF; + Address=Address >> 8; + ReqParams->Items[c].Address[0]=Address & 0x000000FF; + + // Items Data + ReqData[c]=PReqFunWriteDataItem(pbyte(P)+Offset); + ReqData[c]->ReturnCode=0x00; + + switch (Item->WordLen) + { + case S7WLBit : + ReqData[c]->TransportSize=TS_ResBit; + break; + case S7WLInt : + case S7WLDInt : + ReqData[c]->TransportSize=TS_ResInt; + break; + case S7WLReal : + ReqData[c]->TransportSize=TS_ResReal; + break; + case S7WLChar : + case S7WLCounter : + case S7WLTimer : ReqData[c]->TransportSize=TS_ResOctet; + break; + default : + ReqData[c]->TransportSize=TS_ResByte; // byte/word/dword etc. + break; + }; + + WordSize=DataSizeByte(Item->WordLen); + Size=Item->Amount * WordSize; + + if ((ReqData[c]->TransportSize!=TS_ResOctet) && (ReqData[c]->TransportSize!=TS_ResReal) && (ReqData[c]->TransportSize!=TS_ResBit)) + ReqData[c]->DataLength=SwapWord(Size*8); + else + ReqData[c]->DataLength=SwapWord(Size); + + memcpy(ReqData[c]->Data, Item->pdata, Size); + + if ((Size % 2) != 0 && (ItemsCount - c != 1)) + Size++; // Skip fill byte for Odd frame (except for the last one) + + Offset+=(4+Size); // next item + Item++; + }; + + PDUH_out->DataLen=SwapWord(word(Offset)); + + IsoSize=RPSize+sizeof(TS7ReqHeader)+int(Offset); + if (IsoSize>PDULength) + return errCliSizeOverPDU; + Result=isoExchangeBuffer(0,IsoSize); + + if (Result!=0) + return Result; + + // Function level error + if (Answer->Error!=0) + return CpuError(SwapWord(Answer->Error)); + + if (ResParams->ItemCount!=ItemsCount) + return errCliInvalidPlcAnswer; + + Item = PS7DataItem(Job.pData); + for (c = 0; c < ItemsCount; c++) + { + // Item level error + if (ResParams->Data[c]==0xFF) // <-- 0xFF means Result OK + Item->Result=0; + else + Item->Result=CpuError(ResParams->Data[c]); + Item++; + }; + return Result; +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::opListBlocks() +{ + PReqFunGetBlockInfo ReqParams; + PReqDataFunBlocks ReqData; + PResFunGetBlockInfo ResParams; + PDataFunListAll ResData; + PS7ResHeader17 Answer; + PS7BlocksList List; + int IsoSize, Result; + + // Setup pointers (note : PDUH_out and PDU.Payload are the same pointer) + ReqParams=PReqFunGetBlockInfo(pbyte(PDUH_out)+sizeof(TS7ReqHeader)); + ReqData =PReqDataFunBlocks(pbyte(ReqParams)+sizeof(TReqFunGetBlockInfo)); + Answer =PS7ResHeader17(&PDU.Payload); + ResParams=PResFunGetBlockInfo(pbyte(Answer)+ResHeaderSize17); + ResData =PDataFunListAll(pbyte(ResParams)+sizeof(TResFunGetBlockInfo)); + List =PS7BlocksList(Job.pData); + // Fill Header + PDUH_out->P=0x32; // Always 0x32 + PDUH_out->PDUType=PduType_userdata; // 0x07 + PDUH_out->AB_EX=0x0000; // Always 0x0000 + PDUH_out->Sequence=GetNextWord(); // AutoInc + PDUH_out->ParLen=SwapWord(sizeof(TReqFunGetBlockInfo)); // 8 bytes params + PDUH_out->DataLen=SwapWord(sizeof(TReqDataFunBlocks)); // 4 bytes data + // Fill params (mostly constants) + ReqParams->Head[0]=0x00; + ReqParams->Head[1]=0x01; + ReqParams->Head[2]=0x12; + ReqParams->Plen =0x04; + ReqParams->Uk =0x11; + ReqParams->Tg =grBlocksInfo; + ReqParams->SubFun =SFun_ListAll; + ReqParams->Seq =0x00; + // Fill data + ReqData[0] =0x0A; + ReqData[1] =0x00; + ReqData[2] =0x00; + ReqData[3] =0x00; + + IsoSize=sizeof(TS7ReqHeader)+sizeof(TReqFunGetBlockInfo)+sizeof(TReqDataFunBlocks); + Result=isoExchangeBuffer(0,IsoSize); + // Get Data + if (Result==0) + { + if (ResParams->ErrNo==0) + { + if (SwapWord(ResData->Length)!=28) + return errCliInvalidPlcAnswer; + + for (int c = 0; c < 7; c++) + { + switch (ResData->Blocks[c].BType) + { + case Block_OB: + List->OBCount=SwapWord(ResData->Blocks[c].BCount); + break; + case Block_DB: + List->DBCount=SwapWord(ResData->Blocks[c].BCount); + break; + case Block_SDB: + List->SDBCount=SwapWord(ResData->Blocks[c].BCount); + break; + case Block_FC: + List->FCCount=SwapWord(ResData->Blocks[c].BCount); + break; + case Block_SFC: + List->SFCCount=SwapWord(ResData->Blocks[c].BCount); + break; + case Block_FB: + List->FBCount=SwapWord(ResData->Blocks[c].BCount); + break; + case Block_SFB: + List->SFBCount=SwapWord(ResData->Blocks[c].BCount); + break; + } + } + } + else + Result=CpuError(SwapWord(ResParams->ErrNo)); + } + return Result; +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::opListBlocksOfType() +{ + PReqFunGetBlockInfo ReqParams; + PReqDataBlockOfType ReqData; + + PS7ResHeader17 Answer; + PResFunGetBlockInfo ResParams; + PDataFunGetBot ResData; + longword *PadData; + word *List; + bool First; + bool Done = false; + byte BlockType, In_Seq; + int Count, Last, IsoSize, Result; + int c, CThis; + word DataLength; + bool RoomError = false; + + BlockType=Job.Area; + List=(word*)(&opData); + // Setup pointers (note : PDUH_out and PDU.Payload are the same pointer) + ReqParams=PReqFunGetBlockInfo(pbyte(PDUH_out)+sizeof(TS7ReqHeader)); + Answer =PS7ResHeader17(&PDU.Payload); + ResParams=PResFunGetBlockInfo(pbyte(Answer)+ResHeaderSize17); + ResData =PDataFunGetBot(pbyte(ResParams)+sizeof(TResFunGetBlockInfo)); + // Get Data + First =true; + In_Seq=0x00; // first group sequence, next will come from PLC + Count =0; + Last =0; + do + { + //<--------------------------------------------------------- Get next slice + // Fill Header + PDUH_out->P=0x32; // Always 0x32 + PDUH_out->PDUType=PduType_userdata; // 0x07 + PDUH_out->AB_EX=0x0000; // Always 0x0000 + PDUH_out->Sequence=GetNextWord(); // AutoInc + if (First) + { + PDUH_out->ParLen=SwapWord(8); // 8 bytes params + PDUH_out->DataLen=SwapWord(6); // 6 bytes data + DataLength=14; + } + else + { + PDUH_out->ParLen=SwapWord(12); // 12 bytes params + PDUH_out->DataLen=SwapWord(4); // 4 bytes data + DataLength=16; + } + + // Fill params (mostly constants) + ReqParams->Head[0]=0x00; + ReqParams->Head[1]=0x01; + ReqParams->Head[2]=0x12; + + if (First) + ReqParams->Plen =0x04; + else + ReqParams->Plen =0x08; + + if (First) + ReqParams->Uk = 0x11; + else + ReqParams->Uk = 0x12; + + ReqParams->Tg =grBlocksInfo; + ReqParams->SubFun =SFun_ListBoT; + ReqParams->Seq =In_Seq; + + // Fill data + if (First) + { + // overlap resvd and error to avoid another struct... + ReqData =PReqDataBlockOfType(pbyte(ReqParams)+sizeof(TReqFunGetBlockInfo)); + ReqData->RetVal =0xFF; + ReqData->TSize =TS_ResOctet; + ReqData->Length =SwapWord(0x0002); + ReqData->Zero =0x30; // zero ascii '0' + ReqData->BlkType =BlockType; + } + else + { + PadData =(longword*)(pbyte(ReqParams)+sizeof(TReqFunGetBlockInfo)); + ReqData =PReqDataBlockOfType(pbyte(ReqParams)+sizeof(TReqFunGetBlockInfo)+4); + *PadData =0x00000000; + ReqData->RetVal =0x0A; + ReqData->TSize =0x00; + ReqData->Length =0x0000; + ReqData->Zero =0x00; + ReqData->BlkType =0x00; + }; + + IsoSize=sizeof(TS7ReqHeader)+DataLength; + Result=isoExchangeBuffer(0,IsoSize); + + if (Result==0) + { + if (ResParams->ErrNo==0) + { + if (ResData->RetVal==0xFF) + { + Done=((ResParams->Rsvd & 0xFF00) == 0); // Low order byte = 0x00 => the sequence is done + In_Seq=ResParams->Seq; // every next telegram must have this number + CThis=((SwapWord(ResData->DataLen) - 4 ) / 4) + 1; // Partial counter + for (c=0; c < CThis+1; c++) + { + *List=SwapWord(ResData->Items[c].BlockNum); + Last++; + List++; + if (Last==0x8000) + { + Done=true; + break; + }; + }; + Count+=CThis; // Total counter + List--; + } + else + Result=errCliItemNotAvailable; + } + else + Result=errCliItemNotAvailable; + }; + First=false; + //---------------------------------------------------------> Get next slice + } + while ((!Done) && (Result==0)); + + *Job.pAmount=0; + if (Result==0) + { + if (Count>Job.Amount) + { + Count=Job.Amount; + RoomError=true; + } + memcpy(Job.pData, &opData, Count*2); + *Job.pAmount=Count; + + if (RoomError) // Result==0 -> override if romerror + Result=errCliPartialDataRead; + }; + + return Result; +} +//--------------------------------------------------------------------------- +void TSnap7MicroClient::FillTime(word SiemensTime, char *PTime) +{ + // SiemensTime -> number of seconds after 1/1/1984 + // This is not S7 date and time but is used only internally for block info + time_t TheDate = (SiemensTime * 86400)+ DeltaSecs; + struct tm * timeinfo = localtime (&TheDate); + if (timeinfo!=NULL) { + strftime(PTime,11,"%Y/%m/%d",timeinfo); + } + else + *PTime='\0'; +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::opAgBlockInfo() +{ + PS7BlockInfo BlockInfo; + PReqFunGetBlockInfo ReqParams; + PReqDataBlockInfo ReqData; + PS7ResHeader17 Answer; + PResFunGetBlockInfo ResParams; + PResDataBlockInfo ResData; + byte BlockType; + int BlockNum, IsoSize, Result; + + BlockType=Job.Area; + BlockNum =Job.Number; + BlockInfo=PS7BlockInfo(Job.pData); + memset(BlockInfo,0,sizeof(TS7BlockInfo)); + // Setup pointers (note : PDUH_out and PDU.Payload are the same pointer) + ReqParams=PReqFunGetBlockInfo(pbyte(PDUH_out)+sizeof(TS7ReqHeader)); + ReqData =PReqDataBlockInfo(pbyte(ReqParams)+sizeof(TReqFunGetBlockInfo)); + Answer =PS7ResHeader17(&PDU.Payload); + ResParams=PResFunGetBlockInfo(pbyte(Answer)+ResHeaderSize17); + ResData =PResDataBlockInfo(pbyte(ResParams)+sizeof(TResFunGetBlockInfo)); + // Fill Header + PDUH_out->P=0x32; // Always 0x32 + PDUH_out->PDUType=PduType_userdata; // 0x07 + PDUH_out->AB_EX=0x0000; // Always 0x0000 + PDUH_out->Sequence=GetNextWord(); // AutoInc + PDUH_out->ParLen=SwapWord(sizeof(TReqFunGetBlockInfo)); // 8 bytes params + PDUH_out->DataLen=SwapWord(sizeof(TReqDataBlockInfo)); // 4 bytes data + // Fill params (mostly constants) + ReqParams->Head[0]=0x00; + ReqParams->Head[1]=0x01; + ReqParams->Head[2]=0x12; + ReqParams->Plen =0x04; + ReqParams->Uk =0x11; + ReqParams->Tg =grBlocksInfo; + ReqParams->SubFun =SFun_BlkInfo; + ReqParams->Seq =0x00; + // Fill data + ReqData->RetVal =0xFF; + ReqData->TSize =TS_ResOctet; + ReqData->DataLen =SwapWord(0x0008); + ReqData->BlkPrfx =0x30; + ReqData->BlkType =BlockType; + ReqData->A =0x41; + + ReqData->AsciiBlk[0]=(BlockNum / 10000)+0x30; + BlockNum=BlockNum % 10000; + ReqData->AsciiBlk[1]=(BlockNum / 1000)+0x30; + BlockNum=BlockNum % 1000; + ReqData->AsciiBlk[2]=(BlockNum / 100)+0x30; + BlockNum=BlockNum % 100; + ReqData->AsciiBlk[3]=(BlockNum / 10)+0x30; + BlockNum=BlockNum % 10; + ReqData->AsciiBlk[4]=(BlockNum / 1)+0x30; + + IsoSize=sizeof(TS7ReqHeader)+sizeof(TReqFunGetBlockInfo)+sizeof(TReqDataBlockInfo); + Result=isoExchangeBuffer(0,IsoSize); + + // Get Data + if (Result==0) + { + if (ResParams->ErrNo==0) + { + if (SwapWord(ResData->Length)<40) // 78 + return errCliInvalidPlcAnswer; + if (ResData->RetVal==0xFF) // <-- 0xFF means Result OK + { + //<----------------------------------------------Fill block info + BlockInfo->BlkType=ResData->SubBlkType; + BlockInfo->BlkNumber=SwapWord(ResData->BlkNumber); + BlockInfo->BlkLang=ResData->BlkLang; + BlockInfo->BlkFlags=ResData->BlkFlags; + BlockInfo->MC7Size=SwapWord(ResData->MC7Len); + BlockInfo->LoadSize=SwapDWord(ResData->LenLoadMem); + BlockInfo->LocalData=SwapWord(ResData->LocDataLen); + BlockInfo->SBBLength=SwapWord(ResData->SbbLen); + BlockInfo->CheckSum=SwapWord(ResData->BlkChksum); + BlockInfo->Version=ResData->Version; + memcpy(BlockInfo->Author, ResData->Author, 8); + memcpy(BlockInfo->Family,ResData->Family,8); + memcpy(BlockInfo->Header,ResData->Header,8); + FillTime(SwapWord(ResData->CodeTime_dy),BlockInfo->CodeDate); + FillTime(SwapWord(ResData->IntfTime_dy),BlockInfo->IntfDate); + //---------------------------------------------->Fill block info + } + else + Result=CpuError(ResData->RetVal); + } + else + Result=CpuError(SwapWord(ResParams->ErrNo)); + }; + return Result; +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::opDBGet() +{ + TS7BlockInfo BI; + void * usrPData; + int * usrPSize; + int Result, Room; + bool RoomError = false; + + // Stores user pointer + usrPData=Job.pData; + usrPSize=Job.pAmount; + Room =Job.Amount; + + // 1 Pass : Get block info + Job.Area=Block_DB; + Job.pData=&BI; + Result=opAgBlockInfo(); + + // 2 Pass : Read the whole (MC7Size bytes) DB. + if (Result==0) + { + // Check user space + if (BI.MC7Size>Room) + { + Job.Amount=Room; + RoomError=true; + } + else + Job.Amount =BI.MC7Size; + // The data is read even if the buffer is small (the error is reported). + // Imagine that we want to read only a small amount of data at the + // beginning of a DB regardless it's size.... + Job.Area =S7AreaDB; + Job.WordLen=S7WLByte; + Job.Start =0; + Job.pData =usrPData; + Result =opReadArea(); + if (Result==0) + *usrPSize=Job.Amount; + } + + if ((Result==0) && RoomError) + return errCliBufferTooSmall; + else + return Result; +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::opDBFill() +{ + TS7BlockInfo BI; + int Result; + // new op : get block info + Job.Op =s7opAgBlockInfo; + Job.Area =Block_DB; + Job.pData=&BI; + Result =opAgBlockInfo(); + // Restore original op + Job.Op =s7opDBFill; + // Fill internal buffer then write it + if (Result==0) + { + Job.Amount =BI.MC7Size; + Job.Area =S7AreaDB; + Job.WordLen=S7WLByte; + Job.Start =0; + memset(&opData, byte(Job.IParam), Job.Amount); + Job.pData =&opData; + Result =opWriteArea(); + } + return Result; +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::opUpload() +{ + PS7ResHeader23 Answer; + int IsoSize; + byte Upload_ID = 0; // not strictly needed, only to avoid warning + byte BlockType; + int BlockNum, BlockLength, Result; + bool Done, Full; // if full==true, the data will be compatible to full download function + uintptr_t Offset; + bool RoomError = false; + + BlockType=Job.Area; + BlockNum =Job.Number; + Full =Job.IParam==1; + // Setup Answer (is the same for all Upload pdus) + Answer= PS7ResHeader23(&PDU.Payload); + // Init sequence + Done =false; + Offset=0; + //<-------------------------------------------------------------StartUpload + PReqFunStartUploadParams ReqParams; + PResFunStartUploadParams ResParams; + // Setup pointers (note : PDUH_out and PDU.Payload are the same pointer) + ReqParams=PReqFunStartUploadParams(pbyte(PDUH_out)+sizeof(TS7ReqHeader)); + ResParams=PResFunStartUploadParams(pbyte(Answer)+ResHeaderSize23); + // Init Header + PDUH_out->P=0x32; // Always 0x32 + PDUH_out->PDUType=PduType_request; // 0x01 + PDUH_out->AB_EX=0x0000; // Always 0x0000 + PDUH_out->Sequence=GetNextWord(); // AutoInc + PDUH_out->ParLen=SwapWord(sizeof(TReqFunStartUploadParams));// params size + PDUH_out->DataLen=0x0000; // No data + // Init Params + ReqParams->FunSUpld=pduStartUpload; + ReqParams->Uk6[0]=0x00; + ReqParams->Uk6[1]=0x00; + ReqParams->Uk6[2]=0x00; + ReqParams->Uk6[3]=0x00; + ReqParams->Uk6[4]=0x00; + ReqParams->Uk6[5]=0x00; + ReqParams->Upload_ID=Upload_ID; // At begining is 0) we will put upload id incoming from plc + ReqParams->Len_1 =0x09; // 9 bytes from here + ReqParams->Prefix=0x5F; + ReqParams->BlkPrfx=0x30; // '0' + ReqParams->BlkType=BlockType; + // Block number + ReqParams->AsciiBlk[0]=(BlockNum / 10000)+0x30; + BlockNum=BlockNum % 10000; + ReqParams->AsciiBlk[1]=(BlockNum / 1000)+0x30; + BlockNum=BlockNum % 1000; + ReqParams->AsciiBlk[2]=(BlockNum / 100)+0x30; + BlockNum=BlockNum % 100; + ReqParams->AsciiBlk[3]=(BlockNum / 10)+0x30; + BlockNum=BlockNum % 10; + ReqParams->AsciiBlk[4]=(BlockNum / 1)+0x30; + ReqParams->A=0x41; // 'A' + + IsoSize=sizeof(TS7ReqHeader)+sizeof(TReqFunStartUploadParams); + Result=isoExchangeBuffer(0,IsoSize); + // Get Upload Infos (only ID now) + if (Result==0) + { + if (Answer->Error==0) + Upload_ID=ResParams->Upload_ID; + else + Result=CpuError(SwapWord(Answer->Error)); + }; + //------------------------------------------------------------->StartUpload + if (Result==0) + { + //<--------------------------------------------------------FirstUpload + PReqFunUploadParams ReqParams; + PResFunUploadParams ResParams; + PResFunUploadDataHeaderFirst ResDataHeader; + pbyte Source; + pbyte Target; + int Size; + + // Setup pointers (note : PDUH_out and PDU.Payload are the same pointer) + ReqParams=PReqFunUploadParams(pbyte(PDUH_out)+sizeof(TS7ReqHeader)); + // First upload pdu consists of params, block info header, data. + ResParams=PResFunUploadParams(pbyte(Answer)+ResHeaderSize23); + ResDataHeader=PResFunUploadDataHeaderFirst(pbyte(ResParams)+sizeof(TResFunUploadParams)); + if (Full) + Source=pbyte(ResDataHeader)+4; // skip only the mini header + else + Source=pbyte(ResDataHeader)+sizeof(TResFunUploadDataHeaderFirst); // not full : skip the data header + // Init Header + PDUH_out->P=0x32; // Always 0x32 + PDUH_out->PDUType=PduType_request; // 0x01 + PDUH_out->AB_EX=0x0000; // Always 0x0000 + PDUH_out->Sequence=GetNextWord(); // AutoInc + PDUH_out->ParLen=SwapWord(sizeof(TReqFunUploadParams));// params size + PDUH_out->DataLen=0x0000; // No data + // Init Params + ReqParams->FunUpld=pduUpload; + ReqParams->Uk6[0]=0x00; + ReqParams->Uk6[1]=0x00; + ReqParams->Uk6[2]=0x00; + ReqParams->Uk6[3]=0x00; + ReqParams->Uk6[4]=0x00; + ReqParams->Uk6[5]=0x00; + ReqParams->Upload_ID=Upload_ID; // At begining is 0) we will put upload id incoming from plc + + IsoSize=sizeof(TS7ReqHeader)+sizeof(TReqFunUploadParams); + Result=isoExchangeBuffer(0,IsoSize); + // Get Upload Infos (only ID now) + if (Result==0) + { + if (Answer->Error==0) + { + Done=ResParams->EoU==0; + if (Full) + Size=SwapWord(Answer->DataLen)-4; // Full data Size + else + Size=SwapWord(Answer->DataLen)-sizeof(TResFunUploadDataHeaderFirst); // Size of this data slice + + BlockLength=SwapWord(ResDataHeader->MC7Len); // Full block size in byte + Target=pbyte(&opData)+Offset; + memcpy(Target, Source, Size); + Offset+=Size; + } + else + Result=errCliUploadSequenceFailed; + }; + //-------------------------------------------------------->FirstUpload + while (!Done && (Result==0)) + { + //<----------------------------------------------------NextUpload + PReqFunUploadParams ReqParams; + PResFunUploadParams ResParams; + PResFunUploadDataHeaderNext ResDataHeader; + pbyte Source; + pbyte Target; + int Size; + + // Setup pointers (note : PDUH_out and PDU.Payload are the same pointer) + ReqParams=PReqFunUploadParams(pbyte(PDUH_out)+sizeof(TS7ReqHeader)); + // Next upload pdu consists of params, small info header, data. + ResParams=PResFunUploadParams(pbyte(Answer)+ResHeaderSize23); + ResDataHeader=PResFunUploadDataHeaderNext(pbyte(ResParams)+sizeof(TResFunUploadParams)); + Source=pbyte(ResDataHeader)+sizeof(TResFunUploadDataHeaderNext); + // Init Header + PDUH_out->P=0x32; // Always 0x32 + PDUH_out->PDUType=PduType_request; // 0x01 + PDUH_out->AB_EX=0x0000; // Always 0x0000 + PDUH_out->Sequence=GetNextWord(); // AutoInc + PDUH_out->ParLen=SwapWord(sizeof(TReqFunUploadParams));// params size + PDUH_out->DataLen=0x0000; // No data + // Init Params + ReqParams->FunUpld=pduUpload; + ReqParams->Uk6[0]=0x00; + ReqParams->Uk6[1]=0x00; + ReqParams->Uk6[2]=0x00; + ReqParams->Uk6[3]=0x00; + ReqParams->Uk6[4]=0x00; + ReqParams->Uk6[5]=0x00; + ReqParams->Upload_ID=Upload_ID; // At begining is 0) we will put upload id incoming from plc + + IsoSize=sizeof(TS7ReqHeader)+sizeof(TReqFunUploadParams); + Result=isoExchangeBuffer(0,IsoSize); + // Get Upload Infos (only ID now) + if (Result==0) + { + if (Answer->Error==0) + { + Done=ResParams->EoU==0; + Size=SwapWord(Answer->DataLen)-sizeof(TResFunUploadDataHeaderNext); // Size of this data slice + Target=pbyte(&opData)+Offset; + memcpy(Target, Source, Size); + Offset+=Size; + } + else + Result=errCliUploadSequenceFailed; + }; + //---------------------------------------------------->NextUpload + } + if (Result==0) + { + //<----------------------------------------------------EndUpload; + PReqFunEndUploadParams ReqParams; + PResFunEndUploadParams ResParams; + + // Setup pointers (note : PDUH_out and PDU.Payload are the same pointer) + ReqParams=PReqFunEndUploadParams(pbyte(PDUH_out)+sizeof(TS7ReqHeader)); + ResParams=PResFunEndUploadParams(pbyte(Answer)+ResHeaderSize23); + // Init Header + PDUH_out->P=0x32; // Always 0x32 + PDUH_out->PDUType=PduType_request; // 0x01 + PDUH_out->AB_EX=0x0000; // Always 0x0000 + PDUH_out->Sequence=GetNextWord(); // AutoInc + PDUH_out->ParLen=SwapWord(sizeof(TReqFunEndUploadParams));// params size + PDUH_out->DataLen=0x0000; // No data + // Init Params + ReqParams->FunEUpld=pduEndUpload; + ReqParams->Uk6[0]=0x00; + ReqParams->Uk6[1]=0x00; + ReqParams->Uk6[2]=0x00; + ReqParams->Uk6[3]=0x00; + ReqParams->Uk6[4]=0x00; + ReqParams->Uk6[5]=0x00; + ReqParams->Upload_ID=Upload_ID; // At begining is 0) we will put upload id incoming from plc + + IsoSize=sizeof(TS7ReqHeader)+sizeof(TReqFunEndUploadParams); + Result=isoExchangeBuffer(0,IsoSize); + + // Get EndUpload Result + if (Result==0) + { + if ((Answer->Error!=0) || (ResParams->FunEUpld!=pduEndUpload)) + Result=errCliUploadSequenceFailed; + }; + //---------------------------------------------------->EndUpload; + } + }; + + *Job.pAmount=0; + if (Result==0) + { + if (Full) + { + opSize=int(Offset); + if (opSize<78) + Result=errCliInvalidDataSizeRecvd; + } + else + { + opSize=BlockLength; + if (opSize<1) + Result=errCliInvalidDataSizeRecvd; + }; + if (Result==0) + { + // Checks user space + if (Job.Amount override if romerror + Result=errCliPartialDataRead; + }; + }; + return Result; +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::opDownload() +{ + PS7CompactBlockInfo Info; + PS7BlockFooter Footer; + int BlockNum, StoreBlockNum, BlockAmount; + int BlockSize, BlockSizeLd; + int BlockType, Remainder; + int Result, IsoSize; + bool Done = false; + uintptr_t Offset; + + BlockAmount=Job.Amount; + BlockNum =Job.Number; + Result=CheckBlock(-1,-1,&opData,BlockAmount); + if (Result==0) + { + Info=PS7CompactBlockInfo(&opData); + // Gets blocktype + BlockType=SubBlockToBlock(Info->SubBlkType); + + if (BlockNum>=0) + Info->BlkNum=SwapWord(BlockNum); // change the number + else + BlockNum=SwapWord(Info->BlkNum); // use the header's number + + BlockSizeLd=BlockAmount; // load mem needed for this block + BlockSize =SwapWord(Info->MC7Len); // net size + Footer=PS7BlockFooter(pbyte(&opData)+BlockSizeLd-sizeof(TS7BlockFooter)); + Footer->Chksum=0x0000; + + Offset=0; + Remainder=BlockAmount; + //<---------------------------------------------- Start Download request + PReqStartDownloadParams ReqParams; + PResStartDownloadParams ResParams; + PS7ResHeader23 Answer; + + // Setup pointers (note : PDUH_out and PDU.Payload are the same pointer) + ReqParams=PReqStartDownloadParams(pbyte(PDUH_out)+sizeof(TS7ReqHeader)); + Answer =PS7ResHeader23(&PDU.Payload); + ResParams=PResStartDownloadParams(pbyte(Answer)+ResHeaderSize23); + // Init Header + PDUH_out->P=0x32; // Always 0x32 + PDUH_out->PDUType=PduType_request; // 0x01 + PDUH_out->AB_EX=0x0000; // Always 0x0000 + PDUH_out->Sequence=GetNextWord(); // AutoInc + PDUH_out->ParLen=SwapWord(sizeof(TReqStartDownloadParams)); + PDUH_out->DataLen=0x0000; // No data + // Init Params + ReqParams->FunSDwnld = pduReqDownload; + ReqParams->Uk6[0]=0x00; + ReqParams->Uk6[1]=0x01; + ReqParams->Uk6[2]=0x00; + ReqParams->Uk6[3]=0x00; + ReqParams->Uk6[4]=0x00; + ReqParams->Uk6[5]=0x00; + ReqParams->Dwnld_ID=0x00; + ReqParams->Len_1 =0x09; + ReqParams->Prefix=0x5F; + ReqParams->BlkPrfx=0x30; + ReqParams->BlkType=BlockType; + StoreBlockNum=BlockNum; + ReqParams->AsciiBlk[0]=(BlockNum / 10000)+0x30; + BlockNum=BlockNum % 10000; + ReqParams->AsciiBlk[1]=(BlockNum / 1000)+0x30; + BlockNum=BlockNum % 1000; + ReqParams->AsciiBlk[2]=(BlockNum / 100)+0x30; + BlockNum=BlockNum % 100; + ReqParams->AsciiBlk[3]=(BlockNum / 10)+0x30; + BlockNum=BlockNum % 10; + ReqParams->AsciiBlk[4]=(BlockNum / 1)+0x30; + ReqParams->P =0x50; + ReqParams->Len_2=0x0D; + ReqParams->Uk1 =0x31; // '1' + BlockNum=StoreBlockNum; + // Load memory + ReqParams->AsciiLoad[0]=(BlockSizeLd / 100000)+0x30; + BlockSizeLd=BlockSizeLd % 100000; + ReqParams->AsciiLoad[1]=(BlockSizeLd / 10000)+0x30; + BlockSizeLd=BlockSizeLd % 10000; + ReqParams->AsciiLoad[2]=(BlockSizeLd / 1000)+0x30; + BlockSizeLd=BlockSizeLd % 1000; + ReqParams->AsciiLoad[3]=(BlockSizeLd / 100)+0x30; + BlockSizeLd=BlockSizeLd % 100; + ReqParams->AsciiLoad[4]=(BlockSizeLd / 10)+0x30; + BlockSizeLd=BlockSizeLd % 10; + ReqParams->AsciiLoad[5]=(BlockSizeLd / 1)+0x30; + // MC7 memory + ReqParams->AsciiMC7[0]=(BlockSize / 100000)+0x30; + BlockSize=BlockSize % 100000; + ReqParams->AsciiMC7[1]=(BlockSize / 10000)+0x30; + BlockSize=BlockSize % 10000; + ReqParams->AsciiMC7[2]=(BlockSize / 1000)+0x30; + BlockSize=BlockSize % 1000; + ReqParams->AsciiMC7[3]=(BlockSize / 100)+0x30; + BlockSize=BlockSize % 100; + ReqParams->AsciiMC7[4]=(BlockSize / 10)+0x30; + BlockSize=BlockSize % 10; + ReqParams->AsciiMC7[5]=(BlockSize / 1)+0x30; + + IsoSize=sizeof(TS7ReqHeader)+sizeof(TReqStartDownloadParams); + Result=isoExchangeBuffer(0,IsoSize); + + // Get Result + if (Result==0) + { + if (SwapWord(Answer->Error)!=Code7NeedPassword) + { + if ((Answer->Error!=0) || (*ResParams!=pduReqDownload)) + Result=errCliDownloadSequenceFailed; + } + else + Result=errCliNeedPassword; + } + //----------------------------------------------> Start Download request + if (Result==0) + { + do + { + //<-------------------------------- Download sequence (PLC requests) + PReqDownloadParams ReqParams; + PS7ResHeader23 Answer; + PResDownloadParams ResParams; + PResDownloadDataHeader ResData; + int Slice, Size, MaxSlice; + word Sequence; + pbyte Source; + pbyte Target; + + ReqParams=PReqDownloadParams(pbyte(PDUH_out)+sizeof(TS7ReqHeader)); + Answer =PS7ResHeader23(&PDU.Payload); + ResParams=PResDownloadParams(pbyte(Answer)+ResHeaderSize23); + ResData =PResDownloadDataHeader(pbyte(ResParams)+sizeof(TResDownloadParams)); + Target =pbyte(ResData)+sizeof(TResDownloadDataHeader); + Source =pbyte(&opData)+Offset; + + Result=isoRecvBuffer(0,Size); + if (Result==0) + { + if ((u_int(Size)>sizeof(TS7ReqHeader)) && (ReqParams->Fun==pduDownload)) + { + Sequence=PDUH_out->Sequence; + // Max data slice that we can fit in this pdu + MaxSlice=PDULength-ResHeaderSize23-sizeof(TResDownloadParams)-sizeof(TResDownloadDataHeader); + Slice=Remainder; + if (Slice>MaxSlice) + Slice=MaxSlice; + Remainder-=Slice; + Offset+=Slice; + Done=Remainder<=0; + // Init Answer + Answer->P=0x32; + Answer->PDUType=PduType_response; + Answer->AB_EX=0x0000; + Answer->Sequence=Sequence; + Answer->ParLen =SwapWord(sizeof(TResDownloadParams)); + Answer->DataLen=SwapWord(word(sizeof(TResDownloadDataHeader))+Slice); + Answer->Error =0x0000; + + // Init Params + ResParams->FunDwnld=pduDownload; + if (Remainder>0) + ResParams->EoS=0x01; + else + ResParams->EoS=0x00; + + // Init Data + ResData->DataLen=SwapWord(Slice); + ResData->FB_00=0xFB00; + memcpy(Target, Source, Slice); + + // Send the slice + IsoSize=ResHeaderSize23+sizeof(TResDownloadParams)+sizeof(TResDownloadDataHeader)+Slice; + Result=isoSendBuffer(0,IsoSize); + } + else + Result=errCliDownloadSequenceFailed; + }; + //--------------------------------> Download sequence (PLC requests) + } + while (!Done && (Result==0)); + + if (Result==0) + { + //<-------------------------------------------Perform Download Ended + PReqDownloadParams ReqParams; + PS7ResHeader23 Answer; + PResEndDownloadParams ResParams; + int Size; + word Sequence; + + ReqParams=PReqDownloadParams(pbyte(PDUH_out)+sizeof(TS7ReqHeader)); + Answer =PS7ResHeader23(&PDU.Payload); + ResParams=PResEndDownloadParams(pbyte(Answer)+ResHeaderSize23); + + Result=isoRecvBuffer(0,Size); + if (Result==0) + { + if ((u_int(Size)>sizeof(TS7ReqHeader)) && (ReqParams->Fun==pduDownloadEnded)) + { + Sequence=PDUH_out->Sequence; + // Init Answer + Answer->P=0x32; + Answer->PDUType=PduType_response; + Answer->AB_EX=0x0000; + Answer->Sequence=Sequence; + Answer->ParLen =SwapWord(sizeof(TResEndDownloadParams)); + Answer->DataLen=0x0000; + Answer->Error =0x0000; + + // Init Params + *ResParams=pduDownloadEnded; + IsoSize=ResHeaderSize23+sizeof(TResEndDownloadParams); + Result=isoSendBuffer(0,IsoSize); + } + else + Result=errCliDownloadSequenceFailed; + }; + //------------------------------------------->Perform Download Ended + if (Result==0) + { + //<----------------------------------- Insert block into the unit + PReqControlBlockParams ReqParams; + PS7ResHeader23 Answer; + pbyte ResParams; + + // Setup pointers (note : PDUH_out and PDU.Payload are the same pointer) + ReqParams=PReqControlBlockParams(pbyte(PDUH_out)+sizeof(TS7ReqHeader)); + Answer=PS7ResHeader23(&PDU.Payload); + ResParams=pbyte(Answer)+ResHeaderSize23; + // Init Header + PDUH_out->P=0x32; // Always 0x32 + PDUH_out->PDUType=PduType_request; // 0x01 + PDUH_out->AB_EX=0x0000; // Always 0x0000 + PDUH_out->Sequence=GetNextWord(); // AutoInc + PDUH_out->ParLen=SwapWord(sizeof(TReqControlBlockParams)); + PDUH_out->DataLen=0x0000; // No data + // Init Params + ReqParams->Fun = pduControl; + ReqParams->Uk7[0]=0x00; + ReqParams->Uk7[1]=0x00; + ReqParams->Uk7[2]=0x00; + ReqParams->Uk7[3]=0x00; + ReqParams->Uk7[4]=0x00; + ReqParams->Uk7[5]=0x00; + ReqParams->Uk7[6]=0xFD; + ReqParams->Len_1 =SwapWord(0x0A); + ReqParams->NumOfBlocks=0x01; + ReqParams->ByteZero =0x00; + ReqParams->AsciiZero =0x30; + ReqParams->BlkType=BlockType; + ReqParams->AsciiBlk[0]=(BlockNum / 10000)+0x30; + BlockNum=BlockNum % 10000; + ReqParams->AsciiBlk[1]=(BlockNum / 1000)+0x30; + BlockNum=BlockNum % 1000; + ReqParams->AsciiBlk[2]=(BlockNum / 100)+0x30; + BlockNum=BlockNum % 100; + ReqParams->AsciiBlk[3]=(BlockNum / 10)+0x30; + BlockNum=BlockNum % 10; + ReqParams->AsciiBlk[4]=(BlockNum / 1)+0x30; + ReqParams->SFun =SFun_Insert; + ReqParams->Len_2=0x05; + ReqParams->Cmd[0]='_'; + ReqParams->Cmd[1]='I'; + ReqParams->Cmd[2]='N'; + ReqParams->Cmd[3]='S'; + ReqParams->Cmd[4]='E'; + + IsoSize=sizeof(TS7ReqHeader)+sizeof(TReqControlBlockParams); + Result=isoExchangeBuffer(0,IsoSize); + + if (Result==0) + { + if ((Answer->Error!=0) || (*ResParams!=pduControl)) + Result=errCliInsertRefused; + }; + //-----------------------------------> Insert block into the unit + } + }; + }; + }; + return Result; +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::opDelete() +{ + PReqControlBlockParams ReqParams; + PS7ResHeader23 Answer; + pbyte ResParams; + int IsoSize, BlockType, BlockNum, Result; + + BlockType=Job.Area; + BlockNum =Job.Number; + // Setup pointers (note : PDUH_out and PDU.Payload are the same pointer) + ReqParams=PReqControlBlockParams(pbyte(PDUH_out)+sizeof(TS7ReqHeader)); + Answer =PS7ResHeader23(&PDU.Payload); + ResParams=pbyte(Answer)+ResHeaderSize23; + // Init Header + PDUH_out->P=0x32; // Always 0x32 + PDUH_out->PDUType=PduType_request; // 0x01 + PDUH_out->AB_EX=0x0000; // Always 0x0000 + PDUH_out->Sequence=GetNextWord(); // AutoInc + PDUH_out->ParLen=SwapWord(sizeof(TReqControlBlockParams)); + PDUH_out->DataLen=0x0000; // No data + // Init Params + ReqParams->Fun = pduControl; + ReqParams->Uk7[0]=0x00; + ReqParams->Uk7[1]=0x00; + ReqParams->Uk7[2]=0x00; + ReqParams->Uk7[3]=0x00; + ReqParams->Uk7[4]=0x00; + ReqParams->Uk7[5]=0x00; + ReqParams->Uk7[6]=0xFD; + ReqParams->Len_1 =SwapWord(0x0A); + ReqParams->NumOfBlocks=0x01; + ReqParams->ByteZero =0x00; + ReqParams->AsciiZero =0x30; + ReqParams->BlkType=BlockType; + ReqParams->AsciiBlk[0]=(BlockNum / 10000)+0x30; + BlockNum=BlockNum % 10000; + ReqParams->AsciiBlk[1]=(BlockNum / 1000)+0x30; + BlockNum=BlockNum % 1000; + ReqParams->AsciiBlk[2]=(BlockNum / 100)+0x30; + BlockNum=BlockNum % 100; + ReqParams->AsciiBlk[3]=(BlockNum / 10)+0x30; + BlockNum=BlockNum % 10; + ReqParams->AsciiBlk[4]=(BlockNum / 1)+0x30; + ReqParams->SFun =SFun_Delete; + ReqParams->Len_2=0x05; + ReqParams->Cmd[0]='_'; + ReqParams->Cmd[1]='D'; + ReqParams->Cmd[2]='E'; + ReqParams->Cmd[3]='L'; + ReqParams->Cmd[4]='E'; + + IsoSize=sizeof(TS7ReqHeader)+sizeof(TReqControlBlockParams); + Result=isoExchangeBuffer(0,IsoSize); + + if (Result==0) + { + if (SwapWord(Answer->Error)!=Code7NeedPassword) + { + if ((Answer->Error!=0) || (*ResParams!=pduControl)) + Result=errCliDeleteRefused; + } + else + Result=errCliNeedPassword; + } + return Result; +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::opReadSZL() +{ + PS7Answer17 Answer; + PReqFunReadSZLFirst ReqParamsFirst; + PReqFunReadSZLNext ReqParamsNext; + PS7ReqSZLData ReqDataFirst; + PS7ReqSZLData ReqDataNext; + PS7ResParams7 ResParams; + PS7ResSZLDataFirst ResDataFirst; + PS7ResSZLDataNext ResDataNext; + PSZL_HEADER Header; + PS7SZLList Target; + pbyte PDataFirst; + pbyte PDataNext; + word ID, Index; + int IsoSize, DataSize, DataSZL, Result; + bool First, Done; + bool NoRoom = false; + uintptr_t Offset =0; + byte Seq_in =0x00; + + ID=Job.ID; + Index=Job.Index; + opSize=0; + // Setup pointers (note : PDUH_out and PDU.Payload are the same pointer) + ReqParamsFirst=PReqFunReadSZLFirst(pbyte(PDUH_out)+sizeof(TS7ReqHeader)); + ReqParamsNext =PReqFunReadSZLNext(pbyte(PDUH_out)+sizeof(TS7ReqHeader)); + ReqDataFirst =PS7ReqSZLData(pbyte(ReqParamsFirst)+sizeof(TReqFunReadSZLFirst)); + ReqDataNext =PS7ReqSZLData(pbyte(ReqParamsNext)+sizeof(TReqFunReadSZLNext)); + + Answer =PS7Answer17(&PDU.Payload); + ResParams =PS7ResParams7(pbyte(Answer)+ResHeaderSize17); + ResDataFirst =PS7ResSZLDataFirst(pbyte(ResParams)+sizeof(TS7Params7)); + ResDataNext =PS7ResSZLDataNext(pbyte(ResParams)+sizeof(TS7Params7)); + PDataFirst =pbyte(ResDataFirst)+8; // skip header + PDataNext =pbyte(ResDataNext)+4; // skip header + Header =PSZL_HEADER(&opData); + First=true; + Done =false; + do + { + //<------------------------------------------------------- read slices + if (First) + { + //<-------------------------------------------------- prepare first + DataSize=sizeof(TS7ReqSZLData); + // Fill Header + PDUH_out->P=0x32; // Always 0x32 + PDUH_out->PDUType=PduType_userdata; // 0x07 + PDUH_out->AB_EX=0x0000; // Always 0x0000 + PDUH_out->Sequence=GetNextWord(); // AutoInc + PDUH_out->ParLen=SwapWord(sizeof(TReqFunReadSZLFirst)); // 8 bytes params + PDUH_out->DataLen=SwapWord(DataSize); // 8/4 bytes data + // Fill Params + ReqParamsFirst->Head[0]=0x00; + ReqParamsFirst->Head[1]=0x01; + ReqParamsFirst->Head[2]=0x12; + ReqParamsFirst->Plen =0x04; + ReqParamsFirst->Uk =0x11; + ReqParamsFirst->Tg =grSZL; + ReqParamsFirst->SubFun =SFun_ReadSZL; //0x03 + ReqParamsFirst->Seq =Seq_in; + // Fill Data + ReqDataFirst->Ret =0xFF; + ReqDataFirst->TS =TS_ResOctet; + ReqDataFirst->DLen =SwapWord(0x0004); + ReqDataFirst->ID =SwapWord(ID); + ReqDataFirst->Index =SwapWord(Index); + IsoSize=sizeof(TS7ReqHeader)+sizeof(TReqFunReadSZLFirst)+DataSize; + //--------------------------------------------------> prepare first + } + else + { + //<-------------------------------------------------- prepare next + DataSize=sizeof(TS7ReqSZLData)-4; + // Fill Header + PDUH_out->P=0x32; // Always 0x32 + PDUH_out->PDUType=PduType_userdata; // 0x07 + PDUH_out->AB_EX=0x0000; // Always 0x0000 + PDUH_out->Sequence=GetNextWord(); // AutoInc + PDUH_out->ParLen=SwapWord(sizeof(TReqFunReadSZLNext)); // 8 bytes params + PDUH_out->DataLen=SwapWord(DataSize);// 8/4 bytes data + // Fill Params + ReqParamsNext->Head[0]=0x00; + ReqParamsNext->Head[1]=0x01; + ReqParamsNext->Head[2]=0x12; + ReqParamsNext->Plen =0x08; + ReqParamsNext->Uk =0x12; + ReqParamsNext->Tg =grSZL; + ReqParamsNext->SubFun =SFun_ReadSZL; + ReqParamsNext->Seq =Seq_in; + ReqParamsNext->Rsvd =0x0000; + ReqParamsNext->ErrNo =0x0000; + // Fill Data + ReqDataNext->Ret =0x0A; + ReqDataNext->TS =0x00; + ReqDataNext->DLen =0x0000; + ReqDataNext->ID =0x0000; + ReqDataNext->Index =0x0000; + IsoSize=sizeof(TS7ReqHeader)+sizeof(TReqFunReadSZLNext)+DataSize; + //--------------------------------------------------> prepare next + } + Result=isoExchangeBuffer(0,IsoSize); + // Get Data + if (Result==0) + { + if (First) + { + //<------------------------------------------ get data first + if (ResParams->Err==0) + { + if (ResDataFirst->Ret==0xFF) // <-- 0xFF means Result OK + { + // Gets Amount of this slice + DataSZL=SwapWord(ResDataFirst->DLen)-4;// Skips extra params (ID, Index ...) + // Gets end of Sequence Flag + Done=(ResParams->resvd & 0xFF00) == 0; // Low order byte = 0x00 => the sequence is done + // Gets Unit's function sequence + Seq_in=ResParams->Seq; + Target=PS7SZLList(pbyte(&opData)+Offset); + memcpy(Target, PDataFirst, DataSZL); + Offset+=DataSZL; + } + else + Result=CpuError(ResDataFirst->Ret); + } + else + Result=CpuError(ResDataFirst->Ret); + //------------------------------------------> get data first + } + else + { + //<------------------------------------------ get data next + if (ResParams->Err==0) + { + if (ResDataNext->Ret==0xFF) // <-- 0xFF means Result OK + { + // Gets Amount of this slice + DataSZL=SwapWord(ResDataNext->DLen); + // Gets end of Sequence Flag + Done=(ResParams->resvd & 0xFF00) == 0; // Low order byte = 0x00 => the sequence is done + // Gets Unit's function sequence + Seq_in=ResParams->Seq; + Target=PS7SZLList(pbyte(&opData)+Offset); + memcpy(Target, PDataNext, DataSZL); + Offset+=DataSZL; + } + else + Result=CpuError(ResDataNext->Ret); + } + else + Result=CpuError(ResDataNext->Ret); + //------------------------------------------> get data next + } + First=false; + } + //-------------------------------------------------------> read slices + } + while ((!Done) && (Result==0)); + + // Check errors and adjust header + if (Result==0) + { + // Adjust big endian header + Header->LENTHDR=SwapWord(Header->LENTHDR); + Header->N_DR =SwapWord(Header->N_DR); + opSize=int(Offset); + + if (Job.IParam==1) // if 1 data has to be copied into user buffer + { + // Check buffer size + if (opSize>Job.Amount) + { + opSize=Job.Amount; + NoRoom=true; + } + memcpy(Job.pData, &opData, opSize); + *Job.pAmount=opSize; + }; + }; + if ((Result==0)&& NoRoom) + Result=errCliBufferTooSmall; + + return Result; +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::opReadSZLList() +{ + PS7SZLList usrSZLList, opDataList; + int ItemsCount, ItemsCount_in, c, Result; + bool NoRoom = false; + + Job.ID =0x0000; + Job.Index =0x0000; + Job.IParam =0; + ItemsCount_in=Job.Amount; // stores the room + Job.Amount =sizeof(opData); // read into the internal buffer + + Result =opReadSZL(); + if (Result==0) + { + opDataList=PS7SZLList(&opData); // Source + usrSZLList=PS7SZLList(Job.pData); // Target + + ItemsCount=(opSize-sizeof(SZL_HEADER)) / 2; + // Check input size + if (ItemsCount>ItemsCount_in) + { + ItemsCount=ItemsCount_in; // Trim itemscount + NoRoom=true; + } + for (c = 0; c < ItemsCount; c++) + usrSZLList->List[c]=SwapWord(opDataList->List[c]); + *Job.pAmount=ItemsCount; + } + else + *Job.pAmount=0; + + if ((Result==0) && NoRoom) + Result=errCliBufferTooSmall; + + return Result; +} +//--------------------------------------------------------------------------- +byte TSnap7MicroClient::BCDtoByte(byte B) +{ + return ((B >> 4) * 10) + (B & 0x0F); +} +//--------------------------------------------------------------------------- +byte TSnap7MicroClient::WordToBCD(word Value) +{ + return ((Value / 10) << 4) | (Value % 10); +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::opGetDateTime() +{ + PTimeStruct DateTime; + PReqFunDateTime ReqParams; + PReqDataGetDateTime ReqData; + PS7ResParams7 ResParams; + PResDataGetTime ResData; + PS7ResHeader17 Answer; + int IsoSize, Result; + word AYear; + + // Setup pointers (note : PDUH_out and PDU.Payload are the same pointer) + ReqParams=PReqFunDateTime(pbyte(PDUH_out)+sizeof(TS7ReqHeader)); + ReqData =PReqDataGetDateTime(pbyte(ReqParams)+sizeof(TReqFunDateTime)); + Answer =PS7ResHeader17(&PDU.Payload); + ResParams=PS7ResParams7(pbyte(Answer)+ResHeaderSize17); + ResData =PResDataGetTime(pbyte(ResParams)+sizeof(TS7Params7)); + DateTime =PTimeStruct(Job.pData); + // Fill Header + PDUH_out->P=0x32; // Always 0x32 + PDUH_out->PDUType=PduType_userdata; // 0x07 + PDUH_out->AB_EX=0x0000; // Always 0x0000 + PDUH_out->Sequence=GetNextWord(); // AutoInc + PDUH_out->ParLen=SwapWord(sizeof(TReqFunDateTime)); // 8 bytes params + PDUH_out->DataLen=SwapWord(sizeof(TReqDataGetDateTime)); // 4 bytes data + // Fill params (mostly constants) + ReqParams->Head[0]=0x00; + ReqParams->Head[1]=0x01; + ReqParams->Head[2]=0x12; + ReqParams->Plen =0x04; + ReqParams->Uk =0x11; + ReqParams->Tg =grClock; + ReqParams->SubFun =SFun_ReadClock; + ReqParams->Seq =0x00; + // Fill Data + *ReqData =0x0000000A; + + IsoSize=sizeof(TS7ReqHeader)+sizeof(TReqFunDateTime)+sizeof(TReqDataGetDateTime); + Result=isoExchangeBuffer(0,IsoSize); + + // Get Data + if (Result==0) + { + if (ResParams->Err==0) + { + if (ResData->RetVal==0xFF) // <-- 0xFF means Result OK + { + // Decode Plc Date and Time + AYear=BCDtoByte(ResData->Time[0]); + if (AYear<90) + AYear=AYear+100; + DateTime->tm_year=AYear; + DateTime->tm_mon =BCDtoByte(ResData->Time[1])-1; + DateTime->tm_mday=BCDtoByte(ResData->Time[2]); + DateTime->tm_hour=BCDtoByte(ResData->Time[3]); + DateTime->tm_min =BCDtoByte(ResData->Time[4]); + DateTime->tm_sec =BCDtoByte(ResData->Time[5]); + DateTime->tm_wday=(ResData->Time[7] & 0x0F)-1; + } + else + Result=CpuError(ResData->RetVal); + } + else + Result=CpuError(ResData->RetVal); + } + return Result; +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::opSetDateTime() +{ + PTimeStruct DateTime; + PReqFunDateTime ReqParams; + PReqDataSetTime ReqData; + PS7ResParams7 ResParams; + PS7ResHeader17 Answer; + word AYear; + int IsoSize, Result; + + // Setup pointers (note : PDUH_out and PDU.Payload are the same pointer) + ReqParams=PReqFunDateTime(pbyte(PDUH_out)+sizeof(TS7ReqHeader)); + ReqData =PReqDataSetTime(pbyte(ReqParams)+sizeof(TReqFunDateTime)); + Answer =PS7ResHeader17(&PDU.Payload); + ResParams=PS7ResParams7(pbyte(Answer)+ResHeaderSize17); + DateTime =PTimeStruct(Job.pData); + // Fill Header + PDUH_out->P=0x32; // Always 0x32 + PDUH_out->PDUType=PduType_userdata; // 0x07 + PDUH_out->AB_EX=0x0000; // Always 0x0000 + PDUH_out->Sequence=GetNextWord(); // AutoInc + PDUH_out->ParLen=SwapWord(sizeof(TReqFunDateTime)); // 8 bytes params + PDUH_out->DataLen=SwapWord(sizeof(TReqDataSetTime)); // 4 bytes data + // Fill params (mostly constants) + ReqParams->Head[0]=0x00; + ReqParams->Head[1]=0x01; + ReqParams->Head[2]=0x12; + ReqParams->Plen =0x04; + ReqParams->Uk =0x11; + ReqParams->Tg =grClock; + ReqParams->SubFun =SFun_SetClock; + ReqParams->Seq =0x00; + // EncodeSiemensDateTime; + if (DateTime->tm_year<100) + AYear=DateTime->tm_year; + else + AYear=DateTime->tm_year-100; + + ReqData->RetVal=0xFF; + ReqData->TSize =TS_ResOctet; + ReqData->Length=SwapWord(0x000A); + ReqData->Rsvd =0x00; + ReqData->HiYear=0x19; // *must* be 19 tough it's not the Hi part of the year... + + ReqData->Time[0]=WordToBCD(AYear); + ReqData->Time[1]=WordToBCD(DateTime->tm_mon+1); + ReqData->Time[2]=WordToBCD(DateTime->tm_mday); + ReqData->Time[3]=WordToBCD(DateTime->tm_hour); + ReqData->Time[4]=WordToBCD(DateTime->tm_min); + ReqData->Time[5]=WordToBCD(DateTime->tm_sec); + ReqData->Time[6]=0; + ReqData->Time[7]=DateTime->tm_wday+1; + + IsoSize=sizeof(TS7ReqHeader)+sizeof(TReqFunDateTime)+sizeof(TReqDataSetTime); + Result=isoExchangeBuffer(0,IsoSize); + + // Get Result + if (Result==0) + { + if (ResParams->Err!=0) + Result=CpuError(SwapWord(ResParams->Err)); + }; + return Result; +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::opGetOrderCode() +{ + PS7OrderCode OC; + int Result; + + Job.ID =0x0011; + Job.Index =0x0000; + Job.IParam =0; + Result =opReadSZL(); + if (Result==0) + { + OC=PS7OrderCode(Job.pData); + memset(OC,0,sizeof(TS7OrderCode)); + memcpy(OC->Code,&opData[6],20); + OC->V1=opData[opSize-3]; + OC->V2=opData[opSize-2]; + OC->V3=opData[opSize-1]; + }; + return Result; +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::opGetCpuInfo() +{ + PS7CpuInfo Info; + int Result; + + // Store Pointer + Info=PS7CpuInfo(Job.pData); + // Clear data in order to have the end of strings (\0) correctly setted + memset(Info, 0, sizeof(TS7CpuInfo)); + + Job.ID =0x001C; + Job.Index =0x0000; + Job.IParam=0; + Result =opReadSZL(); + if (Result==0) + { + memcpy(Info->ModuleTypeName,&opData[176],32); + memcpy(Info->SerialNumber,&opData[142],24); + memcpy(Info->ASName,&opData[6],24); + memcpy(Info->Copyright,&opData[108],26); + memcpy(Info->ModuleName,&opData[40],24); + } + return Result; +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::opGetCpInfo() +{ + PS7CpInfo Info; + int Result; + // Store Pointer + Info=PS7CpInfo(Job.pData); + memset(Info,0,sizeof(TS7CpInfo)); + Job.ID =0x0131; + Job.Index =0x0001; + Job.IParam=0; + Result =opReadSZL(); + if (Result==0) + { + Info->MaxPduLengt=opData[6]*256+opData[7]; + Info->MaxConnections=opData[8]*256+opData[9]; + Info->MaxMpiRate=DWordAt(&opData[10]); + Info->MaxBusRate=DWordAt(&opData[14]); + }; + return Result; +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::opGetPlcStatus() +{ + int *Status; + int Result; + + Status =(int*)Job.pData; + Job.ID =0x0424; + Job.Index =0x0000; + Job.IParam =0; + Result =opReadSZL(); + if (Result==0) + { + switch (opData[7]) + { + case S7CpuStatusUnknown : + case S7CpuStatusRun : + case S7CpuStatusStop : *Status=opData[7]; + break; + default : + // Since RUN status is always $08 for all CPUs and CPs, STOP status + // sometime can be coded as $03 (especially for old cpu...) + *Status=S7CpuStatusStop; + } + } + else + *Status=0; + return Result; +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::opPlcStop() +{ + PReqFunPlcStop ReqParams; + PResFunCtrl ResParams; + PS7ResHeader23 Answer; + int IsoSize, Result; + + char p_program[] = {'P','_','P','R','O','G','R','A','M'}; + + // Setup pointers (note : PDUH_out and PDU.Payload are the same pointer) + ReqParams=PReqFunPlcStop(pbyte(PDUH_out)+sizeof(TS7ReqHeader)); + Answer =PS7ResHeader23(&PDU.Payload); + ResParams=PResFunCtrl(pbyte(Answer)+ResHeaderSize23); + // Fill Header + PDUH_out->P=0x32; // Always 0x32 + PDUH_out->PDUType=PduType_request; // 0x01 + PDUH_out->AB_EX=0x0000; // Always 0x0000 + PDUH_out->Sequence=GetNextWord(); // AutoInc + PDUH_out->ParLen=SwapWord(sizeof(TReqFunPlcStop)); + PDUH_out->DataLen=0x0000; // No Data + // Fill Params + ReqParams->Fun=pduStop; + memset(ReqParams->Uk_5,0,5); + ReqParams->Len_2=0x09; + memcpy(ReqParams->Cmd,&p_program,9); + + IsoSize=sizeof(TS7ReqHeader)+sizeof(TReqFunPlcStop); + Result=isoExchangeBuffer(0,IsoSize); + + if (Result==0) + { + if (Answer->Error!=0) + { + if (ResParams->ResFun!=pduStop) + Result=errCliCannotStopPLC; + else + if (ResParams->para ==0x07) + Result=errCliAlreadyStop; + else + Result=errCliCannotStopPLC; + }; + }; + return Result; +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::opPlcHotStart() +{ + PReqFunPlcHotStart ReqParams; + PResFunCtrl ResParams; + PS7ResHeader23 Answer; + int IsoSize, Result; + + char p_program[] = {'P','_','P','R','O','G','R','A','M'}; + + // Setup pointers (note : PDUH_out and PDU.Payload are the same pointer) + ReqParams=PReqFunPlcHotStart(pbyte(PDUH_out)+sizeof(TS7ReqHeader)); + Answer =PS7ResHeader23(&PDU.Payload); + ResParams=PResFunCtrl(pbyte(Answer)+ResHeaderSize23); + // Fill Header + PDUH_out->P=0x32; // Always 0x32 + PDUH_out->PDUType=PduType_request; // 0x01 + PDUH_out->AB_EX=0x0000; // Always 0x0000 + PDUH_out->Sequence=GetNextWord(); // AutoInc + PDUH_out->ParLen=SwapWord(sizeof(TReqFunPlcHotStart)); // 16 bytes params + PDUH_out->DataLen=0x0000; // No Data + // Fill Params + ReqParams->Fun=pduStart; + ReqParams->Uk_7[0]=0x00; + ReqParams->Uk_7[1]=0x00; + ReqParams->Uk_7[2]=0x00; + ReqParams->Uk_7[3]=0x00; + ReqParams->Uk_7[4]=0x00; + ReqParams->Uk_7[5]=0x00; + ReqParams->Uk_7[6]=0xFD; + + ReqParams->Len_1=0x0000; + ReqParams->Len_2=0x09; + memcpy(ReqParams->Cmd,&p_program,9); + + IsoSize=sizeof(TS7ReqHeader)+sizeof(TReqFunPlcHotStart); + Result=isoExchangeBuffer(0,IsoSize); + + if (Result==0) + { + if ((Answer->Error!=0)) + { + if ((ResParams->ResFun!=pduStart)) + Result=errCliCannotStartPLC; + else + { + if (ResParams->para==0x03) + Result=errCliAlreadyRun; + else + if (ResParams->para==0x02) + Result=errCliCannotStartPLC; + else + Result=errCliCannotStartPLC; + } + } + }; + return Result; +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::opPlcColdStart() +{ + PReqFunPlcColdStart ReqParams; + PResFunCtrl ResParams; + PS7ResHeader23 Answer; + int IsoSize, Result; + char p_program[] = {'P','_','P','R','O','G','R','A','M'}; + + // Setup pointers (note : PDUH_out and PDU.Payload are the same pointer) + ReqParams=PReqFunPlcColdStart(pbyte(PDUH_out)+sizeof(TS7ReqHeader)); + Answer =PS7ResHeader23(&PDU.Payload); + ResParams=PResFunCtrl(pbyte(Answer)+ResHeaderSize23); + // Fill Header + PDUH_out->P=0x32; // Always 0x32 + PDUH_out->PDUType=PduType_request; // 0x01 + PDUH_out->AB_EX=0x0000; // Always 0x0000 + PDUH_out->Sequence=GetNextWord(); // AutoInc + PDUH_out->ParLen=SwapWord(sizeof(TReqFunPlcColdStart)); // 22 bytes params + PDUH_out->DataLen=0x0000; // No Data + // Fill Params + ReqParams->Fun=pduStart; + ReqParams->Uk_7[0]=0x00; + ReqParams->Uk_7[1]=0x00; + ReqParams->Uk_7[2]=0x00; + ReqParams->Uk_7[3]=0x00; + ReqParams->Uk_7[4]=0x00; + ReqParams->Uk_7[5]=0x00; + ReqParams->Uk_7[6]=0xFD; + + ReqParams->Len_1=SwapWord(0x0002); + ReqParams->SFun =SwapWord(0x4320); // Cold start + ReqParams->Len_2=0x09; + memcpy(ReqParams->Cmd,&p_program,9); + + IsoSize=sizeof(TS7ReqHeader)+sizeof(TReqFunPlcColdStart); + Result=isoExchangeBuffer(0,IsoSize); + + if (Result==0) + { + if ((Answer->Error!=0)) + { + if ((ResParams->ResFun!=pduStart)) + Result=errCliCannotStartPLC; + else + { + if (ResParams->para==0x03) + Result=errCliAlreadyRun; + else + if (ResParams->para==0x02) + Result=errCliCannotStartPLC; + else + Result=errCliCannotStartPLC; + } + } + }; + return Result; +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::opCopyRamToRom() +{ + PReqFunCopyRamToRom ReqParams; + PResFunCtrl ResParams; + PS7ResHeader23 Answer; + int IsoSize, CurTimeout, Result; + char _modu[] = {'_','M','O','D','U'}; + + // Setup pointers (note : PDUH_out and PDU.Payload are the same pointer) + ReqParams=PReqFunCopyRamToRom(pbyte(PDUH_out)+sizeof(TS7ReqHeader)); + Answer =PS7ResHeader23(&PDU.Payload); + ResParams=PResFunCtrl(pbyte(Answer)+ResHeaderSize23); + // Fill Header + PDUH_out->P=0x32; // Always 0x32 + PDUH_out->PDUType=PduType_request; // 0x01 + PDUH_out->AB_EX=0x0000; // Always 0x0000 + PDUH_out->Sequence=GetNextWord(); // AutoInc + PDUH_out->ParLen=SwapWord(sizeof(TReqFunCopyRamToRom)); + PDUH_out->DataLen=0x0000; // No Data + // Fill Params + ReqParams->Fun=pduControl; + ReqParams->Uk_7[0]=0x00; + ReqParams->Uk_7[1]=0x00; + ReqParams->Uk_7[2]=0x00; + ReqParams->Uk_7[3]=0x00; + ReqParams->Uk_7[4]=0x00; + ReqParams->Uk_7[5]=0x00; + ReqParams->Uk_7[6]=0xFD; + + ReqParams->Len_1=SwapWord(0x0002); + ReqParams->SFun =SwapWord(0x4550); + ReqParams->Len_2=0x05; + memcpy(ReqParams->Cmd,&_modu,5); + + IsoSize=sizeof(TS7ReqHeader)+sizeof(TReqFunCopyRamToRom); + // Changes the timeout + CurTimeout=RecvTimeout; + RecvTimeout=Job.IParam; + Result=isoExchangeBuffer(0,IsoSize); + // Restores the timeout + RecvTimeout=CurTimeout; + + if (Result==0) + { + if ((Answer->Error!=0) || (ResParams->ResFun!=pduControl)) + Result=errCliCannotCopyRamToRom; + } + return Result; +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::opCompress() +{ + PReqFunCompress ReqParams; + PResFunCtrl ResParams; + PS7ResHeader23 Answer; + int IsoSize, CurTimeout, Result; + char _garb[] = {'_','G','A','R','B'}; + + // Setup pointers (note : PDUH_out and PDU.Payload are the same pointer) + ReqParams=PReqFunCompress(pbyte(PDUH_out)+sizeof(TS7ReqHeader)); + Answer =PS7ResHeader23(&PDU.Payload); + ResParams=PResFunCtrl(pbyte(Answer)+ResHeaderSize23); + // Fill Header + PDUH_out->P=0x32; // Always 0x32 + PDUH_out->PDUType=PduType_request; // 0x01 + PDUH_out->AB_EX=0x0000; // Always 0x0000 + PDUH_out->Sequence=GetNextWord(); // AutoInc + PDUH_out->ParLen=SwapWord(sizeof(TReqFunCompress)); + PDUH_out->DataLen=0x0000; // No Data + // Fill Params + ReqParams->Fun=pduControl; + ReqParams->Uk_7[0]=0x00; + ReqParams->Uk_7[1]=0x00; + ReqParams->Uk_7[2]=0x00; + ReqParams->Uk_7[3]=0x00; + ReqParams->Uk_7[4]=0x00; + ReqParams->Uk_7[5]=0x00; + ReqParams->Uk_7[6]=0xFD; + + ReqParams->Len_1=0x0000; + ReqParams->Len_2=0x05; + memcpy(ReqParams->Cmd,&_garb,5); + + IsoSize=sizeof(TS7ReqHeader)+sizeof(TReqFunCompress); + // Changes the timeout + CurTimeout=RecvTimeout; + RecvTimeout=Job.IParam; + Result=isoExchangeBuffer(0,IsoSize); + // Restores the timeout + RecvTimeout=CurTimeout; + + if (Result==0) + { + if (((Answer->Error!=0) || (ResParams->ResFun!=pduControl))) + Result=errCliCannotCompress; + } + return Result; +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::opGetProtection() +{ + PS7Protection Info, usrInfo; + int Result; + + // Store Pointer + usrInfo=PS7Protection(Job.pData); + memset(usrInfo, 0, sizeof(TS7Protection)); + + Job.ID =0x0232; + Job.Index =0x0004; + Job.IParam=0; // No copy in Usr Data pointed by Job.pData + Result =opReadSZL(); + if (Result==0) + { + Info=PS7Protection(pbyte(&opData)+6); + usrInfo->sch_schal=SwapWord(Info->sch_schal); + usrInfo->sch_par =SwapWord(Info->sch_par); + usrInfo->sch_rel =SwapWord(Info->sch_rel); + usrInfo->bart_sch =SwapWord(Info->bart_sch); + usrInfo->anl_sch =SwapWord(Info->anl_sch); + } + return Result; +} +//****************************************************************************** +// NOTE +// PASSWORD HACKING IS VERY FAR FROM THE AIM OF THIS PROJECT +// NEXT FUNCTION ONLY ENCODES THE ASCII PASSWORD TO BE DOWNLOADED IN THE PLC. +// +// MOREOVER **YOU NEED TO KNOW** THE CORRECT PASSWORD TO MEET THE CPU +// SECURITY LEVEL +//****************************************************************************** +int TSnap7MicroClient::opSetPassword() +{ + PReqFunSecurity ReqParams; + PReqDataSecurity ReqData; + PResParamsSecurity ResParams; + PS7ResHeader23 Answer; + int c, IsoSize, Result; + + // Setup pointers (note : PDUH_out and PDU.Payload are the same pointer) + ReqParams=PReqFunSecurity(pbyte(PDUH_out)+sizeof(TS7ReqHeader)); + ReqData =PReqDataSecurity(pbyte(ReqParams)+sizeof(TReqFunSecurity)); + Answer =PS7ResHeader23(&PDU.Payload); + ResParams=PResParamsSecurity(pbyte(Answer)+ResHeaderSize17); + // Fill Header + PDUH_out->P=0x32; // Always 0x32 + PDUH_out->PDUType=PduType_userdata; // 0x07 + PDUH_out->AB_EX=0x0000; // Always 0x0000 + PDUH_out->Sequence=GetNextWord(); // AutoInc + PDUH_out->ParLen =SwapWord(sizeof(TReqFunSecurity)); + PDUH_out->DataLen=SwapWord(sizeof(TReqDataSecurity)); + // Fill params (mostly constants) + ReqParams->Head[0]=0x00; + ReqParams->Head[1]=0x01; + ReqParams->Head[2]=0x12; + ReqParams->Plen =0x04; + ReqParams->Uk =0x11; + ReqParams->Tg =grSecurity; + ReqParams->SubFun =SFun_EnterPwd; + ReqParams->Seq =0x00; + // Fill Data + ReqData->Ret =0xFF; + ReqData->TS =TS_ResOctet; + ReqData->DLen =SwapWord(0x0008); // 8 bytes data : password + // Encode the password + ReqData->Pwd[0]=opData[0] ^ 0x55; + ReqData->Pwd[1]=opData[1] ^ 0x55; + for (c = 2; c < 8; c++){ + ReqData->Pwd[c]=opData[c] ^ 0x55 ^ ReqData->Pwd[c-2]; + }; + + IsoSize=sizeof(TS7ReqHeader)+sizeof(TReqFunSecurity)+sizeof(TReqDataSecurity); + Result=isoExchangeBuffer(0,IsoSize); + + // Get Return + if (Result==0) + { + if (ResParams->Err!=0) + Result=CpuError(SwapWord(ResParams->Err)); + }; + + return Result; +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::opClearPassword() +{ + PReqFunSecurity ReqParams; + PReqDataSecurity ReqData; + PResParamsSecurity ResParams; + PS7ResHeader23 Answer; + int IsoSize, Result; + + // Setup pointers (note : PDUH_out and PDU.Payload are the same pointer) + ReqParams=PReqFunSecurity(pbyte(PDUH_out)+sizeof(TS7ReqHeader)); + ReqData =PReqDataSecurity(pbyte(ReqParams)+sizeof(TReqFunSecurity)); + Answer =PS7ResHeader23(&PDU.Payload); + ResParams=PResParamsSecurity(pbyte(Answer)+ResHeaderSize17); + // Fill Header + PDUH_out->P=0x32; // Always 0x32 + PDUH_out->PDUType=PduType_userdata; // 0x07 + PDUH_out->AB_EX=0x0000; // Always 0x0000 + PDUH_out->Sequence=GetNextWord(); // AutoInc + PDUH_out->ParLen =SwapWord(sizeof(TReqFunSecurity)); + PDUH_out->DataLen=SwapWord(0x0004); // We need only 4 bytes + // Fill params (mostly constants) + ReqParams->Head[0]=0x00; + ReqParams->Head[1]=0x01; + ReqParams->Head[2]=0x12; + ReqParams->Plen =0x04; + ReqParams->Uk =0x11; + ReqParams->Tg =grSecurity; + ReqParams->SubFun =SFun_CancelPwd; + ReqParams->Seq =0x00; + // Fill Data + ReqData->Ret =0x0A; + ReqData->TS =0x00; + ReqData->DLen =0x0000; + + IsoSize=sizeof(TS7ReqHeader)+sizeof(TReqFunSecurity)+4; + Result=isoExchangeBuffer(0,IsoSize); + + // Get Return + if (Result==0) + { + if (ResParams->Err!=0) + Result=CpuError(SwapWord(ResParams->Err)); + }; + return Result; +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::CpuError(int Error) +{ + switch(Error) + { + case 0 : return 0; + case Code7AddressOutOfRange : return errCliAddressOutOfRange; + case Code7InvalidTransportSize : return errCliInvalidTransportSize; + case Code7WriteDataSizeMismatch : return errCliWriteDataSizeMismatch; + case Code7ResItemNotAvailable : + case Code7ResItemNotAvailable1 : return errCliItemNotAvailable; + case Code7DataOverPDU : return errCliSizeOverPDU; + case Code7InvalidValue : return errCliInvalidValue; + case Code7FunNotAvailable : return errCliFunNotAvailable; + case Code7NeedPassword : return errCliNeedPassword; + case Code7InvalidPassword : return errCliInvalidPassword; + case Code7NoPasswordToSet : + case Code7NoPasswordToClear : return errCliNoPasswordToSetOrClear; + default: + return errCliFunctionRefused; + }; +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::DataSizeByte(int WordLength) +{ + switch (WordLength){ + case S7WLBit : return 1; // S7 sends 1 byte per bit + case S7WLByte : return 1; + case S7WLChar : return 1; + case S7WLWord : return 2; + case S7WLDWord : return 4; + case S7WLInt : return 2; + case S7WLDInt : return 4; + case S7WLReal : return 4; + case S7WLCounter : return 2; + case S7WLTimer : return 2; + default : return 0; + } +} +//--------------------------------------------------------------------------- +longword TSnap7MicroClient::DWordAt(void * P) +{ + longword DW; + DW=*(longword*)P; + return SwapDWord(DW); +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::CheckBlock(int BlockType, int BlockNum, void * pBlock, int Size) +{ + PS7CompactBlockInfo Info = PS7CompactBlockInfo(pBlock); + + if (BlockType>=0) // if (BlockType<0 the test is skipped + { + if ((BlockType!=Block_OB)&&(BlockType!=Block_DB)&&(BlockType!=Block_FB)&& + (BlockType!=Block_FC)&&(BlockType!=Block_SDB)&&(BlockType!=Block_SFC)&& + (BlockType!=Block_SFB)) + return errCliInvalidBlockType; + } + + if (BlockNum>=0) // if (BlockNum<0 the test is skipped + { + if (BlockNum>0xFFFF) + return errCliInvalidBlockNumber; + }; + + if (SwapDWord(Info->LenLoadMem)!=longword(Size)) + return errCliInvalidBlockSize; + + // Check the presence of the footer + if (SwapWord(Info->MC7Len)+sizeof(TS7CompactBlockInfo)>=u_int(Size)) + return errCliInvalidBlockSize; + + return 0; +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::SubBlockToBlock(int SBB) +{ + switch (SBB) + { + case SubBlk_OB : return Block_OB; + case SubBlk_DB : return Block_DB; + case SubBlk_SDB : return Block_SDB; + case SubBlk_FC : return Block_FC; + case SubBlk_SFC : return Block_SFC; + case SubBlk_FB : return Block_FB; + case SubBlk_SFB : return Block_SFB; + default : return 0; + }; +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::PerformOperation() +{ + ClrError(); + int Operation=Job.Op; + switch(Operation) + { + case s7opNone: + Job.Result=errCliInvalidParams; + break; + case s7opReadArea: + Job.Result=opReadArea(); + break; + case s7opWriteArea: + Job.Result=opWriteArea(); + break; + case s7opReadMultiVars: + Job.Result=opReadMultiVars(); + break; + case s7opWriteMultiVars: + Job.Result=opWriteMultiVars(); + break; + case s7opDBGet: + Job.Result=opDBGet(); + break; + case s7opDBFill: + Job.Result=opDBFill(); + break; + case s7opUpload: + Job.Result=opUpload(); + break; + case s7opDownload: + Job.Result=opDownload(); + break; + case s7opDelete: + Job.Result=opDelete(); + break; + case s7opListBlocks: + Job.Result=opListBlocks(); + break; + case s7opAgBlockInfo: + Job.Result=opAgBlockInfo(); + break; + case s7opListBlocksOfType: + Job.Result=opListBlocksOfType(); + break; + case s7opReadSzlList: + Job.Result=opReadSZLList(); + break; + case s7opReadSZL: + Job.Result=opReadSZL(); + break; + case s7opGetDateTime: + Job.Result=opGetDateTime(); + break; + case s7opSetDateTime: + Job.Result=opSetDateTime(); + break; + case s7opGetOrderCode: + Job.Result=opGetOrderCode(); + break; + case s7opGetCpuInfo: + Job.Result=opGetCpuInfo(); + break; + case s7opGetCpInfo: + Job.Result=opGetCpInfo(); + break; + case s7opGetPlcStatus: + Job.Result=opGetPlcStatus(); + break; + case s7opPlcHotStart: + Job.Result=opPlcHotStart(); + break; + case s7opPlcColdStart: + Job.Result=opPlcColdStart(); + break; + case s7opCopyRamToRom: + Job.Result=opCopyRamToRom(); + break; + case s7opCompress: + Job.Result=opCompress(); + break; + case s7opPlcStop: + Job.Result=opPlcStop(); + break; + case s7opGetProtection: + Job.Result=opGetProtection(); + break; + case s7opSetPassword: + Job.Result=opSetPassword(); + break; + case s7opClearPassword: + Job.Result=opClearPassword(); + break; + } + Job.Time =SysGetTick()-JobStart; + Job.Pending=false; + return SetError(Job.Result); +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::Disconnect() +{ + JobStart=SysGetTick(); + PeerDisconnect(); + Job.Time=SysGetTick()-JobStart; + Job.Pending=false; + return 0; +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::Reset(bool DoReconnect) +{ + Job.Pending=false; + if (DoReconnect) { + Disconnect(); + return Connect(); + } + else + return 0; +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::Connect() +{ + int Result; + JobStart=SysGetTick(); + Result =PeerConnect(); + Job.Time=SysGetTick()-JobStart; + return Result; +} +//--------------------------------------------------------------------------- +void TSnap7MicroClient::SetConnectionType(word ConnType) +{ + ConnectionType=ConnType; +} +//--------------------------------------------------------------------------- +void TSnap7MicroClient::SetConnectionParams(const char *RemAddress, word LocalTSAP, word RemoteTSAP) +{ + SrcTSap = LocalTSAP; + DstTSap = RemoteTSAP; + strncpy(RemoteAddress, RemAddress, 16); +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::ConnectTo(const char *RemAddress, int Rack, int Slot) +{ + word RemoteTSAP = (ConnectionType<<8)+(Rack*0x20)+Slot; + SetConnectionParams(RemAddress, SrcTSap, RemoteTSAP); + return Connect(); +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::GetParam(int ParamNumber, void *pValue) +{ + switch (ParamNumber) + { + case p_u16_RemotePort: + *Puint16_t(pValue)=RemotePort; + break; + case p_i32_PingTimeout: + *Pint32_t(pValue)=PingTimeout; + break; + case p_i32_SendTimeout: + *Pint32_t(pValue)=SendTimeout; + break; + case p_i32_RecvTimeout: + *Pint32_t(pValue)=RecvTimeout; + break; + case p_i32_WorkInterval: + *Pint32_t(pValue)=WorkInterval; + break; + case p_u16_SrcRef: + *Puint16_t(pValue)=SrcRef; + break; + case p_u16_DstRef: + *Puint16_t(pValue)=DstRef; + break; + case p_u16_SrcTSap: + *Puint16_t(pValue)=SrcTSap; + break; + case p_i32_PDURequest: + *Pint32_t(pValue)=PDURequest; + break; + default: return errCliInvalidParamNumber; + } + return 0; +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::SetParam(int ParamNumber, void *pValue) +{ + switch (ParamNumber) + { + case p_u16_RemotePort: + if (!Connected) + RemotePort=*Puint16_t(pValue); + else + return errCliCannotChangeParam; + break; + case p_i32_PingTimeout: + PingTimeout=*Pint32_t(pValue); + break; + case p_i32_SendTimeout: + SendTimeout=*Pint32_t(pValue); + break; + case p_i32_RecvTimeout: + RecvTimeout=*Pint32_t(pValue); + break; + case p_i32_WorkInterval: + WorkInterval=*Pint32_t(pValue); + break; + case p_u16_SrcRef: + SrcRef=*Puint16_t(pValue); + break; + case p_u16_DstRef: + DstRef=*Puint16_t(pValue); + break; + case p_u16_SrcTSap: + SrcTSap=*Puint16_t(pValue); + break; + case p_i32_PDURequest: + PDURequest=*Pint32_t(pValue); + break; + default: return errCliInvalidParamNumber; + } + return 0; +} +//--------------------------------------------------------------------------- +// Data I/O functions +int TSnap7MicroClient::ReadArea(int Area, int DBNumber, int Start, int Amount, int WordLen, void * pUsrData) +{ + if (!Job.Pending) + { + Job.Pending = true; + Job.Op = s7opReadArea; + Job.Area = Area; + Job.Number = DBNumber; + Job.Start = Start; + Job.Amount = Amount; + Job.WordLen = WordLen; + Job.pData = pUsrData; + JobStart = SysGetTick(); + return PerformOperation(); + } + else + return SetError(errCliJobPending); +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::WriteArea(int Area, int DBNumber, int Start, int Amount, int WordLen, void * pUsrData) +{ + if (!Job.Pending) + { + Job.Pending = true; + Job.Op = s7opWriteArea; + Job.Area = Area; + Job.Number = DBNumber; + Job.Start = Start; + Job.Amount = Amount; + Job.WordLen = WordLen; + Job.pData = pUsrData; + JobStart = SysGetTick(); + return PerformOperation(); + } + else + return SetError(errCliJobPending); +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::ReadMultiVars(PS7DataItem Item, int ItemsCount) +{ + if (!Job.Pending) + { + Job.Pending =true; + Job.Op =s7opReadMultiVars; + Job.Amount =ItemsCount; + Job.pData =Item; + JobStart =SysGetTick(); + return PerformOperation(); + } + else + return SetError(errCliJobPending); +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::WriteMultiVars(PS7DataItem Item, int ItemsCount) +{ + if (!Job.Pending) + { + Job.Pending =true; + Job.Op =s7opWriteMultiVars; + Job.Amount =ItemsCount; + Job.pData =Item; + JobStart =SysGetTick(); + return PerformOperation(); + } + else + return SetError(errCliJobPending); +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::DBRead(int DBNumber, int Start, int Size, void * pUsrData) +{ + return ReadArea(S7AreaDB, DBNumber, Start, Size, S7WLByte, pUsrData); +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::DBWrite(int DBNumber, int Start, int Size, void * pUsrData) +{ + return WriteArea(S7AreaDB, DBNumber, Start, Size, S7WLByte, pUsrData); +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::MBRead(int Start, int Size, void * pUsrData) +{ + return ReadArea(S7AreaMK, 0, Start, Size, S7WLByte, pUsrData); +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::MBWrite(int Start, int Size, void * pUsrData) +{ + return WriteArea(S7AreaMK, 0, Start, Size, S7WLByte, pUsrData); +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::EBRead(int Start, int Size, void * pUsrData) +{ + return ReadArea(S7AreaPE, 0, Start, Size, S7WLByte, pUsrData); +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::EBWrite(int Start, int Size, void * pUsrData) +{ + return WriteArea(S7AreaPE, 0, Start, Size, S7WLByte, pUsrData); +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::ABRead(int Start, int Size, void * pUsrData) +{ + return ReadArea(S7AreaPA, 0, Start, Size, S7WLByte, pUsrData); +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::ABWrite(int Start, int Size, void * pUsrData) +{ + return WriteArea(S7AreaPA, 0, Start, Size, S7WLByte, pUsrData); +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::TMRead(int Start, int Amount, void * pUsrData) +{ + return ReadArea(S7AreaTM, 0, Start, Amount, S7WLTimer, pUsrData); +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::TMWrite(int Start, int Amount, void * pUsrData) +{ + return WriteArea(S7AreaTM, 0, Start, Amount, S7WLTimer, pUsrData); +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::CTRead(int Start, int Amount, void * pUsrData) +{ + return ReadArea(S7AreaCT, 0, Start, Amount, S7WLCounter, pUsrData); +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::CTWrite(int Start, int Amount, void * pUsrData) +{ + return WriteArea(S7AreaCT, 0, Start, Amount, S7WLCounter, pUsrData); +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::ListBlocks(PS7BlocksList pUsrData) +{ + if (!Job.Pending) + { + Job.Pending =true; + Job.Op =s7opListBlocks; + Job.pData =pUsrData; + JobStart =SysGetTick(); + return PerformOperation(); + } + else + return SetError(errCliJobPending); +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::GetAgBlockInfo(int BlockType, int BlockNum, PS7BlockInfo pUsrData) +{ + if (!Job.Pending) + { + Job.Pending =true; + Job.Op =s7opAgBlockInfo; + Job.Area =BlockType; + Job.Number =BlockNum; + Job.pData =pUsrData; + JobStart =SysGetTick(); + return PerformOperation(); + } + else + return SetError(errCliJobPending); +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::GetPgBlockInfo(void * pBlock, PS7BlockInfo pUsrData, int Size) +{ + PS7CompactBlockInfo Info; + PS7BlockFooter Footer; + + int Result=CheckBlock(-1,-1,pBlock,Size); + if (Result==0) + { + Info=PS7CompactBlockInfo(pBlock); + pUsrData->BlkType =Info->SubBlkType; + pUsrData->BlkNumber=SwapWord(Info->BlkNum); + pUsrData->BlkLang =Info->BlkLang; + pUsrData->BlkFlags =Info->BlkFlags; + pUsrData->MC7Size =SwapWord(Info->MC7Len); + pUsrData->LoadSize =SwapDWord(Info->LenLoadMem); + pUsrData->LocalData=SwapDWord(Info->LocDataLen); + pUsrData->SBBLength=SwapDWord(Info->SbbLen); + pUsrData->CheckSum =0; // this info is not available + pUsrData->Version =0; // this info is not available + FillTime(SwapWord(Info->CodeTime_dy),pUsrData->CodeDate); + FillTime(SwapWord(Info->IntfTime_dy),pUsrData->IntfDate); + + Footer=PS7BlockFooter(pbyte(Info)+pUsrData->LoadSize-sizeof(TS7BlockFooter)); + + memcpy(pUsrData->Author,Footer->Author,8); + memcpy(pUsrData->Family,Footer->Family,8); + memcpy(pUsrData->Header,Footer->Header,8); + }; + return SetError(Result); +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::ListBlocksOfType(int BlockType, TS7BlocksOfType *pUsrData, int &ItemsCount) +{ + if (!Job.Pending) + { + if (ItemsCount<1) + return SetError(errCliInvalidBlockSize); + Job.Pending =true; + Job.Op =s7opListBlocksOfType; + Job.Area =BlockType; + Job.pData =pUsrData; + Job.pAmount =&ItemsCount; + Job.Amount =ItemsCount; + JobStart =SysGetTick(); + return PerformOperation(); + } + else + return SetError(errCliJobPending); +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::Upload(int BlockType, int BlockNum, void * pUsrData, int & Size) +{ + if (!Job.Pending) + { + if (Size<=0) + return SetError(errCliInvalidBlockSize); + Job.Pending =true; + Job.Op =s7opUpload; + Job.Area =BlockType; + Job.pData =pUsrData; + Job.pAmount =&Size; + Job.Amount =Size; + Job.Number =BlockNum; + Job.IParam =0; // not full upload, only data + JobStart =SysGetTick(); + return PerformOperation(); + } + else + return SetError(errCliJobPending); +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::FullUpload(int BlockType, int BlockNum, void * pUsrData, int & Size) +{ + if (!Job.Pending) + { + if (Size<=0) + return SetError(errCliInvalidBlockSize); + Job.Pending =true; + Job.Op =s7opUpload; + Job.Area =BlockType; + Job.pData =pUsrData; + Job.pAmount =&Size; + Job.Amount =Size; + Job.Number =BlockNum; + Job.IParam =1; // header + data + footer + JobStart =SysGetTick(); + return PerformOperation(); + } + else + return SetError(errCliJobPending); +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::Download(int BlockNum, void * pUsrData, int Size) +{ + if (!Job.Pending) + { + Job.Pending =true; + Job.Op =s7opDownload; + memcpy(&opData, pUsrData, Size); + Job.Number =BlockNum; + Job.Amount =Size; + JobStart =SysGetTick(); + return PerformOperation(); + } + else + return SetError(errCliJobPending); +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::Delete(int BlockType, int BlockNum) +{ + if (!Job.Pending) + { + Job.Pending =true; + Job.Op =s7opDelete; + Job.Area =BlockType; + Job.Number =BlockNum; + JobStart =SysGetTick(); + return PerformOperation(); + } + else + return SetError(errCliJobPending); +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::DBGet(int DBNumber, void * pUsrData, int & Size) +{ + if (!Job.Pending) + { + if (Size<=0) + return SetError(errCliInvalidBlockSize); + Job.Pending =true; + Job.Op =s7opDBGet; + Job.Number =DBNumber; + Job.pData =pUsrData; + Job.pAmount =&Size; + Job.Amount =Size; + JobStart =SysGetTick(); + return PerformOperation(); + } + else + return SetError(errCliJobPending); +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::DBFill(int DBNumber, int FillChar) +{ + if (!Job.Pending) + { + Job.Pending =true; + Job.Op =s7opDBFill; + Job.Number =DBNumber; + Job.IParam =FillChar; + JobStart =SysGetTick(); + return PerformOperation(); + } + else + return SetError(errCliJobPending); +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::GetPlcDateTime(tm &DateTime) +{ + if (!Job.Pending) + { + Job.Pending =true; + Job.Op =s7opGetDateTime; + Job.pData =&DateTime; + JobStart =SysGetTick(); + return PerformOperation(); + } + else + return SetError(errCliJobPending); +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::SetPlcDateTime(tm * DateTime) +{ + if (!Job.Pending) + { + Job.Pending =true; + Job.Op =s7opSetDateTime; + Job.pData =DateTime; + JobStart =SysGetTick(); + return PerformOperation(); + } + else + return SetError(errCliJobPending); +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::SetPlcSystemDateTime() +{ + time_t Now; + time(&Now); + struct tm * DateTime = localtime (&Now); + return SetPlcDateTime(DateTime); +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::GetOrderCode(PS7OrderCode pUsrData) +{ + if (!Job.Pending) + { + Job.Pending =true; + Job.Op =s7opGetOrderCode; + Job.pData =pUsrData; + JobStart =SysGetTick(); + return PerformOperation(); + } + else + return SetError(errCliJobPending); +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::GetCpuInfo(PS7CpuInfo pUsrData) +{ + if (!Job.Pending) + { + Job.Pending =true; + Job.Op =s7opGetCpuInfo; + Job.pData =pUsrData; + JobStart =SysGetTick(); + return PerformOperation(); + } + else + return SetError(errCliJobPending); +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::GetCpInfo(PS7CpInfo pUsrData) +{ + if (!Job.Pending) + { + Job.Pending =true; + Job.Op =s7opGetCpInfo; + Job.pData =pUsrData; + JobStart =SysGetTick(); + return PerformOperation(); + } + else + return SetError(errCliJobPending); +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::ReadSZL(int ID, int Index, PS7SZL pUsrData, int &Size) +{ + if (!Job.Pending) + { + Job.Pending =true; + Job.Op =s7opReadSZL; + Job.ID =ID; + Job.Index =Index; + Job.pData =pUsrData; + Job.pAmount =&Size; + Job.Amount =Size; + Job.IParam =1; // Data has to be copied into user buffer + JobStart =SysGetTick(); + return PerformOperation(); + } + else + return SetError(errCliJobPending); +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::ReadSZLList(PS7SZLList pUsrData, int &ItemsCount) +{ + if (!Job.Pending) + { + Job.Pending =true; + Job.Op =s7opReadSzlList; + Job.pData =pUsrData; + Job.pAmount =&ItemsCount; + Job.Amount =ItemsCount; + JobStart =SysGetTick(); + return PerformOperation(); + } + else + return SetError(errCliJobPending); +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::PlcHotStart() +{ + if (!Job.Pending) + { + Job.Pending =true; + Job.Op =s7opPlcHotStart; + JobStart =SysGetTick(); + return PerformOperation(); + } + else + return SetError(errCliJobPending); +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::PlcColdStart() +{ + if (!Job.Pending) + { + Job.Pending =true; + Job.Op =s7opPlcColdStart; + JobStart =SysGetTick(); + return PerformOperation(); + } + else + return SetError(errCliJobPending); +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::PlcStop() +{ + if (!Job.Pending) + { + Job.Pending =true; + Job.Op =s7opPlcStop; + JobStart =SysGetTick(); + return PerformOperation(); + } + else + return SetError(errCliJobPending); +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::CopyRamToRom(int Timeout) +{ + if (!Job.Pending) + { + if (Timeout>0) + { + Job.Pending =true; + Job.Op =s7opCopyRamToRom; + Job.IParam =Timeout; + JobStart =SysGetTick(); + return PerformOperation(); + } + else + return SetError(errCliInvalidParams); + } + else + return SetError(errCliJobPending); +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::Compress(int Timeout) +{ + if (!Job.Pending) + { + if (Timeout>0) + { + Job.Pending =true; + Job.Op =s7opCompress; + Job.IParam =Timeout; + JobStart =SysGetTick(); + return PerformOperation(); + } + else + return SetError(errCliInvalidParams); + } + else + return SetError(errCliJobPending); +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::GetPlcStatus(int & Status) +{ + if (!Job.Pending) + { + Job.Pending =true; + Job.Op =s7opGetPlcStatus; + Job.pData =&Status; + JobStart =SysGetTick(); + return PerformOperation(); + } + else + return SetError(errCliJobPending); +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::GetProtection(PS7Protection pUsrData) +{ + if (!Job.Pending) + { + Job.Pending =true; + Job.Op =s7opGetProtection; + Job.pData =pUsrData; + JobStart =SysGetTick(); + return PerformOperation(); + } + else + return SetError(errCliJobPending); +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::SetSessionPassword(char *Password) +{ + if (!Job.Pending) + { + size_t L = strlen(Password); + // checks the len + if ((L<1) || (L>8)) + return SetError(errCliInvalidParams); + Job.Pending =true; + // prepares an 8 char string filled with spaces + memset(&opData,0x20,8); + // copies + strncpy((char*)&opData,Password,L); + Job.Op =s7opSetPassword; + JobStart =SysGetTick(); + return PerformOperation(); + } + else + return SetError(errCliJobPending); +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::ClearSessionPassword() +{ + if (!Job.Pending) + { + Job.Pending =true; + Job.Op =s7opClearPassword; + JobStart =SysGetTick(); + return PerformOperation(); + } + else + return SetError(errCliJobPending); +} +//--------------------------------------------------------------------------- + diff --git a/core/src/drivers/plugins/native/s7comm/snap7/core/s7_micro_client.h b/core/src/drivers/plugins/native/s7comm/snap7/core/s7_micro_client.h new file mode 100644 index 00000000..ddbc83ed --- /dev/null +++ b/core/src/drivers/plugins/native/s7comm/snap7/core/s7_micro_client.h @@ -0,0 +1,364 @@ +/*=============================================================================| +| PROJECT SNAP7 1.4.3 | +|==============================================================================| +| Copyright (C) 2013, 2025 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/ | +|=============================================================================*/ +#ifndef s7_micro_client_h +#define s7_micro_client_h +//--------------------------------------------------------------------------- +#include "s7_peer.h" +//--------------------------------------------------------------------------- + +const longword errCliMask = 0xFFF00000; +const longword errCliBase = 0x000FFFFF; + +const longword errCliInvalidParams = 0x00200000; +const longword errCliJobPending = 0x00300000; +const longword errCliTooManyItems = 0x00400000; +const longword errCliInvalidWordLen = 0x00500000; +const longword errCliPartialDataWritten = 0x00600000; +const longword errCliSizeOverPDU = 0x00700000; +const longword errCliInvalidPlcAnswer = 0x00800000; +const longword errCliAddressOutOfRange = 0x00900000; +const longword errCliInvalidTransportSize = 0x00A00000; +const longword errCliWriteDataSizeMismatch = 0x00B00000; +const longword errCliItemNotAvailable = 0x00C00000; +const longword errCliInvalidValue = 0x00D00000; +const longword errCliCannotStartPLC = 0x00E00000; +const longword errCliAlreadyRun = 0x00F00000; +const longword errCliCannotStopPLC = 0x01000000; +const longword errCliCannotCopyRamToRom = 0x01100000; +const longword errCliCannotCompress = 0x01200000; +const longword errCliAlreadyStop = 0x01300000; +const longword errCliFunNotAvailable = 0x01400000; +const longword errCliUploadSequenceFailed = 0x01500000; +const longword errCliInvalidDataSizeRecvd = 0x01600000; +const longword errCliInvalidBlockType = 0x01700000; +const longword errCliInvalidBlockNumber = 0x01800000; +const longword errCliInvalidBlockSize = 0x01900000; +const longword errCliDownloadSequenceFailed = 0x01A00000; +const longword errCliInsertRefused = 0x01B00000; +const longword errCliDeleteRefused = 0x01C00000; +const longword errCliNeedPassword = 0x01D00000; +const longword errCliInvalidPassword = 0x01E00000; +const longword errCliNoPasswordToSetOrClear = 0x01F00000; +const longword errCliJobTimeout = 0x02000000; +const longword errCliPartialDataRead = 0x02100000; +const longword errCliBufferTooSmall = 0x02200000; +const longword errCliFunctionRefused = 0x02300000; +const longword errCliDestroying = 0x02400000; +const longword errCliInvalidParamNumber = 0x02500000; +const longword errCliCannotChangeParam = 0x02600000; + +const time_t DeltaSecs = 441763200; // Seconds between 1970/1/1 (C time base) and 1984/1/1 (Siemens base) + +#pragma pack(1) + +// Read/Write Multivars +typedef struct{ + int Area; + int WordLen; + int Result; + int DBNumber; + int Start; + int Amount; + void *pdata; +} TS7DataItem, *PS7DataItem; + +typedef int TS7ResultItems[MaxVars]; +typedef TS7ResultItems *PS7ResultItems; + +typedef struct { + int OBCount; + int FBCount; + int FCCount; + int SFBCount; + int SFCCount; + int DBCount; + int SDBCount; +} TS7BlocksList, *PS7BlocksList; + +typedef struct { + int BlkType; + int BlkNumber; + int BlkLang; + int BlkFlags; + int MC7Size; // The real size in bytes + int LoadSize; + int LocalData; + int SBBLength; + int CheckSum; + int Version; + // Chars info + char CodeDate[11]; + char IntfDate[11]; + char Author[9]; + char Family[9]; + char Header[9]; +} TS7BlockInfo, *PS7BlockInfo ; + +typedef word TS7BlocksOfType[0x2000]; +typedef TS7BlocksOfType *PS7BlocksOfType; + +typedef struct { + char Code[21]; // Order Code + byte V1; // Version V1.V2.V3 + byte V2; + byte V3; +} TS7OrderCode, *PS7OrderCode; + +typedef struct { + char ModuleTypeName[33]; + char SerialNumber[25]; + char ASName[25]; + char Copyright[27]; + char ModuleName[25]; +} TS7CpuInfo, *PS7CpuInfo; + +typedef struct { + int MaxPduLengt; + int MaxConnections; + int MaxMpiRate; + int MaxBusRate; +} TS7CpInfo, *PS7CpInfo; + +// See §33.1 of "System Software for S7-300/400 System and Standard Functions" +// and see SFC51 description too +typedef struct { + word LENTHDR; + word N_DR; +} SZL_HEADER, *PSZL_HEADER; + +typedef struct { + SZL_HEADER Header; + byte Data[0x4000-4]; +} TS7SZL, *PS7SZL; + +// SZL List of available SZL IDs : same as SZL but List items are big-endian adjusted +typedef struct { + SZL_HEADER Header; + word List[0x2000-2]; +} TS7SZLList, *PS7SZLList; + +// See §33.19 of "System Software for S7-300/400 System and Standard Functions" +typedef struct { + word sch_schal; + word sch_par; + word sch_rel; + word bart_sch; + word anl_sch; +} TS7Protection, *PS7Protection; + +#define s7opNone 0 +#define s7opReadArea 1 +#define s7opWriteArea 2 +#define s7opReadMultiVars 3 +#define s7opWriteMultiVars 4 +#define s7opDBGet 5 +#define s7opUpload 6 +#define s7opDownload 7 +#define s7opDelete 8 +#define s7opListBlocks 9 +#define s7opAgBlockInfo 10 +#define s7opListBlocksOfType 11 +#define s7opReadSzlList 12 +#define s7opReadSZL 13 +#define s7opGetDateTime 14 +#define s7opSetDateTime 15 +#define s7opGetOrderCode 16 +#define s7opGetCpuInfo 17 +#define s7opGetCpInfo 18 +#define s7opGetPlcStatus 19 +#define s7opPlcHotStart 20 +#define s7opPlcColdStart 21 +#define s7opCopyRamToRom 22 +#define s7opCompress 23 +#define s7opPlcStop 24 +#define s7opGetProtection 25 +#define s7opSetPassword 26 +#define s7opClearPassword 27 +#define s7opDBFill 28 + +// Param Number (to use with setparam) + +// Low level : change them to experiment new connections, their defaults normally work well +const int pc_iso_SendTimeout = 6; +const int pc_iso_RecvTimeout = 7; +const int pc_iso_ConnTimeout = 8; +const int pc_iso_SrcRef = 1; +const int pc_iso_DstRef = 2; +const int pc_iso_SrcTSAP = 3; +const int pc_iso_DstTSAP = 4; +const int pc_iso_IsoPduSize = 5; + +// Client Connection Type +const word CONNTYPE_PG = 0x01; // Connect to the PLC as a PG +const word CONNTYPE_OP = 0x02; // Connect to the PLC as an OP +const word CONNTYPE_BASIC = 0x03; // Basic connection + +#pragma pack() + +// Internal struct for operations +// Commands are not executed directly in the function such as "DBRead(...", +// but this struct is filled and then PerformOperation() is called. +// This allow us to implement async function very easily. + +struct TSnap7Job +{ + int Op; // Operation Code + int Result; // Operation result + bool Pending; // A Job is pending + longword Time; // Job Execution time + // Read/Write + int Area; // Also used for Block type and Block of type + int Number; // Used for DB Number, Block number + int Start; // Offset start + int WordLen; // Word length + // SZL + int ID; // SZL ID + int Index; // SZL Index + // ptr info + void * pData; // User data pointer + int Amount; // Items amount/Size in input + int *pAmount; // Items amount/Size in output + // Generic + int IParam; // Used for full upload and CopyRamToRom extended timeout +}; + +class TSnap7MicroClient: public TSnap7Peer +{ +private: + void FillTime(word SiemensTime, char *PTime); + byte BCDtoByte(byte B); + byte WordToBCD(word Value); + int opReadArea(); + int opWriteArea(); + int opReadMultiVars(); + int opWriteMultiVars(); + int opListBlocks(); + int opListBlocksOfType(); + int opAgBlockInfo(); + int opDBGet(); + int opDBFill(); + int opUpload(); + int opDownload(); + int opDelete(); + int opReadSZL(); + int opReadSZLList(); + int opGetDateTime(); + int opSetDateTime(); + int opGetOrderCode(); + int opGetCpuInfo(); + int opGetCpInfo(); + int opGetPlcStatus(); + int opPlcStop(); + int opPlcHotStart(); + int opPlcColdStart(); + int opCopyRamToRom(); + int opCompress(); + int opGetProtection(); + int opSetPassword(); + int opClearPassword(); + int CpuError(int Error); + longword DWordAt(void * P); + int CheckBlock(int BlockType, int BlockNum, void *pBlock, int Size); + int SubBlockToBlock(int SBB); +protected: + word ConnectionType; + longword JobStart; + TSnap7Job Job; + int DataSizeByte(int WordLength); + int opSize; // last operation size + int PerformOperation(); +public: + TS7Buffer opData; + TSnap7MicroClient(); + ~TSnap7MicroClient(); + int Reset(bool DoReconnect); + void SetConnectionParams(const char *RemAddress, word LocalTSAP, word RemoteTsap); + void SetConnectionType(word ConnType); + int ConnectTo(const char *RemAddress, int Rack, int Slot); + int Connect(); + int Disconnect(); + int GetParam(int ParamNumber, void *pValue); + int SetParam(int ParamNumber, void *pValue); + // Fundamental Data I/O functions + int ReadArea(int Area, int DBNumber, int Start, int Amount, int WordLen, void * pUsrData); + int WriteArea(int Area, int DBNumber, int Start, int Amount, int WordLen, void * pUsrData); + int ReadMultiVars(PS7DataItem Item, int ItemsCount); + int WriteMultiVars(PS7DataItem Item, int ItemsCount); + // Data I/O Helper functions + int DBRead(int DBNumber, int Start, int Size, void * pUsrData); + int DBWrite(int DBNumber, int Start, int Size, void * pUsrData); + int MBRead(int Start, int Size, void * pUsrData); + int MBWrite(int Start, int Size, void * pUsrData); + int EBRead(int Start, int Size, void * pUsrData); + int EBWrite(int Start, int Size, void * pUsrData); + int ABRead(int Start, int Size, void * pUsrData); + int ABWrite(int Start, int Size, void * pUsrData); + int TMRead(int Start, int Amount, void * pUsrData); + int TMWrite(int Start, int Amount, void * pUsrData); + int CTRead(int Start, int Amount, void * pUsrData); + int CTWrite(int Start, int Amount, void * pUsrData); + // Directory functions + int ListBlocks(PS7BlocksList pUsrData); + int GetAgBlockInfo(int BlockType, int BlockNum, PS7BlockInfo pUsrData); + int GetPgBlockInfo(void * pBlock, PS7BlockInfo pUsrData, int Size); + int ListBlocksOfType(int BlockType, TS7BlocksOfType *pUsrData, int & ItemsCount); + // Blocks functions + int Upload(int BlockType, int BlockNum, void * pUsrData, int & Size); + int FullUpload(int BlockType, int BlockNum, void * pUsrData, int & Size); + int Download(int BlockNum, void * pUsrData, int Size); + int Delete(int BlockType, int BlockNum); + int DBGet(int DBNumber, void * pUsrData, int & Size); + int DBFill(int DBNumber, int FillChar); + // Date/Time functions + int GetPlcDateTime(tm &DateTime); + int SetPlcDateTime(tm * DateTime); + int SetPlcSystemDateTime(); + // System Info functions + int GetOrderCode(PS7OrderCode pUsrData); + int GetCpuInfo(PS7CpuInfo pUsrData); + int GetCpInfo(PS7CpInfo pUsrData); + int ReadSZL(int ID, int Index, PS7SZL pUsrData, int &Size); + int ReadSZLList(PS7SZLList pUsrData, int &ItemsCount); + // Control functions + int PlcHotStart(); + int PlcColdStart(); + int PlcStop(); + int CopyRamToRom(int Timeout); + int Compress(int Timeout); + int GetPlcStatus(int &Status); + // Security functions + int GetProtection(PS7Protection pUsrData); + int SetSessionPassword(char *Password); + int ClearSessionPassword(); + // Properties + bool Busy(){ return Job.Pending; }; + int Time(){ return int(Job.Time);} +}; + +typedef TSnap7MicroClient *PSnap7MicroClient; + +//--------------------------------------------------------------------------- +#endif // s7_micro_client_h diff --git a/core/src/drivers/plugins/native/s7comm/snap7/core/s7_partner.cpp b/core/src/drivers/plugins/native/s7comm/snap7/core/s7_partner.cpp new file mode 100644 index 00000000..e9e3fdd3 --- /dev/null +++ b/core/src/drivers/plugins/native/s7comm/snap7/core/s7_partner.cpp @@ -0,0 +1,1178 @@ +/*=============================================================================| +| PROJECT SNAP7 1.4.3 | +|==============================================================================| +| Copyright (C) 2013, 2025 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/ | +|=============================================================================*/ +#include "s7_partner.h" +//------------------------------------------------------------------------------ + +static PServersManager ServersManager = NULL; + +//------------------------------------------------------------------------------ +int ServersManager_GetServer(longword BindAddress, PConnectionServer &Server) +{ + if (ServersManager == NULL) + { + ServersManager = new TServersManager(); + } + return ServersManager->GetServer(BindAddress, Server); +} +//------------------------------------------------------------------------------ +void ServersManager_RemovePartner(PConnectionServer Server, PSnap7Partner Partner) +{ + if (ServersManager != NULL) + { + ServersManager->RemovePartner(Server, Partner); + if (ServersManager->ServersCount==0) + { + delete ServersManager; + ServersManager = NULL; + } + } +} +//------------------------------------------------------------------------------ +// CONNECTION SERVERS MANAGER +//------------------------------------------------------------------------------ +TServersManager::TServersManager() +{ + cs = new TSnapCriticalSection; + memset(Servers,0,sizeof(Servers)); + ServersCount=0; +} +//--------------------------------------------------------------------------- +TServersManager::~TServersManager() +{ + int c; + Lock(); + if (ServersCount>0) + { + for (c = 0; c < MaxAdapters; c++) + { + if (Servers[c]!=0) + { + delete Servers[c]; + Servers[c]=0; + ServersCount--; + } + } + } + Unlock(); + delete cs; +} +//--------------------------------------------------------------------------- +void TServersManager::Lock() +{ + cs->Enter(); +} +//--------------------------------------------------------------------------- +void TServersManager::Unlock() +{ + cs->Leave(); +} +//--------------------------------------------------------------------------- +void TServersManager::AddServer(PConnectionServer Server) +{ + int c; + Lock(); + for (c = 0; c < MaxAdapters; c++) + { + if (Servers[c]==0) + { + Servers[c]=Server; + ServersCount++; + break; + } + } + Unlock(); +} +//--------------------------------------------------------------------------- +int TServersManager::CreateServer(longword BindAddress, PConnectionServer &Server) +{ + in_addr sin; + sin.s_addr=BindAddress; + int Result; + + if (ServersCountStartTo(inet_ntoa(sin)); + if (Result!=0) + { + delete Server; + Server=0; + return Result; + } + AddServer(Server); + return 0; + } + else + return errServerNoRoom; +} +//--------------------------------------------------------------------------- +int TServersManager::GetServer(longword BindAddress, PConnectionServer &Server) +{ + int c; + Server=0; + for (c = 0; c < ServersCount; c++) + { + if (Servers[c]->LocalBind==BindAddress) + { + Server=Servers[c]; + break; + } + } + if (Server==0) + return CreateServer(BindAddress, Server); + else + return 0; +} +//--------------------------------------------------------------------------- +void TServersManager::RemovePartner(PConnectionServer Server, PSnap7Partner Partner) +{ + int c; + Server->RemovePartner(Partner); + if (Server->PartnersCount==0) + { + Lock(); + for (c = 0; c < MaxAdapters; c++) + { + if (Servers[c]==Server) + { + Servers[c]=0; + ServersCount--; + break; + } + } + Unlock(); + delete Server; + } +} +//--------------------------------------------------------------------------- +// CONNECTION SERVER +//------------------------------------------------------------------------------ +void TConnListenerThread::Execute() +{ + socket_t Sock; + bool Valid; + + while (!Terminated) + { + if (FListener->CanRead(FListener->WorkInterval)) + { + Sock=FListener->SckAccept(); // in any case we must accept + Valid=Sock!=INVALID_SOCKET; + // check if we are not destroying + if ((!Terminated) && (!FServer->Destroying)) + { + if (Valid) + FServer->Incoming(Sock); + } + else + if (Valid) + Msg_CloseSocket(Sock); + }; + } +} +//------------------------------------------------------------------------------ +TConnectionServer::TConnectionServer() +{ + cs = new TSnapCriticalSection; + memset(Partners,0,sizeof(Partners)); + FRunning = false; + PartnersCount = 0; +} +//------------------------------------------------------------------------------ +TConnectionServer::~TConnectionServer() +{ + Stop(); + delete cs; +} +//--------------------------------------------------------------------------- +void TConnectionServer::Lock() +{ + cs->Enter(); +} +void TConnectionServer::Unlock() +{ + cs->Leave(); +} +//--------------------------------------------------------------------------- +int TConnectionServer::Start() +{ + int Result; + // Creates the listener + SockListener = new TMsgSocket(); + strncpy(SockListener->LocalAddress,FLocalAddress,16); + SockListener->LocalPort=isoTcpPort; + // Binds + Result=SockListener->SckBind(); + if (Result==0) + { + LocalBind=SockListener->LocalBind; + // Listen + Result=SockListener->SckListen(); + if (Result==0) + { + // Creates the Listener thread + ServerThread = new TConnListenerThread(SockListener, this); + ServerThread->Start(); + } + else + delete SockListener; + } + else + delete SockListener; + + FRunning=Result==0; + return Result; +} +//--------------------------------------------------------------------------- +int TConnectionServer::StartTo(const char *Address) +{ + strncpy(FLocalAddress,Address,16); + return Start(); +} +//--------------------------------------------------------------------------- +void TConnectionServer::Stop() +{ + if (FRunning) + { + // Kills the listener thread + ServerThread->Terminate(); + if (ServerThread->WaitFor(csTimeout)!=WAIT_OBJECT_0) + ServerThread->Kill(); + delete ServerThread; + // Kills the listener + delete SockListener; + FRunning = false; + } +} +//--------------------------------------------------------------------------- +PSnap7Partner TConnectionServer::FindPartner(longword Address) +{ + int c; + PSnap7Partner Result; + for (c = 0; c < MaxPartners; c++) + { + Result=Partners[c]; + if (Result!=NULL) + { + if (Result->PeerAddress==Address) + return Result; + } + } + return NULL; +} +//------------------------------------------------------------------------------ +int TConnectionServer::FirstFree() +{ + int i; + for (i = 0; i < MaxPartners; i++) + { + if (Partners[i]==0) + return i; + } + return -1; +} +//------------------------------------------------------------------------------ +int TConnectionServer::RegisterPartner(PSnap7Partner Partner) +{ + PSnap7Partner aPartner; + int idx; + // check if already exists a passive partner linked to the same peer address + aPartner=FindPartner(Partner->PeerAddress); + if (aPartner==NULL) + { + Lock(); + idx=FirstFree(); + if (idx>=0) + { + Partners[idx]=Partner; + PartnersCount++; + } + Unlock(); + if (idx>=0) + return 0; + else + return errParNoRoom; + } + else + return errParAddressInUse; +} +//------------------------------------------------------------------------------ +void TConnectionServer::RemovePartner(PSnap7Partner Partner) +{ + int c; + Lock(); + for (c = 0; c < MaxPartners; c++) + { + if (Partners[c]==Partner) + { + Partners[c]=0; + PartnersCount--; + break; + } + } + Unlock(); +} +//------------------------------------------------------------------------------ +void TConnectionServer::Incoming(socket_t Sock) +{ + longword Address; + PSnap7Partner Partner; + + Address=Msg_GetSockAddr(Sock); + // Looks for a partner that is waiting for a connection from this address + Lock(); + Partner=FindPartner(Address); + Unlock(); + // if partner exists must not be already connected : a partner can be connected + // with only one peer at time + if ((Partner!=NULL) && (!Partner->Stopping) && (!Partner->Connected)) + Partner->SetSocket(Sock); + else + Msg_CloseSocket(Sock); // we are not interested +} +//------------------------------------------------------------------------------ +// PARTHER THREAD +//------------------------------------------------------------------------------ +void TPartnerThread::Execute() +{ + longword TheTime; + + FKaElapsed=SysGetTick(); + while ((!Terminated) && (!FPartner->Destroying)) + { + // Check connection + while (!Terminated && !FPartner->Connected && !FPartner->Destroying) + { + if (!FPartner->ConnectToPeer()) + SysSleep(FRecoveryTime); + } + // Execution + if ((!Terminated) && (!FPartner->Destroying) && (!FPartner->Execute())) + SysSleep(FRecoveryTime); + // Keep Alive + if (!Terminated && (!FPartner->Destroying) && FPartner->Active && FPartner->Connected) + { + TheTime=SysGetTick(); + if (TheTime-FKaElapsed>FPartner->KeepAliveTime) + { + FKaElapsed=TheTime; + if (!FPartner->Ping(FPartner->RemoteAddress)) + FPartner->Disconnect(); + }; + }; + }; +} +//------------------------------------------------------------------------------ +// S7 PARTNER +//------------------------------------------------------------------------------ +TSnap7Partner::TSnap7Partner(bool CreateActive) +{ + // We skip RFC/ISO header, our PDU is the ISO payload + PDUH_in=PS7ReqHeader(&PDU.Payload); + FWorkerThread=0; + OnBRecv = 0; + OnBSend = 0; + Active=CreateActive; + SendEvt = new TSnapEvent(true); + RecvEvt = new TSnapEvent(true); + FSendPending = false; + FRecvPending = false; + memset(&FRecvStatus,0,sizeof(TRecvStatus)); + memset(&FRecvLast,0,sizeof(TRecvLast)); + FSendElapsed = 0; + Destroying = false; + // public + Linked =false; + Running =false; + BindError =false; + BRecvTimeout =3000; + BSendTimeout =3000; + RecoveryTime =500; + KeepAliveTime =5000; + NextByte =0; + PeerAddress =0; + SendTime =0; + RecvTime =0; + BytesSent =0; + BytesRecv =0; + SendErrors =0; + RecvErrors =0; +} +//------------------------------------------------------------------------------ +TSnap7Partner::~TSnap7Partner() +{ + Stop(); + OnBRecv = 0; + OnBSend = 0; + delete SendEvt; + delete RecvEvt; +} +//------------------------------------------------------------------------------ +byte TSnap7Partner::GetNextByte() +{ + NextByte++; + if (NextByte==0xFF) + NextByte=1; + return NextByte; +} +//------------------------------------------------------------------------------ +int TSnap7Partner::Start() +{ + int Result; + PeerAddress=inet_addr(RemoteAddress); + SrcAddress =inet_addr(LocalAddress); + if (!Running) + { + if (!Active) + { + Result=ServersManager_GetServer(SrcAddress,FServer); + if (Result==0) + FServer->RegisterPartner(this); + BindError=Result!=0; + } + else + { + Linked=PeerConnect()==0; + Result=0; // we need to create the worker thread even tough it's not linked + }; + // if ok create the worker thread + if (Result==0) + { + FWorkerThread = new TPartnerThread(this, RecoveryTime); + FWorkerThread->Start(); + } + } + else + Result=0; + + Running=Result==0; + + return Result; +} +//------------------------------------------------------------------------------ +int TSnap7Partner::StartTo(const char *LocAddress, const char *RemAddress, word LocTsap, word RemTsap) +{ + SrcTSap=LocTsap; + DstTSap=RemTsap; + strcpy(LocalAddress,LocAddress); + strcpy(RemoteAddress,RemAddress); + return Start(); +} +//------------------------------------------------------------------------------ +int TSnap7Partner::Stop() +{ + if (Running) + { + Stopping=true; // to prevent incoming connections + CloseWorker(); + if (!Active && (FServer!=0)) + ServersManager_RemovePartner(FServer, this); + if (Connected) + Disconnect(); + Running =false; + Stopping=false; + }; + BindError=false; + return 0; +} +//------------------------------------------------------------------------------ +void TSnap7Partner::Disconnect() +{ + PeerDisconnect(); + Linked=false; +} +//------------------------------------------------------------------------------ +int TSnap7Partner::GetParam(int ParamNumber, void * pValue) +{ + switch (ParamNumber) + { + case p_u16_LocalPort: + *Puint16_t(pValue)=LocalPort; + break; + case p_u16_RemotePort: + *Puint16_t(pValue)=RemotePort; + break; + case p_i32_PingTimeout: + *Pint32_t(pValue)=PingTimeout; + break; + case p_i32_SendTimeout: + *Pint32_t(pValue)=SendTimeout; + break; + case p_i32_RecvTimeout: + *Pint32_t(pValue)=RecvTimeout; + break; + case p_i32_WorkInterval: + *Pint32_t(pValue)=WorkInterval; + break; + case p_u16_SrcRef: + *Puint16_t(pValue)=SrcRef; + break; + case p_u16_DstRef: + *Puint16_t(pValue)=DstRef; + break; + case p_u16_SrcTSap: + *Puint16_t(pValue)=SrcTSap; + break; + case p_i32_PDURequest: + *Pint32_t(pValue)=PDURequest; + break; + case p_i32_BSendTimeout: + *Pint32_t(pValue)=BSendTimeout; + break; + case p_i32_BRecvTimeout: + *Pint32_t(pValue)=BRecvTimeout; + break; + case p_u32_RecoveryTime: + *Puint32_t(pValue)=RecoveryTime; + break; + case p_u32_KeepAliveTime: + *Puint32_t(pValue)=KeepAliveTime; + break; + default: return errParInvalidParamNumber; + } + return 0; +} +//------------------------------------------------------------------------------ +int TSnap7Partner::SetParam(int ParamNumber, void * pValue) +{ + switch (ParamNumber) + { + case p_u16_RemotePort: + if (!Connected && Active) + RemotePort=*Puint16_t(pValue); + else + return errParCannotChangeParam; + break; + case p_i32_PingTimeout: + PingTimeout=*Pint32_t(pValue); + break; + case p_i32_SendTimeout: + SendTimeout=*Pint32_t(pValue); + break; + case p_i32_RecvTimeout: + RecvTimeout=*Pint32_t(pValue); + break; + case p_i32_WorkInterval: + WorkInterval=*Pint32_t(pValue); + break; + case p_u16_SrcRef: + SrcRef=*Puint16_t(pValue); + break; + case p_u16_DstRef: + DstRef=*Puint16_t(pValue); + break; + case p_u16_SrcTSap: + SrcTSap=*Puint16_t(pValue); + break; + case p_i32_PDURequest: + PDURequest=*Pint32_t(pValue); + break; + case p_i32_BSendTimeout: + BSendTimeout=*Pint32_t(pValue); + break; + case p_i32_BRecvTimeout: + BRecvTimeout=*Pint32_t(pValue); + break; + case p_u32_RecoveryTime: + RecoveryTime=*Puint32_t(pValue); + break; + case p_u32_KeepAliveTime: + KeepAliveTime=*Puint32_t(pValue); + break; + default: return errParInvalidParamNumber; + } + return 0; +} +//------------------------------------------------------------------------------ +void TSnap7Partner::ClearRecv() +{ + memset(&FRecvStatus,0,sizeof(TRecvStatus)); + FRecvPending=false; +} +//------------------------------------------------------------------------------ +bool TSnap7Partner::ConnectToPeer() +{ + bool Result; + if (Active) + { + Result=PeerConnect()==0; // try to Connect + Linked=Result; + } + else + Result =false; // nothing : we are waiting for a connection + + return Result; +} +//------------------------------------------------------------------------------ +bool TSnap7Partner::PerformFunctionNegotiate() +{ + PReqFunNegotiateParams ReqParams; + PResFunNegotiateParams ResParams; + TS7Answer23 Answer; + int Size; + + // Setup pointers + ReqParams=PReqFunNegotiateParams(pbyte(PDUH_in)+sizeof(TS7ReqHeader)); + ResParams=PResFunNegotiateParams(pbyte(&Answer)+sizeof(TS7ResHeader23)); + // We are here only because we found a PduType_request, the partner can only + // handle Bs} requests and pdu negotiation requests. + // So, now we must check the incoming function + if (ReqParams->FunNegotiate!=pduNegotiate) + { + LastError=errParInvalidPDU; + return false; + }; + // Prepares the answer + Answer.Header.P=0x32; + Answer.Header.PDUType =0x03; + Answer.Header.AB_EX =0x0000; + Answer.Header.Sequence=PDUH_in->Sequence; + Answer.Header.ParLen =SwapWord(sizeof(TResFunNegotiateParams)); + Answer.Header.DataLen =0x0000; + Answer.Header.Error =0x0000; + // Params point at the } of the header + ResParams->FunNegotiate=pduNegotiate; + ResParams->Unknown=0x0; + // Checks PDU request length + if (SwapWord(ResParams->PDULength)>IsoPayload_Size) + ResParams->PDULength=SwapWord(IsoPayload_Size); + else + ResParams->PDULength=ReqParams->PDULength; + // We offer the same + ResParams->ParallelJobs_1=ReqParams->ParallelJobs_1; + ResParams->ParallelJobs_2=ReqParams->ParallelJobs_2; + // And store the value + PDULength=SwapWord(ResParams->PDULength); + // Sends the answer + Size=sizeof(TS7ResHeader23) + sizeof(TResFunNegotiateParams); + if (isoSendBuffer(&Answer, Size)!=0) + SetError(errParNegotiatingPDU); + + Linked=LastError==0; + return Linked; +} +//------------------------------------------------------------------------------ +void TSnap7Partner::CloseWorker() +{ + int Timeout; + if (FWorkerThread) + { + FWorkerThread->Terminate(); + if (FRecvPending || FSendPending) + Timeout=3000; + else + Timeout=1000; + + if (FWorkerThread->WaitFor(Timeout)!=WAIT_OBJECT_0) + FWorkerThread->Kill(); + try { + delete FWorkerThread; + } + catch (...){ + } + FWorkerThread=0; + } +} +//------------------------------------------------------------------------------ +bool TSnap7Partner::BlockSend() +{ + PBSendReqParams ReqParams; + PBSendReqParams ResParams; + PBsendRequestData DataSendReq; + int TotalSize; + int SentSize; + int Slice; + int MaxSlice; + uintptr_t Offset; + pbyte Source; + bool First, Last; + byte Seq_IN; + int TxIsoSize; + pbyte Data; + pword TotalPackSize; + int DataPtrOffset; + word Extra; + + ClrError(); + TotalSize=TxBuffer.Size; + SentSize =TotalSize; + Offset=0; + First =true; + Seq_IN=0x00; + + // With BSend we can transfer up to 32k (S7300) or 64k (S7400), but splitted + // into slice that cannot exced the PDU size negotiated (including various headers). + MaxSlice=PDULength-sizeof(TS7ReqHeader)-sizeof(TBSendParams)-sizeof(TBsendRequestData)-2; + + ReqParams=PBSendReqParams(pbyte(PDUH_out)+sizeof(TS7ReqHeader)); + ResParams=ReqParams; // pdu 7 is symmetrical + + while ((TotalSize>0) && (LastError==0)) + { + Source=pbyte(&TxBuffer.Data)+Offset; + Slice=TotalSize; + + if (Slice>MaxSlice) + Slice=MaxSlice; + + TotalSize-=Slice; + Offset+=Slice; + Last=TotalSize==0; + + // Prepare send + DataPtrOffset=sizeof(TS7ReqHeader)+sizeof(TBSendParams); + // Header + PDUH_out->P=0x32; // Always 0x32 + PDUH_out->PDUType=PduType_userdata; // 7 + PDUH_out->AB_EX=0x0000; // Always 0x0000 + PDUH_out->Sequence=GetNextWord(); // Autoinc + PDUH_out->ParLen=SwapWord(sizeof(TBSendParams)); // 16 bytes + + ReqParams->Head[0]=0x00; + ReqParams->Head[1]=0x01; + ReqParams->Head[2]=0x12; + ReqParams->Plen =0x08; // length from here up the end of the record + ReqParams->Uk =0x12; + ReqParams->Tg =grBSend; // 0x46 + ReqParams->SubFun =0x01; + ReqParams->Seq =Seq_IN; + ReqParams->Err =0x0000; + if (Last) + ReqParams->EoS =0x00; + else + ReqParams->EoS =0x01; + // Next byte is auto inc and not zero for partial sequences + // Is zero for lonely sequences. + if (First && Last) + ReqParams->IDSeq=0x00; + else + ReqParams->IDSeq=GetNextByte(); + + DataSendReq=PBsendRequestData(pbyte(PDUH_out)+DataPtrOffset); + if (First) + { + // in the first pdu, after data header there is the whole packet length + TotalPackSize=pword(pbyte(DataSendReq)+sizeof(TBsendRequestData)); + Data=pbyte(TotalPackSize)+sizeof(word); + *TotalPackSize=SwapWord(word(TxBuffer.Size)); + Extra=2; // extra bytes (total pack size indicator) + } + else + { + Data=pbyte(DataSendReq)+sizeof(TBsendRequestData); + Extra=0; + }; + + PDUH_out->DataLen=SwapWord(word(sizeof(TBsendRequestData))+Slice+Extra); + DataSendReq->Len =SwapWord(Slice+8+Extra); + TxIsoSize=Slice+sizeof(TS7ReqHeader)+sizeof(TBSendParams)+sizeof(TBsendRequestData)+Extra; + + DataSendReq->FF =0xFF; + DataSendReq->TRSize =TS_ResOctet; + DataSendReq->DHead[0]=0x12; + DataSendReq->DHead[1]=0x06; + DataSendReq->DHead[2]=0x13; + DataSendReq->DHead[3]=0x00; + DataSendReq->R_ID =SwapDWord(TxBuffer.R_ID); + memcpy(Data, Source ,Slice); + + if (isoExchangeBuffer(NULL, TxIsoSize)!=0) + SetError(errParSendingBlock); + + if (LastError==0) + { + Seq_IN=ResParams->Seq; + if (SwapWord(ResParams->Err)!=0) + LastError=errParSendRefused; + } + + if (First) + { + First =false; + MaxSlice+=2; // only in the first frame we have the extra info + }; + }; + + SendTime=SysGetTick()-FSendElapsed; + if (LastError==0) + BytesSent+=SentSize; + + return LastError==0; +} +//------------------------------------------------------------------------------ +bool TSnap7Partner::PickData() +{ + PBSendReqParams ReqParams; + PBSendReqParams ResParams; + PBSendResData ResData; + PBsendRequestData DataSendReq; + pbyte Source, Target; + pword TotalPackSize; + word Slice; + int AnswerLen; + + ClrError(); + // Setup pointers + ReqParams =PBSendReqParams(pbyte(PDUH_in)+sizeof(TS7ReqHeader)); + ResParams =ReqParams; // pdu 7 is symmetrical + DataSendReq=PBsendRequestData(pbyte(ReqParams)+sizeof(TBSendParams)); + + // Checks if PDU is a BSend request + if ((PDUH_in->PDUType!=PduType_userdata) || (ReqParams->Tg!=grBSend)) + { + LastError=errParInvalidPDU; + return false; + } + + if (FRecvStatus.First) + { + TotalPackSize=(word*)(pbyte(DataSendReq)+sizeof(TBsendRequestData)); + FRecvStatus.TotalLength=SwapWord(*TotalPackSize); + Source=pbyte(DataSendReq)+sizeof(TBsendRequestData)+2; + FRecvStatus.In_R_ID=SwapDWord(DataSendReq->R_ID); + FRecvStatus.Offset=0; + Slice=SwapWord(DataSendReq->Len)-10; + } + else { + Slice=SwapWord(DataSendReq->Len)-8; + Source=pbyte(DataSendReq)+sizeof(TBsendRequestData); + } + + FRecvStatus.Done=ReqParams->EoS==0x00; + + Target=pbyte(&RxBuffer)+FRecvStatus.Offset; + memcpy(Target, Source, Slice); + FRecvStatus.Offset+=Slice; + + ResData =PBSendResData(pbyte(ResParams)+sizeof(TBSendParams)); + // Send Answer + PDUH_out->ParLen =SwapWord(sizeof(TBSendParams)); + PDUH_out->DataLen =SwapWord(sizeof(TBSendResData)); + + ResParams->Head[0]=0x00; + ResParams->Head[1]=0x01; + ResParams->Head[2]=0x12; + ResParams->Plen =0x08; // length from here up the end of the record + ResParams->Uk =0x12; + ResParams->Tg =0x86; + ResParams->SubFun =0x01; + ResParams->Seq =FRecvStatus.Seq_Out; + ResParams->Err =0x0000; + ResParams->EoS =0x00; + ResParams->IDSeq =0x00; + + ResData->DHead[0] =0x0A; + ResData->DHead[1] =0x00; + ResData->DHead[2] =0x00; + ResData->DHead[3] =0x00; + + AnswerLen=sizeof(TS7ReqHeader)+sizeof(TBSendParams)+sizeof(TBSendResData); + if (isoSendBuffer(NULL,AnswerLen)!=0) + SetError(errParRecvingBlock); + + return LastError==0; +} +//------------------------------------------------------------------------------ +bool TSnap7Partner::BlockRecv() +{ + bool Result; + if (!FRecvPending) // Start sequence + { + FRecvPending=true; + FRecvStatus.First=true; + FRecvStatus.Done =false; + FRecvStatus.Seq_Out =GetNextByte(); + FRecvStatus.Elapsed =SysGetTick(); + FRecvLast.Done=false; + FRecvLast.Result=0; + FRecvLast.R_ID=0; + FRecvLast.Size=0; + RecvTime =0; + FRecvLast.Count++; + if (FRecvLast.Count==0xFFFFFFFF) + FRecvLast.Count=0; + }; + + Result=PickData(); + FRecvStatus.First=false; + + if (!Result || FRecvStatus.Done) + { + FRecvLast.Result=LastError; + if (Result) + { + BytesRecv+=FRecvStatus.TotalLength; + RecvTime=SysGetTick()-FRecvStatus.Elapsed; + FRecvLast.R_ID=FRecvStatus.In_R_ID; + FRecvLast.Size=FRecvStatus.TotalLength; + }; + RecvEvt->Set(); + if ((OnBRecv!=NULL) && !Destroying) + OnBRecv(FRecvUsrPtr, FRecvLast.Result, FRecvLast.R_ID, &RxBuffer, FRecvLast.Size); + FRecvLast.Done=true; + ClearRecv(); + }; + return Result; +} +//------------------------------------------------------------------------------ +bool TSnap7Partner::ConnectionConfirm() +{ + if (FRecvPending) + ClearRecv(); + IsoConfirmConnection(pdu_type_CC); // <- Connection confirm + return LastTcpError!=WSAECONNRESET; +} +//------------------------------------------------------------------------------ +int TSnap7Partner::Status() +{ + if (Running) + { + if (Linked) + { + if (FRecvPending) + return par_receiving; + else + if (FSendPending) + return par_sending; + else + return par_linked; + } + else + if (Active) + return par_connecting; + else + return par_waiting; + } + else{ + if ((!Active) && BindError) + return par_binderror; + else + return par_stopped; + } +} +//------------------------------------------------------------------------------ +bool TSnap7Partner::Execute() +{ + TPDUKind PduKind; + bool RTimeout; + bool Result =true; + + // Checks if there is something to send (and we are not receiving...) + if (FSendPending && !FRecvPending) + { + Result=BlockSend(); + SendEvt->Set(); + if ((OnBSend!=NULL) && (!Destroying)) + OnBSend(FSendUsrPtr, LastError); + FSendPending=false; + } + + if (Destroying) + return false; + + // Checks if there is something to recv + if (Result && CanRead(WorkInterval)) + { + // Peeks info and returns PDU Kind + isoRecvPDU(&PDU); + if (LastTcpError==0) + { + // First check valid data incoming (most likely situation) + IsoPeek(&PDU,PduKind); + if (PduKind==pkValidData) + { + if (PDUH_in->PDUType==PduType_request) + { + if (FRecvPending) + ClearRecv(); + Result=PerformFunctionNegotiate(); + } + else // Pdu type userdata + Result=BlockRecv(); + } + else + if (PduKind==pkConnectionRequest) + Result=ConnectionConfirm(); + else // nothing else + Purge(); + } + else + Result=false; + }; + + if (LastTcpError==WSAECONNRESET) + { + Result=false; + Linked=false; + } + else + if (!Result) + Disconnect(); + + // Check BRecv sequence timeout + RTimeout= FRecvPending && (SysGetTick()-FRecvStatus.Elapsed>longword(BRecvTimeout)); + + if (RTimeout) + { + LastError=errParFrameTimeout; + RecvEvt->Set(); + if ((OnBRecv!=NULL) && !Destroying) + OnBRecv(FRecvUsrPtr, LastError, 0, &RxBuffer,0); + }; + + if (!Result || RTimeout) + ClearRecv(); // parframetimeout + + return Result; +} + +//------------------------------------------------------------------------------ +int TSnap7Partner::BSend(longword R_ID, void *pUsrData, int Size) +{ + // The block send is managed into the worker thread. + // Sync Bsend consists of AsBSend+WaitAsCompletion + int Result=AsBSend(R_ID, pUsrData, Size); + if (Result==0) + Result=WaitAsBSendCompletion(BSendTimeout); + return Result; +} +//------------------------------------------------------------------------------ +int TSnap7Partner::AsBSend(longword R_ID, void *pUsrData, int Size) +{ + SendTime=0; + if (Linked) + { + if (!FSendPending) + { + memcpy(&TxBuffer.Data, pUsrData, Size); + TxBuffer.R_ID=R_ID; + TxBuffer.Size=Size; + SendEvt->Reset(); + FSendPending=true; + FSendElapsed=SysGetTick(); + return 0; + } + else + return errParBusy; + } + else + return SetError(errParNotLinked); +} +//------------------------------------------------------------------------------ +bool TSnap7Partner::CheckAsBSendCompletion(int &opResult) +{ + if (!Destroying) + { + if (!FSendPending) + opResult=LastError; + else + opResult=errParBusy; + + return !FSendPending; + } + else + { + opResult=errParDestroying; + return true; + } + +} +//------------------------------------------------------------------------------ +int TSnap7Partner::WaitAsBSendCompletion(longword Timeout) +{ + if (SendEvt->WaitFor(BSendTimeout)==WAIT_OBJECT_0) + { + if (!Destroying) + return LastError; + else + return SetError(errParDestroying); + } + else + return SetError(errParSendTimeout); +} +//------------------------------------------------------------------------------ +int TSnap7Partner::SetSendCallback(pfn_ParBSendCompletion pCompletion, void *usrPtr) +{ + OnBSend=pCompletion; + FSendUsrPtr=usrPtr; + return 0; +} +//------------------------------------------------------------------------------ +int TSnap7Partner::BRecv(longword &R_ID, void *pData, int &Size, longword Timeout) +{ + int Result=0; + if (RecvEvt->WaitFor(Timeout)==WAIT_OBJECT_0) + { + R_ID =FRecvLast.R_ID; + Size =FRecvLast.Size; + if (FRecvLast.Result==0) + { + if (pData!=NULL) + memcpy(pData, &RxBuffer, Size); + else + Result=errParInvalidParams; + } + else + Result=FRecvLast.Result; + RecvEvt->Reset(); + } + else + Result=errParRecvTimeout; + + return SetError(Result); +} +//------------------------------------------------------------------------------ +bool TSnap7Partner::CheckAsBRecvCompletion(int &opResult, longword &R_ID, + void *pData, int &Size) +{ + if (Destroying) + { + Size=0; + opResult=errParDestroying; + return true; + } + + bool Result=FRecvLast.Done; + if (Result) + { + Size=FRecvLast.Size; + R_ID=FRecvLast.R_ID; + opResult=FRecvLast.Result; + if ((pData!=NULL) && (Size>0) && (opResult==0)) + memcpy(pData, &RxBuffer, Size); + FRecvLast.Done=false; + } + return Result; +} +//------------------------------------------------------------------------------ +int TSnap7Partner::SetRecvCallback(pfn_ParBRecvCallBack pCompletion, void *usrPtr) +{ + OnBRecv=pCompletion; + FRecvUsrPtr=usrPtr; + return 0; +} +//------------------------------------------------------------------------------ + diff --git a/core/src/drivers/plugins/native/s7comm/snap7/core/s7_partner.h b/core/src/drivers/plugins/native/s7comm/snap7/core/s7_partner.h new file mode 100644 index 00000000..dbc20152 --- /dev/null +++ b/core/src/drivers/plugins/native/s7comm/snap7/core/s7_partner.h @@ -0,0 +1,284 @@ +/*=============================================================================| +| PROJECT SNAP7 1.4.3 | +|==============================================================================| +| Copyright (C) 2013, 2025 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/ | +|=============================================================================*/ +#ifndef s7_partner_h +#define s7_partner_h +//--------------------------------------------------------------------------- +#include "snap_threads.h" +#include "s7_peer.h" +//--------------------------------------------------------------------------- +using namespace std; +//--------------------------------------------------------------------------- + +#define MaxPartners 256 +#define MaxAdapters 256 +#define csTimeout 1500 // Connection server destruction timeout + +const int par_stopped = 0; // stopped +const int par_connecting = 1; // running and active connecting +const int par_waiting = 2; // running and waiting for a connection +const int par_linked = 3; // running and connected +const int par_sending = 4; // sending data +const int par_receiving = 5; // receiving data +const int par_binderror = 6; // error starting passive partner + +const longword errParMask = 0xFFF00000; +const longword errParBase = 0x000FFFFF; + +const longword errParAddressInUse = 0x00200000; +const longword errParNoRoom = 0x00300000; +const longword errServerNoRoom = 0x00400000; +const longword errParInvalidParams = 0x00500000; +const longword errParNotLinked = 0x00600000; +const longword errParBusy = 0x00700000; +const longword errParFrameTimeout = 0x00800000; +const longword errParInvalidPDU = 0x00900000; +const longword errParSendTimeout = 0x00A00000; +const longword errParRecvTimeout = 0x00B00000; +const longword errParSendRefused = 0x00C00000; +const longword errParNegotiatingPDU = 0x00D00000; +const longword errParSendingBlock = 0x00E00000; +const longword errParRecvingBlock = 0x00F00000; +const longword errParBindError = 0x01000000; +const longword errParDestroying = 0x01100000; +const longword errParInvalidParamNumber = 0x01200000; // Invalid param (par_get/set_param) +const longword errParCannotChangeParam = 0x01300000; // Cannot change because running +const longword errParBufferTooSmall = 0x01400000; // Raised by LabVIEW wrapper + +class TSnap7Partner; +typedef TSnap7Partner *PSnap7Partner; + +class TConnectionServer; +typedef TConnectionServer *PConnectionServer; + +//------------------------------------------------------------------------------ +// CONNECTION SERVERS MANAGER +//------------------------------------------------------------------------------ +class TServersManager +{ +private: + PConnectionServer Servers[MaxAdapters]; + TSnapCriticalSection *cs; + void Lock(); + void Unlock(); + int CreateServer(longword BindAddress, PConnectionServer &Server); + void AddServer(PConnectionServer Server); +public: + int ServersCount; + TServersManager(); + ~TServersManager(); + + int GetServer(longword BindAddress, PConnectionServer &Server); + void RemovePartner(PConnectionServer Server, PSnap7Partner Partner); + +}; +typedef TServersManager *PServersManager; + +//------------------------------------------------------------------------------ +// CONNECTION SERVER (Don't inherit from TcpSrv to avoid dependence) +//------------------------------------------------------------------------------ + +class TConnListenerThread : public TSnapThread +{ +private: + TMsgSocket *FListener; + TConnectionServer *FServer; +public: + TConnListenerThread(TMsgSocket *Listener, TConnectionServer *Server) + { + FServer=Server; + FListener=Listener; + FreeOnTerminate=false; + }; + void Execute(); +}; +typedef TConnListenerThread *PConnListenerThread; + + +class TConnectionServer +{ +private: + TSnapCriticalSection *cs; + bool FRunning; + // Bind Address + char FLocalAddress[16]; + // Server listener + PConnListenerThread ServerThread; + // Socket listener + PMsgSocket SockListener; + // Finds a partner bound to the address + PSnap7Partner FindPartner(longword Address); + // Locks the Partner list + void Lock(); + // Unlocks the Partner list + void Unlock(); +protected: + // Workers list + PSnap7Partner Partners[MaxPartners]; + bool Destroying; + void Incoming(socket_t Sock); + int Start(); + int FirstFree(); +public: + int PartnersCount; + longword LocalBind; + TConnectionServer(); + ~TConnectionServer(); + + int StartTo(const char *Address); + void Stop(); + int RegisterPartner(PSnap7Partner Partner); + void RemovePartner(PSnap7Partner Partner); + friend class TConnListenerThread; +}; +typedef TConnectionServer * PConnectionServer; + +//------------------------------------------------------------------------------ +// PARTNER THREAD +//------------------------------------------------------------------------------ +class TPartnerThread : public TSnapThread +{ +private: + TSnap7Partner *FPartner; + longword FRecoveryTime; + longword FKaElapsed; +protected: + void Execute(); +public: + TPartnerThread(TSnap7Partner *Partner, longword RecoveryTime) + { + FPartner = Partner; + FRecoveryTime =RecoveryTime; + FreeOnTerminate =false; + }; + ~TPartnerThread(){}; +}; +typedef TPartnerThread *PPartnerThread; +//------------------------------------------------------------------------------ +// S7 PARTNER +//------------------------------------------------------------------------------ + +typedef struct{ + bool First; + bool Done; + uintptr_t Offset; + longword TotalLength; + longword In_R_ID; + longword Elapsed; + byte Seq_Out; +}TRecvStatus; + +typedef struct{ + bool Done; + int Size; + int Result; + longword R_ID; + longword Count; +}TRecvLast; + +extern "C" { +typedef void (S7API *pfn_ParBRecvCallBack)(void * usrPtr, int opResult, longword R_ID, void *pdata, int Size); +typedef void (S7API *pfn_ParBSendCompletion)(void * usrPtr, int opResult); +} + +class TSnap7Partner : public TSnap7Peer +{ +private: + PS7ReqHeader PDUH_in; + void *FRecvUsrPtr; + void *FSendUsrPtr; + PSnapEvent SendEvt; + PSnapEvent RecvEvt; + PConnectionServer FServer; + PPartnerThread FWorkerThread; + bool FSendPending; + bool FRecvPending; + TRecvStatus FRecvStatus; + TRecvLast FRecvLast; + TPendingBuffer TxBuffer; + TPendingBuffer RxBuffer; + longword FSendElapsed; + bool BindError; + byte NextByte; + pfn_ParBRecvCallBack OnBRecv; + pfn_ParBSendCompletion OnBSend; + void ClearRecv(); + byte GetNextByte(); + void CloseWorker(); + bool BlockSend(); + bool PickData(); + bool BlockRecv(); + bool ConnectionConfirm(); +protected: + bool Stopping; + bool Execute(); + void Disconnect(); + bool ConnectToPeer(); + bool PerformFunctionNegotiate(); +public: + bool Active; + bool Running; + longword PeerAddress; + longword SrcAddress; + int BRecvTimeout; + int BSendTimeout; + longword SendTime; + longword RecvTime; + longword RecoveryTime; + longword KeepAliveTime; + longword BytesSent; + longword BytesRecv; + longword SendErrors; + longword RecvErrors; + // The partner is linked when the init sequence is terminated + //(TCP connection + ISO connection + PDU Length negotiation) + bool Linked; + TSnap7Partner(bool CreateActive); + ~TSnap7Partner(); + // Control + int Start(); + int StartTo(const char *LocAddress, const char *RemAddress, word LocTsap, word RemTsap); + int Stop(); + int Status(); + int GetParam(int ParamNumber, void * pValue); + int SetParam(int ParamNumber, void * pValue); + // Block send + int BSend(longword R_ID, void *pUsrData, int Size); + int AsBSend(longword R_ID, void *pUsrData, int Size); + bool CheckAsBSendCompletion(int &opResult); + int WaitAsBSendCompletion(longword Timeout); + int SetSendCallback(pfn_ParBSendCompletion pCompletion, void *usrPtr); + // Block recv + int BRecv(longword &R_ID, void *pData, int &Size, longword Timeout); + bool CheckAsBRecvCompletion(int &opResult, longword &R_ID, + void *pData, int &Size); + int SetRecvCallback(pfn_ParBRecvCallBack pCompletion, void *usrPtr); + + friend class TConnectionServer; + friend class TPartnerThread; +}; + + +#endif // s7_partner_h diff --git a/core/src/drivers/plugins/native/s7comm/snap7/core/s7_peer.cpp b/core/src/drivers/plugins/native/s7comm/snap7/core/s7_peer.cpp new file mode 100644 index 00000000..5bf328a6 --- /dev/null +++ b/core/src/drivers/plugins/native/s7comm/snap7/core/s7_peer.cpp @@ -0,0 +1,122 @@ +/*=============================================================================| +| PROJECT SNAP7 1.4.3 | +|==============================================================================| +| Copyright (C) 2013, 2025 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/ | +|=============================================================================*/ +#include "s7_peer.h" +//--------------------------------------------------------------------------- + +TSnap7Peer::TSnap7Peer() +{ + PDUH_out=PS7ReqHeader(&PDU.Payload); + PDURequest=480; // Our request, FPDULength will contain the CPU answer + LastError=0; + cntword = 0; + Destroying = false; +} +//--------------------------------------------------------------------------- +TSnap7Peer::~TSnap7Peer() +{ + Destroying = true; +} +//--------------------------------------------------------------------------- +int TSnap7Peer::SetError(int Error) +{ + if (Error==0) + ClrError(); + else + LastError=Error | LastIsoError | LastTcpError; + return Error; +} +//--------------------------------------------------------------------------- +void TSnap7Peer::ClrError() +{ + LastError=0; + LastIsoError=0; + LastTcpError=0; +} +//--------------------------------------------------------------------------- +word TSnap7Peer::GetNextWord() +{ + if (cntword==0xFFFF) + cntword=0; + return cntword++; +} +//--------------------------------------------------------------------------- +int TSnap7Peer::NegotiatePDULength( ) +{ + int Result, IsoSize = 0; + PReqFunNegotiateParams ReqNegotiate; + PResFunNegotiateParams ResNegotiate; + PS7ResHeader23 Answer; + ClrError(); + // Setup Pointers + ReqNegotiate = PReqFunNegotiateParams(pbyte(PDUH_out) + sizeof(TS7ReqHeader)); + // Header + PDUH_out->P = 0x32; // Always $32 + PDUH_out->PDUType = PduType_request; // $01 + PDUH_out->AB_EX = 0x0000; // Always $0000 + PDUH_out->Sequence = GetNextWord(); // AutoInc + PDUH_out->ParLen = SwapWord(sizeof(TReqFunNegotiateParams)); // 8 bytes + PDUH_out->DataLen = 0x0000; + // Params + ReqNegotiate->FunNegotiate = pduNegotiate; + ReqNegotiate->Unknown = 0x00; + ReqNegotiate->ParallelJobs_1 = 0x0100; + ReqNegotiate->ParallelJobs_2 = 0x0100; + ReqNegotiate->PDULength = SwapWord(PDURequest); + IsoSize = sizeof( TS7ReqHeader ) + sizeof( TReqFunNegotiateParams ); + Result = isoExchangeBuffer(NULL, IsoSize); + if ((Result == 0) && (IsoSize == int(sizeof(TS7ResHeader23) + sizeof(TResFunNegotiateParams)))) + { + // Setup pointers + Answer = PS7ResHeader23(&PDU.Payload); + ResNegotiate = PResFunNegotiateParams(pbyte(Answer) + sizeof(TS7ResHeader23)); + if ( Answer->Error != 0 ) + Result = SetError(errNegotiatingPDU); + if ( Result == 0 ) + PDULength = SwapWord(ResNegotiate->PDULength); + } + return Result; +} +//--------------------------------------------------------------------------- +void TSnap7Peer::PeerDisconnect( ) +{ + ClrError(); + isoDisconnect(true); +} +//--------------------------------------------------------------------------- +int TSnap7Peer::PeerConnect( ) +{ + int Result; + + ClrError(); + Result = isoConnect(); + if (Result == 0) + { + Result = NegotiatePDULength(); + if (Result != 0) + PeerDisconnect(); + } + return Result; +} diff --git a/core/src/drivers/plugins/native/s7comm/snap7/core/s7_peer.h b/core/src/drivers/plugins/native/s7comm/snap7/core/s7_peer.h new file mode 100644 index 00000000..cdc56834 --- /dev/null +++ b/core/src/drivers/plugins/native/s7comm/snap7/core/s7_peer.h @@ -0,0 +1,58 @@ +/*=============================================================================| +| PROJECT SNAP7 1.4.3 | +|==============================================================================| +| Copyright (C) 2013, 2025 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/ | +|=============================================================================*/ +#ifndef s7_peer_h +#define s7_peer_h +//--------------------------------------------------------------------------- +#include "s7_types.h" +#include "s7_isotcp.h" +//--------------------------------------------------------------------------- + +const longword errPeerMask = 0xFFF00000; +const longword errPeerBase = 0x000FFFFF; +const longword errNegotiatingPDU = 0x00100000; + +class TSnap7Peer: public TIsoTcpSocket +{ +private: + word cntword; +protected: + bool Destroying; + PS7ReqHeader PDUH_out; + word GetNextWord(); + int SetError(int Error); + int NegotiatePDULength(); + void ClrError(); +public: + int LastError; + int PDULength; + int PDURequest; + TSnap7Peer(); + ~TSnap7Peer(); + void PeerDisconnect(); + int PeerConnect(); +}; +//--------------------------------------------------------------------------- +#endif diff --git a/core/src/drivers/plugins/native/s7comm/snap7/core/s7_server.cpp b/core/src/drivers/plugins/native/s7comm/snap7/core/s7_server.cpp new file mode 100644 index 00000000..c5c2cda1 --- /dev/null +++ b/core/src/drivers/plugins/native/s7comm/snap7/core/s7_server.cpp @@ -0,0 +1,2158 @@ +/*=============================================================================| +| PROJECT SNAP7 1.4.3 | +|==============================================================================| +| Copyright (C) 2013, 2025 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/ | +|=============================================================================*/ +#include "s7_server.h" +#include "s7_firmware.h" + +const byte BitMask[8] = {0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80}; + +//------------------------------------------------------------------------------ +// ISO/TCP WORKER CLASS +//------------------------------------------------------------------------------ +bool TIsoTcpWorker::IsoPerformCommand(int &Size) +{ + return true; +} +//--------------------------------------------------------------------------- +bool TIsoTcpWorker::ExecuteSend() +{ + return true; +} +//--------------------------------------------------------------------------- +bool TIsoTcpWorker::ExecuteRecv() +{ + TPDUKind PduKind; + int PayloadSize; + + if (CanRead(WorkInterval)) // should be Small to avoid time wait during the close + { + isoRecvPDU(&PDU); + if (LastTcpError==0) + { + IsoPeek(&PDU,PduKind); + // First check valid data incoming (most likely situation) + if (PduKind==pkValidData) + { + PayloadSize=PDUSize(&PDU)-DataHeaderSize; + return IsoPerformCommand(PayloadSize); + }; + // Connection request incoming + if (PduKind==pkConnectionRequest) + { + IsoConfirmConnection(pdu_type_CC); // <- Connection confirm + return LastTcpError!=WSAECONNRESET; + }; + // Disconnect request incoming (only for isotcp full complient equipment, not S7) + if (PduKind==pkDisconnectRequest) + { + IsoConfirmConnection(pdu_type_DC); // <- Disconnect confirm + return false; + }; + // Empty fragment, maybe an ACK + if (PduKind==pkEmptyFragment) + { + PayloadSize=0; + return IsoPerformCommand(PayloadSize); + }; + // Valid PDU format but we have to discard it + if (PduKind==pkUnrecognizedType) + { + return LastTcpError!=WSAECONNRESET; + }; + // Here we have an Invalid PDU + Purge(); + return true; + } + else + return LastTcpError!=WSAECONNRESET; + } + else + return true; +} +//--------------------------------------------------------------------------- +bool TIsoTcpWorker::Execute() +{ + return ExecuteSend() && ExecuteRecv(); +} +//------------------------------------------------------------------------------ +// S7 WORKER CLASS +//------------------------------------------------------------------------------ +TS7Worker::TS7Worker() +{ + // We skip RFC/ISO header, our PDU is the payload + PDUH_in =PS7ReqHeader(&PDU.Payload); + FPDULength=2048; + DBCnt =0; + LastBlk =Block_DB; +} + +bool TS7Worker::ExecuteRecv() +{ + WorkInterval=FServer->WorkInterval; + return TIsoTcpWorker::ExecuteRecv(); +} +//------------------------------------------------------------------------------ +bool TS7Worker::CheckPDU_in(int PayloadSize) +{ + // Checks the size : packet size must match with header infos + int Size=SwapWord(PDUH_in->ParLen)+SwapWord(PDUH_in->DataLen)+ReqHeaderSize; + if (Size!=PayloadSize) + return false; + // Checks PDUType : must be 1 or 7 + if ((PDUH_in->PDUType!=PduType_request) && + (PDUH_in->PDUType!=PduType_userdata)) + return false; + else + return true; +} +//------------------------------------------------------------------------------ +byte TS7Worker::BCD(word Value) +{ + return ((Value / 10) << 4) + (Value % 10); +} +//------------------------------------------------------------------------------ +void TS7Worker::FillTime(PS7Time PTime) +{ + time_t Now; + time(&Now); + struct tm *DT = localtime (&Now); + + PTime->bcd_year=BCD(DT->tm_year-100); + PTime->bcd_mon =BCD(DT->tm_mon+1); + PTime->bcd_day =BCD(DT->tm_mday); + PTime->bcd_hour=BCD(DT->tm_hour); + PTime->bcd_min =BCD(DT->tm_min); + PTime->bcd_sec =BCD(DT->tm_sec); + PTime->bcd_himsec=0; + PTime->bcd_dow =BCD(DT->tm_wday); +} +//------------------------------------------------------------------------------ +void TS7Worker::DoEvent(longword Code, word RetCode, word Param1, word Param2, + word Param3, word Param4) +{ + FServer->DoEvent(ClientHandle,Code,RetCode,Param1,Param2,Param3,Param4); +} +//------------------------------------------------------------------------------ +void TS7Worker::DoReadEvent(longword Code, word RetCode, word Param1, word Param2, + word Param3, word Param4) +{ + FServer->DoReadEvent(ClientHandle,Code,RetCode,Param1,Param2,Param3,Param4); +} +//------------------------------------------------------------------------------ +void TS7Worker::FragmentSkipped(int Size) +{ +// do nothing could be used for debug purpose +} +//------------------------------------------------------------------------------ +bool TS7Worker::IsoPerformCommand(int &Size) +{ + // Checks for Ack fragment + if (Size==0) + return PerformPDUAck(Size); + // First checks PDU consistence + if (CheckPDU_in(Size)) + { + switch (PDUH_in->PDUType) + { + case PduType_request : return PerformPDURequest(Size); + case PduType_userdata : return PerformPDUUsrData(Size); + } + } + else + DoEvent(evcPDUincoming, evrMalformedPDU, Size, 0, 0, 0); + return false; +} +//------------------------------------------------------------------------------ +bool TS7Worker::PerformPDUAck(int &Size) +{ + // here we could keep track of ack empty fragment for debug purpose + return true; +} +//------------------------------------------------------------------------------ +bool TS7Worker::PerformPDURequest(int &Size) +{ + pbyte P; + byte PDUFun; + bool Result = true; + + // We have to store PDUfun since it will be overwritten + P = pbyte(PDUH_in)+ReqHeaderSize; + PDUFun=*P; + // Watches the function + switch (PDUFun) + { + case pduFuncRead : Result=PerformFunctionRead(); + break; + case pduFuncWrite : Result=PerformFunctionWrite(); + break; + case pduNegotiate : Result=PerformFunctionNegotiate(); + break; + case pduStart : + case pduStop : Result=PerformFunctionControl(PDUFun); + break; + case pduStartUpload : + case pduUpload : + case pduEndUpload : Result=PerformFunctionUpload(); + break; + case pduReqDownload : Result=PerformFunctionDownload(); + break; + // <-- Further (custom) functions can be added here + default: + DoEvent(evcPDUincoming, evrCannotHandlePDU, Size, 0, 0, 0); + }; + return Result; +} +//------------------------------------------------------------------------------ +bool TS7Worker::PerformPDUUsrData(int &Size) +{ + PS7ReqParams7 ReqParams; + byte Tg, SubFun; + bool Result = true; + // Set Pointer to request params + ReqParams=PS7ReqParams7(pbyte(PDUH_in)+ReqHeaderSize); + Tg=ReqParams->Tg; + SubFun=ReqParams->SubFun; + // Switch type_group + switch (Tg) + { + case grProgrammer : Result=PerformGroupProgrammer(); + break; + case grCyclicData : Result=PerformGroupCyclicData(); + break; + case grBlocksInfo : Result=PerformGroupBlockInfo(); + break; + case grSZL : Result=PerformGroupSZL(); + break; + case grPassword : Result=PerformGroupSecurity(); + break; + case grClock : switch (SubFun) + { + case 0x01 : Result=PerformGetClock(); + break; + case 0x02 : Result=PerformSetClock(); + break; + }; + break; + default: + DoEvent(evcPDUincoming, evrInvalidGroupUData, Tg, 0, 0, 0); + }; + return Result; +} +//------------------------------------------------------------------------------ +int TS7Worker::DataSizeByte(int WordLength) +{ + switch (WordLength){ + case S7WLBit : return 1; // S7 sends 1 byte per bit + case S7WLByte : return 1; + case S7WLChar : return 1; + case S7WLWord : return 2; + case S7WLDWord : return 4; + case S7WLInt : return 2; + case S7WLDInt : return 4; + case S7WLReal : return 4; + case S7WLCounter : return 2; + case S7WLTimer : return 2; + default : return 0; + } +} +//============================================================================== +// FUNCTION READ +//============================================================================== +word TS7Worker::RA_NotFound(PResFunReadItem ResItem, TEv &EV) +{ + ResItem->DataLength=SwapWord(0x0004); + ResItem->ReturnCode=Code7ResItemNotAvailable; + ResItem->TransportSize=0x00; + EV.EvRetCode=evrErrAreaNotFound; + return 0; +} +//------------------------------------------------------------------------------ +word TS7Worker::RA_OutOfRange(PResFunReadItem ResItem, TEv &EV) +{ + ResItem->DataLength=SwapWord(0x0004); + ResItem->ReturnCode=Code7AddressOutOfRange; + ResItem->TransportSize=0x00; + EV.EvRetCode=evrErrOutOfRange; + return 0; +} +//------------------------------------------------------------------------------ +word TS7Worker::RA_SizeOverPDU(PResFunReadItem ResItem, TEv &EV) +{ + ResItem->DataLength=SwapWord(0x0004); + ResItem->ReturnCode=byte(SwapWord(Code7DataOverPDU)); + ResItem->TransportSize=0x00; + EV.EvRetCode=evrErrOverPDU; + return 0; +} +//------------------------------------------------------------------------------ +PS7Area TS7Worker::GetArea(byte S7Code, word index) +{ + switch(S7Code) + { + case S7AreaPE : return FServer->HA[srvAreaPE]; + case S7AreaPA : return FServer->HA[srvAreaPA]; + case S7AreaMK : return FServer->HA[srvAreaMK]; + case S7AreaCT : return FServer->HA[srvAreaCT]; + case S7AreaTM : return FServer->HA[srvAreaTM]; + case S7AreaDB : return FServer->FindDB(index); + default : return NULL; + }; +} +//------------------------------------------------------------------------------ +word TS7Worker::ReadArea(PResFunReadItem ResItemData, PReqFunReadItem ReqItemPar, + int &PDURemainder, TEv &EV) +{ + PS7Area P; + word DBNum = 0; + word Elements; + longword Start, Size, ASize, AStart; + longword *PAdd; + byte BitIndex, ByteVal; + int Multiplier; + void *Source = NULL; + PSnapCriticalSection pcs; + + P=NULL; + EV.EvStart =0; + EV.EvSize =0; + EV.EvRetCode =0; + EV.EvIndex =0; + + EV.EvArea=ReqItemPar->Area; + // Get Pointer to selected Area + + if (ReqItemPar->Area==S7AreaDB) + { + DBNum=SwapWord(ReqItemPar->DBNumber); + EV.EvIndex=DBNum; + }; + + if (!FServer->ResourceLess) + { + P = GetArea(ReqItemPar->Area, DBNum); + if (P == NULL) + return RA_NotFound(ResItemData, EV); + } + + // Calcs the amount + Multiplier = DataSizeByte(ReqItemPar->TransportSize); + if (Multiplier==0) + return RA_OutOfRange(ResItemData, EV); + + // Checks timers/counters coherence + if ((ReqItemPar->Area==S7AreaTM) ^ (ReqItemPar->TransportSize==S7WLTimer)) + return RA_OutOfRange(ResItemData, EV); + + if ((ReqItemPar->Area==S7AreaCT) ^ (ReqItemPar->TransportSize==S7WLCounter)) + return RA_OutOfRange(ResItemData, EV); + + // Calcs size + Elements = SwapWord(ReqItemPar->Length); + Size=Multiplier*Elements; + EV.EvSize=Size; + + // The sum of the items must not exceed the PDU size negotiated + if (PDURemainder-Size<=0) + return RA_SizeOverPDU(ResItemData, EV); + else + PDURemainder-=Size; + + // More then 1 bit is not supported by S7 CPU + if ((ReqItemPar->TransportSize==S7WLBit) && (Size>1)) + return RA_OutOfRange(ResItemData, EV); + + // Calcs the start point + PAdd=(longword*)(&ReqItemPar->Area); // points to area since we need 4 bytes for a pointer + Start=SwapDWord(*PAdd & 0xFFFFFF00); + + // Checks if the address is not multiple of 8 when transport size is neither bit nor timer nor counter + if ( + (ReqItemPar->TransportSize!=S7WLBit) && + (ReqItemPar->TransportSize!=S7WLTimer) && + (ReqItemPar->TransportSize!=S7WLCounter) && + ((Start % 8) !=0) + ) + return RA_OutOfRange(ResItemData, EV); + + // AStart is only for callback + if ((ReqItemPar->TransportSize != S7WLBit) && (ReqItemPar->TransportSize != S7WLCounter) && (ReqItemPar->TransportSize != S7WLTimer)) + AStart = Start >> 3; + else + AStart = Start; + + if ((ReqItemPar->TransportSize == S7WLCounter) || (ReqItemPar->TransportSize == S7WLTimer)) + { + Start = Start >> 1; // 1 Timer or Counter = 2 bytes + } + else + { + BitIndex =Start & 0x07; // start bit + Start =Start >> 3; // start byte + } + + EV.EvStart=Start; + + // Checks bounds + if (!FServer->ResourceLess) + { + ASize = P->Size; // Area size + if (Start + Size > ASize) + return RA_OutOfRange(ResItemData, EV); + Source = P->PData + Start; + } + + // Read Event (before copy data) + DoReadEvent(evcDataRead,0,EV.EvArea,EV.EvIndex,EV.EvStart,EV.EvSize); + + if (FServer->ResourceLess) + { + memset(&ResItemData->Data, 0, Size); + if (!FServer->DoReadArea(ClientHandle, EV.EvArea, EV.EvIndex, AStart, Elements, ReqItemPar->TransportSize, &ResItemData->Data)) + return RA_NotFound(ResItemData, EV); + } + else + { + // Lock the area + pcs = P->cs; + pcs->Enter(); + // Get Data + memcpy(&ResItemData->Data, Source, Size); + // Unlock the area + pcs->Leave(); + } + + ResItemData->ReturnCode=0xFF; + // Set Result transport size and, for bit, performs the mask + switch (ReqItemPar->TransportSize) + { + case S7WLBit: + { + ByteVal=ResItemData->Data[0]; + + if ((ByteVal & BitMask[BitIndex])!=0) + ResItemData->Data[0]=0x01; + else + ResItemData->Data[0]=0x00; + + ResItemData->TransportSize=TS_ResBit; + ResItemData->DataLength=SwapWord(Size); + };break; + case S7WLByte: + case S7WLWord: + case S7WLDWord: + { + ResItemData->TransportSize=TS_ResByte; + ResItemData->DataLength=SwapWord(Size*8); + };break; + case S7WLInt: + case S7WLDInt: + { + ResItemData->TransportSize=TS_ResInt; + ResItemData->DataLength=SwapWord(Size*8); + };break; + case S7WLReal: + { + ResItemData->TransportSize=TS_ResReal; + ResItemData->DataLength=SwapWord(Size); + };break; + case S7WLChar: + case S7WLTimer: + case S7WLCounter: + { + ResItemData->TransportSize=TS_ResOctet; + ResItemData->DataLength=SwapWord(Size); + };break; + default : + { + ResItemData->TransportSize=TS_ResByte; + ResItemData->DataLength=SwapWord(Size*8); + };break; + } + EV.EvRetCode=evrNoError; + return Size; +} +//------------------------------------------------------------------------------ +bool TS7Worker::PerformFunctionRead() +{ + PReqFunReadParams ReqParams; + PResFunReadParams ResParams; + TResFunReadData ResData; + TS7Answer23 Answer; + uintptr_t Offset; + word ItemSize; + int ItemsCount, c, + TotalSize, + PDURemainder; + TEv EV; + + PDURemainder=FPDULength; + // Stage 1 : Setup pointers and initial check + ReqParams=PReqFunReadParams(pbyte(PDUH_in)+sizeof(TS7ReqHeader)); + ResParams=PResFunReadParams(pbyte(&Answer)+ResHeaderSize23); // Params after the header + + // trunk to 20 max items. + if (ReqParams->ItemsCount>MaxVars) + ReqParams->ItemsCount=MaxVars; + + ItemsCount=ReqParams->ItemsCount; + + // Stage 2 : gather data + Offset=sizeof(TResFunReadParams); // = 2 + + for (c = 0; c < ItemsCount; c++) + { + ResData[c]=PResFunReadItem(pbyte(ResParams)+Offset); + ItemSize=ReadArea(ResData[c],&ReqParams->Items[c],PDURemainder, EV); + + // S7 doesn't xfer odd byte amount + if ((c1) + DoEvent(evcDataRead,EV.EvRetCode,EV.EvArea,EV.EvIndex,EV.EvStart,EV.EvSize); + } + // Stage 3 : finalize the answer and send the packet + Answer.Header.P=0x32; + Answer.Header.PDUType=0x03; + Answer.Header.AB_EX=0x0000; + Answer.Header.Sequence=PDUH_in->Sequence; + Answer.Header.ParLen=SwapWord(sizeof(TResFunReadParams)); + Answer.Header.Error=0x0000; // this is zero, we will find the error in ResData.ReturnCode + Answer.Header.DataLen=SwapWord(word(Offset)-2); + + ResParams->FunRead =ReqParams->FunRead; + ResParams->ItemCount=ReqParams->ItemsCount; + + TotalSize=ResHeaderSize23+int(Offset); + isoSendBuffer(&Answer, TotalSize); + + // For single item (most likely case) it's better to work with the event after + // we sent the answer + if (ItemsCount==1) + DoEvent(evcDataRead,EV.EvRetCode,EV.EvArea,EV.EvIndex,EV.EvStart,EV.EvSize); + + return true; +} +//============================================================================== +// FUNCTION WRITE +//============================================================================== +byte TS7Worker::WA_NotFound(TEv &EV) +{ + EV.EvRetCode=evrErrAreaNotFound; + return Code7ResItemNotAvailable; +} +//------------------------------------------------------------------------------ +byte TS7Worker::WA_InvalidTransportSize(TEv &EV) +{ + EV.EvRetCode=evrErrTransportSize; + return Code7InvalidTransportSize; +} +//------------------------------------------------------------------------------ +byte TS7Worker::WA_OutOfRange(TEv &EV) +{ + EV.EvRetCode=evrErrOutOfRange; + return Code7AddressOutOfRange; +} +//------------------------------------------------------------------------------ +byte TS7Worker::WA_DataSizeMismatch(TEv &EV) +{ + EV.EvRetCode=evrDataSizeMismatch; + return Code7WriteDataSizeMismatch; +} +//------------------------------------------------------------------------------ +byte TS7Worker::WriteArea(PReqFunWriteDataItem ReqItemData, PReqFunWriteItem ReqItemPar, + TEv &EV) +{ + int Multiplier; + PS7Area P = NULL; + word DBNum = 0; + word Elements; + longword *PAdd; + PSnapCriticalSection pcs; + longword Start, Size, ASize, DataLen, AStart; + pbyte Target = NULL; + byte BitIndex; + + EV.EvStart =0; + EV.EvSize =0; + EV.EvRetCode =evrNoError; + EV.EvIndex =0; + + EV.EvArea=ReqItemPar->Area; + // Get Pointer to selected Area + if (ReqItemPar->Area==S7AreaDB) + { + DBNum=SwapWord(ReqItemPar->DBNumber); + EV.EvIndex=DBNum; + }; + + if (!FServer->ResourceLess) + { + P=GetArea(ReqItemPar->Area, DBNum); + if (P==NULL) + return WA_NotFound(EV); + } + + // Calcs the amount + Multiplier = DataSizeByte(ReqItemPar->TransportSize); + if (Multiplier==0) + return WA_InvalidTransportSize(EV); + + // Checks timers/counters coherence + if ((ReqItemPar->Area==S7AreaTM) ^ (ReqItemPar->TransportSize==S7WLTimer)) + return WA_OutOfRange(EV); + + if ((ReqItemPar->Area==S7AreaCT) ^ (ReqItemPar->TransportSize==S7WLCounter)) + return WA_OutOfRange(EV); + + // Calcs size + Elements = SwapWord(ReqItemPar->Length); + Size = Multiplier*Elements; + EV.EvSize=Size; + + // More) 1 bit is not supported by S7 CPU + if ((ReqItemPar->TransportSize==S7WLBit) && (Size>1)) + return WA_OutOfRange(EV); + + // Calcs the start point ?? + PAdd=(longword*)&ReqItemPar->Area; // points to area since we need 4 bytes for a pointer + Start=SwapDWord(*PAdd & 0xFFFFFF00); + + // Checks if the address is not multiple of 8 when transport size is neither bit nor timer nor counter + if ( + (ReqItemPar->TransportSize!=S7WLBit) && + (ReqItemPar->TransportSize!=S7WLTimer) && + (ReqItemPar->TransportSize!=S7WLCounter) && + ((Start % 8) !=0) + ) + return WA_OutOfRange(EV); + + // AStart is only for callback + if ((ReqItemPar->TransportSize != S7WLBit) && (ReqItemPar->TransportSize != S7WLCounter) && (ReqItemPar->TransportSize != S7WLTimer)) + AStart = Start >> 3; + else + AStart = Start; + + if ((ReqItemPar->TransportSize == S7WLCounter) || (ReqItemPar->TransportSize == S7WLTimer)) + { + Start = Start >> 1; // 1 Timer or Counter = 2 bytes + } + else + { + BitIndex = Start & 0x07; // start bit + Start = Start >> 3; // start byte + } + EV.EvStart =Start; + + if (!FServer->ResourceLess) + { + // Checks bounds + ASize = P->Size; // Area size + if (Start + Size > ASize) + return WA_OutOfRange(EV); + Target = pbyte(P->PData + Start); + } + // Checks data size coherence + DataLen=SwapWord(ReqItemData->DataLength); + + if ((ReqItemData->TransportSize!=TS_ResOctet) && (ReqItemData->TransportSize!=TS_ResReal) && (ReqItemData->TransportSize!=TS_ResBit)) + DataLen=DataLen / 8; + + if (DataLen!=Size) + return WA_DataSizeMismatch(EV); + + if (FServer->ResourceLess) + { + if (!FServer->DoWriteArea(ClientHandle, EV.EvArea, EV.EvIndex, AStart, Elements, ReqItemPar->TransportSize, &ReqItemData->Data[0])) + return WA_NotFound(EV); + } + else + { + if (ReqItemPar->TransportSize==S7WLBit) + { + if ((ReqItemData->Data[0] & 0x01) != 0) // bit set + *Target=*Target | BitMask[BitIndex]; + else // bit reset + *Target=*Target & (~BitMask[BitIndex]); + } + else { + // Lock the area + pcs = P->cs; + pcs->Enter(); + // Write Data + memcpy(Target, &ReqItemData->Data[0], Size); + pcs->Leave(); + }; + } + + return 0xFF; +} +//------------------------------------------------------------------------------ +bool TS7Worker::PerformFunctionWrite() +{ + PReqFunWriteParams ReqParams; + TReqFunWriteData ReqData; + PResFunWrite ResData; + TS7Answer23 Answer; + int L; + + uintptr_t StartData; + int c, ItemsCount; + int ResDSize; + TEv EV; + + // Stage 1 : Setup pointers and initial check + ReqParams=PReqFunWriteParams(pbyte(PDUH_in)+sizeof(TS7ReqHeader)); + ResData =PResFunWrite(pbyte(&Answer)+ResHeaderSize23); + + StartData=sizeof(TS7ReqHeader)+SwapWord(PDUH_in->ParLen); + + ItemsCount=ReqParams->ItemsCount; + ResDSize =ResHeaderSize23+2+ItemsCount; + for (c = 0; c < ItemsCount; c++) + { + ReqData[c]=PReqFunWriteDataItem(pbyte(PDUH_in)+StartData); + + if ((ReqParams->Items[c].TransportSize == S7WLTimer) || (ReqParams->Items[c].TransportSize == S7WLCounter) || (ReqParams->Items[c].TransportSize == S7WLBit)) + L = SwapWord(ReqData[c]->DataLength); + else + L = (SwapWord(ReqData[c]->DataLength) / 8); + + StartData+=L+4; + // the datalength is always even + if ( L % 2 != 0) StartData++; + } + + ResData->FunWrite =pduFuncWrite; + ResData->ItemCount=ReqParams->ItemsCount; + + // Stage 2 : Write data + for (c = 0; c < ItemsCount; c++) + { + ResData->Data[c]=WriteArea(ReqData[c],&ReqParams->Items[c], EV); + // For multiple items we have to create multiple events + if (ItemsCount>1) + DoEvent(evcDataWrite,EV.EvRetCode,EV.EvArea,EV.EvIndex,EV.EvStart,EV.EvSize); + } + + // Stage 3 : finalize the answer + Answer.Header.P=0x32; + Answer.Header.PDUType=0x03; + Answer.Header.AB_EX=0x0000; + Answer.Header.Sequence=PDUH_in->Sequence; + Answer.Header.ParLen=SwapWord(0x02); + Answer.Header.Error=0x0000; // this is zero, we will find the error in ResData.ReturnCode if any + Answer.Header.DataLen=SwapWord(ItemsCount); + + isoSendBuffer(&Answer,ResDSize); + // For single item (most likely case) it's better to fire the event after + // we sent the answer + if (ItemsCount==1) + DoEvent(evcDataWrite,EV.EvRetCode,EV.EvArea,EV.EvIndex,EV.EvStart,EV.EvSize); + return true; +} +//============================================================================== +// FUNCTION NEGOTIATE +//============================================================================== +bool TS7Worker::PerformFunctionNegotiate() +{ + PReqFunNegotiateParams ReqParams; + PResFunNegotiateParams ResParams; + word ReqLen; + TS7Answer23 Answer; + int Size; + + // Setup pointers + ReqParams=PReqFunNegotiateParams(pbyte(PDUH_in)+sizeof(TS7ReqHeader)); + ResParams=PResFunNegotiateParams(pbyte(&Answer)+sizeof(TS7ResHeader23)); + // Prepares the answer + Answer.Header.P=0x32; + Answer.Header.PDUType=0x03; + Answer.Header.AB_EX=0x0000; + Answer.Header.Sequence=PDUH_in->Sequence; + Answer.Header.ParLen=SwapWord(sizeof(TResFunNegotiateParams)); + Answer.Header.DataLen=0x0000; + Answer.Header.Error=0x0000; + // Params point at the end of the header + ResParams->FunNegotiate=pduNegotiate; + ResParams->Unknown=0x0; + // We offer the same + ResParams->ParallelJobs_1=ReqParams->ParallelJobs_1; + ResParams->ParallelJobs_2=ReqParams->ParallelJobs_2; + + if (FServer->ForcePDU == 0) + { + ReqLen = SwapWord(ReqParams->PDULength); + if (ReqLenPDULength = SwapWord(MinPduSize); + else + if (ReqLen>IsoPayload_Size) + ResParams->PDULength = SwapWord(IsoPayload_Size); + else + ResParams->PDULength = ReqParams->PDULength; + } + else + ResParams->PDULength = SwapWord(FServer->ForcePDU); + + FPDULength=SwapWord(ResParams->PDULength); // Stores the value + // Sends the answer + Size=sizeof(TS7ResHeader23) + sizeof(TResFunNegotiateParams); + isoSendBuffer(&Answer, Size); + // Store the event + DoEvent(evcNegotiatePDU, evrNoError, FPDULength, 0, 0, 0); + return true; +} +//============================================================================== +// FUNCTION CONTROL +//============================================================================== +bool TS7Worker::PerformFunctionControl(byte PduFun) +{ + TS7Answer23 Answer; + PResFunCtrl ResParams; + word ParLen; + word CtrlCode; + + // Setup pointer + ResParams=PResFunCtrl(pbyte(&Answer)+sizeof(TS7ResHeader23)); + // Prepares the answer + Answer.Header.P=0x32; + Answer.Header.PDUType=0x03; + Answer.Header.AB_EX=0x0000; + Answer.Header.Sequence=PDUH_in->Sequence; + Answer.Header.ParLen=SwapWord(0x0001); // We send only Res fun without para + Answer.Header.DataLen=0x0000; + Answer.Header.Error=0x0000; + ResParams->ResFun=PduFun; + ResParams->para =0x00; + + ParLen=SwapWord(PDUH_in->ParLen); + if (PduFun==pduStop) + CtrlCode=CodeControlStop; + else + { + switch (ParLen) + { + case 16 : CtrlCode=CodeControlCompress; break; + case 18 : CtrlCode=CodeControlCpyRamRom; break; + case 20 : CtrlCode=CodeControlWarmStart; break; + case 22 : CtrlCode=CodeControlColdStart; break; + case 26 : CtrlCode=CodeControlInsDel; break; + default : CtrlCode=CodeControlUnknown; + } + } + // Sends the answer + isoSendBuffer(&Answer,sizeof(TS7ResHeader23)+1); + // Stores the event + DoEvent(evcControl,0,CtrlCode,0,0,0); + + if ((CtrlCode==CodeControlWarmStart) || (CtrlCode==CodeControlColdStart)) + FServer->CpuStatus=S7CpuStatusRun; + + if (CtrlCode==CodeControlStop) + FServer->CpuStatus=S7CpuStatusStop; + + return true; +} +//============================================================================== +// FUNCTION UPLOAD +//============================================================================== +bool TS7Worker::PerformFunctionUpload() +{ + TS7Answer23 Answer; + + // Upload is not implemented, however to avoid that S7 manager hangs + // we simulate a cpu read/write protected. + // We can see the directory but can't upload/download anything + + // Prepares the answer + Answer.Header.P=0x32; + Answer.Header.PDUType =pduResponse; + Answer.Header.AB_EX=0x0000; + Answer.Header.Sequence=PDUH_in->Sequence; + Answer.Header.ParLen=0; + Answer.Header.DataLen=0; + Answer.Header.Error=SwapWord(Code7NeedPassword); + // Sends the answer + isoSendBuffer(&Answer,sizeof(TS7ResHeader23)); + + DoEvent(evcUpload,evrCannotUpload,evsStartUpload,0,0,0); + return true; +} +//============================================================================== +// FUNCTION DOWNLOAD +//============================================================================== +bool TS7Worker::PerformFunctionDownload() +{ + TS7Answer23 Answer; + + // Download is not implemented, however to avoid that S7 manager hangs + // we simulate a cpu read/write protected. + // We can see the directory but can't upload/download anything + + // Prepares the answer + Answer.Header.P=0x32; + Answer.Header.PDUType =pduResponse; + Answer.Header.AB_EX=0x0000; + Answer.Header.Sequence=PDUH_in->Sequence; + Answer.Header.ParLen=0; + Answer.Header.DataLen=0; + Answer.Header.Error=SwapWord(Code7NeedPassword); + // Sends the answer + isoSendBuffer(&Answer,sizeof(TS7ResHeader23)); + + DoEvent(evcUpload,evrCannotDownload, evsStartDownload,0,0,0); + return true; +} +//============================================================================== +// FUNCTIONS PROGRAMMER AND CYCLIC DATA (NOT IMPLEMENTED...yet) +//============================================================================== +bool TS7Worker::PerformGroupProgrammer() +{ + DoEvent(evcPDUincoming,evrNotImplemented,grProgrammer,0,0,0); + return true; +} +//------------------------------------------------------------------------------ +bool TS7Worker::PerformGroupCyclicData() +{ + DoEvent(evcPDUincoming,evrNotImplemented,grCyclicData,0,0,0); + return true; +} +//============================================================================== +// BLOCK(S) INFO FUNCTIONS +//============================================================================== +void TS7Worker::BLK_ListAll(TCB &CB) +{ + PDataFunListAll Data; + int TotalSize; + + TotalSize = ResHeaderSize17+sizeof(TResFunGetBlockInfo)+sizeof(TDataFunListAll); + // Prepares the answer + CB.Answer.Header.P=0x32; + CB.Answer.Header.PDUType=PduType_userdata; + CB.Answer.Header.AB_EX=0x0000; + CB.Answer.Header.Sequence=PDUH_in->Sequence; + CB.Answer.Header.ParLen =SwapWord(sizeof(TResFunGetBlockInfo)); + CB.Answer.Header.DataLen=SwapWord(sizeof(TDataFunListAll)); + + CB.ResParams->Head[0]=CB.ReqParams->Head[0]; + CB.ResParams->Head[1]=CB.ReqParams->Head[1]; + CB.ResParams->Head[2]=CB.ReqParams->Head[2]; + CB.ResParams->Plen =0x08; + CB.ResParams->Uk =0x12; + CB.ResParams->Tg =0x83; // Type response, group functions info + CB.ResParams->SubFun=SFun_ListAll; + + CB.ResParams->Seq =CB.ReqParams->Seq; + CB.ResParams->Rsvd =0x0000; + CB.ResParams->ErrNo =0x0000; + + Data=PDataFunListAll(pbyte(&CB.Answer)+ResHeaderSize17+sizeof(TResFunGetBlockInfo)); + Data->RetVal=0xFF; + Data->TRSize=TS_ResOctet; + Data->Length=SwapWord(28); // 28 = Size of TDataFunListAll.Blocks + // Fill elements, only DB will have a valid number + Data->Blocks[0].Zero=0x30; + Data->Blocks[0].BType=Block_OB; + Data->Blocks[0].BCount=0x0000; // We don't have OBs + Data->Blocks[1].Zero=0x30; + Data->Blocks[1].BType=Block_FB; + Data->Blocks[1].BCount=0x0000; // We don't have FBs + Data->Blocks[2].Zero=0x30; + Data->Blocks[2].BType=Block_FC; + Data->Blocks[2].BCount=0x0000; // We don't have FCs + Data->Blocks[3].Zero=0x30; + Data->Blocks[3].BType=Block_DB; + Data->Blocks[3].BCount=SwapWord(FServer->DBCount); // Most likely we HAVE DBs + Data->Blocks[4].Zero=0x30; + Data->Blocks[4].BType=Block_SDB; + Data->Blocks[4].BCount=0x0000; // We don't have SDBs + Data->Blocks[5].Zero=0x30; + Data->Blocks[5].BType=Block_SFC; + Data->Blocks[5].BCount=0x0000; // We don't have SFCs + Data->Blocks[6].Zero=0x30; + Data->Blocks[6].BType=Block_SFB; + Data->Blocks[6].BCount=0x0000; // We don't have SFBs + // Sends + isoSendBuffer(&CB.Answer,TotalSize); + DoEvent(evcDirectory, 0, evsGetBlockList, 0, 0, 0); +} +//------------------------------------------------------------------------------ +void TS7Worker::BLK_NoResource_ListBoT(PDataFunGetBot Data, TCB &CB) +{ + CB.DataLength =4; + DBCnt =0; // Reset counter + CB.Answer.Header.DataLen=SwapWord(CB.DataLength); + CB.ResParams->ErrNo =0x0ED2; // function in error + Data->RetVal =0x0A; // No resource available + Data->TSize =0x00; // No transport size; + Data->DataLen =0x0000; // No data; + CB.evError =evrResNotFound; +} +//------------------------------------------------------------------------------ +void TS7Worker::BLK_ListBoT(byte BlockType, bool Start, TCB &CB) +{ + PDataFunGetBot Data; + int MaxItems, TotalSize, cnt; + int HiBound = FServer->DBLimit+1; + + CB.evError=0; + MaxItems=(FPDULength - 32) / 4; + // Prepares the answer + CB.Answer.Header.P=0x32; + CB.Answer.Header.PDUType=PduType_userdata; + CB.Answer.Header.AB_EX=0x0000; + CB.Answer.Header.Sequence=PDUH_in->Sequence; + CB.Answer.Header.ParLen =SwapWord(sizeof(TResFunGetBlockInfo)); + + CB.ResParams->Head[0]=CB.ReqParams->Head[0]; + CB.ResParams->Head[1]=CB.ReqParams->Head[1]; + CB.ResParams->Head[2]=CB.ReqParams->Head[2]; + CB.ResParams->Plen =0x08; + CB.ResParams->Uk =0x12; + CB.ResParams->Tg =0x83; // Type response, group functions info + CB.ResParams->SubFun=SFun_ListBoT; + CB.ResParams->Seq =CB.ReqParams->Seq; + CB.ResParams->Rsvd =0x0000; + Data=PDataFunGetBot(pbyte(&CB.Answer)+ResHeaderSize17+sizeof(TResFunGetBlockInfo)); + + if (BlockType==Block_DB) + { + cnt =0; // Local couter + if (Start) + DBCnt=-1; // Global counter + + if (FServer->DBCount>0) + { + while ((cntDB[DBCnt]!=NULL) + { + Data->Items[cnt].BlockNum=SwapWord(FServer->DB[DBCnt]->Number); + Data->Items[cnt].Unknown =0x22; + Data->Items[cnt].BlockLang=0x05; + cnt++; + }; + }; + + if ((cntRsvd=0x0023; + } + else + CB.ResParams->Rsvd=0x0123; + + if (cnt>0) + { + CB.ResParams->ErrNo =0x0000; + Data->TSize =TS_ResOctet; + Data->RetVal=0xFF; + CB.DataLength=4+(cnt*word(sizeof(TDataFunGetBotItem))); + CB.Answer.Header.DataLen=SwapWord(CB.DataLength); + Data->DataLen=SwapWord(CB.DataLength-4); + } + else + BLK_NoResource_ListBoT(Data, CB); + } + else + BLK_NoResource_ListBoT(Data, CB); + } + else // we store only DBs + BLK_NoResource_ListBoT(Data, CB); + + TotalSize = ResHeaderSize17+sizeof(TResFunGetBlockInfo)+CB.DataLength; + isoSendBuffer(&CB.Answer,TotalSize); + if (Start) + DoEvent(evcDirectory, CB.evError, evsStartListBoT, BlockType, 0, 0); + else + DoEvent(evcDirectory, CB.evError, evsListBoT, BlockType, 0, 0); +} +//------------------------------------------------------------------------------ +void TS7Worker::BLK_NoResource_GetBlkInfo(PResDataBlockInfo Data, TCB &CB) +{ + CB.DataLength =4; + CB.Answer.Header.DataLen=SwapWord(CB.DataLength); + CB.ResParams->ErrNo =0x09D2; // function in error + Data->RetVal =0x0A; // No resource available + Data->TSize =0x00; // No transport size; + Data->Length =0x0000; // No data; + CB.evError =evrResNotFound; +} +//------------------------------------------------------------------------------ +void TS7Worker::BLK_GetBlockNum_GetBlkInfo(int &BlkNum, PReqDataBlockInfo ReqData) +{ + BlkNum = (ReqData->AsciiBlk[4] - 0x30) + + (ReqData->AsciiBlk[3] - 0x30) * 10 + + (ReqData->AsciiBlk[2] - 0x30) * 100 + + (ReqData->AsciiBlk[1] - 0x30) * 1000 + + (ReqData->AsciiBlk[0] - 0x30) * 10000; + + if (BlkNum>65535) + BlkNum=-1; +} +//------------------------------------------------------------------------------ +void TS7Worker::BLK_DoBlockInfo_GetBlkInfo(PS7Area DB, PResDataBlockInfo Data, TCB &CB) +{ + // Prepares the answer + CB.Answer.Header.P=0x32; + CB.Answer.Header.PDUType=PduType_userdata; + CB.Answer.Header.AB_EX=0x0000; + CB.Answer.Header.Sequence=PDUH_in->Sequence; + CB.Answer.Header.ParLen =SwapWord(sizeof(TResFunGetBlockInfo)); + + CB.ResParams->Head[0]=CB.ReqParams->Head[0]; + CB.ResParams->Head[1]=CB.ReqParams->Head[1]; + CB.ResParams->Head[2]=CB.ReqParams->Head[2]; + CB.ResParams->Plen =0x08; + CB.ResParams->Uk =0x12; + CB.ResParams->Tg =0x83; // Type response, group functions info + CB.ResParams->SubFun=SFun_BlkInfo; + CB.ResParams->Seq =CB.ReqParams->Seq; + CB.ResParams->Rsvd =0x0000; + CB.ResParams->ErrNo =0x0000; + + CB.DataLength =sizeof(TResDataBlockInfo); + CB.Answer.Header.DataLen=SwapWord(CB.DataLength); + CB.ResParams->ErrNo =0x0000; + + Data->RetVal =0xFF; + Data->TSize =TS_ResOctet; + Data->Length =SwapWord(78); // this struct - RetValData->Tsize and length + Data->Cst_b =0x01; + Data->BlkType =0x00; + Data->Cst_w1 =0x4A00; + Data->Cst_w2 =0x0022; + Data->Cst_pp =0x7070; + Data->Unknown_1 =0x01; + Data->BlkFlags =0x01; + Data->BlkLang =BlockLangDB; + Data->SubBlkType =0x0A; + Data->CodeTime_dy =SwapWord(5800);// Nov/18/1999 my princess's birthdate + Data->IntfTime_dy =Data->CodeTime_dy; + Data->LocDataLen =0x0000; + Data->BlkNumber =SwapWord(DB->Number); + Data->SbbLen =0x1400; + Data->AddLen =0x0000; + Data->MC7Len =SwapWord(DB->Size); + Data->LenLoadMem =SwapDWord(DB->Size+92); + Data->Version =0x01; + Data->Unknown_2 =0x00; + Data->BlkChksum =0x0000; +} +//------------------------------------------------------------------------------ +void TS7Worker::BLK_GetBlkInfo(TCB &CB) +{ + PReqDataBlockInfo ReqData; + PResDataBlockInfo Data; + int BlkNum; + PS7Area BlkDB; + byte BlkTypeInfo; + int TotalSize; + + CB.evError=0; + Data =PResDataBlockInfo(pbyte(&CB.Answer)+ResHeaderSize17+sizeof(TResFunGetBlockInfo)); + ReqData=PReqDataBlockInfo(pbyte(PDUH_in)+ReqHeaderSize+sizeof(TReqFunGetBlockInfo)); + memset(Data,0,sizeof(TResDataBlockInfo)); // many fields are 0 + + BLK_GetBlockNum_GetBlkInfo(BlkNum, ReqData); + BlkTypeInfo=ReqData->BlkType; + if (BlkTypeInfo==Block_DB) + { + if (BlkNum>=0) + { + BlkDB=FServer->FindDB(BlkNum); + if (BlkDB!=NULL) + BLK_DoBlockInfo_GetBlkInfo(BlkDB, Data, CB); + else + BLK_NoResource_GetBlkInfo(Data, CB); + } + else + BLK_NoResource_GetBlkInfo(Data, CB); + } + else + BLK_NoResource_GetBlkInfo(Data, CB); + + TotalSize = ResHeaderSize17+sizeof(TResFunGetBlockInfo)+sizeof(TResDataBlockInfo); + isoSendBuffer(&CB.Answer, TotalSize); + DoEvent(evcDirectory,CB.evError,evsGetBlockInfo,BlkTypeInfo,BlkNum,0); +} +//------------------------------------------------------------------------------ +bool TS7Worker::PerformGroupBlockInfo() +{ + TCB CB; + pbyte BlockType; + + // Setup pointers + CB.ReqParams=PReqFunGetBlockInfo(pbyte(PDUH_in)+ReqHeaderSize); + CB.ResParams=PResFunGetBlockInfo(pbyte(&CB.Answer)+ResHeaderSize17); + BlockType =pbyte(PDUH_in)+23; + + switch (CB.ReqParams->SubFun) + { + case SFun_ListAll : BLK_ListAll(CB); break; // List all blocks + case SFun_ListBoT : + { + if (CB.ReqParams->Plen==4) + { + LastBlk=*BlockType; + BLK_ListBoT(*BlockType, true, CB); // start sequence from beginning + } + else + BLK_ListBoT(LastBlk, false, CB); // Continue sequence + }; break; + case SFun_BlkInfo : BLK_GetBlkInfo(CB); // Get Block info + } + return true; +} +//============================================================================== +// FUNCTION SZL +//============================================================================== +void TS7Worker::SZLNotAvailable() +{ + SZL.Answer.Header.DataLen=SwapWord(sizeof(SZLNotAvail)); + SZL.ResParams->Err = 0x02D4; + memcpy(SZL.ResData, &SZLNotAvail, sizeof(SZLNotAvail)); + isoSendBuffer(&SZL.Answer,26); + SZL.SZLDone=false; +} +void TS7Worker::SZLSystemState() +{ + SZL.Answer.Header.DataLen=SwapWord(sizeof(SZLSysState)); + SZL.ResParams->Err =0x0000; + memcpy(SZL.ResData,&SZLSysState,sizeof(SZLSysState)); + isoSendBuffer(&SZL.Answer,28); + SZL.SZLDone=true; +} +void TS7Worker::SZLData(void *P, int len) +{ + int MaxSzl=FPDULength-22; + + if (len>MaxSzl) { + len=MaxSzl; + } + + SZL.Answer.Header.DataLen=SwapWord(word(len)); + SZL.ResParams->Err =0x0000; + SZL.ResParams->resvd=0x0000; // this is the end, no more packets + memcpy(SZL.ResData, P, len); + + SZL.ResData[2]=((len-4)>>8) & 0xFF; + SZL.ResData[3]=(len-4) & 0xFF; + + isoSendBuffer(&SZL.Answer,22+len); + SZL.SZLDone=true; +} +// this block is dynamic (contains date/time and cpu status) +void TS7Worker::SZL_ID424() +{ + PS7Time PTime; + pbyte PStatus; + + SZL.Answer.Header.DataLen=SwapWord(sizeof(SZL_ID_0424_IDX_XXXX)); + SZL.ResParams->Err =0x0000; + PTime=PS7Time(pbyte(SZL.ResData)+24); + PStatus =pbyte(SZL.ResData)+15; + memcpy(SZL.ResData,&SZL_ID_0424_IDX_XXXX,sizeof(SZL_ID_0424_IDX_XXXX)); + FillTime(PTime); + *PStatus=FServer->CpuStatus; + SZL.SZLDone=true; + isoSendBuffer(&SZL.Answer,22+sizeof(SZL_ID_0424_IDX_XXXX)); +} + +void TS7Worker::SZL_ID131_IDX003() +{ + word len = sizeof(SZL_ID_0131_IDX_0003); + SZL.Answer.Header.DataLen=SwapWord(len); + SZL.ResParams->Err =0x0000; + SZL.ResParams->resvd=0x0000; // this is the end, no more packets + memcpy(SZL.ResData, &SZL_ID_0131_IDX_0003, len); + // Set the max consistent data window to PDU size + SZL.ResData[18]=((FPDULength)>>8) & 0xFF; + SZL.ResData[19]=(FPDULength) & 0xFF; + + isoSendBuffer(&SZL.Answer,22+len); + SZL.SZLDone=true; +} + +bool TS7Worker::PerformGroupSZL() +{ + SZL.SZLDone=false; + // Setup pointers + SZL.ReqParams=PReqFunReadSZLFirst(pbyte(PDUH_in)+ReqHeaderSize); + SZL.ResParams=PS7ResParams7(pbyte(&SZL.Answer)+ResHeaderSize17); + SZL.ResData =pbyte(&SZL.Answer)+ResHeaderSize17+sizeof(TS7Params7); + // Prepare Answer header + SZL.Answer.Header.P=0x32; + SZL.Answer.Header.PDUType=PduType_userdata; + SZL.Answer.Header.AB_EX=0x0000; + SZL.Answer.Header.Sequence=PDUH_in->Sequence; + SZL.Answer.Header.ParLen =SwapWord(sizeof(TS7Params7)); + + SZL.ResParams->Head[0]=SZL.ReqParams->Head[0]; + SZL.ResParams->Head[1]=SZL.ReqParams->Head[1]; + SZL.ResParams->Head[2]=SZL.ReqParams->Head[2]; + SZL.ResParams->Plen =0x08; + SZL.ResParams->Uk =0x12; + SZL.ResParams->Tg =0x84; // Type response + group szl + SZL.ResParams->SubFun=SZL.ReqParams->SubFun; + SZL.ResParams->Seq =SZL.ReqParams->Seq; + SZL.ResParams->resvd=0x0000; // this is the end, no more packets + + // only two subfunction are defined : 0x01 read, 0x02 system state + if (SZL.ResParams->SubFun==0x02) // 0x02 = subfunction system state + { + SZLSystemState(); + return true; + }; + if (SZL.ResParams->SubFun!=0x01) + { + SZLNotAvailable(); + return true; + }; + // From here we assume subfunction = 0x01 + SZL.ReqData=PS7ReqSZLData(pbyte(PDUH_in)+ReqHeaderSize+sizeof(TReqFunReadSZLFirst));// Data after params + + SZL.ID=SwapWord(SZL.ReqData->ID); + SZL.Index=SwapWord(SZL.ReqData->Index); + + // Switch prebuilt Data Bank (they come from a physical CPU) + switch (SZL.ID) + { + case 0x0000 : SZLData(&SZL_ID_0000_IDX_XXXX,sizeof(SZL_ID_0000_IDX_XXXX));break; + case 0x0F00 : SZLData(&SZL_ID_0F00_IDX_XXXX,sizeof(SZL_ID_0F00_IDX_XXXX));break; + case 0x0002 : SZLData(&SZL_ID_0002_IDX_XXXX,sizeof(SZL_ID_0002_IDX_XXXX));break; + case 0x0011 : SZLData(&SZL_ID_0011_IDX_XXXX,sizeof(SZL_ID_0011_IDX_XXXX));break; + case 0x0012 : SZLData(&SZL_ID_0012_IDX_XXXX,sizeof(SZL_ID_0012_IDX_XXXX));break; + case 0x0013 : SZLData(&SZL_ID_0013_IDX_XXXX,sizeof(SZL_ID_0013_IDX_XXXX));break; + case 0x0014 : SZLData(&SZL_ID_0014_IDX_XXXX,sizeof(SZL_ID_0014_IDX_XXXX));break; + case 0x0015 : SZLData(&SZL_ID_0015_IDX_XXXX,sizeof(SZL_ID_0015_IDX_XXXX));break; + case 0x0F14 : SZLData(&SZL_ID_0F14_IDX_XXXX,sizeof(SZL_ID_0F14_IDX_XXXX));break; + case 0x0019 : SZLData(&SZL_ID_0019_IDX_XXXX,sizeof(SZL_ID_0019_IDX_XXXX));break; + case 0x0F19 : SZLData(&SZL_ID_0F19_IDX_XXXX,sizeof(SZL_ID_0F19_IDX_XXXX));break; + case 0x001C : SZLData(&SZL_ID_001C_IDX_XXXX,sizeof(SZL_ID_001C_IDX_XXXX));break; + case 0x0F1C : SZLData(&SZL_ID_0F1C_IDX_XXXX,sizeof(SZL_ID_0F1C_IDX_XXXX));break; + case 0x0036 : SZLData(&SZL_ID_0036_IDX_XXXX,sizeof(SZL_ID_0036_IDX_XXXX));break; + case 0x0F36 : SZLData(&SZL_ID_0F36_IDX_XXXX,sizeof(SZL_ID_0F36_IDX_XXXX));break; + case 0x0025 : SZLData(&SZL_ID_0025_IDX_XXXX,sizeof(SZL_ID_0025_IDX_XXXX));break; + case 0x0F25 : SZLData(&SZL_ID_0F25_IDX_XXXX,sizeof(SZL_ID_0F25_IDX_XXXX));break; + case 0x0037 : SZLData(&SZL_ID_0037_IDX_XXXX,sizeof(SZL_ID_0037_IDX_XXXX));break; + case 0x0F37 : SZLData(&SZL_ID_0F37_IDX_XXXX,sizeof(SZL_ID_0F37_IDX_XXXX));break; + case 0x0074 : SZLData(&SZL_ID_0074_IDX_XXXX,sizeof(SZL_ID_0074_IDX_XXXX));break; + case 0x0F74 : SZLData(&SZL_ID_0F74_IDX_XXXX,sizeof(SZL_ID_0F74_IDX_XXXX));break; + case 0x0591 : SZLData(&SZL_ID_0591_IDX_XXXX,sizeof(SZL_ID_0591_IDX_XXXX));break; + case 0x0A91 : SZLData(&SZL_ID_0A91_IDX_XXXX,sizeof(SZL_ID_0A91_IDX_XXXX));break; + case 0x0F92 : SZLData(&SZL_ID_0F92_IDX_XXXX,sizeof(SZL_ID_0F92_IDX_XXXX));break; + case 0x0294 : SZLData(&SZL_ID_0294_IDX_XXXX,sizeof(SZL_ID_0294_IDX_XXXX));break; + case 0x0794 : SZLData(&SZL_ID_0794_IDX_XXXX,sizeof(SZL_ID_0794_IDX_XXXX));break; + case 0x0F94 : SZLData(&SZL_ID_0F94_IDX_XXXX,sizeof(SZL_ID_0F94_IDX_XXXX));break; + case 0x0095 : SZLData(&SZL_ID_0095_IDX_XXXX,sizeof(SZL_ID_0095_IDX_XXXX));break; + case 0x0F95 : SZLData(&SZL_ID_0F95_IDX_XXXX,sizeof(SZL_ID_0F95_IDX_XXXX));break; + case 0x00A0 : SZLData(&SZL_ID_00A0_IDX_XXXX,sizeof(SZL_ID_00A0_IDX_XXXX));break; + case 0x0FA0 : SZLData(&SZL_ID_0FA0_IDX_XXXX,sizeof(SZL_ID_0FA0_IDX_XXXX));break; + case 0x0017 : SZLData(&SZL_ID_0017_IDX_XXXX,sizeof(SZL_ID_0017_IDX_XXXX));break; + case 0x0F17 : SZLData(&SZL_ID_0F17_IDX_XXXX,sizeof(SZL_ID_0F17_IDX_XXXX));break; + case 0x0018 : SZLData(&SZL_ID_0018_IDX_XXXX,sizeof(SZL_ID_0018_IDX_XXXX));break; + case 0x0F18 : SZLData(&SZL_ID_0F18_IDX_XXXX,sizeof(SZL_ID_0F18_IDX_XXXX));break; + case 0x001A : SZLData(&SZL_ID_001A_IDX_XXXX,sizeof(SZL_ID_001A_IDX_XXXX));break; + case 0x0F1A : SZLData(&SZL_ID_0F1A_IDX_XXXX,sizeof(SZL_ID_0F1A_IDX_XXXX));break; + case 0x001B : SZLData(&SZL_ID_001B_IDX_XXXX,sizeof(SZL_ID_001B_IDX_XXXX));break; + case 0x0F1B : SZLData(&SZL_ID_0F1B_IDX_XXXX,sizeof(SZL_ID_0F1B_IDX_XXXX));break; + case 0x0021 : SZLData(&SZL_ID_0021_IDX_XXXX,sizeof(SZL_ID_0021_IDX_XXXX));break; + case 0x0A21 : SZLData(&SZL_ID_0A21_IDX_XXXX,sizeof(SZL_ID_0A21_IDX_XXXX));break; + case 0x0F21 : SZLData(&SZL_ID_0F21_IDX_XXXX,sizeof(SZL_ID_0F21_IDX_XXXX));break; + case 0x0023 : SZLData(&SZL_ID_0023_IDX_XXXX,sizeof(SZL_ID_0023_IDX_XXXX));break; + case 0x0F23 : SZLData(&SZL_ID_0F23_IDX_XXXX,sizeof(SZL_ID_0F23_IDX_XXXX));break; + case 0x0024 : SZLData(&SZL_ID_0024_IDX_XXXX,sizeof(SZL_ID_0024_IDX_XXXX));break; + case 0x0124 : SZLData(&SZL_ID_0124_IDX_XXXX,sizeof(SZL_ID_0124_IDX_XXXX));break; + case 0x0424 : SZL_ID424();break; + case 0x0038 : SZLData(&SZL_ID_0038_IDX_XXXX,sizeof(SZL_ID_0038_IDX_XXXX));break; + case 0x0F38 : SZLData(&SZL_ID_0F38_IDX_XXXX,sizeof(SZL_ID_0F38_IDX_XXXX));break; + case 0x003A : SZLData(&SZL_ID_003A_IDX_XXXX,sizeof(SZL_ID_003A_IDX_XXXX));break; + case 0x0F3A : SZLData(&SZL_ID_0F3A_IDX_XXXX,sizeof(SZL_ID_0F3A_IDX_XXXX));break; + case 0x0F9A : SZLData(&SZL_ID_0F9A_IDX_XXXX,sizeof(SZL_ID_0F9A_IDX_XXXX));break; + case 0x0D91 : switch(SZL.Index){ + case 0x0000 : SZLData(&SZL_ID_0D91_IDX_0000,sizeof(SZL_ID_0D91_IDX_0000));break; + default: SZLNotAvailable();break; + }; + break; + case 0x0092 : switch(SZL.Index){ + case 0x0000 : SZLData(&SZL_ID_0092_IDX_0000,sizeof(SZL_ID_0092_IDX_0000));break; + default : SZLNotAvailable();break; + };break; + case 0x0292 : switch(SZL.Index){ + case 0x0000 : SZLData(&SZL_ID_0292_IDX_0000,sizeof(SZL_ID_0292_IDX_0000));break; + default : SZLNotAvailable();break; + };break; + case 0x0692 : switch(SZL.Index){ + case 0x0000 : SZLData(&SZL_ID_0692_IDX_0000,sizeof(SZL_ID_0692_IDX_0000));break; + default : SZLNotAvailable();break; + };break; + case 0x0094 : switch(SZL.Index){ + case 0x0000 : SZLData(&SZL_ID_0094_IDX_0000,sizeof(SZL_ID_0094_IDX_0000));break; + default : SZLNotAvailable();break; + };break; + case 0x0D97 : switch(SZL.Index){ + case 0x0000 : SZLData(&SZL_ID_0D97_IDX_0000,sizeof(SZL_ID_0D97_IDX_0000));break; + default : SZLNotAvailable();break; + };break; + case 0x0111 : switch(SZL.Index){ + case 0x0001 : SZLData(&SZL_ID_0111_IDX_0001,sizeof(SZL_ID_0111_IDX_0001));break; + case 0x0006 : SZLData(&SZL_ID_0111_IDX_0006,sizeof(SZL_ID_0111_IDX_0006));break; + case 0x0007 : SZLData(&SZL_ID_0111_IDX_0007,sizeof(SZL_ID_0111_IDX_0007));break; + default : SZLNotAvailable();break; + };break; + case 0x0F11 : switch(SZL.Index){ + case 0x0001 : SZLData(&SZL_ID_0F11_IDX_0001,sizeof(SZL_ID_0F11_IDX_0001));break; + case 0x0006 : SZLData(&SZL_ID_0F11_IDX_0006,sizeof(SZL_ID_0F11_IDX_0006));break; + case 0x0007 : SZLData(&SZL_ID_0F11_IDX_0007,sizeof(SZL_ID_0F11_IDX_0007));break; + default : SZLNotAvailable();break; + };break; + case 0x0112 : switch(SZL.Index){ + case 0x0000 : SZLData(&SZL_ID_0112_IDX_0000,sizeof(SZL_ID_0112_IDX_0000));break; + case 0x0100 : SZLData(&SZL_ID_0112_IDX_0100,sizeof(SZL_ID_0112_IDX_0100));break; + case 0x0200 : SZLData(&SZL_ID_0112_IDX_0200,sizeof(SZL_ID_0112_IDX_0200));break; + case 0x0400 : SZLData(&SZL_ID_0112_IDX_0400,sizeof(SZL_ID_0112_IDX_0400));break; + default : SZLNotAvailable();break; + };break; + case 0x0F12 : switch(SZL.Index){ + case 0x0000 : SZLData(&SZL_ID_0F12_IDX_0000,sizeof(SZL_ID_0F12_IDX_0000));break; + case 0x0100 : SZLData(&SZL_ID_0F12_IDX_0100,sizeof(SZL_ID_0F12_IDX_0100));break; + case 0x0200 : SZLData(&SZL_ID_0F12_IDX_0200,sizeof(SZL_ID_0F12_IDX_0200));break; + case 0x0400 : SZLData(&SZL_ID_0F12_IDX_0400,sizeof(SZL_ID_0F12_IDX_0400));break; + default : SZLNotAvailable();break; + };break; + case 0x0113 : switch(SZL.Index){ + case 0x0001 : SZLData(&SZL_ID_0113_IDX_0001,sizeof(SZL_ID_0113_IDX_0001));break; + default : SZLNotAvailable();break; + };break; + case 0x0115 : switch(SZL.Index){ + case 0x0800 : SZLData(&SZL_ID_0115_IDX_0800,sizeof(SZL_ID_0115_IDX_0800));break; + default : SZLNotAvailable();break; + };break; + case 0x011C : switch(SZL.Index){ + case 0x0001 : SZLData(&SZL_ID_011C_IDX_0001,sizeof(SZL_ID_011C_IDX_0001));break; + case 0x0002 : SZLData(&SZL_ID_011C_IDX_0002,sizeof(SZL_ID_011C_IDX_0002));break; + case 0x0003 : SZLData(&SZL_ID_011C_IDX_0003,sizeof(SZL_ID_011C_IDX_0003));break; + case 0x0004 : SZLData(&SZL_ID_011C_IDX_0004,sizeof(SZL_ID_011C_IDX_0004));break; + case 0x0005 : SZLData(&SZL_ID_011C_IDX_0005,sizeof(SZL_ID_011C_IDX_0005));break; + case 0x0007 : SZLData(&SZL_ID_011C_IDX_0007,sizeof(SZL_ID_011C_IDX_0007));break; + case 0x0008 : SZLData(&SZL_ID_011C_IDX_0008,sizeof(SZL_ID_011C_IDX_0008));break; + case 0x0009 : SZLData(&SZL_ID_011C_IDX_0009,sizeof(SZL_ID_011C_IDX_0009));break; + case 0x000A : SZLData(&SZL_ID_011C_IDX_000A,sizeof(SZL_ID_011C_IDX_000A));break; + case 0x000B : SZLData(&SZL_ID_011C_IDX_000B,sizeof(SZL_ID_011C_IDX_000B));break; + default : SZLNotAvailable();break; + };break; + case 0x0222 : switch(SZL.Index){ + case 0x0001 : SZLData(&SZL_ID_0222_IDX_0001,sizeof(SZL_ID_0222_IDX_0001));break; + case 0x000A : SZLData(&SZL_ID_0222_IDX_000A,sizeof(SZL_ID_0222_IDX_000A));break; + case 0x0014 : SZLData(&SZL_ID_0222_IDX_0014,sizeof(SZL_ID_0222_IDX_0014));break; + case 0x0028 : SZLData(&SZL_ID_0222_IDX_0028,sizeof(SZL_ID_0222_IDX_0028));break; + case 0x0050 : SZLData(&SZL_ID_0222_IDX_0050,sizeof(SZL_ID_0222_IDX_0050));break; + case 0x0064 : SZLData(&SZL_ID_0222_IDX_0064,sizeof(SZL_ID_0222_IDX_0064));break; + default : SZLNotAvailable();break; + };break; + case 0x0125 : switch(SZL.Index){ + case 0x0000 : SZLData(&SZL_ID_0125_IDX_0000,sizeof(SZL_ID_0125_IDX_0000));break; + case 0x0001 : SZLData(&SZL_ID_0125_IDX_0001,sizeof(SZL_ID_0125_IDX_0001));break; + default : SZLNotAvailable();break; + };break; + case 0x0225 : switch(SZL.Index){ + case 0x0001 : SZLData(&SZL_ID_0225_IDX_0001,sizeof(SZL_ID_0225_IDX_0001));break; + default : SZLNotAvailable();break; + };break; + case 0x0131 : switch(SZL.Index){ + case 0x0001 : SZLData(&SZL_ID_0131_IDX_0001,sizeof(SZL_ID_0131_IDX_0001));break; + case 0x0002 : SZLData(&SZL_ID_0131_IDX_0002,sizeof(SZL_ID_0131_IDX_0002));break; + case 0x0003 : SZL_ID131_IDX003();break; + case 0x0004 : SZLData(&SZL_ID_0131_IDX_0004,sizeof(SZL_ID_0131_IDX_0004));break; + case 0x0005 : SZLData(&SZL_ID_0131_IDX_0005,sizeof(SZL_ID_0131_IDX_0005));break; + case 0x0006 : SZLData(&SZL_ID_0131_IDX_0006,sizeof(SZL_ID_0131_IDX_0006));break; + case 0x0007 : SZLData(&SZL_ID_0131_IDX_0007,sizeof(SZL_ID_0131_IDX_0007));break; + case 0x0008 : SZLData(&SZL_ID_0131_IDX_0008,sizeof(SZL_ID_0131_IDX_0008));break; + case 0x0009 : SZLData(&SZL_ID_0131_IDX_0009,sizeof(SZL_ID_0131_IDX_0009));break; + default : SZLNotAvailable();break; + };break; + case 0x0117 : switch(SZL.Index){ + case 0x0000 : SZLData(&SZL_ID_0117_IDX_0000,sizeof(SZL_ID_0117_IDX_0000));break; + case 0x0001 : SZLData(&SZL_ID_0117_IDX_0001,sizeof(SZL_ID_0117_IDX_0001));break; + case 0x0002 : SZLData(&SZL_ID_0117_IDX_0002,sizeof(SZL_ID_0117_IDX_0002));break; + case 0x0003 : SZLData(&SZL_ID_0117_IDX_0003,sizeof(SZL_ID_0117_IDX_0003));break; + case 0x0004 : SZLData(&SZL_ID_0117_IDX_0004,sizeof(SZL_ID_0117_IDX_0004));break; + default : SZLNotAvailable();break; + };break; + case 0x0118 : switch(SZL.Index){ + case 0x0000 : SZLData(&SZL_ID_0118_IDX_0000,sizeof(SZL_ID_0118_IDX_0000));break; + case 0x0001 : SZLData(&SZL_ID_0118_IDX_0001,sizeof(SZL_ID_0118_IDX_0001));break; + case 0x0002 : SZLData(&SZL_ID_0118_IDX_0002,sizeof(SZL_ID_0118_IDX_0002));break; + case 0x0003 : SZLData(&SZL_ID_0118_IDX_0003,sizeof(SZL_ID_0118_IDX_0003));break; + default : SZLNotAvailable();break; + };break; + case 0x0132 : switch(SZL.Index){ + case 0x0001 : SZLData(&SZL_ID_0132_IDX_0001,sizeof(SZL_ID_0132_IDX_0001));break; + case 0x0002 : SZLData(&SZL_ID_0132_IDX_0002,sizeof(SZL_ID_0132_IDX_0002));break; + case 0x0003 : SZLData(&SZL_ID_0132_IDX_0003,sizeof(SZL_ID_0132_IDX_0003));break; + case 0x0004 : SZLData(&SZL_ID_0132_IDX_0004,sizeof(SZL_ID_0132_IDX_0004));break; + case 0x0005 : SZLData(&SZL_ID_0132_IDX_0005,sizeof(SZL_ID_0132_IDX_0005));break; + case 0x0006 : SZLData(&SZL_ID_0132_IDX_0006,sizeof(SZL_ID_0132_IDX_0006));break; + case 0x0007 : SZLData(&SZL_ID_0132_IDX_0007,sizeof(SZL_ID_0132_IDX_0007));break; + case 0x0008 : SZLData(&SZL_ID_0132_IDX_0008,sizeof(SZL_ID_0132_IDX_0008));break; + case 0x0009 : SZLData(&SZL_ID_0132_IDX_0009,sizeof(SZL_ID_0132_IDX_0009));break; + case 0x000A : SZLData(&SZL_ID_0132_IDX_000A,sizeof(SZL_ID_0132_IDX_000A));break; + case 0x000B : SZLData(&SZL_ID_0132_IDX_000B,sizeof(SZL_ID_0132_IDX_000B));break; + case 0x000C : SZLData(&SZL_ID_0132_IDX_000C,sizeof(SZL_ID_0132_IDX_000C));break; + default : SZLNotAvailable();break; + };break; + case 0x0137 : switch(SZL.Index){ + case 0x07FE : SZLData(&SZL_ID_0137_IDX_07FE,sizeof(SZL_ID_0137_IDX_07FE));break; + default : SZLNotAvailable();break; + };break; + case 0x01A0 : switch(SZL.Index){ + case 0x0000 : SZLData(&SZL_ID_01A0_IDX_0000,sizeof(SZL_ID_01A0_IDX_0000));break; + case 0x0001 : SZLData(&SZL_ID_01A0_IDX_0001,sizeof(SZL_ID_01A0_IDX_0001));break; + case 0x0002 : SZLData(&SZL_ID_01A0_IDX_0002,sizeof(SZL_ID_01A0_IDX_0002));break; + case 0x0003 : SZLData(&SZL_ID_01A0_IDX_0003,sizeof(SZL_ID_01A0_IDX_0003));break; + case 0x0004 : SZLData(&SZL_ID_01A0_IDX_0004,sizeof(SZL_ID_01A0_IDX_0004));break; + case 0x0005 : SZLData(&SZL_ID_01A0_IDX_0005,sizeof(SZL_ID_01A0_IDX_0005));break; + case 0x0006 : SZLData(&SZL_ID_01A0_IDX_0006,sizeof(SZL_ID_01A0_IDX_0006));break; + case 0x0007 : SZLData(&SZL_ID_01A0_IDX_0007,sizeof(SZL_ID_01A0_IDX_0007));break; + case 0x0008 : SZLData(&SZL_ID_01A0_IDX_0008,sizeof(SZL_ID_01A0_IDX_0008));break; + case 0x0009 : SZLData(&SZL_ID_01A0_IDX_0009,sizeof(SZL_ID_01A0_IDX_0009));break; + case 0x000A : SZLData(&SZL_ID_01A0_IDX_000A,sizeof(SZL_ID_01A0_IDX_000A));break; + case 0x000B : SZLData(&SZL_ID_01A0_IDX_000B,sizeof(SZL_ID_01A0_IDX_000B));break; + case 0x000C : SZLData(&SZL_ID_01A0_IDX_000C,sizeof(SZL_ID_01A0_IDX_000C));break; + case 0x000D : SZLData(&SZL_ID_01A0_IDX_000D,sizeof(SZL_ID_01A0_IDX_000D));break; + case 0x000E : SZLData(&SZL_ID_01A0_IDX_000E,sizeof(SZL_ID_01A0_IDX_000E));break; + case 0x000F : SZLData(&SZL_ID_01A0_IDX_000F,sizeof(SZL_ID_01A0_IDX_000F));break; + case 0x0010 : SZLData(&SZL_ID_01A0_IDX_0010,sizeof(SZL_ID_01A0_IDX_0010));break; + case 0x0011 : SZLData(&SZL_ID_01A0_IDX_0011,sizeof(SZL_ID_01A0_IDX_0011));break; + case 0x0012 : SZLData(&SZL_ID_01A0_IDX_0012,sizeof(SZL_ID_01A0_IDX_0012));break; + case 0x0013 : SZLData(&SZL_ID_01A0_IDX_0013,sizeof(SZL_ID_01A0_IDX_0013));break; + case 0x0014 : SZLData(&SZL_ID_01A0_IDX_0014,sizeof(SZL_ID_01A0_IDX_0014));break; + case 0x0015 : SZLData(&SZL_ID_01A0_IDX_0015,sizeof(SZL_ID_01A0_IDX_0015));break; + default : SZLNotAvailable();break; + };break; + case 0x0174 : switch(SZL.Index){ + case 0x0001 : SZLData(&SZL_ID_0174_IDX_0001,sizeof(SZL_ID_0174_IDX_0001));break; + case 0x0004 : SZLData(&SZL_ID_0174_IDX_0004,sizeof(SZL_ID_0174_IDX_0004));break; + case 0x0005 : SZLData(&SZL_ID_0174_IDX_0005,sizeof(SZL_ID_0174_IDX_0005));break; + case 0x0006 : SZLData(&SZL_ID_0174_IDX_0006,sizeof(SZL_ID_0174_IDX_0006));break; + case 0x000B : SZLData(&SZL_ID_0174_IDX_000B,sizeof(SZL_ID_0174_IDX_000B));break; + case 0x000C : SZLData(&SZL_ID_0174_IDX_000C,sizeof(SZL_ID_0174_IDX_000C));break; + default : SZLNotAvailable();break; + };break; + case 0x0194 : switch(SZL.Index){ + case 0x0064 : SZLData(&SZL_ID_0194_IDX_0064,sizeof(SZL_ID_0194_IDX_0064));break; + default : SZLNotAvailable();break; + };break; + case 0x0694 : switch(SZL.Index){ + case 0x0064 : SZLData(&SZL_ID_0694_IDX_0064,sizeof(SZL_ID_0694_IDX_0064));break; + default : SZLNotAvailable();break; + };break; + case 0x0232 : switch(SZL.Index){ + case 0x0001 : SZLData(&SZL_ID_0232_IDX_0001,sizeof(SZL_ID_0232_IDX_0001));break; + case 0x0004 : SZLData(&SZL_ID_0232_IDX_0004,sizeof(SZL_ID_0232_IDX_0004));break; + default : SZLNotAvailable();break; + };break; + case 0x0C91 : switch(SZL.Index){ + case 0x07FE : SZLData(&SZL_ID_0C91_IDX_07FE,sizeof(SZL_ID_0C91_IDX_07FE));break; + default : SZLNotAvailable();break; + };break; + default : SZLNotAvailable();break; + } + // Event + if (SZL.SZLDone) + DoEvent(evcReadSZL,evrNoError,SZL.ID,SZL.Index,0,0); + else + DoEvent(evcReadSZL,evrInvalidSZL,SZL.ID,SZL.Index,0,0); + return true; +} +//------------------------------------------------------------------------------ +bool TS7Worker::PerformGroupSecurity() +{ + PReqFunSecurity ReqParams; + PResParamsSecurity ResParams; + PResDataSecurity ResData; + TS7Answer17 Answer; + int TotalSize; + + ReqParams=PReqFunSecurity(pbyte(PDUH_in)+ReqHeaderSize); + ResParams=PResParamsSecurity(pbyte(&Answer)+ResHeaderSize17); + ResData =PResDataSecurity(pbyte(ResParams)+sizeof(TResParamsSecurity)); + + // Prepares the answer + Answer.Header.P=0x32; + Answer.Header.PDUType=PduType_userdata; + Answer.Header.AB_EX=0x0000; + Answer.Header.Sequence=PDUH_in->Sequence; + Answer.Header.ParLen =SwapWord(sizeof(TResParamsSecurity)); + Answer.Header.DataLen=SwapWord(0x0004); + // Params + ResParams->Head[0]=ReqParams->Head[0]; + ResParams->Head[1]=ReqParams->Head[1]; + ResParams->Head[2]=ReqParams->Head[2]; + ResParams->Plen =0x08; + ResParams->Uk =0x12; + ResParams->Tg =0x85; // Type response, group functions info + ResParams->SubFun=ReqParams->SubFun; + ResParams->Seq =ReqParams->Seq; + ResParams->resvd =0x0000; + ResParams->Err =0x0000; + // Data + ResData->Ret =0x0A; + ResData->TS =0x00; + ResData->DLen=0x0000; + + TotalSize=26; + isoSendBuffer(&Answer,TotalSize); + + switch (ReqParams->SubFun) + { + case SFun_EnterPwd : DoEvent(evcSecurity,evrNoError,evsSetPassword,0,0,0); break; + case SFun_CancelPwd : DoEvent(evcSecurity,evrNoError,evsClrPassword,0,0,0); break; + default : DoEvent(evcSecurity,evrNoError,evsUnknown,0,0,0); + }; + + return true; +} +//------------------------------------------------------------------------------ +bool TS7Worker::PerformGetClock() +{ + PS7ReqParams7 ReqParams; + PS7ResParams7 ResParams; + TS7Answer17 Answer; + PResDataGetTime Data; + PS7Time PTime; + int TotalSize; + + ReqParams=PS7ReqParams7(pbyte(PDUH_in)+ReqHeaderSize); + ResParams=PS7ResParams7(pbyte(&Answer)+ResHeaderSize17); + Data =PResDataGetTime(pbyte(&Answer)+ResHeaderSize17+sizeof(TS7Params7)); + PTime =PS7Time(pbyte(Data)+6); + + // Prepares the answer + Answer.Header.P=0x32; + Answer.Header.PDUType=PduType_userdata; + Answer.Header.AB_EX=0x0000; + Answer.Header.Sequence=PDUH_in->Sequence; + Answer.Header.ParLen =SwapWord(sizeof(TS7Params7)); + Answer.Header.DataLen=SwapWord(sizeof(TResDataGetTime)); + + ResParams->Head[0]=ReqParams->Head[0]; + ResParams->Head[1]=ReqParams->Head[1]; + ResParams->Head[2]=ReqParams->Head[2]; + ResParams->Plen =0x08; + ResParams->Uk =0x12; + ResParams->Tg =0x87; // Type response, group functions info + ResParams->SubFun=ReqParams->SubFun; + ResParams->Seq =ReqParams->Seq; + ResParams->resvd =0x0000; + ResParams->Err =0x0000; + + Data->RetVal =0xFF; + Data->TSize =TS_ResOctet; + Data->Length =SwapWord(10); + Data->Rsvd =0x00; + Data->HiYear =0x20; // Year 2000 + + FillTime(PTime); + + TotalSize=36; + isoSendBuffer(&Answer,TotalSize); + DoEvent(evcClock,evrNoError,evsGetClock,0,0,0); + return true; +} +//------------------------------------------------------------------------------ +bool TS7Worker::PerformSetClock() +{ + PS7ReqParams7 ReqParams; + PS7ResParams7 ResParams; + PResDataSetTime Data; + TS7Answer17 Answer; + int TotalSize; + + ReqParams=PS7ReqParams7(pbyte(PDUH_in)+ReqHeaderSize); + ResParams=PS7ResParams7(pbyte(&Answer)+ResHeaderSize17); + Data =PResDataSetTime(pbyte(&Answer)+ResHeaderSize17+sizeof(TS7Params7)); + + // Prepares the answer + Answer.Header.P=0x32; + Answer.Header.PDUType=PduType_userdata; + Answer.Header.AB_EX=0x0000; + Answer.Header.Sequence=PDUH_in->Sequence; + Answer.Header.ParLen =SwapWord(sizeof(TS7Params7)); + Answer.Header.DataLen=SwapWord(sizeof(TResDataSetTime)); + + ResParams->Head[0]=ReqParams->Head[0]; + ResParams->Head[1]=ReqParams->Head[1]; + ResParams->Head[2]=ReqParams->Head[2]; + ResParams->Plen =0x08; + ResParams->Uk =0x12; + ResParams->Tg =0x87; // Type response, group functions info + ResParams->SubFun=ReqParams->SubFun; + ResParams->Seq =ReqParams->Seq; + ResParams->resvd =0x0000; + ResParams->Err =0x0000; + + Data->RetVal =0x0A; + Data->TSize =0x00; + Data->Length =0x0000; + + TotalSize=26; + isoSendBuffer(&Answer,TotalSize); + DoEvent(evcClock,evrNoError,evsSetClock,0,0,0); + return true; +} +//------------------------------------------------------------------------------ +// S7 SERVER CLASS +//------------------------------------------------------------------------------ +TSnap7Server::TSnap7Server() +{ + CSRWHook = new TSnapCriticalSection(); + OnReadEvent=NULL; + memset(&DB,0,sizeof(DB)); + memset(&HA,0,sizeof(HA)); + DBCount=0; + DBLimit=0; + ForcePDU = 0; + ResourceLess = false; + LocalPort=isoTcpPort; + CpuStatus=S7CpuStatusRun; + WorkInterval=100; +} +//------------------------------------------------------------------------------ +TSnap7Server::~TSnap7Server() +{ + DisposeAll(); + delete CSRWHook; +} +//------------------------------------------------------------------------------ +PWorkerSocket TSnap7Server::CreateWorkerSocket(socket_t Sock) +{ + PWorkerSocket Result; + Result = new TS7Worker(); + Result->SetSocket(Sock); + PS7Worker(Result)->FServer=this; + return Result; +} +//------------------------------------------------------------------------------ +PS7Area TSnap7Server::FindDB(word DBNumber) +{ + int c; + int max=DBLimit+1; + + for (c=0; cNumber==DBNumber) + { + return DB[c]; + } + } + } + return NULL; +} +//------------------------------------------------------------------------------ +int TSnap7Server::IndexOfDB(word DBNumber) +{ + int c; + int max=DBLimit+1; + + for (c=0; cNumber==DBNumber) + { + return c; + } + } + } + return -1; +} +//------------------------------------------------------------------------------ +int TSnap7Server::FindFirstFreeDB() +{ + int c; + for (c=0; c < MaxDB; c++) + { + if (DB[c]==NULL) + return c; + } + return -1; +} +//------------------------------------------------------------------------------ +int TSnap7Server::RegisterDB(word Number, void *pUsrData, word Size) +{ + PS7Area TheArea; + int index; + + if (pUsrData==0) + return errSrvDBNullPointer; + + if (FindDB(Number)!=NULL) + return errSrvAreaAlreadyExists; + + index=FindFirstFreeDB(); + if (index==-1) + return errSrvTooManyDB; + + TheArea =new TS7Area; + TheArea->Number=Number; + TheArea->cs=new TSnapCriticalSection(); + TheArea->PData=pbyte(pUsrData); + TheArea->Size=Size; + DB[index]=TheArea; + DBCount++; + if (DBLimitcs!=0) + delete TheDB->cs; + delete TheDB; + } + } + DBCount=0; + // Unregister other + for (c = srvAreaPE; c < srvAreaDB; c++) + UnregisterSys(c); +} +//------------------------------------------------------------------------------ +int TSnap7Server::RegisterSys(int AreaCode, void *pUsrData, word Size) +{ + PS7Area TheArea; + + if (pUsrData==0) + return errSrvDBNullPointer; + + if ((AreaCodesrvAreaTM)) + return errSrvUnknownArea; + + if (HA[AreaCode]==0) + { + TheArea=new TS7Area; + TheArea->cs=new TSnapCriticalSection(); + TheArea->PData=pbyte(pUsrData); + TheArea->Size=Size; + HA[AreaCode]=TheArea; + return 0; + } + else + return errSrvAreaAlreadyExists; +} +//------------------------------------------------------------------------------ +int TSnap7Server::UnregisterDB(word DBNumber) +{ + PS7Area TheDB; + int index = IndexOfDB(DBNumber); + if (index==-1) + return errSrvInvalidParams; + + // Unregister should be done with the server in stop mode + // however we can minimize the risk... + TheDB=DB[index]; + DB[index]=NULL; + if (TheDB->cs!=NULL) + delete TheDB->cs; + delete TheDB; + DBCount--; + + return 0; +} +//------------------------------------------------------------------------------ +int TSnap7Server::UnregisterSys(int AreaCode) +{ + PS7Area TheArea; + if (HA[AreaCode]!=NULL) + { + // Unregister should be done with the server in stop mode + // however we can minimize the risk... + TheArea=HA[AreaCode]; + HA[AreaCode]=NULL; + if (TheArea->cs!=NULL) + delete TheArea->cs; + delete TheArea; + } + return 0; +} +//------------------------------------------------------------------------------ +int TSnap7Server::StartTo(const char *Address) +{ + return TCustomMsgServer::StartTo(Address, LocalPort); +} +//------------------------------------------------------------------------------ +int TSnap7Server::GetParam(int ParamNumber, void *pValue) +{ + switch (ParamNumber) + { + case p_u16_LocalPort: + *Puint16_t(pValue)=LocalPort; + break; + case p_i32_WorkInterval: + *Pint32_t(pValue)=WorkInterval; + break; + case p_i32_MaxClients: + *Pint32_t(pValue)=MaxClients; + break; + case p_i32_PDURequest: + *Pint32_t(pValue) = ForcePDU; + break; + default: return errSrvInvalidParamNumber; + } + return 0; +} +//------------------------------------------------------------------------------ +int TSnap7Server::SetParam(int ParamNumber, void *pValue) +{ + switch (ParamNumber) + { + case p_u16_LocalPort: + if (Status == SrvStopped) + LocalPort = *Puint16_t(pValue); + else + return errSrvCannotChangeParam; + break; + case p_i32_PDURequest: + if (Status == SrvStopped) + { + int PDU = *Pint32_t(pValue); + if (PDU == 0) + ForcePDU = 0; // ForcePDU=0 --> The server accepts the client's proposal + else + { + if ((PDU < MinPduSize) || (PDU>IsoPayload_Size)) + return errSrvInvalidParams; // Wrong value + else + ForcePDU = PDU; // The server imposes ForcePDU as PDU size + } + } + else + return errSrvCannotChangeParam; + break; + case p_i32_WorkInterval: + WorkInterval=*Pint32_t(pValue); + break; + case p_i32_MaxClients: + if (ClientsCount==0 && Status==SrvStopped) + MaxClients=*Pint32_t(pValue); + else + return errSrvCannotChangeParam; + break; + default: return errSrvInvalidParamNumber; + } + return 0; +} +//------------------------------------------------------------------------------ +int TSnap7Server::RegisterArea(int AreaCode, word Index, void *pUsrData, word Size) +{ + if (AreaCode==srvAreaDB) + return RegisterDB(Index, pUsrData, Size); + else + return RegisterSys(AreaCode,pUsrData, Size); +} +//------------------------------------------------------------------------------ +int TSnap7Server::UnregisterArea(int AreaCode, word Index) +{ + if (AreaCode==srvAreaDB) + return UnregisterDB(Index); + else + if ((AreaCode>=srvAreaPE) && (AreaCode<=srvAreaTM)) + return UnregisterSys(AreaCode); + else + return errSrvInvalidParams; +} +//------------------------------------------------------------------------------ +int TSnap7Server::LockArea(int AreaCode, word DBNumber) +{ + int index; + + if ((AreaCode>=srvAreaPE) && (AreaCode<=srvAreaTM)) + { + if (HA[AreaCode]!=0) + { + HA[AreaCode]->cs->Enter(); + return 0; + } + else + return errSrvInvalidParams; + } + else + if (AreaCode==srvAreaDB) + { + index=IndexOfDB(DBNumber); + if (index!=-1) + { + DB[index]->cs->Enter(); + return 0; + } + else + return errSrvInvalidParams; + } + else + return errSrvInvalidParams; +} +//------------------------------------------------------------------------------ +int TSnap7Server::UnlockArea(int AreaCode, word DBNumber) +{ + int index; + + if ((AreaCode>=srvAreaPE) && (AreaCode<=srvAreaTM)) + { + if (HA[AreaCode]!=0) + { + HA[AreaCode]->cs->Leave(); + return 0; + } + else + return errSrvInvalidParams; + } + else + if (AreaCode==srvAreaDB) + { + index=IndexOfDB(DBNumber); + if (index!=-1) + { + DB[index]->cs->Leave(); + return 0; + } + else + return errSrvInvalidParams; + } + else + return errSrvInvalidParams; +} +//------------------------------------------------------------------------------ +int TSnap7Server::SetReadEventsCallBack(pfn_SrvCallBack PCallBack, void *UsrPtr) +{ + OnReadEvent = PCallBack; + FReadUsrPtr = UsrPtr; + return 0; +} +//--------------------------------------------------------------------------- +int TSnap7Server::SetRWAreaCallBack(pfn_RWAreaCallBack PCallBack, void *UsrPtr) +{ + OnRWArea = PCallBack; + FRWAreaUsrPtr = UsrPtr; + ResourceLess = OnRWArea != NULL; + return 0; +} +//--------------------------------------------------------------------------- +void TSnap7Server::DoReadEvent(int Sender, longword Code, word RetCode, word Param1, + word Param2, word Param3, word Param4) +{ + TSrvEvent SrvReadEvent; + if (!Destroying && (OnReadEvent != NULL)) + { + CSEvent->Enter(); + + time(&SrvReadEvent.EvtTime); + SrvReadEvent.EvtSender = Sender; + SrvReadEvent.EvtCode = Code; + SrvReadEvent.EvtRetCode = RetCode; + SrvReadEvent.EvtParam1 = Param1; + SrvReadEvent.EvtParam2 = Param2; + SrvReadEvent.EvtParam3 = Param3; + SrvReadEvent.EvtParam4 = Param4; + + try + { // callback is outside here, we have to shield it + OnReadEvent(FReadUsrPtr, &SrvReadEvent, sizeof (TSrvEvent)); + } catch (...) + { + }; + CSEvent->Leave(); + }; +} +//--------------------------------------------------------------------------- +bool TSnap7Server::DoReadArea(int Sender, int Area, int DBNumber, int Start, int Size, int WordLen, void *pUsrData) +{ + TS7Tag Tag; + bool Result = false; + if (!Destroying && (OnRWArea != NULL)) + { + CSRWHook->Enter(); + try + { + Tag.Area = Area; + Tag.DBNumber = DBNumber; + Tag.Start = Start; + Tag.Size = Size; + Tag.WordLen = WordLen; + // callback is outside here, we have to shield it + Result = OnRWArea(FRWAreaUsrPtr, Sender, OperationRead, &Tag, pUsrData) == 0; + } + catch (...) + { + Result = false; + }; + CSRWHook->Leave(); + } + return Result; +} +//--------------------------------------------------------------------------- +bool TSnap7Server::DoWriteArea(int Sender, int Area, int DBNumber, int Start, int Size, int WordLen, void *pUsrData) +{ + TS7Tag Tag; + bool Result = false; + if (!Destroying && (OnRWArea != NULL)) + { + CSRWHook->Enter(); + try + { + Tag.Area = Area; + Tag.DBNumber = DBNumber; + Tag.Start = Start; + Tag.Size = Size; + Tag.WordLen = WordLen; + // callback is outside here, we have to shield it + Result = OnRWArea(FRWAreaUsrPtr, Sender, OperationWrite, &Tag, pUsrData) == 0; + } + catch (...) + { + Result = false; + }; + CSRWHook->Leave(); + } + return Result; +} + + diff --git a/core/src/drivers/plugins/native/s7comm/snap7/core/s7_server.h b/core/src/drivers/plugins/native/s7comm/snap7/core/s7_server.h new file mode 100644 index 00000000..6d502cac --- /dev/null +++ b/core/src/drivers/plugins/native/s7comm/snap7/core/s7_server.h @@ -0,0 +1,261 @@ +/*=============================================================================| +| PROJECT SNAP7 1.4.3 | +|==============================================================================| +| Copyright (C) 2013, 2025 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/ | +|=============================================================================*/ +#ifndef s7_server_h +#define s7_server_h +//--------------------------------------------------------------------------- +#include "snap_tcpsrvr.h" +#include "s7_types.h" +#include "s7_isotcp.h" +//--------------------------------------------------------------------------- + +// Maximum number of DB, change it to increase/decrease the limit. +// The DB table size is 12*MaxDB bytes + +#define MaxDB 2048 // Like a S7 318 +#define MinPduSize 240 +#define CPU315PduSize 240 +//--------------------------------------------------------------------------- +// Server Interface errors +const longword errSrvDBNullPointer = 0x00200000; // Pssed null as PData +const longword errSrvAreaAlreadyExists = 0x00300000; // Area Re-registration +const longword errSrvUnknownArea = 0x00400000; // Unknown area +const longword errSrvInvalidParams = 0x00500000; // Invalid param(s) supplied +const longword errSrvTooManyDB = 0x00600000; // Cannot register DB +const longword errSrvInvalidParamNumber = 0x00700000; // Invalid param (srv_get/set_param) +const longword errSrvCannotChangeParam = 0x00800000; // Cannot change because running + +// Server Area ID (use with Register/unregister - Lock/unlock Area) +const int srvAreaPE = 0; +const int srvAreaPA = 1; +const int srvAreaMK = 2; +const int srvAreaCT = 3; +const int srvAreaTM = 4; +const int srvAreaDB = 5; + +typedef struct{ + word Number; // Number (only for DB) + word Size; // Area size (in bytes) + pbyte PData; // Pointer to area + PSnapCriticalSection cs; +}TS7Area, *PS7Area; + +//------------------------------------------------------------------------------ +// ISOTCP WORKER CLASS +//------------------------------------------------------------------------------ +class TIsoTcpWorker : public TIsoTcpSocket +{ +protected: + virtual bool IsoPerformCommand(int &Size); + virtual bool ExecuteSend(); + virtual bool ExecuteRecv(); +public: + TIsoTcpWorker(){}; + ~TIsoTcpWorker(){}; + // Worker execution + bool Execute(); +}; +//------------------------------------------------------------------------------ +// S7 WORKER CLASS +//------------------------------------------------------------------------------ + +// SZL frame +typedef struct{ + TS7Answer17 Answer; + PReqFunReadSZLFirst ReqParams; + PS7ReqSZLData ReqData; + PS7ResParams7 ResParams; + pbyte ResData; + int ID; + int Index; + bool SZLDone; +}TSZL; + +// Current Event Info +typedef struct{ + word EvRetCode; + word EvArea; + word EvIndex; + word EvStart; + word EvSize; +}TEv; + +// Current Block info +typedef struct{ + PReqFunGetBlockInfo ReqParams; + PResFunGetBlockInfo ResParams; + TS7Answer17 Answer; + word evError; + word DataLength; +}TCB; + +class TSnap7Server; // forward declaration + +class TS7Worker : public TIsoTcpWorker +{ +private: + PS7ReqHeader PDUH_in; + int DBCnt; + byte LastBlk; + TSZL SZL; + byte BCD(word Value); + // Checks the consistence of the incoming PDU + bool CheckPDU_in(int PayloadSize); + void FillTime(PS7Time PTime); +protected: + int DataSizeByte(int WordLength); + bool ExecuteRecv(); + void DoEvent(longword Code, word RetCode, word Param1, word Param2, + word Param3, word Param4); + void DoReadEvent(longword Code, word RetCode, word Param1, word Param2, + word Param3, word Param4); + void FragmentSkipped(int Size); + // Entry parse + bool IsoPerformCommand(int &Size); + // First stage parse + bool PerformPDUAck(int &Size); + bool PerformPDURequest(int &Size); + bool PerformPDUUsrData(int &Size); + // Second stage parse : PDU Request + PS7Area GetArea(byte S7Code, word index); + // Group Read Area + bool PerformFunctionRead(); + // Subfunctions Read Data + word ReadArea(PResFunReadItem ResItemData, PReqFunReadItem ReqItemPar, + int &PDURemainder,TEv &EV); + word RA_NotFound(PResFunReadItem ResItem, TEv &EV); + word RA_OutOfRange(PResFunReadItem ResItem, TEv &EV); + word RA_SizeOverPDU(PResFunReadItem ResItem, TEv &EV); + // Group Write Area + bool PerformFunctionWrite(); + // Subfunctions Write Data + byte WriteArea(PReqFunWriteDataItem ReqItemData, PReqFunWriteItem ReqItemPar, + TEv &EV); + byte WA_NotFound(TEv &EV); + byte WA_InvalidTransportSize(TEv &EV); + byte WA_OutOfRange(TEv &EV); + byte WA_DataSizeMismatch(TEv &EV); + // Negotiate PDU Length + bool PerformFunctionNegotiate(); + // Control + bool PerformFunctionControl(byte PduFun); + // Up/Download + bool PerformFunctionUpload(); + bool PerformFunctionDownload(); + // Second stage parse : PDU User data + bool PerformGroupProgrammer(); + bool PerformGroupCyclicData(); + bool PerformGroupSecurity(); + // Group Block(s) Info + bool PerformGroupBlockInfo(); + // Subfunctions Block info + void BLK_ListAll(TCB &CB); + void BLK_ListBoT(byte BlockType, bool Start, TCB &CB); + void BLK_NoResource_ListBoT(PDataFunGetBot Data, TCB &CB); + void BLK_GetBlkInfo(TCB &CB); + void BLK_NoResource_GetBlkInfo(PResDataBlockInfo Data, TCB &CB); + void BLK_GetBlockNum_GetBlkInfo(int &BlkNum, PReqDataBlockInfo ReqData); + void BLK_DoBlockInfo_GetBlkInfo(PS7Area DB, PResDataBlockInfo Data, TCB &CB); + // Clock Group + bool PerformGetClock(); + bool PerformSetClock(); + // SZL Group + bool PerformGroupSZL(); + // Subfunctions (called by PerformGroupSZL) + void SZLNotAvailable(); + void SZLSystemState(); + void SZLData(void *P, int len); + void SZL_ID424(); + void SZL_ID131_IDX003(); +public: + TSnap7Server *FServer; + int FPDULength; + TS7Worker(); + ~TS7Worker(){}; +}; + +typedef TS7Worker *PS7Worker; +//------------------------------------------------------------------------------ +// S7 SERVER CLASS +//------------------------------------------------------------------------------ +extern "C" +{ + typedef int (S7API *pfn_RWAreaCallBack)(void *usrPtr, int Sender, int Operation, PS7Tag PTag, void *pUsrData); +} +const int OperationRead = 0; +const int OperationWrite = 1; + +class TSnap7Server : public TCustomMsgServer +{ +private: + // Read Callback related + pfn_SrvCallBack OnReadEvent; + pfn_RWAreaCallBack OnRWArea; + // Critical section to lock Read/Write Hook Area + PSnapCriticalSection CSRWHook; + void *FReadUsrPtr; + void *FRWAreaUsrPtr; + void DisposeAll(); + int FindFirstFreeDB(); + int IndexOfDB(word DBNumber); +protected: + int DBCount; + int DBLimit; + PS7Area DB[MaxDB]; // DB + PS7Area HA[5]; // MK,PE,PA,TM,CT + PS7Area FindDB(word DBNumber); + PWorkerSocket CreateWorkerSocket(socket_t Sock); + bool ResourceLess; + word ForcePDU; + int RegisterDB(word Number, void *pUsrData, word Size); + int RegisterSys(int AreaCode, void *pUsrData, word Size); + int UnregisterDB(word DBNumber); + int UnregisterSys(int AreaCode); + // The Read event + void DoReadEvent(int Sender, longword Code, word RetCode, word Param1, + word Param2, word Param3, word Param4); + bool DoReadArea(int Sender, int Area, int DBNumber, int Start, int Size, int WordLen, void *pUsrData); + bool DoWriteArea(int Sender, int Area, int DBNumber, int Start, int Size, int WordLen, void *pUsrData); +public: + int WorkInterval; + byte CpuStatus; + TSnap7Server(); + ~TSnap7Server(); + int StartTo(const char *Address); + int GetParam(int ParamNumber, void *pValue); + int SetParam(int ParamNumber, void *pValue); + int RegisterArea(int AreaCode, word Index, void *pUsrData, word Size); + int UnregisterArea(int AreaCode, word Index); + int LockArea(int AreaCode, word DBNumber); + int UnlockArea(int AreaCode, word DBNumber); + // Sets Event callback + int SetReadEventsCallBack(pfn_SrvCallBack PCallBack, void *UsrPtr); + int SetRWAreaCallBack(pfn_RWAreaCallBack PCallBack, void *UsrPtr); + friend class TS7Worker; +}; +typedef TSnap7Server *PSnap7Server; + +#endif // s7_server_h + diff --git a/core/src/drivers/plugins/native/s7comm/snap7/core/s7_text.cpp b/core/src/drivers/plugins/native/s7comm/snap7/core/s7_text.cpp new file mode 100644 index 00000000..d1ec4c68 --- /dev/null +++ b/core/src/drivers/plugins/native/s7comm/snap7/core/s7_text.cpp @@ -0,0 +1,788 @@ +/*=============================================================================| +| PROJECT SNAP7 1.4.3 | +|==============================================================================| +| Copyright (C) 2013, 2025 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/ | +|=============================================================================*/ +#include "s7_text.h" +//--------------------------------------------------------------------------- +#ifndef OS_WINDOWS +static char* itoa(int value, char* result, int base) { + // check that the base if valid + if (base < 2 || base > 36){ + *result = '\0'; return result; + + } + char* ptr = result, *ptr1 = result, tmp_char; + int tmp_value; + + do { + tmp_value = value; + value /= base; + *ptr++ = "zyxwvutsrqponmlkjihgfedcba9876543210123456789abcdefghijklmnopqrstuvwxyz" [35 + (tmp_value - value * base)]; + } while ( value ); + + // Apply negative sign + if (tmp_value < 0) *ptr++ = '-'; + *ptr-- = '\0'; + while(ptr1 < ptr) { + tmp_char = *ptr; + *ptr--= *ptr1; + *ptr1++ = tmp_char; + } + return result; +} +#endif +//--------------------------------------------------------------------------- +char* NumToString(int Value, int Base, int Len, char* Result) +{ + char CNumber[64]; + char Pad[65] = "0000000000000000000000000000000000000000000000000000000000000000"; + itoa(Value, CNumber, Base); + + if (Len > 0) + { + int Delta = Len - strlen(CNumber); // Len is max 8 in this program + if (Delta > 0) + { + strncpy(Result, Pad, Delta); + Result[Delta] = '\0'; + strcat(Result, CNumber); + } + else + strcpy(Result, CNumber); + } + else + strcpy(Result, CNumber); + + return Result; +} +//--------------------------------------------------------------------------- +char* IntToString(int Value, char* Result) +{ + return NumToString(Value, 10, 0, Result); +} +//--------------------------------------------------------------------------- +char* TimeToString(time_t dt, char* Result) +{ + struct tm * DateTime = localtime(&dt); + if (DateTime != NULL) + strftime(Result, 50, "%Y-%m-%d %H:%M:%S", DateTime); + else + *Result = '\0'; + return Result; +} + +//--------------------------------------------------------------------------- +char* IpAddressToString(int IP, char* Result) +{ + in_addr Addr; + Addr.s_addr = IP; + strcpy(Result, inet_ntoa(Addr)); + return Result; +} +//--------------------------------------------------------------------------- +#define WSAEINVALIDADDRESS 12001 + +char* TcpTextOf(int Error, char* Result) +{ + switch (Error) + { + case 0: *Result='\0';break; + case WSAEINTR: strcpy(Result," TCP : Interrupted system call\0");break; + case WSAEBADF: strcpy(Result," TCP : Bad file number\0");break; + case WSAEACCES: strcpy(Result," TCP : Permission denied\0");break; + case WSAEFAULT: strcpy(Result," TCP : Bad address\0");break; + case WSAEINVAL: strcpy(Result," TCP : Invalid argument\0");break; + case WSAEMFILE: strcpy(Result," TCP : Too many open files\0");break; + case WSAEWOULDBLOCK: strcpy(Result," TCP : Operation would block\0");break; + case WSAEINPROGRESS: strcpy(Result," TCP : Operation now in progress\0");break; + case WSAEALREADY: strcpy(Result," TCP : Operation already in progress\0");break; + case WSAENOTSOCK: strcpy(Result," TCP : Socket operation on non socket\0");break; + case WSAEDESTADDRREQ: strcpy(Result," TCP : Destination address required\0");break; + case WSAEMSGSIZE: strcpy(Result," TCP : Message too long\0");break; + case WSAEPROTOTYPE: strcpy(Result," TCP : Protocol wrong type for Socket\0");break; + case WSAENOPROTOOPT: strcpy(Result," TCP : Protocol not available\0");break; + case WSAEPROTONOSUPPORT: strcpy(Result," TCP : Protocol not supported\0");break; + case WSAESOCKTNOSUPPORT: strcpy(Result," TCP : Socket not supported\0");break; + case WSAEOPNOTSUPP: strcpy(Result," TCP : Operation not supported on Socket\0");break; + case WSAEPFNOSUPPORT: strcpy(Result," TCP : Protocol family not supported\0");break; + case WSAEAFNOSUPPORT: strcpy(Result," TCP : Address family not supported\0");break; + case WSAEADDRINUSE: strcpy(Result," TCP : Address already in use\0");break; + case WSAEADDRNOTAVAIL: strcpy(Result," TCP : Can't assign requested address\0");break; + case WSAENETDOWN: strcpy(Result," TCP : Network is down\0");break; + case WSAENETUNREACH: strcpy(Result," TCP : Network is unreachable\0");break; + case WSAENETRESET: strcpy(Result," TCP : Network dropped connection on reset\0");break; + case WSAECONNABORTED: strcpy(Result," TCP : Software caused connection abort\0");break; + case WSAECONNRESET: strcpy(Result," TCP : Connection reset by peer\0");break; + case WSAENOBUFS: strcpy(Result," TCP : No Buffer space available\0");break; + case WSAEISCONN: strcpy(Result," TCP : Socket is already connected\0");break; + case WSAENOTCONN: strcpy(Result," TCP : Socket is not connected\0");break; + case WSAESHUTDOWN: strcpy(Result," TCP : Can't send after Socket shutdown\0");break; + case WSAETOOMANYREFS: strcpy(Result," TCP : Too many references:can't splice\0");break; + case WSAETIMEDOUT: strcpy(Result," TCP : Connection timed out\0");break; + case WSAECONNREFUSED: strcpy(Result," TCP : Connection refused\0");break; + case WSAELOOP: strcpy(Result," TCP : Too many levels of symbolic links\0");break; + case WSAENAMETOOLONG: strcpy(Result," TCP : File name is too long\0");break; + case WSAEHOSTDOWN: strcpy(Result," TCP : Host is down\0");break; + case WSAEHOSTUNREACH: strcpy(Result," TCP : Unreachable peer\0");break; + case WSAENOTEMPTY: strcpy(Result," TCP : Directory is not empty\0");break; + case WSAEUSERS: strcpy(Result," TCP : Too many users\0");break; + case WSAEDQUOT: strcpy(Result," TCP : Disk quota exceeded\0");break; + case WSAESTALE: strcpy(Result," TCP : Stale NFS file handle\0");break; + case WSAEREMOTE: strcpy(Result," TCP : Too many levels of remote in path\0");break; + #ifdef OS_WINDOWS + case WSAEPROCLIM: strcpy(Result," TCP : Too many processes\0");break; + case WSASYSNOTREADY: strcpy(Result," TCP : Network subsystem is unusable\0");break; + case WSAVERNOTSUPPORTED: strcpy(Result," TCP : Winsock DLL cannot support this application\0");break; + case WSANOTINITIALISED: strcpy(Result," TCP : Winsock not initialized\0");break; + case WSAEDISCON: strcpy(Result," TCP : Disconnect\0");break; + case WSAHOST_NOT_FOUND: strcpy(Result," TCP : Host not found\0");break; + case WSATRY_AGAIN: strcpy(Result," TCP : Non authoritative - host not found\0");break; + case WSANO_RECOVERY: strcpy(Result," TCP : Non recoverable error\0");break; + case WSANO_DATA: strcpy(Result," TCP : Valid name, no data record of requested type\0");break; + #endif + case WSAEINVALIDADDRESS: strcpy(Result," TCP : Invalid address\0");break; + default: + { + char CNumber[16]; + strcpy(Result, " TCP : Other Socket error ("); + strcat(Result, IntToString(Error, CNumber)); + strcat(Result, ")"); + break; + } + } + return Result; +} +//--------------------------------------------------------------------------- +char* IsoTextOf(int Error, char* Result) +{ + switch (Error) + { + case 0 : *Result='\0';break; + case errIsoConnect: strcpy(Result," ISO : Connection error\0");break; + case errIsoDisconnect: strcpy(Result," ISO : Disconnect error\0");break; + case errIsoInvalidPDU: strcpy(Result," ISO : Bad PDU format\0");break; + case errIsoInvalidDataSize: strcpy(Result," ISO : Datasize passed to send/recv buffer is invalid\0");break; + case errIsoNullPointer: strcpy(Result," ISO : Null passed as pointer\0");break; + case errIsoShortPacket: strcpy(Result," ISO : A short packet received\0");break; + case errIsoTooManyFragments: strcpy(Result," ISO : Too many packets without EoT flag\0");break; + case errIsoPduOverflow: strcpy(Result," ISO : The sum of fragments data exceded maximum packet size\0");break; + case errIsoSendPacket: strcpy(Result," ISO : An error occurred during send\0");break; + case errIsoRecvPacket: strcpy(Result," ISO : An error occurred during recv\0");break; + case errIsoInvalidParams: strcpy(Result," ISO : Invalid connection params (wrong TSAPs)\0");break; + default: + { + char CNumber[16]; + strcpy(Result, " ISO : Unknown error (0x"); + strcat(Result, NumToString(Error, 16, 8, CNumber)); + strcat(Result, ")"); + break; + } + } + return Result; +} +//--------------------------------------------------------------------------- +char* CliTextOf(int Error, char* Result) +{ + switch (Error) + { + case 0 : *Result='\0';break; + case errNegotiatingPDU : strcpy(Result,"CPU : Error in PDU negotiation\0");break; + case errCliInvalidParams : strcpy(Result,"CLI : invalid param(s) supplied\0");break; + case errCliJobPending : strcpy(Result,"CLI : Job pending\0");break; + case errCliTooManyItems : strcpy(Result,"CLI : too may items (>20) in multi read/write\0");break; + case errCliInvalidWordLen : strcpy(Result,"CLI : invalid WordLength\0");break; + case errCliPartialDataWritten : strcpy(Result,"CLI : Partial data written\0");break; + case errCliSizeOverPDU : strcpy(Result,"CPU : total data exceeds the PDU size\0");break; + case errCliInvalidPlcAnswer : strcpy(Result,"CLI : invalid CPU answer\0");break; + case errCliAddressOutOfRange : strcpy(Result,"CPU : Address out of range\0");break; + case errCliInvalidTransportSize : strcpy(Result,"CPU : Invalid Transport size\0");break; + case errCliWriteDataSizeMismatch : strcpy(Result,"CPU : Data size mismatch\0");break; + case errCliItemNotAvailable : strcpy(Result,"CPU : Item not available\0");break; + case errCliInvalidValue : strcpy(Result,"CPU : Invalid value supplied\0");break; + case errCliCannotStartPLC : strcpy(Result,"CPU : Cannot start PLC\0");break; + case errCliAlreadyRun : strcpy(Result,"CPU : PLC already RUN\0");break; + case errCliCannotStopPLC : strcpy(Result,"CPU : Cannot stop PLC\0");break; + case errCliCannotCopyRamToRom : strcpy(Result,"CPU : Cannot copy RAM to ROM\0");break; + case errCliCannotCompress : strcpy(Result,"CPU : Cannot compress\0");break; + case errCliAlreadyStop : strcpy(Result,"CPU : PLC already STOP\0");break; + case errCliFunNotAvailable : strcpy(Result,"CPU : Function not available\0");break; + case errCliUploadSequenceFailed : strcpy(Result,"CPU : Upload sequence failed\0");break; + case errCliInvalidDataSizeRecvd : strcpy(Result,"CLI : Invalid data size received\0");break; + case errCliInvalidBlockType : strcpy(Result,"CLI : Invalid block type\0");break; + case errCliInvalidBlockNumber : strcpy(Result,"CLI : Invalid block number\0");break; + case errCliInvalidBlockSize : strcpy(Result,"CLI : Invalid block size\0");break; + case errCliDownloadSequenceFailed : strcpy(Result,"CPU : Download sequence failed\0");break; + case errCliInsertRefused : strcpy(Result,"CPU : block insert refused\0");break; + case errCliDeleteRefused : strcpy(Result,"CPU : block delete refused\0");break; + case errCliNeedPassword : strcpy(Result,"CPU : Function not authorized for current protection level\0");break; + case errCliInvalidPassword : strcpy(Result,"CPU : Invalid password\0");break; + case errCliNoPasswordToSetOrClear : strcpy(Result,"CPU : No password to set or clear\0");break; + case errCliJobTimeout : strcpy(Result,"CLI : Job Timeout\0");break; + case errCliFunctionRefused : strcpy(Result,"CLI : function refused by CPU (Unknown error)\0");break; + case errCliPartialDataRead : strcpy(Result,"CLI : Partial data read\0");break; + case errCliBufferTooSmall : strcpy(Result,"CLI : The buffer supplied is too small to accomplish the operation\0");break; + case errCliDestroying : strcpy(Result,"CLI : Cannot perform (destroying)\0");break; + case errCliInvalidParamNumber : strcpy(Result,"CLI : Invalid Param Number\0");break; + case errCliCannotChangeParam : strcpy(Result,"CLI : Cannot change this param now\0");break; + default : + { + char CNumber[16]; + strcpy(Result, "CLI : Unknown error (0x"); + strcat(Result, NumToString(Error, 16, 8, CNumber)); + strcat(Result, ")"); + break; + } + }; + return Result; +} +//--------------------------------------------------------------------------- +char* SrvTextOf(int Error, char* Result) +{ + switch (Error) + { + case 0: *Result = '\0'; break; + case errSrvCannotStart: strcpy(Result, "SRV : Server cannot start\0"); break; + case errSrvDBNullPointer: strcpy(Result, "SRV : Null passed as area pointer\0"); break; + case errSrvAreaAlreadyExists: strcpy(Result, "SRV : Cannot register area since already exists\0"); break; + case errSrvUnknownArea: strcpy(Result, "SRV : Unknown Area code\0"); break; + case errSrvInvalidParams: strcpy(Result, "SRV : Invalid param(s) supplied\0"); break; + case errSrvTooManyDB: strcpy(Result, "SRV : DB Limit reached\0"); break; + case errSrvInvalidParamNumber: strcpy(Result, "SRV : Invalid Param Number\0"); break; + case errSrvCannotChangeParam: strcpy(Result, "SRV : Cannot change this param now\0");break; + default: + { + char CNumber[16]; + strcpy(Result, "SRV : Unknown error (0x"); + strcat(Result, NumToString(Error, 16, 8, CNumber)); + strcat(Result, ")"); + break; + } + }; + return Result; +} +//--------------------------------------------------------------------------- +char* ParTextOf(int Error, char* Result) +{ + switch(Error) + { + case 0: *Result = '\0'; break; + case errParAddressInUse : strcpy(Result, "PAR : Local address already in use");break; + case errParNoRoom : strcpy(Result, "PAR : No more partners available");break; + case errServerNoRoom : strcpy(Result, "PAR : No more servers available");break; + case errParInvalidParams : strcpy(Result, "PAR : Invalid parameter supplied");break; + case errParNotLinked : strcpy(Result, "PAR : Cannot perform, Partner not linked");break; + case errParBusy : strcpy(Result, "PAR : Cannot perform, Partner Busy");break; + case errParFrameTimeout : strcpy(Result, "PAR : Frame timeout");break; + case errParInvalidPDU : strcpy(Result, "PAR : Invalid PDU received");break; + case errParSendTimeout : strcpy(Result, "PAR : Send timeout");break; + case errParRecvTimeout : strcpy(Result, "PAR : Recv timeout");break; + case errParSendRefused : strcpy(Result, "PAR : Send refused by peer");break; + case errParNegotiatingPDU : strcpy(Result, "PAR : Error negotiating PDU");break; + case errParSendingBlock : strcpy(Result, "PAR : Error Sending Block");break; + case errParRecvingBlock : strcpy(Result, "PAR : Error Receiving Block");break; + case errParBindError : strcpy(Result, "PAR : Error Binding");break; + case errParDestroying : strcpy(Result, "PAR : Cannot perform (destroying)");break; + case errParInvalidParamNumber: strcpy(Result, "PAR : Invalid Param Number");break; + case errParCannotChangeParam : strcpy(Result, "PAR : Cannot change this param now");break; + case errParBufferTooSmall : strcpy(Result, "PAR : The buffer supplied is too small to accomplish the operation");break; + default: + { + char CNumber[16]; + strcpy(Result, "PAR : Unknown error (0x"); + strcat(Result, NumToString(Error, 16, 8, CNumber)); + strcat(Result, ")"); + break; + } + } + return Result; +} +//--------------------------------------------------------------------------- +char* ErrCliText(int Error, char * Result, int TextLen) +{ + char TcpError[128]; + char IsoError[128]; + char CliError[256]; + if (Error != 0) + { + switch (Error) + { + case errLibInvalidParam : strncpy(Result,"LIB : Invalid param supplied\0",TextLen);break; + case errLibInvalidObject: strncpy(Result, "LIB : Invalid object supplied\0", TextLen); break; + default : + { + CliTextOf(Error & ErrS7Mask, CliError); + strcat(CliError, IsoTextOf(Error & ErrIsoMask, IsoError)); + strcat(CliError, TcpTextOf(Error & ErrTcpMask, TcpError)); + strncpy(Result, CliError, TextLen); + } + } + } + else + strncpy(Result, "OK\0", TextLen); + return Result; +} +//--------------------------------------------------------------------------- +char* ErrSrvText(int Error, char* Result, int TextLen) +{ + char TcpError[128]; + char IsoError[128]; + char SrvError[256]; + if (Error != 0) + { + switch (Error) + { + case errLibInvalidParam: strncpy(Result, "LIB : Invalid param supplied\0", TextLen); break; + case errLibInvalidObject: strncpy(Result, "LIB : Invalid object supplied\0", TextLen); break; + default: + { + SrvTextOf(Error & ErrS7Mask, SrvError); + strcat(SrvError, IsoTextOf(Error & ErrIsoMask, IsoError)); + strcat(SrvError, TcpTextOf(Error & ErrTcpMask, TcpError)); + strncpy(Result, SrvError, TextLen); + } + } + } + else + strncpy(Result, "OK\0", TextLen); + return Result; +} +//--------------------------------------------------------------------------- +char* ErrParText(int Error, char* Result, int TextLen) +{ + char TcpError[128]; + char IsoError[128]; + char ParError[256]; + if (Error != 0) + { + switch (Error) + { + case errLibInvalidParam: strncpy(Result, "LIB : Invalid param supplied\0", TextLen); break; + case errLibInvalidObject: strncpy(Result, "LIB : Invalid object supplied\0", TextLen); break; + default: + { + ParTextOf(Error & ErrS7Mask, ParError); + strcat(ParError, IsoTextOf(Error & ErrIsoMask, IsoError)); + strcat(ParError, TcpTextOf(Error & ErrTcpMask, TcpError)); + strncpy(Result, ParError, TextLen); + } + } + } + else + strncpy(Result, "OK\0", TextLen); + return Result; +} +//--------------------------------------------------------------------------- +// SERVER EVENTS TEXT +//--------------------------------------------------------------------------- +char* SenderText(TSrvEvent &Event, char* Result) +{ + char Buf[64]; + char Add[16]; + TimeToString(Event.EvtTime, Buf); + if (Event.EvtSender != 0) + { + strcat(Buf, " ["); + strcat(Buf, IpAddressToString(Event.EvtSender, Add)); + strcat(Buf, "] "); + } + else + strcat(Buf, " Server "); + strcpy(Result, Buf); + return Result; +} +//--------------------------------------------------------------------------- +char* TcpServerEventText(TSrvEvent &Event, char* Result) +{ + char S[256]; + char Buf[128]; + + strcpy(S, SenderText(Event, Buf)); + + switch (Event.EvtCode) + { + case evcServerStarted : strcat(S,"started");break; + case evcServerStopped : strcat(S,"stopped");break; + case evcListenerCannotStart: + strcat(S, "Cannot start listener - Socket Error : "); + strcat(S, TcpTextOf(Event.EvtRetCode,Buf)); + break; + case evcClientAdded : strcat(S,"Client added");break; + case evcClientRejected : strcat(S,"Client refused");break; + case evcClientNoRoom : strcat(S,"A client was refused due to maximum connections number");break; + case evcClientException : strcat(S,"Client exception");break; + case evcClientDisconnected : strcat(S,"Client disconnected by peer");break; + case evcClientTerminated : strcat(S,"Client terminated");break; + case evcClientsDropped: + strcat(S, IntToString(Event.EvtParam1, Buf)); + strcat(S, " clients have been dropped bacause unresponsive"); + break; + default: + strcat(S, "Unknown event ("); + strcat(S, IntToString(Event.EvtCode, Buf)); + strcat(S,")"); + break; + }; + strcpy(Result, S); + return Result; +} +//--------------------------------------------------------------------------- +char* PDUText(TSrvEvent &Event, char* Result) +{ + char S[256]; + char Buf[128]; + switch (Event.EvtRetCode) + { + case evrFragmentRejected: + strcpy(S, "Fragment of "); + strcat(S, IntToString(Event.EvtParam1, Buf)); + strcat(S, " bytes rejected"); + break; + case evrMalformedPDU: + strcpy(S, "Malformed PDU of "); + strcat(S, IntToString(Event.EvtParam1, Buf)); + strcat(S, " bytes rejected"); + break; + case evrSparseBytes: + strcpy(S, "Message of sparse "); + strcat(S, IntToString(Event.EvtParam1, Buf)); + strcat(S, " bytes rejected"); + break; + case evrCannotHandlePDU: + strcpy(S, "Cannot handle this PDU"); + break; + case evrNotImplemented: + switch (Event.EvtParam1) + { + case grCyclicData: + strcpy(S, "Function group cyclic data not yet implemented"); + break; + case grProgrammer: + strcpy(S, "Function group programmer not yet implemented"); + break; + } + break; + default: + strcpy(S, "Unknown Return code ("); + strcat(S, IntToString(Event.EvtRetCode, Buf)); + strcat(S, ")"); + break; + } + strcpy(Result, S); + return Result; +} +//--------------------------------------------------------------------------- +char* TxtArea(TSrvEvent &Event, char* Result) +{ + char S[64]; + char Buf[32]; + switch (Event.EvtParam1) + { + case S7AreaPE: strcpy(S, "Area : PE, "); break; + case S7AreaPA: strcpy(S, "Area : PA, "); break; + case S7AreaMK: strcpy(S, "Area : MK, "); break; + case S7AreaCT: strcpy(S, "Area : CT, "); break; + case S7AreaTM: strcpy(S, "Area : TM, "); break; + case S7AreaDB: + strcpy(S, "Area : DB"); + strcat(S, IntToString(Event.EvtParam2, Buf)); + strcat(S,", "); + break; + default: + strcpy(S, "Unknown area ("); + strcat(S, IntToString(Event.EvtParam2, Buf)); + strcat(S,")"); + break; + } + strcpy(Result, S); + return Result; +} +//--------------------------------------------------------------------------- +char* TxtStartSize(TSrvEvent &Event, char* Result) +{ + char N[32]; + strcpy(Result, "Start : "); + strcat(Result, IntToString(Event.EvtParam3, N)); + strcat(Result, ", Size : "); + strcat(Result, IntToString(Event.EvtParam4, N)); + return Result; +} +//--------------------------------------------------------------------------- +char* TxtDataResult(TSrvEvent &Event, char* Result) +{ + char N[32]; + switch (Event.EvtRetCode) + { + case evrNoError: + strcpy(Result," --> OK"); + break; + case evrErrException: + strcpy(Result, " --> Exception error"); + break; + case evrErrAreaNotFound: + strcpy(Result, " --> Area not found"); + break; + case evrErrOutOfRange: + strcpy(Result, " --> Out of range"); + break; + case evrErrOverPDU: + strcpy(Result, " --> Data size exceeds PDU size"); + break; + case evrErrTransportSize: + strcpy(Result, " --> Invalid transport size"); + break; + case evrDataSizeMismatch: + strcpy(Result, " --> Data size mismatch"); + break; + default: + strcpy(Result, " --> Unknown error code ("); + strcat(Result, IntToString(Event.EvtRetCode, N)); + strcat(Result,")"); + break; + }; + return Result; +} +//--------------------------------------------------------------------------- +char* ControlText(word Code, char* Result) +{ + char N[64]; + strcpy(Result, "CPU Control request : "); + switch (Code) + { + case CodeControlUnknown: + strcat(Result,"Unknown"); + break; + case CodeControlColdStart: + strcat(Result, "Cold START --> OK"); + break; + case CodeControlWarmStart: + strcat(Result, "Warm START --> OK"); + break; + case CodeControlStop: + strcat(Result, "STOP --> OK"); + break; + case CodeControlCompress: + strcat(Result, "Memory compress --> OK"); + break; + case CodeControlCpyRamRom: + strcat(Result, "Copy Ram to Rom --> OK"); + break; + case CodeControlInsDel: + strcat(Result, "Block Insert or Delete --> OK"); + break; + default : + strcat(Result, "Unknown control code ("); + strcat(Result, IntToString(Code, N)); + strcat(Result,")"); + } + return Result; +} +//--------------------------------------------------------------------------- +char* ClockText(word Code, char* Result) +{ + if (Code==evsGetClock) + strcpy(Result,"System clock read requested"); + else + strcpy(Result, "System clock write requested"); + return Result; +} +//--------------------------------------------------------------------------- +char* ReadSZLText(TSrvEvent &Event, char* Result) +{ + char S[128]; + char N[64]; + strcpy(S, "Read SZL request, ID:0x"); + strcat(S, NumToString(Event.EvtParam1, 16, 4, N)); + strcat(S, " INDEX:0x"); + strcat(S, NumToString(Event.EvtParam2, 16, 4, N)); + + if (Event.EvtRetCode == evrNoError) + strcat(S, " --> OK"); + else + strcat(S, " --> NOT AVAILABLE"); + strcpy(Result, S); + return Result; +} +//--------------------------------------------------------------------------- +char* UploadText(TSrvEvent &Event, char* Result) +{ + strcpy(Result,"Block upload requested --> NOT PERFORMED (due to invalid security level)"); + return Result; +} +//--------------------------------------------------------------------------- +char* DownloadText(TSrvEvent &Event, char* Result) +{ + strcpy(Result, "Block download requested --> NOT PERFORMED (due to invalid security level)"); + return Result; +} +//--------------------------------------------------------------------------- +char* StrBlockType(word Code, char* Result) +{ + char N[64]; + switch (Code) + { + case Block_OB: + strcpy(Result, "OB"); + break; + case Block_DB: + strcpy(Result, "DB"); + break; + case Block_SDB: + strcpy(Result, "SDB"); + break; + case Block_FC: + strcpy(Result, "FC"); + break; + case Block_SFC: + strcpy(Result, "SFC"); + break; + case Block_FB: + strcpy(Result, "FB"); + break; + case Block_SFB: + strcpy(Result, "SFB"); + break; + default: + strcpy(Result, "[Unknown 0x"); + strcat(Result, NumToString(Code, 16, 4, N)); + strcat(Result,"]"); + break; + }; + return Result; +} +//--------------------------------------------------------------------------- +char* BlockInfoText(TSrvEvent &Event, char* Result) +{ + char S[64]; + switch (Event.EvtParam1) + { + case evsGetBlockList: + strcpy(Result, "Block list requested"); + break; + case evsStartListBoT: + strcpy(Result, "Block of type "); + strcat(Result, StrBlockType(Event.EvtParam2,S)); + strcat(Result, " list requested (start sequence)"); + break; + case evsListBoT: + strcpy(Result, "Block of type "); + strcat(Result, StrBlockType(Event.EvtParam2, S)); + strcat(Result, " list requested (next part)"); + break; + case evsGetBlockInfo: + strcpy(Result, "Block info requested "); + strcat(Result, StrBlockType(Event.EvtParam2, S)); + strcat(Result, " "); + strcat(Result, IntToString(Event.EvtParam3,S)); + break; + }; + if (Event.EvtRetCode == evrNoError) + strcat(Result, " --> OK"); + else + strcat(Result, " --> NOT AVAILABLE"); + return Result; +} +//--------------------------------------------------------------------------- +char* SecurityText(TSrvEvent &Event, char* Result) +{ + switch (Event.EvtParam1) + { + case evsSetPassword: + strcpy(Result,"Security request : Set session password --> OK"); + break; + case evsClrPassword: + strcpy(Result, "Security request : Clear session password --> OK"); + break; + default: + strcpy(Result, "Security request : Unknown Subfunction"); + break; + }; + return Result; +} +//--------------------------------------------------------------------------- +char* EvtSrvText(TSrvEvent &Event, char* Result, int TextLen) +{ + char S[256]; + char C[128]; + + if (Event.EvtCode > evcSnap7Base) + { + strcpy(S, SenderText(Event, C)); + switch (Event.EvtCode) + { + case evcPDUincoming: + strcat(S, "PDU incoming : "); + strcat(S,PDUText(Event,C)); + break; + case evcDataRead: + strcat(S, "Read request, "); + strcat(S, TxtArea(Event, C)); + strcat(S, TxtStartSize(Event, C)); + strcat(S, TxtDataResult(Event, C)); + break; + case evcDataWrite: + strcat(S, "Write request, "); + strcat(S, TxtArea(Event, C)); + strcat(S, TxtStartSize(Event, C)); + strcat(S, TxtDataResult(Event, C)); + break; + case evcNegotiatePDU: + strcat(S, "The client requires a PDU size of "); + strcat(S, IntToString(Event.EvtParam1, C)); + strcat(S," bytes"); + break; + case evcControl: + strcat(S, ControlText(Event.EvtParam1,C)); + break; + case evcReadSZL: + strcat(S, ReadSZLText(Event,C)); + break; + case evcClock: + strcat(S, ClockText(Event.EvtParam1,C)); + break; + case evcUpload: + strcat(S, UploadText(Event,C)); + break; + case evcDownload: + strcat(S, DownloadText(Event,C)); + break; + case evcDirectory: + strcat(S, BlockInfoText(Event,C)); + break; + case evcSecurity: + strcat(S, SecurityText(Event,C)); + break; + default: + strcat(S, "Unknown event ("); + strcat(S, IntToString(Event.EvtCode, C)); + strcat(S,")"); + break; + } + } + else + strcpy(S,TcpServerEventText(Event,C)); + + strncpy(Result, S, TextLen); + return Result; +} + diff --git a/core/src/drivers/plugins/native/s7comm/snap7/core/s7_text.h b/core/src/drivers/plugins/native/s7comm/snap7/core/s7_text.h new file mode 100644 index 00000000..b8b5ba3d --- /dev/null +++ b/core/src/drivers/plugins/native/s7comm/snap7/core/s7_text.h @@ -0,0 +1,49 @@ +/*=============================================================================| +| PROJECT SNAP7 1.4.3 | +|==============================================================================| +| Copyright (C) 2013, 2025 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/ | +|=============================================================================*/ +#ifndef s7_text_h +#define s7_text_h +//--------------------------------------------------------------------------- +#include "s7_micro_client.h" +#include "s7_server.h" +#include "s7_partner.h" +//--------------------------------------------------------------------------- + +const int errLibInvalidParam = -1; +const int errLibInvalidObject = -2; +// Errors areas definition +const longword ErrTcpMask = 0x0000FFFF; +const longword ErrIsoMask = 0x000F0000; +const longword ErrS7Mask = 0xFFF00000; + +char* ErrCliText(int Error, char* Result, int TextLen); +char* ErrSrvText(int Error, char* Result, int TextLen); +char* ErrParText(int Error, char* Result, int TextLen); +char* EvtSrvText(TSrvEvent &Event, char* Result, int TextLen); + + +#endif + + diff --git a/core/src/drivers/plugins/native/s7comm/snap7/core/s7_types.h b/core/src/drivers/plugins/native/s7comm/snap7/core/s7_types.h new file mode 100644 index 00000000..eca31c62 --- /dev/null +++ b/core/src/drivers/plugins/native/s7comm/snap7/core/s7_types.h @@ -0,0 +1,1066 @@ +/*=============================================================================| +| PROJECT SNAP7 1.4.3 | +|==============================================================================| +| Copyright (C) 2013, 2025 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/ | +|=============================================================================*/ +#ifndef s7_types_h +#define s7_types_h +//------------------------------------------------------------------------------ +#include "s7_isotcp.h" +//------------------------------------------------------------------------------ +// EXPORT CONSTANTS +// Everything added in this section has to be copied into wrappers interface +//------------------------------------------------------------------------------ + +#ifdef OS_WINDOWS +#define SM7API __stdcall +#else +#define SM7API +#endif + + // Area ID +const byte S7AreaPE = 0x81; +const byte S7AreaPA = 0x82; +const byte S7AreaMK = 0x83; +const byte S7AreaDB = 0x84; +const byte S7AreaCT = 0x1C; +const byte S7AreaTM = 0x1D; + +const int MaxVars = 20; + +const int S7WLBit = 0x01; +const int S7WLByte = 0x02; +const int S7WLChar = 0x03; +const int S7WLWord = 0x04; +const int S7WLInt = 0x05; +const int S7WLDWord = 0x06; +const int S7WLDInt = 0x07; +const int S7WLReal = 0x08; +const int S7WLCounter = 0x1C; +const int S7WLTimer = 0x1D; + + // Block type +const byte Block_OB = 0x38; +const byte Block_DB = 0x41; +const byte Block_SDB = 0x42; +const byte Block_FC = 0x43; +const byte Block_SFC = 0x44; +const byte Block_FB = 0x45; +const byte Block_SFB = 0x46; + + // Sub Block Type +const byte SubBlk_OB = 0x08; +const byte SubBlk_DB = 0x0A; +const byte SubBlk_SDB = 0x0B; +const byte SubBlk_FC = 0x0C; +const byte SubBlk_SFC = 0x0D; +const byte SubBlk_FB = 0x0E; +const byte SubBlk_SFB = 0x0F; + + // Block languages +const byte BlockLangAWL = 0x01; +const byte BlockLangKOP = 0x02; +const byte BlockLangFUP = 0x03; +const byte BlockLangSCL = 0x04; +const byte BlockLangDB = 0x05; +const byte BlockLangGRAPH = 0x06; + + // CPU status +const byte S7CpuStatusUnknown = 0x00; +const byte S7CpuStatusRun = 0x08; +const byte S7CpuStatusStop = 0x04; + +const longword evcSnap7Base = 0x00008000; +// S7 Server Event Code +const longword evcPDUincoming = 0x00010000; +const longword evcDataRead = 0x00020000; +const longword evcDataWrite = 0x00040000; +const longword evcNegotiatePDU = 0x00080000; +const longword evcReadSZL = 0x00100000; +const longword evcClock = 0x00200000; +const longword evcUpload = 0x00400000; +const longword evcDownload = 0x00800000; +const longword evcDirectory = 0x01000000; +const longword evcSecurity = 0x02000000; +const longword evcControl = 0x04000000; +const longword evcReserved_08000000 = 0x08000000; +const longword evcReserved_10000000 = 0x10000000; +const longword evcReserved_20000000 = 0x20000000; +const longword evcReserved_40000000 = 0x40000000; +const longword evcReserved_80000000 = 0x80000000; +// Event SubCodes +const word evsUnknown = 0x0000; +const word evsStartUpload = 0x0001; +const word evsStartDownload = 0x0001; +const word evsGetBlockList = 0x0001; +const word evsStartListBoT = 0x0002; +const word evsListBoT = 0x0003; +const word evsGetBlockInfo = 0x0004; +const word evsGetClock = 0x0001; +const word evsSetClock = 0x0002; +const word evsSetPassword = 0x0001; +const word evsClrPassword = 0x0002; +// Event Result +const word evrNoError = 0; +const word evrFragmentRejected = 0x0001; +const word evrMalformedPDU = 0x0002; +const word evrSparseBytes = 0x0003; +const word evrCannotHandlePDU = 0x0004; +const word evrNotImplemented = 0x0005; +const word evrErrException = 0x0006; +const word evrErrAreaNotFound = 0x0007; +const word evrErrOutOfRange = 0x0008; +const word evrErrOverPDU = 0x0009; +const word evrErrTransportSize = 0x000A; +const word evrInvalidGroupUData = 0x000B; +const word evrInvalidSZL = 0x000C; +const word evrDataSizeMismatch = 0x000D; +const word evrCannotUpload = 0x000E; +const word evrCannotDownload = 0x000F; +const word evrUploadInvalidID = 0x0010; +const word evrResNotFound = 0x0011; + + // Async mode +const int amPolling = 0; +const int amEvent = 1; +const int amCallBack = 2; + +//------------------------------------------------------------------------------ +// PARAMS LIST +// Notes for Local/Remote Port +// If the local port for a server and remote port for a client is != 102 they +// will be *no more compatible with S7 IsoTCP* +// A good reason to change them could be inside a debug session under Unix. +// Increasing the port over 1024 avoids the need of be root. +// Obviously you need to work with the couple Snap7Client/Snap7Server and change +// both, or, use iptable and nat the port. +//------------------------------------------------------------------------------ +const int p_u16_LocalPort = 1; +const int p_u16_RemotePort = 2; +const int p_i32_PingTimeout = 3; +const int p_i32_SendTimeout = 4; +const int p_i32_RecvTimeout = 5; +const int p_i32_WorkInterval = 6; +const int p_u16_SrcRef = 7; +const int p_u16_DstRef = 8; +const int p_u16_SrcTSap = 9; +const int p_i32_PDURequest = 10; +const int p_i32_MaxClients = 11; +const int p_i32_BSendTimeout = 12; +const int p_i32_BRecvTimeout = 13; +const int p_u32_RecoveryTime = 14; +const int p_u32_KeepAliveTime = 15; + +// Bool param is passed as int32_t : 0->false, 1->true +// String param (only set) is passed as pointer + +typedef int16_t *Pint16_t; +typedef uint16_t *Puint16_t; +typedef int32_t *Pint32_t; +typedef uint32_t *Puint32_t; +typedef int64_t *Pint64_t; +typedef uint64_t *Puint64_t; +typedef uintptr_t *Puintptr_t; +//----------------------------------------------------------------------------- +// INTERNALS CONSTANTS +//------------------------------------------------------------------------------ + +const word DBMaxName = 0xFFFF; // max number (name) of DB + +const longword errS7Mask = 0xFFF00000; +const longword errS7Base = 0x000FFFFF; +const longword errS7notConnected = errS7Base+0x0001; // Client not connected +const longword errS7InvalidMode = errS7Base+0x0002; // Requested a connection to... +const longword errS7InvalidPDUin = errS7Base+0x0003; // Malformed input PDU + +// S7 outcoming Error code +const word Code7Ok = 0x0000; +const word Code7AddressOutOfRange = 0x0005; +const word Code7InvalidTransportSize = 0x0006; +const word Code7WriteDataSizeMismatch = 0x0007; +const word Code7ResItemNotAvailable = 0x000A; +const word Code7ResItemNotAvailable1 = 0xD209; +const word Code7InvalidValue = 0xDC01; +const word Code7NeedPassword = 0xD241; +const word Code7InvalidPassword = 0xD602; +const word Code7NoPasswordToClear = 0xD604; +const word Code7NoPasswordToSet = 0xD605; +const word Code7FunNotAvailable = 0x8104; +const word Code7DataOverPDU = 0x8500; + +// Result transport size +const byte TS_ResBit = 0x03; +const byte TS_ResByte = 0x04; +const byte TS_ResInt = 0x05; +const byte TS_ResReal = 0x07; +const byte TS_ResOctet = 0x09; + +// Client Job status (lib internals, not S7) +const int JobComplete = 0; +const int JobPending = 1; + +// Control codes +const word CodeControlUnknown = 0; +const word CodeControlColdStart = 1; // Cold start +const word CodeControlWarmStart = 2; // Warm start +const word CodeControlStop = 3; // Stop +const word CodeControlCompress = 4; // Compress +const word CodeControlCpyRamRom = 5; // Copy Ram to Rom +const word CodeControlInsDel = 6; // Insert in working ram the block downloaded + // Delete from working ram the block selected +// PDU Type +const byte PduType_request = 1; // family request +const byte PduType_response = 3; // family response +const byte PduType_userdata = 7; // family user data + +// PDU Functions +const byte pduResponse = 0x02; // Response (when error) +const byte pduFuncRead = 0x04; // Read area +const byte pduFuncWrite = 0x05; // Write area +const byte pduNegotiate = 0xF0; // Negotiate PDU length +const byte pduStart = 0x28; // CPU start +const byte pduStop = 0x29; // CPU stop +const byte pduStartUpload = 0x1D; // Start Upload +const byte pduUpload = 0x1E; // Upload +const byte pduEndUpload = 0x1F; // EndUpload +const byte pduReqDownload = 0x1A; // Start Download request +const byte pduDownload = 0x1B; // Download request +const byte pduDownloadEnded = 0x1C; // Download end request +const byte pduControl = 0x28; // Control (insert/delete..) + +// PDU SubFunctions +const byte SFun_ListAll = 0x01; // List all blocks +const byte SFun_ListBoT = 0x02; // List Blocks of type +const byte SFun_BlkInfo = 0x03; // Get Block info +const byte SFun_ReadSZL = 0x01; // Read SZL +const byte SFun_ReadClock = 0x01; // Read Clock (Date and Time) +const byte SFun_SetClock = 0x02; // Set Clock (Date and Time) +const byte SFun_EnterPwd = 0x01; // Enter password for this session +const byte SFun_CancelPwd = 0x02; // Cancel password for this session +const byte SFun_Insert = 0x50; // Insert block +const byte SFun_Delete = 0x42; // Delete block + +typedef tm *PTimeStruct; + +//============================================================================== +// HEADERS +//============================================================================== +#pragma pack(1) + +// Tag Struct +typedef struct{ + int Area; + int DBNumber; + int Start; + int Size; + int WordLen; +}TS7Tag, *PS7Tag; + +// Incoming header, it will be mapped onto IsoPDU payload +typedef struct { + byte P; // Telegram ID, always 32 + byte PDUType; // Header type 1 or 7 + word AB_EX; // AB currently unknown, maybe it can be used for long numbers. + word Sequence; // Message ID. This can be used to make sure a received answer + word ParLen; // Length of parameters which follow this header + word DataLen; // Length of data which follow the parameters +}TS7ReqHeader; + +typedef TS7ReqHeader* PS7ReqHeader; + +// Outcoming 12 bytes header , response for Request type 1 +typedef struct{ + byte P; // Telegram ID, always 32 + byte PDUType; // Header type 2 or 3 + word AB_EX; // AB currently unknown, maybe it can be used for long numbers. + word Sequence; // Message ID. This can be used to make sure a received answer + word ParLen; // Length of parameters which follow this header + word DataLen; // Length of data which follow the parameters + word Error; // Error code +} TS7ResHeader23; + +typedef TS7ResHeader23* PS7ResHeader23; + +// Outcoming 10 bytes header , response for Request type 7 +typedef struct{ + byte P; // Telegram ID, always 32 + byte PDUType; // Header type 1 or 7 + word AB_EX; // AB currently unknown, maybe it can be used for long numbers. + word Sequence; // Message ID. This can be used to make sure a received answer + word ParLen; // Length of parameters which follow this header + word DataLen; // Length of data which follow the parameters +}TS7ResHeader17; + +typedef TS7ResHeader17* PS7ResHeader17; + +// Outcoming 10 bytes header , response for Request type 8 (server control) +typedef struct { + byte P; // Telegram ID, always 32 + byte PDUType; // Header type 8 + word AB_EX; // Zero + word Sequence; // Message ID. This can be used to make sure a received answer + word DataLen; // Length of data which follow this header + word Error; // Error code +} TS7ResHeader8; + +typedef TS7ResHeader8* PS7ResHeader8; + +// Outcoming answer buffer header type 2 or header type 3 +typedef struct{ + TS7ResHeader23 Header; + byte ResData [IsoPayload_Size - sizeof(TS7ResHeader23)]; +} TS7Answer23; + +typedef TS7Answer23* PS7Answer23; + +// Outcoming buffer header type 1 or header type 7 +typedef struct { + TS7ResHeader17 Header; + byte ResData [IsoPayload_Size - sizeof(TS7ResHeader17)]; +} TS7Answer17; + +typedef TS7Answer17* PS7Answer17; + +typedef byte TTimeBuffer[8]; +typedef byte *PTimeBuffer[8]; + +typedef struct{ + byte bcd_year; + byte bcd_mon; + byte bcd_day; + byte bcd_hour; + byte bcd_min; + byte bcd_sec; + byte bcd_himsec; + byte bcd_dow; +}TS7Time, *PS7Time; + +typedef byte TS7Buffer[65536]; +typedef byte *PS7Buffer; + +const int ReqHeaderSize = sizeof(TS7ReqHeader); +const int ResHeaderSize23 = sizeof(TS7ResHeader23); +const int ResHeaderSize17 = sizeof(TS7ResHeader17); + +// Most used request type parameters record +typedef struct { + byte Head[3];// 0x00 0x01 0x12 + byte Plen; // par len 0x04 + byte Uk; // unknown + byte Tg; // type and group (4 bits type and 4 bits group) + byte SubFun; // subfunction + byte Seq; // sequence +}TReqFunTypedParams; + +//============================================================================== +// FUNCTION NEGOTIATE +//============================================================================== +typedef struct { + byte FunNegotiate; + byte Unknown; + word ParallelJobs_1; + word ParallelJobs_2; + word PDULength; +}TReqFunNegotiateParams; + +typedef TReqFunNegotiateParams* PReqFunNegotiateParams; + +typedef struct { + byte FunNegotiate; + byte Unknown; + word ParallelJobs_1; + word ParallelJobs_2; + word PDULength; +}TResFunNegotiateParams; + +typedef TResFunNegotiateParams* PResFunNegotiateParams; + +//============================================================================== +// FUNCTION READ +//============================================================================== +typedef struct { + byte ItemHead[3]; + byte TransportSize; + word Length; + word DBNumber; + byte Area; + byte Address[3]; +}TReqFunReadItem, * PReqFunReadItem; + +//typedef TReqFunReadItem; + +typedef struct { + byte FunRead; + byte ItemsCount; + TReqFunReadItem Items[MaxVars]; +}TReqFunReadParams; + +typedef TReqFunReadParams* PReqFunReadParams; + +typedef struct { + byte FunRead; + byte ItemCount; +}TResFunReadParams; + +typedef TResFunReadParams* PResFunReadParams; + +typedef struct { + byte ReturnCode; + byte TransportSize; + word DataLength; + byte Data[IsoPayload_Size - 17]; // 17 = header + params + data header - 1 +}TResFunReadItem, *PResFunReadItem; + +typedef PResFunReadItem TResFunReadData[MaxVars]; + +//============================================================================== +// FUNCTION WRITE +//============================================================================== +typedef struct { + byte ItemHead[3]; + byte TransportSize; + word Length; + word DBNumber; + byte Area; + byte Address[3]; +}TReqFunWriteItem, * PReqFunWriteItem; + +typedef struct { + byte FunWrite; + byte ItemsCount; + TReqFunWriteItem Items[MaxVars]; +}TReqFunWriteParams; + +typedef TReqFunWriteParams* PReqFunWriteParams; + +typedef struct { + byte ReturnCode; + byte TransportSize; + word DataLength; + byte Data [IsoPayload_Size - 17]; // 17 = header + params + data header -1 +}TReqFunWriteDataItem, *PReqFunWriteDataItem; + +typedef PReqFunWriteDataItem TReqFunWriteData[MaxVars]; + +typedef struct { + byte FunWrite; + byte ItemCount; + byte Data[MaxVars]; +}TResFunWrite; + +typedef TResFunWrite* PResFunWrite; + +//============================================================================== +// GROUP UPLOAD +//============================================================================== +typedef struct { + byte FunSUpld; // function start upload 0x1D + byte Uk6 [6]; // Unknown 6 bytes + byte Upload_ID; + byte Len_1; + byte Prefix; + byte BlkPrfx; // always 0x30 + byte BlkType; + byte AsciiBlk[5]; // BlockNum in ascii + byte A; // always 0x41 ('A') +}TReqFunStartUploadParams; + +typedef TReqFunStartUploadParams* PReqFunStartUploadParams; + +typedef struct { + byte FunSUpld; // function start upload 0x1D + byte Data_1[6]; + byte Upload_ID; + byte Uk[3]; + byte LenLoad[5]; +}TResFunStartUploadParams; + +typedef TResFunStartUploadParams* PResFunStartUploadParams; + +typedef struct { + byte FunUpld; // function upload 0x1E + byte Uk6[6]; // Unknown 6 bytes + byte Upload_ID; +}TReqFunUploadParams; + +typedef TReqFunUploadParams* PReqFunUploadParams; + +typedef struct { + byte FunUpld; // function upload 0x1E + byte EoU; // 0 = End Of Upload, 1 = Upload in progress +}TResFunUploadParams; + +typedef TResFunUploadParams* PResFunUploadParams; + +typedef struct { + word Length; // Payload length - 4 + byte Uk_00; // Unknown 0x00 + byte Uk_FB; // Unknown 0xFB + // from here is the same of TS7CompactBlockInfo + word Cst_pp; + byte Uk_01; // Unknown 0x01 + byte BlkFlags; + byte BlkLang; + byte SubBlkType; + word BlkNum; + u_int LenLoadMem; + u_int BlkSec; + u_int CodeTime_ms; + word CodeTime_dy; + u_int IntfTime_ms; + word IntfTime_dy; + word SbbLen; + word AddLen; + word LocDataLen; + word MC7Len; +}TResFunUploadDataHeaderFirst; + +typedef TResFunUploadDataHeaderFirst* PResFunUploadDataHeaderFirst; + +typedef struct { + word Length;// Payload length - 4 + byte Uk_00; // Unknown 0x00 + byte Uk_FB; // Unknown 0xFB +}TResFunUploadDataHeaderNext; + +typedef TResFunUploadDataHeaderNext* PResFunUploadDataHeaderNext; + +typedef struct { + word Length;// Payload length - 4 + byte Uk_00; // Unknown 0x00 + byte Uk_FB; // Unknown 0xFB +}TResFunUploadDataHeader; + +typedef TResFunUploadDataHeader* PResFunUploadDataHeader; + +typedef struct { + byte ID; // 0x65 + word Seq; // Sequence + byte Const_1[10]; + word Lo_bound; + word Hi_Bound; + byte u_shortLen;// 0x02 byte + // 0x04 word + // 0x05 int + // 0x06 dword + // 0x07 dint + // 0x08 real + byte c1, c2; + char Author[8]; + char Family[8]; + char Header[8]; + byte B1; // 0x11 + byte B2; // 0x00 + word Chksum; + byte Uk_8[8]; +}TArrayUpldFooter; + +typedef TArrayUpldFooter* PArrayUpldFooter; + +typedef struct { + byte FunEUpld; // function end upload 0x1F + byte Uk6[6]; // Unknown 6 bytes + byte Upload_ID; +}TReqFunEndUploadParams; + +typedef TReqFunEndUploadParams* PReqFunEndUploadParams; + +typedef struct { + byte FunEUpld; // function end upload 0x1F +}TResFunEndUploadParams; + +typedef TResFunEndUploadParams* PResFunEndUploadParams; + +//============================================================================== +// GROUP DOWNLOAD +//============================================================================== +typedef struct { + byte FunSDwnld; // function start Download 0x1A + byte Uk6[6]; // Unknown 6 bytes + byte Dwnld_ID; + byte Len_1; // 0x09 + byte Prefix; // 0x5F + byte BlkPrfx; // always 0x30 + byte BlkType; + byte AsciiBlk[5]; // BlockNum in ascii + byte P; // 0x50 ('P') + byte Len_2; // 0x0D + byte Uk1; // 0x01 + byte AsciiLoad[6];// load memory size (MC7 size + 92) + byte AsciiMC7[6]; // Block size in bytes +}TReqStartDownloadParams; + +typedef TReqStartDownloadParams* PReqStartDownloadParams; +typedef byte TResStartDownloadParams; +typedef TResStartDownloadParams* PResStartDownloadParams; + +typedef struct { + byte Fun; // pduDownload or pduDownloadEnded + byte Uk7[7]; + byte Len_1; // 0x09 + byte Prefix; // 0x5F + byte BlkPrfx; // always 0x30 + byte BlkType; + byte AsciiBlk[5]; // BlockNum in ascii + byte P; // 0x50 ('P') +}TReqDownloadParams; + +typedef TReqDownloadParams* PReqDownloadParams; + +typedef struct { + byte FunDwnld; // 0x1B + byte EoS; // End of sequence : 0x00 - Sequence in progress : 0x01 +}TResDownloadParams; + +typedef TResDownloadParams* PResDownloadParams; + +typedef struct { + word DataLen; + word FB_00; // 0x00 0xFB +}TResDownloadDataHeader; + +typedef TResDownloadDataHeader* PResDownloadDataHeader; +typedef byte TResEndDownloadParams; +typedef TResEndDownloadParams* PResEndDownloadParams; + +typedef struct { + word Cst_pp; + byte Uk_01; // Unknown 0x01 + byte BlkFlags; + byte BlkLang; + byte SubBlkType; + word BlkNum; + u_int LenLoadMem; + u_int BlkSec; + u_int CodeTime_ms; + word CodeTime_dy; + u_int IntfTime_ms; + word IntfTime_dy; + word SbbLen; + word AddLen; + word LocDataLen; + word MC7Len; +}TS7CompactBlockInfo; + +typedef TS7CompactBlockInfo* PS7CompactBlockInfo; + +typedef struct { + byte Uk_20[20]; + byte Author[8]; + byte Family[8]; + byte Header[8]; + byte B1; // 0x11 + byte B2; // 0x00 + word Chksum; + byte Uk_12[8]; +}TS7BlockFooter; + +typedef TS7BlockFooter* PS7BlockFooter; + +//============================================================================== +// FUNCTION INSERT/DELETE +//============================================================================== +typedef struct { + byte Fun; // plc control 0x28 + byte Uk7[7]; // unknown 7 + word Len_1; // Length part 1 : 10 + byte NumOfBlocks; // number of blocks to insert + byte ByteZero; // 0x00 + byte AsciiZero; // 0x30 '0' + byte BlkType; + byte AsciiBlk[5]; // BlockNum in ascii + byte SFun; // 0x50 or 0x42 + byte Len_2; // Length part 2 : 0x05 bytes + char Cmd[5]; // ascii '_INSE' or '_DELE' +}TReqControlBlockParams; + +typedef TReqControlBlockParams* PReqControlBlockParams; + +//============================================================================== +// FUNCTIONS START/STOP/COPY RAM TO ROM/COMPRESS +//============================================================================== +typedef struct { + byte Fun; // stop 0x29 + byte Uk_5[5]; // unknown 5 bytes 0x00 + byte Len_2; // Length part 2 : 0x09 + char Cmd[9]; // ascii 'P_PROGRAM' +}TReqFunPlcStop; + +typedef TReqFunPlcStop* PReqFunPlcStop; + +typedef struct { + byte Fun; // start 0x28 + byte Uk_7[7]; // unknown 7 + word Len_1; // Length part 1 : 0x0000 + byte Len_2; // Length part 2 : 0x09 + char Cmd [9]; // ascii 'P_PROGRAM' +}TReqFunPlcHotStart; + +typedef TReqFunPlcHotStart* PReqFunPlcHotStart; + +typedef struct { + byte Fun; // start 0x28 + byte Uk_7[7]; // unknown 7 + word Len_1; // Length part 1 : 0x0002 + word SFun; // 'C ' 0x4320 + byte Len_2; // Length part 2 : 0x09 + char Cmd[9]; // ascii 'P_PROGRAM' +}TReqFunPlcColdStart; + +typedef TReqFunPlcColdStart* PReqFunPlcColdStart; + +typedef struct { + byte Fun; // pduControl 0x28 + byte Uk_7[7]; // unknown 7 + word Len_1; // Length part 1 : 0x0002 + word SFun; // 'EP' 0x4550 + byte Len_2; // Length part 2 : 0x05 + char Cmd[5]; // ascii '_MODU' +}TReqFunCopyRamToRom; + +typedef TReqFunCopyRamToRom* PReqFunCopyRamToRom; + +typedef struct { + byte Fun; // pduControl 0x28 + byte Uk_7[7]; // unknown 7 + word Len_1; // Length part 1 : 0x00 + byte Len_2; // Length part 2 : 0x05 + char Cmd[5]; // ascii '_GARB' +}TReqFunCompress; + +typedef TReqFunCompress* PReqFunCompress; + +typedef struct { + byte ResFun; + byte para; +}TResFunCtrl; + +typedef TResFunCtrl* PResFunCtrl; + +//============================================================================== +// FUNCTIONS USERDATA +//============================================================================== +typedef struct { + byte Head[3]; // Always 0x00 0x01 0x12 + byte Plen; // par len 0x04 or 0x08 + byte Uk; // unknown + byte Tg; // type and group (4 bits type and 4 bits group) + byte SubFun; // subfunction + byte Seq; // sequence + word resvd; // present if plen=0x08 (S7 manager online functions) + word Err; // present if plen=0x08 (S7 manager online functions) +}TS7Params7; + +typedef TS7Params7* PS7ReqParams7; +typedef TS7Params7* PS7ResParams7; + +// for convenience Hi order bit of type are included (0x4X) +const byte grProgrammer = 0x41; +const byte grCyclicData = 0x42; +const byte grBlocksInfo = 0x43; +const byte grSZL = 0x44; +const byte grPassword = 0x45; +const byte grBSend = 0x46; +const byte grClock = 0x47; +const byte grSecurity = 0x45; + +//============================================================================== +// GROUP SECURITY +//============================================================================== +typedef TReqFunTypedParams TReqFunSecurity; +typedef TReqFunSecurity* PReqFunSecurity; + +typedef char TS7Password[8]; + +typedef struct { + byte Ret; // 0xFF for request + byte TS; // 0x09 Transport size + word DLen; // Data len : 8 bytes + byte Pwd[8]; // Password encoded into "AG" format +}TReqDataSecurity; + +typedef TReqDataSecurity* PReqDataSecurity; +typedef TS7Params7 TResParamsSecurity; +typedef TResParamsSecurity* PResParamsSecurity; + +typedef struct { + byte Ret; + byte TS; + word DLen; +}TResDataSecurity; + +typedef TResDataSecurity* PResDataSecurity; + +//============================================================================== +// GROUP BLOCKS SZL +//============================================================================== +typedef TReqFunTypedParams TReqFunReadSZLFirst; +typedef TReqFunReadSZLFirst* PReqFunReadSZLFirst; + +typedef struct { + byte Head[3]; // 0x00 0x01 0x12 + byte Plen; // par len 0x04 + byte Uk; // unknown + byte Tg; // type and group (4 bits type and 4 bits group) + byte SubFun; // subfunction + byte Seq; // sequence + word Rsvd; // Reserved 0x0000 + word ErrNo; // Error Code +}TReqFunReadSZLNext; + +typedef TReqFunReadSZLNext* PReqFunReadSZLNext; + +typedef struct { + byte Ret; // 0xFF for request + byte TS; // 0x09 Transport size + word DLen; // Data len + word ID; // SZL-ID + word Index;// SZL-Index +}TS7ReqSZLData; + +typedef TS7ReqSZLData* PS7ReqSZLData; + +typedef struct { + byte Ret; + byte TS; + word DLen; + word ID; + word Index; + word ListLen; + word ListCount; + word Data[32747]; +}TS7ResSZLDataFirst; + +typedef TS7ResSZLDataFirst* PS7ResSZLDataFirst; + +typedef struct { + byte Ret; + byte TS; + word DLen; + word Data[32751]; +}TS7ResSZLDataNext; + +typedef TS7ResSZLDataNext* PS7ResSZLDataNext; + +typedef struct { + byte Ret; + byte OtherInfo[9]; + word Count; + word Items[32747]; +}TS7ResSZLData_0; + +typedef TS7ResSZLData_0* PS7ResSZLData_0; + +//============================================================================== +// GROUP CLOCK +//============================================================================== +typedef TReqFunTypedParams TReqFunDateTime; +typedef TReqFunDateTime* PReqFunDateTime; + +typedef byte TReqDataGetDateTime[4]; + +typedef longword *PReqDataGetDateTime; + +typedef struct { + byte RetVal; + byte TSize; + word Length; + byte Rsvd; + byte HiYear; + TTimeBuffer Time; +}TResDataGetTime; + +typedef TResDataGetTime* PResDataGetTime; +typedef TResDataGetTime TReqDataSetTime; +typedef TReqDataSetTime* PReqDataSetTime; + +typedef struct { + byte RetVal; + byte TSize; + word Length; +}TResDataSetTime; + +typedef TResDataSetTime* PResDataSetTime; + +//============================================================================== +// GROUP BLOCKS INFO +//============================================================================== +typedef TReqFunTypedParams TReqFunGetBlockInfo; +typedef TReqFunGetBlockInfo* PReqFunGetBlockInfo; + +typedef byte TReqDataFunBlocks[4]; +typedef u_char* PReqDataFunBlocks; + +typedef struct { + byte Head[3]; // 0x00 0x01 0x12 + byte Plen; // par len 0x04 + byte Uk; // unknown + byte Tg; // type and group (4 bits type and 4 bits group) + byte SubFun; // subfunction + byte Seq; // sequence + word Rsvd; // Reserved 0x0000 + word ErrNo; // Error Code +}TResFunGetBlockInfo; + +typedef TResFunGetBlockInfo* PResFunGetBlockInfo; + +typedef struct { + byte Zero; // always 0x30 -> Ascii 0 + byte BType; // Block Type + word BCount; // Block count +}TResFunGetBlockItem; + +typedef struct { + byte RetVal; + byte TRSize; + word Length; + TResFunGetBlockItem Blocks[7]; +}TDataFunListAll; + +typedef TDataFunListAll* PDataFunListAll; + +typedef struct { + word BlockNum; + byte Unknown; + byte BlockLang; +}TDataFunGetBotItem; + +typedef struct { + byte RetVal; + byte TSize; + word DataLen; + TDataFunGetBotItem Items[(IsoPayload_Size - 29 ) / 4]; +}TDataFunGetBot; +// Note : 29 is the size of headers iso, COPT, S7 header, params, data + +typedef TDataFunGetBot* PDataFunGetBot; + +typedef struct { + byte RetVal; // 0xFF + byte TSize; // Octet (0x09) + word Length; // 0x0002 + byte Zero; // Ascii '0' (0x30) + byte BlkType; +}TReqDataBlockOfType; + +typedef TReqDataBlockOfType* PReqDataBlockOfType; + +typedef struct { + byte RetVal; + byte TSize; + word DataLen; + byte BlkPrfx; // always 0x30 + byte BlkType; + byte AsciiBlk[5]; // BlockNum in ascii + byte A; // always 0x41 ('A') +}TReqDataBlockInfo; + +typedef TReqDataBlockInfo* PReqDataBlockInfo; + +typedef struct { + byte RetVal; + byte TSize; + word Length; + byte Cst_b; + byte BlkType; + word Cst_w1; + word Cst_w2; + word Cst_pp; + byte Unknown_1; + byte BlkFlags; + byte BlkLang; + byte SubBlkType; + word BlkNumber; + u_int LenLoadMem; + byte BlkSec[4]; + u_int CodeTime_ms; + word CodeTime_dy; + u_int IntfTime_ms; + word IntfTime_dy; + word SbbLen; + word AddLen; + word LocDataLen; + word MC7Len; + byte Author[8]; + byte Family[8]; + byte Header[8]; + byte Version; + byte Unknown_2; + word BlkChksum; + byte Resvd1[4]; + byte Resvd2[4]; +}TResDataBlockInfo; + +typedef TResDataBlockInfo* PResDataBlockInfo; + +//============================================================================== +// BSEND / BRECV +//============================================================================== +typedef struct { + int Size; + longword R_ID; + byte Data[65536]; +}TPendingBuffer; + +typedef struct { + TTPKT TPKT; + TCOTP_DT COTP; + byte P; + byte PDUType; +}TPacketInfo; + +typedef struct { + byte Head[3];// Always 0x00 0x01 0x12 + byte Plen; // par len 0x04 or 0x08 + byte Uk; // unknown (0x12) + byte Tg; // type and group, 4 bits type and 4 bits group (0x46) + byte SubFun; // subfunction (0x01) + byte Seq; // sequence + byte IDSeq; // ID Sequence (come from partner) + byte EoS; // End of Sequence = 0x00 Sequence in progress = 0x01; + word Err; // +}TBSendParams; + +typedef TBSendParams* PBSendReqParams; +typedef TBSendParams* PBSendResParams; + +// Data frame + +typedef struct { + byte FF; // 0xFF + byte TRSize; // Transport Size 0x09 (octet) + word Len; // This Telegram Length + byte DHead[4];// sequence 0x12 0x06 0x13 0x00 + u_int R_ID; // R_ID +}TBsendRequestData; + +typedef TBsendRequestData* PBsendRequestData; + +typedef struct { + byte DHead[4]; // sequence 0x0A 0x00 0x00 0x00 +}TBSendResData; + +typedef TBSendResData* PBSendResData; + +#pragma pack() +#endif // s7_types_h diff --git a/core/src/drivers/plugins/native/s7comm/snap7/lib/snap7_libmain.cpp b/core/src/drivers/plugins/native/s7comm/snap7/lib/snap7_libmain.cpp new file mode 100644 index 00000000..5887c6e6 --- /dev/null +++ b/core/src/drivers/plugins/native/s7comm/snap7/lib/snap7_libmain.cpp @@ -0,0 +1,1196 @@ +/*=============================================================================| +| PROJECT SNAP7 1.4.3 | +|==============================================================================| +| Copyright (C) 2013, 2025 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/ | +|=============================================================================*/ +#include "snap7_libmain.h" + +#ifndef OS_WINDOWS +void libinit(void) __attribute__((constructor)); +void libdone(void) __attribute__((destructor)); +#endif + +static bool libresult = true; + +void libinit(void) +{ + // in future expansions here can be inserted some initialization code + libresult=true; +} + +void libdone(void) +{ + // in future expansions here can be inserted some destruction code +} + +#ifdef OS_WINDOWS +BOOL APIENTRY DllMain (HINSTANCE hInst, + DWORD reason, + LPVOID reserved) +{ + switch (reason) + { + case DLL_PROCESS_ATTACH: + libinit(); + break; + case DLL_PROCESS_DETACH: + libdone(); + break; + case DLL_THREAD_ATTACH: + break; + case DLL_THREAD_DETACH: + break; + } + return libresult; +} +#endif + +//*************************************************************************** +// CLIENT +//*************************************************************************** +S7Object S7API Cli_Create() +{ + return S7Object(new TSnap7Client()); +} +//--------------------------------------------------------------------------- +void S7API Cli_Destroy(S7Object &Client) +{ + if (Client) + { + delete PSnap7Client(Client); + Client=0; + } +} +//--------------------------------------------------------------------------- +int S7API Cli_SetConnectionParams(S7Object Client, const char *Address, word LocalTSAP, word RemoteTSAP) +{ + if (Client) + { + PSnap7Client(Client)->SetConnectionParams(Address, LocalTSAP, RemoteTSAP); + return 0; + } + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_SetConnectionType(S7Object Client, word ConnectionType) +{ + if (Client) + { + PSnap7Client(Client)->SetConnectionType(ConnectionType); + return 0; + } + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_ConnectTo(S7Object Client, const char *Address, int Rack, int Slot) +{ + if (Client) + return PSnap7Client(Client)->ConnectTo(Address, Rack, Slot); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_Connect(S7Object Client) +{ + if (Client) + return PSnap7Client(Client)->Connect(); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_Disconnect(S7Object Client) +{ + if (Client) + return PSnap7Client(Client)->Disconnect(); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_GetParam(S7Object Client, int ParamNumber, void *pValue) +{ + if (Client) + return PSnap7Client(Client)->GetParam(ParamNumber, pValue); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_SetParam(S7Object Client, int ParamNumber, void *pValue) +{ + if (Client) + return PSnap7Client(Client)->SetParam(ParamNumber, pValue); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_SetAsCallback(S7Object Client, pfn_CliCompletion pCompletion, void *usrPtr) +{ + if (Client) + return PSnap7Client(Client)->SetAsCallback(pCompletion, usrPtr); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_ReadArea(S7Object Client, int Area, int DBNumber, int Start, int Amount, int WordLen, void *pUsrData) +{ + if (Client) + return PSnap7Client(Client)->ReadArea(Area, DBNumber, Start, Amount, WordLen, pUsrData); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_WriteArea(S7Object Client, int Area, int DBNumber, int Start, int Amount, int WordLen, void *pUsrData) +{ + if (Client) + return PSnap7Client(Client)->WriteArea(Area, DBNumber, Start, Amount, WordLen, pUsrData); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_ReadMultiVars(S7Object Client, PS7DataItem Item, int ItemsCount) +{ + if (Client) + return PSnap7Client(Client)->ReadMultiVars(Item, ItemsCount); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_WriteMultiVars(S7Object Client, PS7DataItem Item, int ItemsCount) +{ + if (Client) + return PSnap7Client(Client)->WriteMultiVars(Item, ItemsCount); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_DBRead(S7Object Client, int DBNumber, int Start, int Size, void *pUsrData) +{ + if (Client) + return PSnap7Client(Client)->DBRead(DBNumber, Start, Size, pUsrData); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_DBWrite(S7Object Client, int DBNumber, int Start, int Size, void *pUsrData) +{ + if (Client) + return PSnap7Client(Client)->DBWrite(DBNumber, Start, Size, pUsrData); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_MBRead(S7Object Client, int Start, int Size, void *pUsrData) +{ + if (Client) + return PSnap7Client(Client)->MBRead(Start, Size, pUsrData); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_MBWrite(S7Object Client, int Start, int Size, void *pUsrData) +{ + if (Client) + return PSnap7Client(Client)->MBWrite(Start, Size, pUsrData); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_EBRead(S7Object Client, int Start, int Size, void *pUsrData) +{ + if (Client) + return PSnap7Client(Client)->EBRead(Start, Size, pUsrData); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_EBWrite(S7Object Client, int Start, int Size, void *pUsrData) +{ + if (Client) + return PSnap7Client(Client)->EBWrite(Start, Size, pUsrData); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_ABRead(S7Object Client, int Start, int Size, void *pUsrData) +{ + if (Client) + return PSnap7Client(Client)->ABRead(Start, Size, pUsrData); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_ABWrite(S7Object Client, int Start, int Size, void *pUsrData) +{ + if (Client) + return PSnap7Client(Client)->ABWrite(Start, Size, pUsrData); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_TMRead(S7Object Client, int Start, int Amount, void *pUsrData) +{ + if (Client) + return PSnap7Client(Client)->TMRead(Start, Amount, pUsrData); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_TMWrite(S7Object Client, int Start, int Amount, void *pUsrData) +{ + if (Client) + return PSnap7Client(Client)->TMWrite(Start, Amount, pUsrData); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_CTRead(S7Object Client, int Start, int Amount, void *pUsrData) +{ + if (Client) + return PSnap7Client(Client)->CTRead(Start, Amount, pUsrData); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_CTWrite(S7Object Client, int Start, int Amount, void *pUsrData) +{ + if (Client) + return PSnap7Client(Client)->CTWrite(Start, Amount, pUsrData); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_ListBlocks(S7Object Client, TS7BlocksList *pUsrData) +{ + if (Client) + return PSnap7Client(Client)->ListBlocks(pUsrData); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_GetAgBlockInfo(S7Object Client, int BlockType, int BlockNum, TS7BlockInfo *pUsrData) +{ + if (Client) + return PSnap7Client(Client)->GetAgBlockInfo(BlockType, BlockNum, pUsrData); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_GetPgBlockInfo(S7Object Client, void *pBlock, TS7BlockInfo *pUsrData, int Size) +{ + if (Client) + return PSnap7Client(Client)->GetPgBlockInfo(pBlock, pUsrData, Size); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_ListBlocksOfType(S7Object Client, int BlockType, TS7BlocksOfType *pUsrData, int &ItemsCount) +{ + if (Client) + return PSnap7Client(Client)->ListBlocksOfType(BlockType, pUsrData, ItemsCount); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_Upload(S7Object Client, int BlockType, int BlockNum, void *pUsrData, int &Size) +{ + if (Client) + return PSnap7Client(Client)->Upload(BlockType, BlockNum, pUsrData, Size); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_FullUpload(S7Object Client, int BlockType, int BlockNum, void *pUsrData, int &Size) +{ + if (Client) + return PSnap7Client(Client)->FullUpload(BlockType, BlockNum, pUsrData, Size); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_Download(S7Object Client, int BlockNum, void *pUsrData, int Size) +{ + if (Client) + return PSnap7Client(Client)->Download(BlockNum, pUsrData, Size); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_Delete(S7Object Client, int BlockType, int BlockNum) +{ + if (Client) + return PSnap7Client(Client)->Delete(BlockType, BlockNum); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_DBGet(S7Object Client, int DBNumber, void *pUsrData, int &Size) +{ + if (Client) + return PSnap7Client(Client)->DBGet(DBNumber, pUsrData, Size); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_DBFill(S7Object Client, int DBNumber, int FillChar) +{ + if (Client) + return PSnap7Client(Client)->DBFill(DBNumber, FillChar); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_GetPlcDateTime(S7Object Client, tm &DateTime) +{ + if (Client) + return PSnap7Client(Client)->GetPlcDateTime(DateTime); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_SetPlcDateTime(S7Object Client, tm *DateTime) +{ + if (Client) + return PSnap7Client(Client)->SetPlcDateTime(DateTime); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_SetPlcSystemDateTime(S7Object Client) +{ + if (Client) + return PSnap7Client(Client)->SetPlcSystemDateTime(); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_GetOrderCode(S7Object Client, TS7OrderCode *pUsrData) +{ + if (Client) + return PSnap7Client(Client)->GetOrderCode(pUsrData); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_GetCpuInfo(S7Object Client, TS7CpuInfo *pUsrData) +{ + if (Client) + return PSnap7Client(Client)->GetCpuInfo(pUsrData); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_GetCpInfo(S7Object Client, TS7CpInfo *pUsrData) +{ + if (Client) + return PSnap7Client(Client)->GetCpInfo(pUsrData); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_ReadSZL(S7Object Client, int ID, int Index, TS7SZL *pUsrData, int &Size) +{ + if (Client) + return PSnap7Client(Client)->ReadSZL(ID, Index, pUsrData, Size); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_ReadSZLList(S7Object Client, TS7SZLList *pUsrData, int &ItemsCount) +{ + if (Client) + return PSnap7Client(Client)->ReadSZLList(pUsrData, ItemsCount); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_PlcHotStart(S7Object Client) +{ + if (Client) + return PSnap7Client(Client)->PlcHotStart(); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_PlcColdStart(S7Object Client) +{ + if (Client) + return PSnap7Client(Client)->PlcColdStart(); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_PlcStop(S7Object Client) +{ + if (Client) + return PSnap7Client(Client)->PlcStop(); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_CopyRamToRom(S7Object Client, int Timeout) +{ + if (Client) + return PSnap7Client(Client)->CopyRamToRom(Timeout); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_Compress(S7Object Client, int Timeout) +{ + if (Client) + return PSnap7Client(Client)->Compress(Timeout); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_GetPlcStatus(S7Object Client, int &Status) +{ + if (Client) + return PSnap7Client(Client)->GetPlcStatus(Status); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_GetProtection(S7Object Client, TS7Protection *pUsrData) +{ + if (Client) + return PSnap7Client(Client)->GetProtection(pUsrData); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_SetSessionPassword(S7Object Client, char *Password) +{ + if (Client) + return PSnap7Client(Client)->SetSessionPassword(Password); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_ClearSessionPassword(S7Object Client) +{ + if (Client) + return PSnap7Client(Client)->ClearSessionPassword(); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_IsoExchangeBuffer(S7Object Client, void *pUsrData, int &Size) +{ + if (Client) + return PSnap7Client(Client)->isoExchangeBuffer(pUsrData, Size); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_GetExecTime(S7Object Client, int &Time) +{ + if (Client) + { + Time=PSnap7Client(Client)->Time(); + return 0; + } + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_GetLastError(S7Object Client, int &LastError) +{ + if (Client) + { + LastError=PSnap7Client(Client)->LastError; + return 0; + } + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_GetPduLength(S7Object Client, int &Requested, int &Negotiated) +{ + if (Client) + { + Negotiated=PSnap7Client(Client)->PDULength; + Requested =PSnap7Client(Client)->PDURequest; + return 0; + } + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_ErrorText(int Error, char *Text, int TextLen) +{ + try{ + ErrCliText(Error, Text, TextLen); + Text[TextLen-1] = '\0'; + } + catch (...){ + return errLibInvalidParam; + } + return 0; +} +//--------------------------------------------------------------------------- +int S7API Cli_GetConnected(S7Object Client, int &Connected) +{ + Connected=0; + if (Client) + { + Connected=PSnap7Client(Client)->Connected; + return 0; + } + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_AsReadArea(S7Object Client, int Area, int DBNumber, int Start, int Amount, int WordLen, void *pUsrData) +{ + if (Client) + return PSnap7Client(Client)->AsReadArea(Area, DBNumber, Start, Amount, WordLen, pUsrData); + else + return errLibInvalidParam; +} +//--------------------------------------------------------------------------- +int S7API Cli_AsWriteArea(S7Object Client, int Area, int DBNumber, int Start, int Amount, int WordLen, void *pUsrData) +{ + if (Client) + return PSnap7Client(Client)->AsWriteArea(Area, DBNumber, Start, Amount, WordLen, pUsrData); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_AsDBRead(S7Object Client, int DBNumber, int Start, int Size, void *pUsrData) +{ + if (Client) + return PSnap7Client(Client)->AsDBRead(DBNumber, Start, Size, pUsrData); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_AsDBWrite(S7Object Client, int DBNumber, int Start, int Size, void *pUsrData) +{ + if (Client) + return PSnap7Client(Client)->AsDBWrite(DBNumber, Start, Size, pUsrData); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_AsMBRead(S7Object Client, int Start, int Size, void *pUsrData) +{ + if (Client) + return PSnap7Client(Client)->AsMBRead(Start, Size, pUsrData); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_AsMBWrite(S7Object Client, int Start, int Size, void *pUsrData) +{ + if (Client) + return PSnap7Client(Client)->AsMBWrite(Start, Size, pUsrData); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_AsEBRead(S7Object Client, int Start, int Size, void *pUsrData) +{ + if (Client) + return PSnap7Client(Client)->AsEBRead(Start, Size, pUsrData); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_AsEBWrite(S7Object Client, int Start, int Size, void *pUsrData) +{ + if (Client) + return PSnap7Client(Client)->AsEBWrite(Start, Size, pUsrData); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_AsABRead(S7Object Client, int Start, int Size, void *pUsrData) +{ + if (Client) + return PSnap7Client(Client)->AsABRead(Start, Size, pUsrData); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_AsABWrite(S7Object Client, int Start, int Size, void *pUsrData) +{ + if (Client) + return PSnap7Client(Client)->AsABWrite(Start, Size, pUsrData); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_AsTMRead(S7Object Client, int Start, int Amount, void *pUsrData) +{ + if (Client) + return PSnap7Client(Client)->AsTMRead(Start, Amount, pUsrData); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_AsTMWrite(S7Object Client, int Start, int Amount, void *pUsrData) +{ + if (Client) + return PSnap7Client(Client)->AsTMWrite(Start, Amount, pUsrData); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_AsCTRead(S7Object Client, int Start, int Amount, void *pUsrData) +{ + if (Client) + return PSnap7Client(Client)->AsCTRead(Start, Amount, pUsrData); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_AsCTWrite(S7Object Client, int Start, int Amount, void *pUsrData) +{ + if (Client) + return PSnap7Client(Client)->AsCTWrite(Start, Amount, pUsrData); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_AsListBlocksOfType(S7Object Client, int BlockType, TS7BlocksOfType *pUsrData, int &ItemsCount) +{ + if (Client) + return PSnap7Client(Client)->AsListBlocksOfType(BlockType, pUsrData, ItemsCount); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_AsReadSZL(S7Object Client, int ID, int Index, TS7SZL *pUsrData, int &Size) +{ + if (Client) + return PSnap7Client(Client)->AsReadSZL(ID, Index, pUsrData, Size); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_AsReadSZLList(S7Object Client, TS7SZLList *pUsrData, int &ItemsCount) +{ + if (Client) + return PSnap7Client(Client)->AsReadSZLList(pUsrData, ItemsCount); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_AsUpload(S7Object Client, int BlockType, int BlockNum, void *pUsrData, int &Size) +{ + if (Client) + return PSnap7Client(Client)->AsUpload(BlockType, BlockNum, pUsrData, Size); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_AsFullUpload(S7Object Client, int BlockType, int BlockNum, void *pUsrData, int &Size) +{ + if (Client) + return PSnap7Client(Client)->AsFullUpload(BlockType, BlockNum, pUsrData, Size); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_AsDownload(S7Object Client, int BlockNum, void *pUsrData, int Size) +{ + if (Client) + return PSnap7Client(Client)->AsDownload(BlockNum, pUsrData, Size); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_AsCopyRamToRom(S7Object Client, int Timeout) +{ + if (Client) + return PSnap7Client(Client)->AsCopyRamToRom(Timeout); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_AsCompress(S7Object Client, int Timeout) +{ + if (Client) + return PSnap7Client(Client)->Compress(Timeout); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_AsDBGet(S7Object Client, int DBNumber, void *pUsrData, int &Size) +{ + if (Client) + return PSnap7Client(Client)->AsDBGet(DBNumber, pUsrData, Size); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_AsDBFill(S7Object Client, int DBNumber, int FillChar) +{ + if (Client) + return PSnap7Client(Client)->AsDBFill(DBNumber, FillChar); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_CheckAsCompletion(S7Object Client, int &opResult) +{ + if (Client) + { + if (PSnap7Client(Client)->CheckAsCompletion(opResult)) + return JobComplete; + else + return JobPending; + } + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_WaitAsCompletion(S7Object Client, int Timeout) +{ + if (Client) + return PSnap7Client(Client)->WaitAsCompletion(Timeout); + else + return errLibInvalidObject; +} +//*************************************************************************** +// SERVER +//*************************************************************************** +S7Object S7API Srv_Create() +{ + return S7Object(new TSnap7Server()); +} +//--------------------------------------------------------------------------- +void S7API Srv_Destroy(S7Object &Server) +{ + if (Server) + { + delete PSnap7Server(Server); + Server=0; + } +} +//--------------------------------------------------------------------------- +int S7API Srv_GetParam(S7Object Server, int ParamNumber, void *pValue) +{ + if (Server) + return PSnap7Server(Server)->GetParam(ParamNumber, pValue); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Srv_SetParam(S7Object Server, int ParamNumber, void *pValue) +{ + if (Server) + return PSnap7Server(Server)->SetParam(ParamNumber, pValue); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Srv_StartTo(S7Object Server, const char *Address) +{ + if (Server) + return PSnap7Server(Server)->StartTo(Address); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Srv_Start(S7Object Server) +{ + if (Server) + return PSnap7Server(Server)->Start(); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Srv_Stop(S7Object Server) +{ + if (Server) + { + PSnap7Server(Server)->Stop(); + return 0; + } + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Srv_RegisterArea(S7Object Server, int AreaCode, word Index, void *pUsrData, int Size) +{ + if (Server) + return PSnap7Server(Server)->RegisterArea(AreaCode, Index, pUsrData, Size); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Srv_UnregisterArea(S7Object Server, int AreaCode, word Index) +{ + if (Server) + return PSnap7Server(Server)->UnregisterArea(AreaCode, Index); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Srv_LockArea(S7Object Server, int AreaCode, word Index) +{ + if (Server) + return PSnap7Server(Server)->LockArea(AreaCode, Index); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Srv_UnlockArea(S7Object Server, int AreaCode, word Index) +{ + if (Server) + return PSnap7Server(Server)->UnlockArea(AreaCode, Index); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Srv_GetStatus(S7Object Server, int &ServerStatus, int &CpuStatus, int &ClientsCount) +{ + if (Server) + { + ServerStatus=PSnap7Server(Server)->Status; + CpuStatus=PSnap7Server(Server)->CpuStatus; + ClientsCount=PSnap7Server(Server)->ClientsCount; + return 0; + } + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Srv_SetCpuStatus(S7Object Server, int CpuStatus) +{ + if (Server) + { + PSnap7Server(Server)->CpuStatus=CpuStatus; + return 0; + } + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Srv_ErrorText(int Error, char *Text, int TextLen) +{ + try{ + ErrSrvText(Error, Text, TextLen); + Text[TextLen-1] = '\0'; + } + catch (...){ + return errLibInvalidParam; + } + return 0; +} +//--------------------------------------------------------------------------- +int S7API Srv_EventText(TSrvEvent &Event, char *Text, int TextLen) +{ + try{ + EvtSrvText(Event, Text, TextLen); + //Text[TextLen] = '\0'; + } + catch (...){ + return errLibInvalidParam; + } + return 0; +} +//--------------------------------------------------------------------------- +int S7API Srv_PickEvent(S7Object Server, TSrvEvent *pEvent, int &EvtReady) +{ + EvtReady=0; + if (Server) + { + EvtReady=int(PSnap7Server(Server)->PickEvent(pEvent)); + return 0; + } + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Srv_ClearEvents(S7Object Server) +{ + if (Server) + { + PSnap7Server(Server)->EventsFlush(); + return 0; + } + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Srv_GetMask(S7Object Server, int MaskKind, longword &Mask) +{ + if (Server) + { + Mask=0; + if ((MaskKind==mkEvent) || (MaskKind==mkLog)) + { + if (MaskKind==mkEvent) + Mask=PSnap7Server(Server)->EventMask; + else + Mask=PSnap7Server(Server)->LogMask; + return 0; + } + else + return errLibInvalidParam; + } + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Srv_SetMask(S7Object Server, int MaskKind, longword Mask) +{ + if (Server) + { + if ((MaskKind==mkEvent) || (MaskKind==mkLog)) + { + if (MaskKind==mkEvent) + PSnap7Server(Server)->EventMask=Mask; + else + PSnap7Server(Server)->LogMask=Mask; + return 0; + } + else + return errLibInvalidParam; + } + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Srv_SetEventsCallback(S7Object Server, pfn_SrvCallBack pCallback, void *usrPtr) +{ + if (Server) + return PSnap7Server(Server)->SetEventsCallBack(pCallback, usrPtr); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Srv_SetReadEventsCallback(S7Object Server, pfn_SrvCallBack pCallback, void *usrPtr) +{ + if (Server) + return PSnap7Server(Server)->SetReadEventsCallBack(pCallback, usrPtr); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Srv_SetRWAreaCallback(S7Object Server, pfn_RWAreaCallBack pCallback, void *usrPtr) +{ + if (Server) + return PSnap7Server(Server)->SetRWAreaCallBack(pCallback, usrPtr); + else + return errLibInvalidObject; +} +//*************************************************************************** +// PARTNER +//*************************************************************************** +S7Object S7API Par_Create(int Active) +{ + return S7Object(new TSnap7Partner(Active!=0)); +} +//--------------------------------------------------------------------------- +void S7API Par_Destroy(S7Object &Partner) +{ + if (Partner) + { + delete PSnap7Partner(Partner); + Partner=0; + } +} +//--------------------------------------------------------------------------- +int S7API Par_GetParam(S7Object Partner, int ParamNumber, void *pValue) +{ + if (Partner) + return PSnap7Partner(Partner)->GetParam(ParamNumber, pValue); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Par_SetParam(S7Object Partner, int ParamNumber, void *pValue) +{ + if (Partner) + return PSnap7Partner(Partner)->SetParam(ParamNumber, pValue); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Par_StartTo(S7Object Partner, const char *LocalAddress, const char *RemoteAddress, + word LocTsap, word RemTsap) +{ + if (Partner) + return PSnap7Partner(Partner)->StartTo(LocalAddress, RemoteAddress, LocTsap, RemTsap); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Par_Start(S7Object Partner) +{ + if (Partner) + return PSnap7Partner(Partner)->Start(); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Par_Stop(S7Object Partner) +{ + if (Partner) + return PSnap7Partner(Partner)->Stop(); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Par_BSend(S7Object Partner, longword R_ID, void *pUsrData, int Size) +{ + if (Partner) + return PSnap7Partner(Partner)->BSend(R_ID, pUsrData, Size); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Par_AsBSend(S7Object Partner, longword R_ID, void *pUsrData, int Size) +{ + if (Partner) + return PSnap7Partner(Partner)->AsBSend(R_ID, pUsrData, Size); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Par_CheckAsBSendCompletion(S7Object Partner, int &opResult) +{ + if (Partner) + { + if (PSnap7Partner(Partner)->CheckAsBSendCompletion(opResult)) + return JobComplete; + else + return JobPending; + } + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Par_WaitAsBSendCompletion(S7Object Partner, longword Timeout) +{ + if (Partner) + return PSnap7Partner(Partner)->WaitAsBSendCompletion(Timeout); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Par_SetSendCallback(S7Object Partner, pfn_ParBSendCompletion pCompletion, void *usrPtr) +{ + if (Partner) + return PSnap7Partner(Partner)->SetSendCallback(pCompletion, usrPtr); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Par_BRecv(S7Object Partner, longword &R_ID, void *pData, int &Size, longword Timeout) +{ + if (Partner) + return PSnap7Partner(Partner)->BRecv(R_ID, pData, Size, Timeout); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Par_CheckAsBRecvCompletion(S7Object Partner, int &opResult, longword &R_ID, + void *pData, int &Size) +{ + if (Partner) + { + if (PSnap7Partner(Partner)->CheckAsBRecvCompletion(opResult, R_ID, pData, Size)) + return JobComplete; + else + return JobPending; + } + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Par_SetRecvCallback(S7Object Partner, pfn_ParBRecvCallBack pCompletion, void *usrPtr) +{ + if (Partner) + return PSnap7Partner(Partner)->SetRecvCallback(pCompletion, usrPtr); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Par_GetTimes(S7Object Partner, longword &SendTime, longword &RecvTime) +{ + if (Partner) + { + SendTime=PSnap7Partner(Partner)->SendTime; + RecvTime=PSnap7Partner(Partner)->RecvTime; + return 0; + } + else + return errLibInvalidObject; + +} +//--------------------------------------------------------------------------- +int S7API Par_GetStats(S7Object Partner, longword &BytesSent, longword &BytesRecv, + longword &SendErrors, longword &RecvErrors) +{ + if (Partner) + { + BytesSent=PSnap7Partner(Partner)->BytesSent; + BytesRecv=PSnap7Partner(Partner)->BytesRecv; + SendErrors=PSnap7Partner(Partner)->SendErrors; + RecvErrors=PSnap7Partner(Partner)->RecvErrors; + return 0; + } + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Par_GetLastError(S7Object Partner, int &LastError) +{ + if (Partner) + { + LastError=PSnap7Partner(Partner)->LastError; + return 0; + } + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Par_GetStatus(S7Object Partner, int &Status) +{ + if (Partner) + { + Status=PSnap7Partner(Partner)->Status(); + return 0; + } + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Par_ErrorText(int Error, char *Text, int TextLen) +{ + try{ + ErrParText(Error, Text, TextLen); + Text[TextLen - 1] = '\0'; + } + catch (...){ + return errLibInvalidParam; + } + return 0; +} + diff --git a/core/src/drivers/plugins/native/s7comm/snap7/lib/snap7_libmain.h b/core/src/drivers/plugins/native/s7comm/snap7/lib/snap7_libmain.h new file mode 100644 index 00000000..d2eec096 --- /dev/null +++ b/core/src/drivers/plugins/native/s7comm/snap7/lib/snap7_libmain.h @@ -0,0 +1,201 @@ +/*=============================================================================| +| PROJECT SNAP7 1.4.3 | +|==============================================================================| +| Copyright (C) 2013, 2025 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/ | +|=============================================================================*/ +#ifndef snap7_libmain_h +#define snap7_libmain_h +//--------------------------------------------------------------------------- +#include "s7_client.h" +#include "s7_server.h" +#include "s7_partner.h" +#include "s7_text.h" +//--------------------------------------------------------------------------- + +const int mkEvent = 0; +const int mkLog = 1; + +typedef uintptr_t S7Object; // multi platform/processor object reference + +//============================================================================== +// CLIENT EXPORT LIST - Sync functions +//============================================================================== +EXPORTSPEC S7Object S7API Cli_Create(); +EXPORTSPEC void S7API Cli_Destroy(S7Object &Client); +EXPORTSPEC int S7API Cli_Connect(S7Object Client); +EXPORTSPEC int S7API Cli_SetConnectionParams(S7Object Client, const char *Address, word LocalTSAP, word RemoteTSAP); +EXPORTSPEC int S7API Cli_SetConnectionType(S7Object Client, word ConnectionType); +EXPORTSPEC int S7API Cli_ConnectTo(S7Object Client, const char *Address, int Rack, int Slot); +EXPORTSPEC int S7API Cli_Disconnect(S7Object Client); +EXPORTSPEC int S7API Cli_GetParam(S7Object Client, int ParamNumber, void *pValue); +EXPORTSPEC int S7API Cli_SetParam(S7Object Client, int ParamNumber, void *pValue); +EXPORTSPEC int S7API Cli_SetAsCallback(S7Object Client, pfn_CliCompletion pCompletion, void *usrPtr); +// Data I/O functions +EXPORTSPEC int S7API Cli_ReadArea(S7Object Client, int Area, int DBNumber, int Start, int Amount, int WordLen, void *pUsrData); +EXPORTSPEC int S7API Cli_WriteArea(S7Object Client, int Area, int DBNumber, int Start, int Amount, int WordLen, void *pUsrData); +EXPORTSPEC int S7API Cli_ReadMultiVars(S7Object Client, PS7DataItem Item, int ItemsCount); +EXPORTSPEC int S7API Cli_WriteMultiVars(S7Object Client, PS7DataItem Item, int ItemsCount); +// Data I/O Lean functions +EXPORTSPEC int S7API Cli_DBRead(S7Object Client, int DBNumber, int Start, int Size, void *pUsrData); +EXPORTSPEC int S7API Cli_DBWrite(S7Object Client, int DBNumber, int Start, int Size, void *pUsrData); +EXPORTSPEC int S7API Cli_MBRead(S7Object Client, int Start, int Size, void *pUsrData); +EXPORTSPEC int S7API Cli_MBWrite(S7Object Client, int Start, int Size, void *pUsrData); +EXPORTSPEC int S7API Cli_EBRead(S7Object Client, int Start, int Size, void *pUsrData); +EXPORTSPEC int S7API Cli_EBWrite(S7Object Client, int Start, int Size, void *pUsrData); +EXPORTSPEC int S7API Cli_ABRead(S7Object Client, int Start, int Size, void *pUsrData); +EXPORTSPEC int S7API Cli_ABWrite(S7Object Client, int Start, int Size, void *pUsrData); +EXPORTSPEC int S7API Cli_TMRead(S7Object Client, int Start, int Amount, void *pUsrData); +EXPORTSPEC int S7API Cli_TMWrite(S7Object Client, int Start, int Amount, void *pUsrData); +EXPORTSPEC int S7API Cli_CTRead(S7Object Client, int Start, int Amount, void *pUsrData); +EXPORTSPEC int S7API Cli_CTWrite(S7Object Client, int Start, int Amount, void *pUsrData); +// Directory functions +EXPORTSPEC int S7API Cli_ListBlocks(S7Object Client, TS7BlocksList *pUsrData); +EXPORTSPEC int S7API Cli_GetAgBlockInfo(S7Object Client, int BlockType, int BlockNum, TS7BlockInfo *pUsrData); +EXPORTSPEC int S7API Cli_GetPgBlockInfo(S7Object Client, void *pBlock, TS7BlockInfo *pUsrData, int Size); +EXPORTSPEC int S7API Cli_ListBlocksOfType(S7Object Client, int BlockType, TS7BlocksOfType *pUsrData, int &ItemsCount); +// Blocks functions +EXPORTSPEC int S7API Cli_Upload(S7Object Client, int BlockType, int BlockNum, void *pUsrData, int &Size); +EXPORTSPEC int S7API Cli_FullUpload(S7Object Client, int BlockType, int BlockNum, void *pUsrData, int &Size); +EXPORTSPEC int S7API Cli_Download(S7Object Client, int BlockNum, void *pUsrData, int Size); +EXPORTSPEC int S7API Cli_Delete(S7Object Client, int BlockType, int BlockNum); +EXPORTSPEC int S7API Cli_DBGet(S7Object Client, int DBNumber, void *pUsrData, int &Size); +EXPORTSPEC int S7API Cli_DBFill(S7Object Client, int DBNumber, int FillChar); +// Date/Time functions +EXPORTSPEC int S7API Cli_GetPlcDateTime(S7Object Client, tm &DateTime); +EXPORTSPEC int S7API Cli_SetPlcDateTime(S7Object Client, tm *DateTime); +EXPORTSPEC int S7API Cli_SetPlcSystemDateTime(S7Object Client); +// System Info functions +EXPORTSPEC int S7API Cli_GetOrderCode(S7Object Client, TS7OrderCode *pUsrData); +EXPORTSPEC int S7API Cli_GetCpuInfo(S7Object Client, TS7CpuInfo *pUsrData); +EXPORTSPEC int S7API Cli_GetCpInfo(S7Object Client, TS7CpInfo *pUsrData); +EXPORTSPEC int S7API Cli_ReadSZL(S7Object Client, int ID, int Index, TS7SZL *pUsrData, int &Size); +EXPORTSPEC int S7API Cli_ReadSZLList(S7Object Client, TS7SZLList *pUsrData, int &ItemsCount); +// Control functions +EXPORTSPEC int S7API Cli_PlcHotStart(S7Object Client); +EXPORTSPEC int S7API Cli_PlcColdStart(S7Object Client); +EXPORTSPEC int S7API Cli_PlcStop(S7Object Client); +EXPORTSPEC int S7API Cli_CopyRamToRom(S7Object Client, int Timeout); +EXPORTSPEC int S7API Cli_Compress(S7Object Client, int Timeout); +EXPORTSPEC int S7API Cli_GetPlcStatus(S7Object Client, int &Status); +// Security functions +EXPORTSPEC int S7API Cli_GetProtection(S7Object Client, TS7Protection *pUsrData); +EXPORTSPEC int S7API Cli_SetSessionPassword(S7Object Client, char *Password); +EXPORTSPEC int S7API Cli_ClearSessionPassword(S7Object Client); +// Low level +EXPORTSPEC int S7API Cli_IsoExchangeBuffer(S7Object Client, void *pUsrData, int &Size); +// Misc +EXPORTSPEC int S7API Cli_GetExecTime(S7Object Client, int &Time); +EXPORTSPEC int S7API Cli_GetLastError(S7Object Client, int &LastError); +EXPORTSPEC int S7API Cli_GetPduLength(S7Object Client, int &Requested, int &Negotiated); +EXPORTSPEC int S7API Cli_ErrorText(int Error, char *Text, int TextLen); +EXPORTSPEC int S7API Cli_GetConnected(S7Object Client, int &Connected); +//============================================================================== +// CLIENT EXPORT LIST - Async functions +//============================================================================== +EXPORTSPEC int S7API Cli_AsReadArea(S7Object Client, int Area, int DBNumber, int Start, int Amount, int WordLen, void *pUsrData); +EXPORTSPEC int S7API Cli_AsWriteArea(S7Object Client, int Area, int DBNumber, int Start, int Amount, int WordLen, void *pUsrData); +EXPORTSPEC int S7API Cli_AsDBRead(S7Object Client, int DBNumber, int Start, int Size, void *pUsrData); +EXPORTSPEC int S7API Cli_AsDBWrite(S7Object Client, int DBNumber, int Start, int Size, void *pUsrData); +EXPORTSPEC int S7API Cli_AsMBRead(S7Object Client, int Start, int Size, void *pUsrData); +EXPORTSPEC int S7API Cli_AsMBWrite(S7Object Client, int Start, int Size, void *pUsrData); +EXPORTSPEC int S7API Cli_AsEBRead(S7Object Client, int Start, int Size, void *pUsrData); +EXPORTSPEC int S7API Cli_AsEBWrite(S7Object Client, int Start, int Size, void *pUsrData); +EXPORTSPEC int S7API Cli_AsABRead(S7Object Client, int Start, int Size, void *pUsrData); +EXPORTSPEC int S7API Cli_AsABWrite(S7Object Client, int Start, int Size, void *pUsrData); +EXPORTSPEC int S7API Cli_AsTMRead(S7Object Client, int Start, int Amount, void *pUsrData); +EXPORTSPEC int S7API Cli_AsTMWrite(S7Object Client, int Start, int Amount, void *pUsrData); +EXPORTSPEC int S7API Cli_AsCTRead(S7Object Client, int Start, int Amount, void *pUsrData); +EXPORTSPEC int S7API Cli_AsCTWrite(S7Object Client, int Start, int Amount, void *pUsrData); +EXPORTSPEC int S7API Cli_AsListBlocksOfType(S7Object Client, int BlockType, TS7BlocksOfType *pUsrData, int &ItemsCount); +EXPORTSPEC int S7API Cli_AsReadSZL(S7Object Client, int ID, int Index, TS7SZL *pUsrData, int &Size); +EXPORTSPEC int S7API Cli_AsReadSZLList(S7Object Client, TS7SZLList *pUsrData, int &ItemsCount); +EXPORTSPEC int S7API Cli_AsUpload(S7Object Client, int BlockType, int BlockNum, void *pUsrData, int &Size); +EXPORTSPEC int S7API Cli_AsFullUpload(S7Object Client, int BlockType, int BlockNum, void *pUsrData, int &Size); +EXPORTSPEC int S7API Cli_AsDownload(S7Object Client, int BlockNum, void *pUsrData, int Size); +EXPORTSPEC int S7API Cli_AsCopyRamToRom(S7Object Client, int Timeout); +EXPORTSPEC int S7API Cli_AsCompress(S7Object Client, int Timeout); +EXPORTSPEC int S7API Cli_AsDBGet(S7Object Client, int DBNumber, void *pUsrData, int &Size); +EXPORTSPEC int S7API Cli_AsDBFill(S7Object Client, int DBNumber, int FillChar); +EXPORTSPEC int S7API Cli_CheckAsCompletion(S7Object Client, int &opResult); +EXPORTSPEC int S7API Cli_WaitAsCompletion(S7Object Client, int Timeout); +//============================================================================== +// SERVER EXPORT LIST +//============================================================================== +EXPORTSPEC S7Object S7API Srv_Create(); +EXPORTSPEC void S7API Srv_Destroy(S7Object &Server); +EXPORTSPEC int S7API Srv_GetParam(S7Object Server, int ParamNumber, void *pValue); +EXPORTSPEC int S7API Srv_SetParam(S7Object Server, int ParamNumber, void *pValue); +EXPORTSPEC int S7API Srv_Start(S7Object Server); +EXPORTSPEC int S7API Srv_StartTo(S7Object Server, const char *Address); +EXPORTSPEC int S7API Srv_Stop(S7Object Server); +// Data +EXPORTSPEC int S7API Srv_RegisterArea(S7Object Server, int AreaCode, word Index, void *pUsrData, int Size); +EXPORTSPEC int S7API Srv_UnregisterArea(S7Object Server, int AreaCode, word Index); +EXPORTSPEC int S7API Srv_LockArea(S7Object Server, int AreaCode, word Index); +EXPORTSPEC int S7API Srv_UnlockArea(S7Object Server, int AreaCode, word Index); +// Events +EXPORTSPEC int S7API Srv_ClearEvents(S7Object Server); +EXPORTSPEC int S7API Srv_PickEvent(S7Object Server, TSrvEvent *pEvent, int &EvtReady); +EXPORTSPEC int S7API Srv_GetMask(S7Object Server, int MaskKind, longword &Mask); +EXPORTSPEC int S7API Srv_SetMask(S7Object Server, int MaskKind, longword Mask); +EXPORTSPEC int S7API Srv_SetEventsCallback(S7Object Server, pfn_SrvCallBack pCallback, void *usrPtr); +EXPORTSPEC int S7API Srv_SetReadEventsCallback(S7Object Server, pfn_SrvCallBack pCallback, void *usrPtr); +EXPORTSPEC int S7API Srv_EventText(TSrvEvent &Event, char *Text, int TextLen); +EXPORTSPEC int S7API Srv_SetRWAreaCallback(S7Object Server, pfn_RWAreaCallBack pCallback, void *usrPtr); +// Misc +EXPORTSPEC int S7API Srv_GetStatus(S7Object Server, int &ServerStatus, int &CpuStatus, int &ClientsCount); +EXPORTSPEC int S7API Srv_SetCpuStatus(S7Object Server, int CpuStatus); +EXPORTSPEC int S7API Srv_ErrorText(int Error, char *Text, int TextLen); +//============================================================================== +// PARTNER EXPORT LIST +//============================================================================== +EXPORTSPEC S7Object S7API Par_Create(int Active); +EXPORTSPEC void S7API Par_Destroy(S7Object &Partner); +EXPORTSPEC int S7API Par_GetParam(S7Object Partner, int ParamNumber, void *pValue); +EXPORTSPEC int S7API Par_SetParam(S7Object Partner, int ParamNumber, void *pValue); +EXPORTSPEC int S7API Par_Start(S7Object Partner); +EXPORTSPEC int S7API Par_StartTo(S7Object Partner, const char *LocalAddress, const char *RemoteAddress, + word LocTsap, word RemTsap); +EXPORTSPEC int S7API Par_Stop(S7Object Partner); +// BSend +EXPORTSPEC int S7API Par_BSend(S7Object Partner, longword R_ID, void *pUsrData, int Size); +EXPORTSPEC int S7API Par_AsBSend(S7Object Partner, longword R_ID, void *pUsrData, int Size); +EXPORTSPEC int S7API Par_CheckAsBSendCompletion(S7Object Partner, int &opResult); +EXPORTSPEC int S7API Par_WaitAsBSendCompletion(S7Object Partner, longword Timeout); +EXPORTSPEC int S7API Par_SetSendCallback(S7Object Partner, pfn_ParBSendCompletion pCompletion, void *usrPtr); +// BRecv +EXPORTSPEC int S7API Par_BRecv(S7Object Partner, longword &R_ID, void *pData, int &Size, longword Timeout); +EXPORTSPEC int S7API Par_CheckAsBRecvCompletion(S7Object Partner, int &opResult, longword &R_ID, + void *pData, int &Size); +EXPORTSPEC int S7API Par_SetRecvCallback(S7Object Partner, pfn_ParBRecvCallBack pCompletion, void *usrPtr); +// Stat +EXPORTSPEC int S7API Par_GetTimes(S7Object Partner, longword &SendTime, longword &RecvTime); +EXPORTSPEC int S7API Par_GetStats(S7Object Partner, longword &BytesSent, longword &BytesRecv, + longword &SendErrors, longword &RecvErrors); +EXPORTSPEC int S7API Par_GetLastError(S7Object Partner, int &LastError); +EXPORTSPEC int S7API Par_GetStatus(S7Object Partner, int &Status); +EXPORTSPEC int S7API Par_ErrorText(int Error, char *Text, int TextLen); + + + +#endif // snap7_libmain_h diff --git a/core/src/drivers/plugins/native/s7comm/snap7/sys/snap_msgsock.cpp b/core/src/drivers/plugins/native/s7comm/snap7/sys/snap_msgsock.cpp new file mode 100644 index 00000000..6f384c2e --- /dev/null +++ b/core/src/drivers/plugins/native/s7comm/snap7/sys/snap_msgsock.cpp @@ -0,0 +1,923 @@ +/*=============================================================================| +| PROJECT SNAP7 1.4.3 | +|==============================================================================| +| Copyright (C) 2013, 2025 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/ | +|=============================================================================*/ + +#include "snap_msgsock.h" + +//--------------------------------------------------------------------------- + +static SocketsLayer SocketsLayerInitializer; + +//--------------------------------------------------------------------------- +// Base class endian aware +//--------------------------------------------------------------------------- +TSnapBase::TSnapBase() +{ + int x = 1; + LittleEndian=*(char *)&x == 1; +} +//--------------------------------------------------------------------------- +word TSnapBase::SwapWord(word Value) +{ + if (LittleEndian) + return ((Value >> 8) & 0xFF) | ((Value << 8) & 0xFF00); + else + return Value; +} +//--------------------------------------------------------------------------- +longword TSnapBase::SwapDWord(longword Value) +{ + if (LittleEndian) + return (Value >> 24) | ((Value << 8) & 0x00FF0000) | ((Value >> 8) & 0x0000FF00) | (Value << 24); + else + return Value; +} +//--------------------------------------------------------------------------- +void Msg_CloseSocket(socket_t FSocket) +{ + #ifdef OS_WINDOWS + closesocket(FSocket); + #else + close(FSocket); + #endif +} +//--------------------------------------------------------------------------- +longword Msg_GetSockAddr(socket_t FSocket) +{ + sockaddr_in RemoteSin; + #ifdef OS_WINDOWS + int namelen = sizeof(RemoteSin); + #else + uint32_t namelen = sizeof(RemoteSin); + #endif + namelen=sizeof(sockaddr_in); + if (getpeername(FSocket,(struct sockaddr*)&RemoteSin, &namelen)==0) + return RemoteSin.sin_addr.s_addr; + else + return 0; +} +//--------------------------------------------------------------------------- +TMsgSocket::TMsgSocket() +{ + Pinger = new TPinger(); + // Set Defaults + strcpy(LocalAddress,"0.0.0.0"); + LocalPort=0; + strcpy(RemoteAddress,"127.0.0.1"); + RemotePort=0; + WorkInterval=100; + RecvTimeout=500; + SendTimeout=10; + PingTimeout=750; + Connected=false; + FSocket=INVALID_SOCKET; + LastTcpError=0; + LocalBind=0; +} +//--------------------------------------------------------------------------- +TMsgSocket::~TMsgSocket() +{ + DestroySocket(); + delete Pinger; +} +//--------------------------------------------------------------------------- +void TMsgSocket::SetSin(sockaddr_in &sin, char *Address, u_short Port) +{ + uint32_t in_addr; + in_addr=inet_addr(Address); + memset(&sin, 0, sizeof(sin)); + LastTcpError=0; + + if (in_addr!=INADDR_NONE) + { + sin.sin_addr.s_addr = in_addr; + sin.sin_family = AF_INET; + sin.sin_port = htons(Port); + } + else + LastTcpError=WSAEINVALIDADDRESS; +} +//--------------------------------------------------------------------------- +void TMsgSocket::GetSin(sockaddr_in sin, char *Address, u_short &Port) +{ + strcpy(Address,inet_ntoa(sin.sin_addr)); + Port=htons(sin.sin_port); +} +//--------------------------------------------------------------------------- +void TMsgSocket::GetLocal() +{ + #ifdef OS_WINDOWS + int namelen = sizeof(LocalSin); + #else + uint32_t namelen = sizeof(LocalSin); + #endif + if (getsockname(FSocket, (struct sockaddr*)&LocalSin, &namelen)==0) + GetSin(LocalSin, LocalAddress, LocalPort); +} +//--------------------------------------------------------------------------- +void TMsgSocket::GetRemote() +{ + #ifdef OS_WINDOWS + int namelen = sizeof(RemoteSin); + #else + uint32_t namelen = sizeof(RemoteSin); + #endif + if (getpeername(FSocket,(struct sockaddr*)&RemoteSin, &namelen)==0) + GetSin(RemoteSin, RemoteAddress, RemotePort); +} +//--------------------------------------------------------------------------- +int TMsgSocket::GetLastSocketError() +{ +#ifdef OS_WINDOWS + return WSAGetLastError(); +#else + return errno; +#endif +} +//--------------------------------------------------------------------------- +void TMsgSocket::Purge() +{ + // small buffer to empty the socket + char Trash[512]; + int Read; + if (LastTcpError!=WSAECONNRESET) + { + if (CanRead(0)) { + do + { + Read=recv(FSocket, Trash, 512, MSG_NOSIGNAL ); + } while(Read==512); + } + } +} +//--------------------------------------------------------------------------- +void TMsgSocket::CreateSocket() +{ + DestroySocket(); + LastTcpError=0; + FSocket =socket(AF_INET, SOCK_STREAM, IPPROTO_TCP ); + if (FSocket!=INVALID_SOCKET) + SetSocketOptions(); + else + LastTcpError =GetLastSocketError(); +} +//--------------------------------------------------------------------------- +void TMsgSocket::GotSocket() +{ + ClientHandle=RemoteSin.sin_addr.s_addr; + // could be inherited it if wee need further actions on the socket +} +//--------------------------------------------------------------------------- +void TMsgSocket::SetSocket(socket_t s) +{ + FSocket=s; + if (FSocket!=INVALID_SOCKET) + { + SetSocketOptions(); + GetLocal(); + GetRemote(); + GotSocket(); + } + Connected=FSocket!=INVALID_SOCKET; +} +//--------------------------------------------------------------------------- +void TMsgSocket::DestroySocket() +{ + if(FSocket != INVALID_SOCKET) + { + if (shutdown(FSocket, SD_SEND)==0) + Purge(); + #ifdef OS_WINDOWS + closesocket(FSocket); + #else + close(FSocket); + #endif + FSocket=INVALID_SOCKET; + } + LastTcpError=0; +} +//--------------------------------------------------------------------------- +int TMsgSocket::WaitingData() +{ + int result = 0; + u_long x = 0; +#ifdef OS_WINDOWS + if (ioctlsocket(FSocket, FIONREAD, &x) == 0) + result = x; +#else + if (ioctl(FSocket, FIONREAD, &x) == 0) + result = x; +#endif + if (result>MaxPacketSize) + result = MaxPacketSize; + return result; +} +//--------------------------------------------------------------------------- +int TMsgSocket::WaitForData(int Size, int Timeout) +{ + longword Elapsed; + + // Check for connection active + if (CanRead(0) && (WaitingData()==0)) + LastTcpError=WSAECONNRESET; + else + LastTcpError=0; + + // Enter main loop + if (LastTcpError==0) + { + Elapsed =SysGetTick(); + while((WaitingData()=(longword)(Timeout)) + LastTcpError =WSAETIMEDOUT; + else + SysSleep(1); + } + } + if(LastTcpError==WSAECONNRESET) + Connected =false; + + return LastTcpError; +} +//--------------------------------------------------------------------------- +void TMsgSocket::SetSocketOptions() +{ + int NoDelay = 1; + int KeepAlive = 1; + LastTcpError=0; + SockCheck(setsockopt(FSocket, IPPROTO_TCP, TCP_NODELAY,(char*)&NoDelay, sizeof(NoDelay))); + + if (LastTcpError==0) + SockCheck(setsockopt(FSocket, SOL_SOCKET, SO_KEEPALIVE,(char*)&KeepAlive, sizeof(KeepAlive))); +} +//--------------------------------------------------------------------------- +int TMsgSocket::SockCheck(int SockResult) +{ + if (SockResult == (int)(SOCKET_ERROR)) + LastTcpError = GetLastSocketError(); + + return LastTcpError; +} +//--------------------------------------------------------------------------- +bool TMsgSocket::CanWrite(int Timeout) +{ + timeval TimeV; + int64_t x; + fd_set FDset; + + if(FSocket == INVALID_SOCKET) + return false; + + TimeV.tv_usec = (Timeout % 1000) * 1000; + TimeV.tv_sec = Timeout / 1000; + + FD_ZERO(&FDset); + FD_SET(FSocket, &FDset); + + x = select(FSocket + 1, NULL, &FDset, NULL, &TimeV); //<-Ignore this warning in 64bit Visual Studio + if (x==(int)SOCKET_ERROR) + { + LastTcpError = GetLastSocketError(); + x=0; + } + return (x > 0); +} +//--------------------------------------------------------------------------- +bool TMsgSocket::CanRead(int Timeout) +{ + timeval TimeV; + int64_t x; + fd_set FDset; + + if(FSocket == INVALID_SOCKET) + return false; + + TimeV.tv_usec = (Timeout % 1000) * 1000; + TimeV.tv_sec = Timeout / 1000; + + FD_ZERO(&FDset); + FD_SET(FSocket, &FDset); + + x = select(FSocket + 1, &FDset, NULL, NULL, &TimeV); //<-Ignore this warning in 64bit Visual Studio + if (x==(int)SOCKET_ERROR) + { + LastTcpError = GetLastSocketError(); + x=0; + } + return (x > 0); +} +//--------------------------------------------------------------------------- +#ifdef NON_BLOCKING_CONNECT +// +// Non blocking connection (UNIX) Thanks to Rolf Stalder +// +int TMsgSocket::SckConnect() +{ + int n, flags, err; + socklen_t len; + fd_set rset, wset; + struct timeval tval; + + SetSin(RemoteSin, RemoteAddress, RemotePort); + + if (LastTcpError == 0) { + CreateSocket(); + if (LastTcpError == 0) { + flags = fcntl(FSocket, F_GETFL, 0); + if (flags >= 0) { + if (fcntl(FSocket, F_SETFL, flags | O_NONBLOCK) != -1) { + n = connect(FSocket, (struct sockaddr*)&RemoteSin, sizeof(RemoteSin)); + if (n < 0) { + if (errno != EINPROGRESS) { + LastTcpError = GetLastSocketError(); + } + else { + // still connecting ... + FD_ZERO(&rset); + FD_SET(FSocket, &rset); + wset = rset; + tval.tv_sec = PingTimeout / 1000; + tval.tv_usec = (PingTimeout % 1000) * 1000; + + n = select(FSocket+1, &rset, &wset, NULL, + (PingTimeout ? &tval : NULL)); + if (n == 0) { + // timeout + LastTcpError = WSAEHOSTUNREACH; + } + else { + if (FD_ISSET(FSocket, &rset) || FD_ISSET(FSocket, &wset)) { + err = 0; + len = sizeof(err); + if (getsockopt( + FSocket, SOL_SOCKET, SO_ERROR, &err, &len) == 0) { + if (err) { + LastTcpError = err; + } + else { + if (fcntl(FSocket, F_SETFL, flags) != -1) { + GetLocal(); + ClientHandle = LocalSin.sin_addr.s_addr; + } + else { + LastTcpError = GetLastSocketError(); + } + } + } + else { + LastTcpError = GetLastSocketError(); + } + } + else { + LastTcpError = -1; + } + } + } // still connecting + } + else if (n == 0) { + // connected immediatly + GetLocal(); + ClientHandle = LocalSin.sin_addr.s_addr; + } + } + else { + LastTcpError = GetLastSocketError(); + } // fcntl(F_SETFL) + } + else { + LastTcpError = GetLastSocketError(); + } // fcntl(F_GETFL) + } //valid socket + } // LastTcpError==0 + Connected=LastTcpError==0; + return LastTcpError; +} +#else +// +// Regular connection (Windows) +// +int TMsgSocket::SckConnect() +{ + int Result; + SetSin(RemoteSin, RemoteAddress, RemotePort); + if (LastTcpError==0) + { + if (Ping(RemoteSin)) + { + CreateSocket(); + if (LastTcpError==0) + { + Result=connect(FSocket, (struct sockaddr*)&RemoteSin, sizeof(RemoteSin)); + if (SockCheck(Result)==0) + { + GetLocal(); + // Client handle is self_address (here the connection is ACTIVE) + ClientHandle=LocalSin.sin_addr.s_addr; + } + } + } + else + LastTcpError=WSAEHOSTUNREACH; + } + Connected=LastTcpError==0; + return LastTcpError; +} +#endif +//--------------------------------------------------------------------------- +void TMsgSocket::SckDisconnect() +{ + DestroySocket(); + Connected=false; +} +//--------------------------------------------------------------------------- +void TMsgSocket::ForceClose() +{ + if(FSocket != INVALID_SOCKET) + { + try { + #ifdef OS_WINDOWS + closesocket(FSocket); + #else + close(FSocket); + #endif + } catch (...) { + } + FSocket=INVALID_SOCKET; + } + LastTcpError=0; +} +//--------------------------------------------------------------------------- +int TMsgSocket::SckBind() +{ + int Res; + int Opt=1; + SetSin(LocalSin, LocalAddress, LocalPort); + if (LastTcpError==0) + { + CreateSocket(); + if (LastTcpError==0) + { + setsockopt(FSocket ,SOL_SOCKET, SO_REUSEADDR, (const char *)&Opt, sizeof(int)); + Res =bind(FSocket, (struct sockaddr*)&LocalSin, sizeof(sockaddr_in)); + SockCheck(Res); + if (Res==0) + { + LocalBind=LocalSin.sin_addr.s_addr; + } + } + } + else + LastTcpError=WSAEINVALIDADDRESS; + + return LastTcpError; +} +//--------------------------------------------------------------------------- +int TMsgSocket::SckListen() +{ + LastTcpError=0; + SockCheck(listen(FSocket ,SOMAXCONN)); + return LastTcpError; +} +//--------------------------------------------------------------------------- +bool TMsgSocket::Ping(char *Host) +{ + return Pinger->Ping(Host, PingTimeout); +} +//--------------------------------------------------------------------------- +bool TMsgSocket::Ping(sockaddr_in Addr) +{ + if (PingTimeout == 0) + return true; + else + return Pinger->Ping(Addr.sin_addr.s_addr, PingTimeout); +} +//--------------------------------------------------------------------------- +socket_t TMsgSocket::SckAccept() +{ + socket_t result; + LastTcpError=0; + result = accept(FSocket, NULL, NULL); + if(result==INVALID_SOCKET) + LastTcpError =GetLastSocketError(); + return result; +} +//--------------------------------------------------------------------------- +int TMsgSocket::SendPacket(void *Data, int Size) +{ + int Result; + + LastTcpError=0; + if (SendTimeout>0) + { + if (!CanWrite(SendTimeout)) + { + LastTcpError = WSAETIMEDOUT; + return LastTcpError; + } + } + if (send(FSocket, (char*)Data, Size, MSG_NOSIGNAL)==Size) + return 0; + else + Result =SOCKET_ERROR; + + SockCheck(Result); + return Result; +} +//--------------------------------------------------------------------------- +bool TMsgSocket::PacketReady(int Size) +{ + return (WaitingData()>=Size); +} +//--------------------------------------------------------------------------- +int TMsgSocket::Receive(void *Data, int BufSize, int &SizeRecvd) +{ + LastTcpError=0; + if (CanRead(RecvTimeout)) + { + SizeRecvd=recv(FSocket ,(char*)Data ,BufSize ,MSG_NOSIGNAL ); + + if (SizeRecvd>0) // something read (default case) + LastTcpError=0; + else + if (SizeRecvd==0) + LastTcpError = WSAECONNRESET; // Connection reset by Peer + else + LastTcpError=GetLastSocketError(); // we need to know what happened + } + else + LastTcpError = WSAETIMEDOUT; + + if (LastTcpError==WSAECONNRESET) + Connected = false; + + return LastTcpError; +} +//--------------------------------------------------------------------------- +int TMsgSocket::RecvPacket(void *Data, int Size) +{ + int BytesRead; + WaitForData(Size, RecvTimeout); + if (LastTcpError==0) + { + BytesRead=recv(FSocket, (char*)Data, Size, MSG_NOSIGNAL); + if (BytesRead==0) + LastTcpError = WSAECONNRESET; // Connection reset by Peer + else + if (BytesRead<0) + LastTcpError = GetLastSocketError(); + } + else // After the timeout the bytes waiting were less then we expected + if (LastTcpError==WSAETIMEDOUT) + Purge(); + + if (LastTcpError==WSAECONNRESET) + Connected =false; + + return LastTcpError; +} +//--------------------------------------------------------------------------- +int TMsgSocket::PeekPacket(void *Data, int Size) +{ + int BytesRead; + WaitForData(Size, RecvTimeout); + if (LastTcpError==0) + { + BytesRead=recv(FSocket, (char*)Data, Size, MSG_PEEK | MSG_NOSIGNAL ); + if (BytesRead==0) + LastTcpError = WSAECONNRESET; // Connection reset by Peer + else + if (BytesRead<0) + LastTcpError = GetLastSocketError(); + } + else // After the timeout the bytes waiting were less then we expected + if (LastTcpError==WSAETIMEDOUT) + Purge(); + + if (LastTcpError==WSAECONNRESET) + Connected =false; + + return LastTcpError; +} +//--------------------------------------------------------------------------- +bool TMsgSocket::Execute() +{ + return true; +} +//============================================================================== +// PING +//============================================================================== + +static int PingKind; + +#ifdef OS_WINDOWS +// iphlpapi, is loaded dinamically because if this fails we can still try +// to use raw sockets + +static char const *iphlpapi = "\\iphlpapi.dll"; +#pragma pack(1) + +//typedef byte TTxBuffer[40]; +typedef byte TTxBuffer[32]; +#pragma pack() + +typedef HANDLE (__stdcall *pfn_IcmpCreateFile)(); +typedef bool (__stdcall *pfn_IcmpCloseHandle)(HANDLE PingHandle); + +typedef int (__stdcall *pfn_IcmpSendEcho2)( + HANDLE PingHandle, + void *Event, + void *AcpRoutine, + void *AcpContext, + unsigned long DestinationAddress, + void *RequestData, + int RequestSize, + void *not_used, //should be *IP_OPTION_INFORMATION but we don't use it + void *ReplyBuffer, + int ReplySize, + int Timeout +); + +static pfn_IcmpCreateFile IcmpCreateFile; +static pfn_IcmpCloseHandle IcmpCloseHandle; +static pfn_IcmpSendEcho2 IcmpSendEcho2; +static HINSTANCE IcmpDllHandle = 0; +static bool IcmpAvail = false; + +bool IcmpInit() +{ + char iphlppath[MAX_PATH+12]; + + int PathLen = GetSystemDirectoryA(iphlppath, MAX_PATH); + if (PathLen != 0) + { + strcat(iphlppath, iphlpapi); + IcmpDllHandle = LoadLibraryA(iphlppath); + } + else + IcmpDllHandle = 0; + + if (IcmpDllHandle != 0) + { + IcmpCreateFile=(pfn_IcmpCreateFile)GetProcAddress(IcmpDllHandle,"IcmpCreateFile"); + IcmpCloseHandle=(pfn_IcmpCloseHandle)GetProcAddress(IcmpDllHandle,"IcmpCloseHandle"); + IcmpSendEcho2=(pfn_IcmpSendEcho2)GetProcAddress(IcmpDllHandle,"IcmpSendEcho2"); + return (IcmpCreateFile!=NULL) && (IcmpCloseHandle!=NULL) && (IcmpSendEcho2!=NULL); + } + else + return false; +} + +void IcmpDone() +{ + if (IcmpDllHandle!=0) + FreeLibrary(IcmpDllHandle); + IcmpAvail=false; +} +#endif + +//--------------------------------------------------------------------------- +// RAW Socket Pinger +//--------------------------------------------------------------------------- +TRawSocketPinger::TRawSocketPinger() +{ + FSocket =socket(AF_INET, SOCK_RAW, IPPROTO_ICMP); + FId =word(size_t(this)); + FSeq =0; +} +//--------------------------------------------------------------------------- +TRawSocketPinger::~TRawSocketPinger() +{ + if (FSocket!=INVALID_SOCKET) + { + #ifdef OS_WINDOWS + closesocket(FSocket); + #else + close(FSocket); + #endif + FSocket=INVALID_SOCKET; + }; +} +//--------------------------------------------------------------------------- +void TRawSocketPinger::InitPacket() +{ + memset(&IcmpBuffer,0,ICmpBufferSize); + FSeq++; + + SendPacket=PIcmpPacket(pbyte(&IcmpBuffer)+sizeof(TIPHeader)); + SendPacket->Header.ic_type=ICMP_ECHORQ; + SendPacket->Header.ic_code=0; + SendPacket->Header.ic_cksum=0; + SendPacket->Header.ic_id=FId; + SendPacket->Header.ic_seq=FSeq; + + memset(&SendPacket->Data,0,sizeof(SendPacket->Data)); + SendPacket->Header.ic_cksum=PacketChecksum(); +} +//--------------------------------------------------------------------------- +word TRawSocketPinger::PacketChecksum() +{ + word *P = (word*)(SendPacket); + longword Sum = 0; + int c; + for (c = 0; c < int(sizeof(TIcmpPacket) / 2); c++) { + Sum+=*P; + P++; + } + Sum=(Sum >> 16) + (Sum & 0xFFFF); + Sum=Sum+(Sum >> 16); + return word(~Sum); +} +//--------------------------------------------------------------------------- +bool TRawSocketPinger::CanRead(int Timeout) +{ + timeval TimeV; + int64_t x; + fd_set FDset; + + TimeV.tv_usec = (Timeout % 1000) * 1000; + TimeV.tv_sec = Timeout / 1000; + + FD_ZERO(&FDset); + FD_SET(FSocket, &FDset); + + x = select(FSocket + 1, &FDset, NULL, NULL, &TimeV); //<-Ignore this warning in 64bit Visual Studio + if (x==(int)(SOCKET_ERROR)) + x=0; + return (x > 0); +} +//--------------------------------------------------------------------------- +bool TRawSocketPinger::Ping(longword ip_addr, int Timeout) +{ + sockaddr_in LSockAddr; + sockaddr_in RSockAddr; + PIcmpReply Reply; + + if (FSocket==INVALID_SOCKET) + return true; + + // Init packet + InitPacket(); + Reply=PIcmpReply(&IcmpBuffer); + // Init Remote and Local Addresses struct + RSockAddr.sin_family=AF_INET; + RSockAddr.sin_port=0; + RSockAddr.sin_addr.s_addr=ip_addr; + + LSockAddr.sin_family=AF_INET; + LSockAddr.sin_port=0; + LSockAddr.sin_addr.s_addr=inet_addr("0.0.0.0"); + + // Bind to local + if (bind(FSocket, (struct sockaddr*)&LSockAddr, sizeof(sockaddr_in))!=0) + return false; + // Connect to remote (not a really TCP connection, only to setup the socket) + if (connect(FSocket, (struct sockaddr*)&RSockAddr, sizeof(sockaddr_in))!=0) + return false; + // Send ICMP packet + if (send(FSocket, (char*)SendPacket, sizeof(TIcmpPacket), MSG_NOSIGNAL)!=int(sizeof(TIcmpPacket))) + return false; + // Wait for a reply + if (!CanRead(Timeout)) + return false;// time expired + // Get the answer + if (recv(FSocket, (char*)&IcmpBuffer, ICmpBufferSize, MSG_NOSIGNAL)IPH.ip_src==RSockAddr.sin_addr.s_addr) && // the peer is what we are looking for + (Reply->ICmpReply.Header.ic_type==ICMP_ECHORP); // type = reply +} +//--------------------------------------------------------------------------- +// Pinger +//--------------------------------------------------------------------------- +TPinger::TPinger() +{ +} +//--------------------------------------------------------------------------- +TPinger::~TPinger() +{ +} +//--------------------------------------------------------------------------- +bool TPinger::RawPing(longword ip_addr, int Timeout) +{ + PRawSocketPinger RawPinger = new TRawSocketPinger(); + bool Result; + Result=RawPinger->Ping(ip_addr, Timeout); + delete RawPinger; + return Result; +} +//--------------------------------------------------------------------------- +#ifdef OS_WINDOWS +bool TPinger::WinPing(longword ip_addr, int Timeout) +{ + HANDLE PingHandle; + TTxBuffer TxBuffer; + TIcmpBuffer IcmpBuffer; + bool Result; + + PingHandle = IcmpCreateFile(); + if (PingHandle != INVALID_HANDLE_VALUE) + { + memset(&TxBuffer,'\55',sizeof(TTxBuffer)); + Result=(IcmpSendEcho2(PingHandle, NULL, NULL, NULL, ip_addr, + &TxBuffer, sizeof(TxBuffer), NULL, &IcmpBuffer, ICmpBufferSize, Timeout))>0; + IcmpCloseHandle(PingHandle); + return Result; + } + else + return false; +} +#endif +//--------------------------------------------------------------------------- +bool TPinger::Ping(char *Host, int Timeout) +{ + longword Addr; + Addr=inet_addr(Host); + return Ping(Addr, Timeout); +} +//--------------------------------------------------------------------------- +bool TPinger::Ping(longword ip_addr, int Timeout) +{ +#ifdef OS_WINDOWS + if (PingKind==pkWinHelper) + return WinPing(ip_addr, Timeout); + else +#endif + if (PingKind==pkRawSocket) + return RawPing(ip_addr, Timeout); + else + return true; // we still need to continue +} +//--------------------------------------------------------------------------- +// Checks if raw sockets are allowed +//--------------------------------------------------------------------------- +bool RawSocketsCheck() +{ + socket_t RawSocket; + bool Result; + RawSocket = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP); + + Result=RawSocket != INVALID_SOCKET; + if (Result) + #ifdef OS_WINDOWS + closesocket(RawSocket); + #else + close(RawSocket); + #endif + + return Result; +} +//--------------------------------------------------------------------------- +// Sockets init +// - Winsock Startup (Windows) +// - ICMP Helper Load (Windows) +// - Check for raw socket (Unix or Windows if ICMP load failed) +//--------------------------------------------------------------------------- +SocketsLayer::SocketsLayer() +{ +#ifdef OS_WINDOWS + timeBeginPeriod(1); // it's not strictly related to socket but here is a nice place + WSAStartup(0x202,&wsaData); + if (IcmpInit()) + PingKind=pkWinHelper; + else +#endif + if (RawSocketsCheck()) + PingKind=pkRawSocket; + else + PingKind=pkCannotPing; +} + +SocketsLayer::~SocketsLayer() +{ +#ifdef OS_WINDOWS + IcmpDone(); + WSACleanup(); + timeEndPeriod(1); +#endif +} + + diff --git a/core/src/drivers/plugins/native/s7comm/snap7/sys/snap_msgsock.h b/core/src/drivers/plugins/native/s7comm/snap7/sys/snap_msgsock.h new file mode 100644 index 00000000..d9826ba9 --- /dev/null +++ b/core/src/drivers/plugins/native/s7comm/snap7/sys/snap_msgsock.h @@ -0,0 +1,339 @@ +/*=============================================================================| +| PROJECT SNAP7 1.4.3 | +|==============================================================================| +| Copyright (C) 2013, 2025 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/ | +|=============================================================================*/ +#ifndef snap_msgsock_h +#define snap_msgsock_h +//--------------------------------------------------------------------------- +#include "snap_platform.h" +#include "snap_sysutils.h" +//---------------------------------------------------------------------------- +#if defined(OS_WINDOWS) || defined (OS_SOLARIS) || defined(OS_OSX) +# define MSG_NOSIGNAL 0 +#endif +//---------------------------------------------------------------------------- +// Non blocking connection to avoid root priviledges under UNIX +// i.e. raw socket pinger is not more used. +// Thanks to Rolf Stalder that made it ;) +//---------------------------------------------------------------------------- +#ifdef PLATFORM_UNIX + #define NON_BLOCKING_CONNECT +#endif +#ifdef NON_BLOCKING_CONNECT + #include +#endif +//---------------------------------------------------------------------------- +/* + In Windows sizeof socket varies depending of the platform : + win32 -> sizeof(SOCKET) = 4 + win64 -> sizeof(SOCKET) = 8 + + Even though sizeof(SOCKET) is 8, should be safe to cast it to int, because + the value constitutes an index in per-process table of limited size + and not a real pointer. + + Other Os define the socket as int regardless of the processor. + + We want to sleep peacefully, so it's better to define a portable socket. +*/ + +#ifdef OS_WINDOWS +typedef SOCKET socket_t; +#else +typedef int socket_t; +#endif + +//---------------------------------------------------------------------------- +#define SD_RECEIVE 0x00 +#define SD_SEND 0x01 +#define SD_BOTH 0x02 +#define MaxPacketSize 65536 + +//---------------------------------------------------------------------------- +// For other platform we need to re-define next constants +#if defined(PLATFORM_UNIX) || defined(OS_OSX) + +#define INVALID_SOCKET (socket_t)(~0) +#define SOCKET_ERROR (-1) + +#define WSAEINTR EINTR +#define WSAEBADF EBADF +#define WSAEACCES EACCES +#define WSAEFAULT EFAULT +#define WSAEINVAL EINVAL +#define WSAEMFILE EMFILE +#define WSAEWOULDBLOCK EWOULDBLOCK +#define WSAEINPROGRESS EINPROGRESS +#define WSAEALREADY EALREADY +#define WSAENOTSOCK ENOTSOCK +#define WSAEDESTADDRREQ EDESTADDRREQ +#define WSAEMSGSIZE EMSGSIZE +#define WSAEPROTOTYPE EPROTOTYPE +#define WSAENOPROTOOPT ENOPROTOOPT +#define WSAEPROTONOSUPPORT EPROTONOSUPPORT +#define WSAESOCKTNOSUPPORT ESOCKTNOSUPPORT +#define WSAEOPNOTSUPP EOPNOTSUPP +#define WSAEPFNOSUPPORT EPFNOSUPPORT +#define WSAEAFNOSUPPORT EAFNOSUPPORT +#define WSAEADDRINUSE EADDRINUSE +#define WSAEADDRNOTAVAIL EADDRNOTAVAIL +#define WSAENETDOWN ENETDOWN +#define WSAENETUNREACH ENETUNREACH +#define WSAENETRESET ENETRESET +#define WSAECONNABORTED ECONNABORTED +#define WSAECONNRESET ECONNRESET +#define WSAENOBUFS ENOBUFS +#define WSAEISCONN EISCONN +#define WSAENOTCONN ENOTCONN +#define WSAESHUTDOWN ESHUTDOWN +#define WSAETOOMANYREFS ETOOMANYREFS +#define WSAETIMEDOUT ETIMEDOUT +#define WSAECONNREFUSED ECONNREFUSED +#define WSAELOOP ELOOP +#define WSAENAMETOOLONG ENAMETOOLONG +#define WSAEHOSTDOWN EHOSTDOWN +#define WSAEHOSTUNREACH EHOSTUNREACH +#define WSAENOTEMPTY ENOTEMPTY +#define WSAEUSERS EUSERS +#define WSAEDQUOT EDQUOT +#define WSAESTALE ESTALE +#define WSAEREMOTE EREMOTE +#endif + +#define WSAEINVALIDADDRESS 12001 + +#define ICmpBufferSize 4096 +typedef byte TIcmpBuffer[ICmpBufferSize]; + +// Ping result +#define PR_CANNOT_PERFORM -1 // cannot ping : + // unix : no root rights or SUID flag set to + // open raw sockets + // windows : neither helper DLL found nor raw + // sockets can be opened (no administrator rights) + // In this case the execution continues whitout + // the benefit of the smart-connect. + +#define PR_SUCCESS 0 // Host found +#define PR_ERROR 1 // Ping Error, Ping was performed but ... + // - host didn't replied (not found) + // - routing error + // - TTL expired + // - ... all other icmp error that we don't need + // to know. + +// Ping Kind +#define pkCannotPing 1 // see PR_CANNOT_PERFORM comments +#define pkWinHelper 2 // use iphlpapi.dll (only windows) +#define pkRawSocket 3 // use raw sockets (unix/windows) + +const byte ICMP_ECHORP = 0; // ECHO Reply +const byte ICMP_ECHORQ = 8; // ECHO Request +//--------------------------------------------------------------------------- +// RAW SOCKET PING STRUCTS +//--------------------------------------------------------------------------- +#pragma pack(1) +typedef struct{ + byte ip_hl_v; + byte ip_tos; + word ip_len; + word ip_id ; + word ip_off; + byte ip_ttl; + byte ip_p; + word ip_sum; + longword ip_src; + longword ip_dst; +}TIPHeader; + +typedef struct{ + byte ic_type; // Type of message + byte ic_code; // Code + word ic_cksum; // 16 bit checksum + word ic_id; // ID (ic1 : ipv4) + word ic_seq; // Sequence +}TIcmpHeader; + +typedef struct{ + TIcmpHeader Header; + byte Data[32]; // use the well known default +}TIcmpPacket, *PIcmpPacket; + +typedef struct{ + TIPHeader IPH; + TIcmpPacket ICmpReply; +}TIcmpReply, *PIcmpReply; +#pragma pack() + +//--------------------------------------------------------------------------- +class TRawSocketPinger +{ +private: + socket_t FSocket; + PIcmpPacket SendPacket; + TIcmpBuffer IcmpBuffer; + word FId, FSeq; + void InitPacket(); + word PacketChecksum(); + bool CanRead(int Timeout); +public: + bool Ping(longword ip_addr, int Timeout); + TRawSocketPinger(); + ~TRawSocketPinger(); +}; +typedef TRawSocketPinger *PRawSocketPinger; +//--------------------------------------------------------------------------- +class TPinger +{ +private: + PRawSocketPinger RawPinger; + bool RawAvail; +#ifdef OS_WINDOWS + bool WinPing(longword ip_addr, int Timeout); +#endif + bool RawPing(longword ip_addr, int Timeout); +public: + TPinger(); + ~TPinger(); + bool Ping(char *Host, int Timeout); + bool Ping(longword ip_addr, int Timeout); +}; +typedef TPinger *PPinger; +//--------------------------------------------------------------------------- +class TSnapBase // base class endian-aware +{ +private: + bool LittleEndian; +protected: + longword SwapDWord(longword Value); + word SwapWord(word Value); +public: + TSnapBase(); +}; +//--------------------------------------------------------------------------- +class TMsgSocket : public TSnapBase +{ +private: + PPinger Pinger; + int GetLastSocketError(); + int SockCheck(int SockResult); + void DestroySocket(); + void SetSocketOptions(); + bool CanWrite(int Timeout); + void GetLocal(); + void GetRemote(); + void SetSin(sockaddr_in &sin, char *Address, u_short Port); + void GetSin(sockaddr_in sin, char *Address, u_short &Port); +protected: + socket_t FSocket; + sockaddr_in LocalSin; + sockaddr_in RemoteSin; + //-------------------------------------------------------------------------- + // low level socket + void CreateSocket(); + // Called when a socket is assigned externally + void GotSocket(); + // Returns how many bytes are ready to be read in the winsock buffer + int WaitingData(); + // Waits until there at least "size" bytes ready to be read or until receive timeout occurs + int WaitForData(int Size, int Timeout); + // Clear socket input buffer + void Purge(); +public: + longword ClientHandle; + longword LocalBind; + // Coordinates Address:Port + char LocalAddress[16]; + char RemoteAddress[16]; + word LocalPort; + word RemotePort; + // "speed" of the socket listener (used server-side) + int WorkInterval; + // Timeouts : 3 different values for fine tuning. + // Send timeout should be small since with work with small packets and TCP_NO_DELAY + // option, so we don't expect "time to wait". + // Recv timeout depends of equipment's processing time : we send a packet, the equipment + // processes the message, finally it sends the answer. In any case Recv timeout > Send Timeout. + // PingTimeout is the maximum time interval during which we expect that the PLC answers. + // By default is 750 ms, increase it if there are many switch/repeaters. + int PingTimeout; + int RecvTimeout; + int SendTimeout; + //int ConnTimeout; + // Output : Last operation error + int LastTcpError; + // Output : Connected to the remote Host/Peer/Client + bool Connected; + //-------------------------------------------------------------------------- + TMsgSocket(); + virtual ~TMsgSocket(); + // Returns true if "something" can be read during the Timeout interval.. + bool CanRead(int Timeout); + // Connects to a peer (using RemoteAddress and RemotePort) + int SckConnect(); // (client-side) + // Disconnects from a peer (gracefully) + void SckDisconnect(); + // Disconnects RAW + void ForceClose(); + // Binds to a local adapter (using LocalAddress and LocalPort) (server-side) + int SckBind(); + // Listens for an incoming connection (server-side) + int SckListen(); + // Set an external socket reference (tipically from a listener) + void SetSocket(socket_t s); + // Accepts an incoming connection returning a socket descriptor (server-side) + socket_t SckAccept(); + // Pings the peer before connecting + bool Ping(char *Host); + bool Ping(sockaddr_in Addr); + // Sends a packet + int SendPacket(void *Data, int Size); + // Returns true if a Packet at least of "Size" bytes is ready to be read + bool PacketReady(int Size); + // Receives everything + int Receive(void *Data, int BufSize, int & SizeRecvd); + // Receives a packet of size specified. + int RecvPacket(void *Data, int Size); + // Peeks a packet of size specified without extract it from the socket queue + int PeekPacket(void *Data, int Size); + virtual bool Execute(); +}; + +typedef TMsgSocket *PMsgSocket; +//--------------------------------------------------------------------------- +void Msg_CloseSocket(socket_t FSocket); +longword Msg_GetSockAddr(socket_t FSocket); +//--------------------------------------------------------------------------- +class SocketsLayer +{ +private: +#ifdef OS_WINDOWS + WSADATA wsaData; +#endif +public: + SocketsLayer(); + ~SocketsLayer(); +}; + +#endif // snap_msgsock_h diff --git a/core/src/drivers/plugins/native/s7comm/snap7/sys/snap_platform.h b/core/src/drivers/plugins/native/s7comm/snap7/sys/snap_platform.h new file mode 100644 index 00000000..53e8a2f8 --- /dev/null +++ b/core/src/drivers/plugins/native/s7comm/snap7/sys/snap_platform.h @@ -0,0 +1,130 @@ +/*=============================================================================| +| PROJECT SNAP7 1.4.3 | +|==============================================================================| +| Copyright (C) 2013, 2025 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/ | +|=============================================================================*/ +#ifndef snap_platform_h +#define snap_platform_h +//--------------------------------------------------------------------------- +#if defined (_WIN32)|| defined(_WIN64)|| defined(__WIN32__) || defined(__WINDOWS__) +# define OS_WINDOWS +#endif + +// Visual Studio needs this to use the correct time_t size +#if defined (_WIN32) && !defined(_WIN64) && !defined(_EMBEDDING_VS2013UP) + # define _USE_32BIT_TIME_T +#endif + +// Linux, BSD and Solaris define "unix", OSX doesn't, even though it derives from BSD +#if defined(unix) || defined(__unix__) || defined(__unix) +# define PLATFORM_UNIX +#endif + +#if BSD>=0 +# define OS_BSD +#endif + +#if __APPLE__ +# define OS_OSX +#endif + +#if defined(__SVR4) || defined(__svr4__) +# define OS_SOLARIS +// Thanks to Rolf Stalder now it's possible to use pthreads also for Solaris +// In any case the Solaris native threads model is still present and can be +// used uncommenting the #define line below. +# undef OS_SOLARIS_NATIVE_THREADS +// # define OS_SOLARIS_NATIVE_THREADS +#endif + +#if defined(PLATFORM_UNIX) +# include +# include +# if defined(_POSIX_VERSION) +# define POSIX +# endif +#endif + +#ifdef OS_OSX +# include +#endif + +#if (!defined (OS_WINDOWS)) && (!defined(PLATFORM_UNIX)) && (!defined(OS_BSD)) && (!defined(OS_OSX)) +# error platform still unsupported (please add it yourself and report ;-) +#endif + +#include +#include +#include +#include + +#ifdef OS_WINDOWS +# define WIN32_LEAN_AND_MEAN +# include +# include +# include +#endif + +#ifdef OS_SOLARIS +# include +# include +# include +#endif + +#if defined(PLATFORM_UNIX) || defined(OS_OSX) +# include +# include +# include +# include +# include +# include +# include +#endif + +#ifdef OS_WINDOWS +# define EXPORTSPEC extern "C" __declspec ( dllexport ) +# define S7API __stdcall +#else +# define EXPORTSPEC extern "C" +# define S7API +#endif + +// Exact length types regardless of platform/processor +// We absolute need of them, all structs have an exact size that +// must be the same across the processor used 32/64 bit + +// *Use them* if you change/expand the code and avoid long, u_long and so on... + +typedef uint8_t byte; +typedef uint16_t word; +typedef uint32_t longword; +typedef byte *pbyte; +typedef word *pword; +typedef uintptr_t snap_obj; // multi platform/processor object reference + +#ifndef OS_WINDOWS +# define INFINITE 0XFFFFFFFF +#endif + + +#endif // snap_platform_h diff --git a/core/src/drivers/plugins/native/s7comm/snap7/sys/snap_sysutils.cpp b/core/src/drivers/plugins/native/s7comm/snap7/sys/snap_sysutils.cpp new file mode 100644 index 00000000..92b6f7c8 --- /dev/null +++ b/core/src/drivers/plugins/native/s7comm/snap7/sys/snap_sysutils.cpp @@ -0,0 +1,73 @@ +/*=============================================================================| +| PROJECT SNAP7 1.4.3 | +|==============================================================================| +| Copyright (C) 2013, 2025 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/ | +|=============================================================================*/ + +#include "snap_sysutils.h" + +#ifdef OS_OSX +int clock_gettime(int clk_id, struct timespec* t) +{ + struct timeval now; + int rv = gettimeofday(&now, NULL); + if (rv) return rv; + t->tv_sec = now.tv_sec; + t->tv_nsec = now.tv_usec * 1000; + return 0; +} +#endif + +//--------------------------------------------------------------------------- +longword SysGetTick() +{ +#ifdef OS_WINDOWS + return timeGetTime(); +#else + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return (longword) (ts.tv_sec * 1000) + (ts.tv_nsec / 1000000); +#endif +} +//--------------------------------------------------------------------------- +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) * 1000000); + nanosleep(&ts, (struct timespec *)0); +#endif +} +//--------------------------------------------------------------------------- +longword DeltaTime(longword &Elapsed) +{ + longword TheTime; + TheTime=SysGetTick(); + // Checks for rollover + if (TheTimeDestroying) + { + try + { + if (!WorkerSocket->Execute()) // False -> End of Activities + SelfClose = true; + } catch (...) + { + Exception = true; + } + }; + if (!FServer->Destroying) + { + // Exception detected during Worker activity + if (Exception) + { + WorkerSocket->ForceClose(); + FServer->DoEvent(WorkerSocket->ClientHandle, evcClientException, 0, 0, 0, 0, 0); + } + else + if (SelfClose) + { + FServer->DoEvent(WorkerSocket->ClientHandle, evcClientDisconnected, 0, 0, 0, 0, 0); + } + else + FServer->DoEvent(WorkerSocket->ClientHandle, evcClientTerminated, 0, 0, 0, 0, 0); + } + delete WorkerSocket; + // Delete reference from list + FServer->Delete(Index); +} +//--------------------------------------------------------------------------- +// LISTENER THREAD +//--------------------------------------------------------------------------- + +TMsgListenerThread::TMsgListenerThread(TMsgSocket *Listener, TCustomMsgServer *Server) +{ + FServer = Server; + FListener = Listener; + FreeOnTerminate = false; +} +//--------------------------------------------------------------------------- + +void TMsgListenerThread::Execute() +{ + socket_t Sock; + bool Valid; + + while (!Terminated) + { + if (FListener->CanRead(FListener->WorkInterval)) + { + Sock = FListener->SckAccept(); // in any case we must accept + Valid = Sock != INVALID_SOCKET; + // check if we are not destroying + if ((!Terminated) && (!FServer->Destroying)) + { + if (Valid) + FServer->Incoming(Sock); + } + else + if (Valid) + Msg_CloseSocket(Sock); + }; + } +} +//--------------------------------------------------------------------------- +// TCP SERVER +//--------------------------------------------------------------------------- +TCustomMsgServer::TCustomMsgServer() +{ + strcpy(FLocalAddress, "0.0.0.0"); + CSList = new TSnapCriticalSection(); + CSEvent = new TSnapCriticalSection(); + FEventQueue = new TMsgEventQueue(MaxEvents, sizeof (TSrvEvent)); + memset(Workers, 0, sizeof (Workers)); + for (int i = 0; i < MaxWorkers; i++) + Workers[i] = NULL; + Status = SrvStopped; + EventMask = 0xFFFFFFFF; + LogMask = 0xFFFFFFFF; + Destroying = false; + FLastError = 0; + ClientsCount = 0; + LocalBind = 0; + MaxClients = MaxWorkers; + OnEvent = NULL; +} +//--------------------------------------------------------------------------- +TCustomMsgServer::~TCustomMsgServer() +{ + Destroying = true; + Stop(); + OnEvent = NULL; + delete CSList; + delete CSEvent; + delete FEventQueue; +} +//--------------------------------------------------------------------------- +void TCustomMsgServer::LockList() +{ + CSList->Enter(); +} +//--------------------------------------------------------------------------- +void TCustomMsgServer::UnlockList() +{ + CSList->Leave(); +} +//--------------------------------------------------------------------------- +int TCustomMsgServer::FirstFree() +{ + int i; + for (i = 0; i < MaxWorkers; i++) + { + if (Workers[i] == 0) + return i; + } + return -1; +} +//--------------------------------------------------------------------------- + +int TCustomMsgServer::StartListener() +{ + int Result; + // Creates the listener + SockListener = new TMsgSocket(); + strncpy(SockListener->LocalAddress, FLocalAddress, 16); + SockListener->LocalPort = LocalPort; + // Binds + Result = SockListener->SckBind(); + if (Result == 0) + { + LocalBind = SockListener->LocalBind; + // Listen + Result = SockListener->SckListen(); + if (Result == 0) + { + // Creates the Listener thread + ServerThread = new TMsgListenerThread(SockListener, this); + ServerThread->Start(); + } + else + delete SockListener; + } + else + delete SockListener; + + return Result; +} +//--------------------------------------------------------------------------- +void TCustomMsgServer::TerminateAll() +{ + int c; + longword Elapsed; + bool Timeout; + + if (ClientsCount > 0) + { + for (c = 0; c < MaxWorkers; c++) + { + if (Workers[c] != 0) + PMsgWorkerThread(Workers[c])->Terminate(); + } + // Wait for closing + Elapsed = SysGetTick(); + Timeout = false; + while (!Timeout && (ClientsCount > 0)) + { + Timeout = DeltaTime(Elapsed) > WkTimeout; + if (!Timeout) + SysSleep(100); + }; + if (ClientsCount > 0) + KillAll(); // one o more threads are hanged + ClientsCount = 0; + } +} +//--------------------------------------------------------------------------- +void TCustomMsgServer::KillAll() +{ + int c, cnt = 0; + LockList(); + for (c = 0; c < MaxWorkers; c++) + { + if (Workers[c] != 0) + try + { + PMsgWorkerThread(Workers[c])->Kill(); + PMsgWorkerThread(Workers[c])->WorkerSocket->ForceClose(); + delete PMsgWorkerThread(Workers[c]); + Workers[c] = 0; + cnt++; + } catch (...) + { + }; + } + UnlockList(); + DoEvent(0, evcClientsDropped, 0, cnt, 0, 0, 0); +} +//--------------------------------------------------------------------------- +bool TCustomMsgServer::CanAccept(socket_t Socket) +{ + return ((MaxClients == 0) || (ClientsCount < MaxClients)); +} +//--------------------------------------------------------------------------- +PWorkerSocket TCustomMsgServer::CreateWorkerSocket(socket_t Sock) +{ + PWorkerSocket Result; + // Creates a funny default class : a tcp echo worker + Result = new TEcoTcpWorker(); + Result->SetSocket(Sock); + return Result; +} +//--------------------------------------------------------------------------- +void TCustomMsgServer::DoEvent(int Sender, longword Code, word RetCode, word Param1, word Param2, word Param3, word Param4) +{ + TSrvEvent SrvEvent; + bool GoLog = (Code & LogMask) != 0; + bool GoEvent = (Code & EventMask) != 0; + + if (!Destroying && (GoLog || GoEvent)) + { + CSEvent->Enter(); + + time(&SrvEvent.EvtTime); + SrvEvent.EvtSender = Sender; + SrvEvent.EvtCode = Code; + SrvEvent.EvtRetCode = RetCode; + SrvEvent.EvtParam1 = Param1; + SrvEvent.EvtParam2 = Param2; + SrvEvent.EvtParam3 = Param3; + SrvEvent.EvtParam4 = Param4; + + if (GoEvent && (OnEvent != NULL)) + try + { // callback is outside here, we have to shield it + OnEvent(FUsrPtr, &SrvEvent, sizeof (TSrvEvent)); + } catch (...) + { + }; + + if (GoLog) + FEventQueue->Insert(&SrvEvent); + + CSEvent->Leave(); + }; +} +//--------------------------------------------------------------------------- +void TCustomMsgServer::Delete(int Index) +{ + LockList(); + Workers[Index] = 0; + ClientsCount--; + UnlockList(); +} +//--------------------------------------------------------------------------- +void TCustomMsgServer::Incoming(socket_t Sock) +{ + int idx; + PWorkerSocket WorkerSocket; + longword ClientHandle = Msg_GetSockAddr(Sock); + + if (CanAccept(Sock)) + { + LockList(); + // First position available in the thread buffer + idx = FirstFree(); + if (idx >= 0) + { + // Creates the Worker and assigns it the connected socket + WorkerSocket = CreateWorkerSocket(Sock); + // Creates the Worker thread + Workers[idx] = new TMsgWorkerThread(WorkerSocket, this); + PMsgWorkerThread(Workers[idx])->Index = idx; + // Update the number + ClientsCount++; + // And Starts the worker + PMsgWorkerThread(Workers[idx])->Start(); + DoEvent(WorkerSocket->ClientHandle, evcClientAdded, 0, 0, 0, 0, 0); + } + else + { + DoEvent(ClientHandle, evcClientNoRoom, 0, 0, 0, 0, 0); + Msg_CloseSocket(Sock); + } + UnlockList(); + } + else + { + Msg_CloseSocket(Sock); + DoEvent(ClientHandle, evcClientRejected, 0, 0, 0, 0, 0); + }; +} +//--------------------------------------------------------------------------- +int TCustomMsgServer::Start() +{ + int Result = 0; + if (Status != SrvRunning) + { + Result = StartListener(); + if (Result != 0) + { + DoEvent(0, evcListenerCannotStart, Result, 0, 0, 0, 0); + Status = SrvError; + } + else + { + DoEvent(0, evcServerStarted, SockListener->ClientHandle, LocalPort, 0, 0, 0); + Status = SrvRunning; + }; + }; + FLastError = Result; + return Result; +} +//--------------------------------------------------------------------------- +int TCustomMsgServer::StartTo(const char *Address, word Port) +{ + strncpy(FLocalAddress, Address, 16); + LocalPort = Port; + return Start(); +} +//--------------------------------------------------------------------------- +void TCustomMsgServer::Stop() +{ + if (Status == SrvRunning) + { + // Kills the listener thread + ServerThread->Terminate(); + if (ServerThread->WaitFor(ThTimeout) != WAIT_OBJECT_0) + ServerThread->Kill(); + delete ServerThread; + // Kills the listener + delete SockListener; + + // Terminate all client threads + TerminateAll(); + + Status = SrvStopped; + LocalBind = 0; + DoEvent(0, evcServerStopped, 0, 0, 0, 0, 0); + }; + FLastError = 0; +} +//--------------------------------------------------------------------------- +int TCustomMsgServer::SetEventsCallBack(pfn_SrvCallBack PCallBack, void *UsrPtr) +{ + OnEvent = PCallBack; + FUsrPtr = UsrPtr; + return 0; +} +//--------------------------------------------------------------------------- +bool TCustomMsgServer::PickEvent(void *pEvent) +{ + try + { + return FEventQueue->Extract(pEvent); + } catch (...) + { + return false; + }; +} +//--------------------------------------------------------------------------- +bool TCustomMsgServer::EventEmpty() +{ + return FEventQueue->Empty(); +} +//--------------------------------------------------------------------------- +void TCustomMsgServer::EventsFlush() +{ + CSEvent->Enter(); + FEventQueue->Flush(); + CSEvent->Leave(); +} +//--------------------------------------------------------------------------- + + diff --git a/core/src/drivers/plugins/native/s7comm/snap7/sys/snap_tcpsrvr.h b/core/src/drivers/plugins/native/s7comm/snap7/sys/snap_tcpsrvr.h new file mode 100644 index 00000000..7264b766 --- /dev/null +++ b/core/src/drivers/plugins/native/s7comm/snap7/sys/snap_tcpsrvr.h @@ -0,0 +1,247 @@ +/*=============================================================================| +| PROJECT SNAP7 1.4.3 | +|==============================================================================| +| Copyright (C) 2013, 2025 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/ | +|=============================================================================*/ +#ifndef snap_tcpsrvr_h +#define snap_tcpsrvr_h +//--------------------------------------------------------------------------- +#include "snap_msgsock.h" +#include "snap_threads.h" +//--------------------------------------------------------------------------- + +#define MaxWorkers 1024 +#define MaxEvents 1500 + +const int SrvStopped = 0; +const int SrvRunning = 1; +const int SrvError = 2; + +const longword evcServerStarted = 0x00000001; +const longword evcServerStopped = 0x00000002; +const longword evcListenerCannotStart = 0x00000004; +const longword evcClientAdded = 0x00000008; +const longword evcClientRejected = 0x00000010; +const longword evcClientNoRoom = 0x00000020; +const longword evcClientException = 0x00000040; +const longword evcClientDisconnected = 0x00000080; +const longword evcClientTerminated = 0x00000100; +const longword evcClientsDropped = 0x00000200; +const longword evcReserved_00000400 = 0x00000400; +const longword evcReserved_00000800 = 0x00000800; +const longword evcReserved_00001000 = 0x00001000; +const longword evcReserved_00002000 = 0x00002000; +const longword evcReserved_00004000 = 0x00004000; +const longword evcReserved_00008000 = 0x00008000; + +// Server Interface errors +const longword errSrvBase = 0x0000FFFF; +const longword errSrvMask = 0xFFFF0000; +const longword errSrvCannotStart = 0x00100000; + +const longword ThTimeout = 2000; // Thread timeout +const longword WkTimeout = 3000; // Workers termination timeout + +#pragma pack(1) + +typedef struct{ + time_t EvtTime; // Timestamp + int EvtSender; // Sender + longword EvtCode; // Event code + word EvtRetCode; // Event result + word EvtParam1; // Param 1 (if available) + word EvtParam2; // Param 2 (if available) + word EvtParam3; // Param 3 (if available) + word EvtParam4; // Param 4 (if available) +}TSrvEvent, *PSrvEvent; + +extern "C" +{ +typedef void (S7API *pfn_SrvCallBack)(void * usrPtr, PSrvEvent PEvent, int Size); +} +#pragma pack() + +//--------------------------------------------------------------------------- +// EVENTS QUEUE +//--------------------------------------------------------------------------- +class TMsgEventQueue +{ +private: + int IndexIn; // <-- insert index + int IndexOut; // --> extract index + int Max; // Buffer upper bound [0..Max] + int FCapacity; // Queue capacity + pbyte Buffer; + int FBlockSize; +public: + TMsgEventQueue(const int Capacity, const int BlockSize); + ~TMsgEventQueue(); + void Flush(); + void Insert(void *lpdata); + bool Extract(void *lpdata); + bool Empty(); + bool Full(); +}; +typedef TMsgEventQueue *PMsgEventQueue; + +//--------------------------------------------------------------------------- +// WORKER THREAD +//--------------------------------------------------------------------------- +class TCustomMsgServer; // forward declaration + +// It's created when connection is accepted, it will interface with the client. +class TMsgWorkerThread : public TSnapThread +{ +private: + TCustomMsgServer *FServer; +protected: + TMsgSocket *WorkerSocket; +public: + int Index; + friend class TCustomMsgServer; + TMsgWorkerThread(TMsgSocket *Socket, TCustomMsgServer *Server); + void Execute(); +}; +typedef TMsgWorkerThread *PMsgWorkerThread; + +//--------------------------------------------------------------------------- +// LISTENER THREAD +//--------------------------------------------------------------------------- +// It listens for incoming connection. +class TMsgListenerThread : public TSnapThread +{ +private: + TMsgSocket *FListener; + TCustomMsgServer *FServer; +public: + TMsgListenerThread(TMsgSocket *Listener, TCustomMsgServer *Server); + void Execute(); +}; +typedef TMsgListenerThread *PMsgListenerThread; + +//--------------------------------------------------------------------------- +// TCP SERVER +//--------------------------------------------------------------------------- +typedef TMsgSocket *PWorkerSocket; + +class TCustomMsgServer +{ +private: + int FLastError; + char FLocalAddress[16]; + // Socket listener + PMsgSocket SockListener; + // Server listener + PMsgListenerThread ServerThread; + // Critical section to lock Workers list activities + PSnapCriticalSection CSList; + // Event queue + PMsgEventQueue FEventQueue; + // Callback related + pfn_SrvCallBack OnEvent; + void *FUsrPtr; + // private methods + int StartListener(); + void LockList(); + void UnlockList(); + int FirstFree(); +protected: + bool Destroying; + // Critical section to lock Event activities + PSnapCriticalSection CSEvent; + // Workers list + void *Workers[MaxWorkers]; + // Terminates all worker threads + virtual void TerminateAll(); + // Kills all worker threads that are unresponsive + void KillAll(); + // if (true the connection is accepted, otherwise the connection + // is closed gracefully + virtual bool CanAccept(socket_t Socket); + // Returns the class of the worker socket, override it for real servers + virtual PWorkerSocket CreateWorkerSocket(socket_t Sock); + // Handles the event + virtual void DoEvent(int Sender, longword Code, word RetCode, word Param1, + word Param2, word Param3, word Param4); + // Delete the worker from the list (It's invoked by Worker Thread) + void Delete(int Index); + // Incoming connection (It's invoked by ServerThread, the listener) + virtual void Incoming(socket_t Sock); +public: + friend class TMsgWorkerThread; + friend class TMsgListenerThread; + word LocalPort; + longword LocalBind; + longword LogMask; + longword EventMask; + int Status; + int ClientsCount; + int MaxClients; + TCustomMsgServer(); + virtual ~TCustomMsgServer(); + // Starts the server + int Start(); + int StartTo(const char *Address, word Port); + // Stops the server + void Stop(); + // Sets Event callback + int SetEventsCallBack(pfn_SrvCallBack PCallBack, void *UsrPtr); + // Pick an event from the circular queue + bool PickEvent(void *pEvent); + // Returns true if (the Event queue is empty + bool EventEmpty(); + // Flushes Event queue + void EventsFlush(); +}; + +//--------------------------------------------------------------------------- +// TCP WORKER +//--------------------------------------------------------------------------- +// Default worker class, a simply tcp echo to test the connection and +// data I/O to use the server outside the project +class TEcoTcpWorker : public TMsgSocket +{ +public: + bool Execute() + { + byte Buffer[4096]; + int Size; + + if (CanRead(WorkInterval)) // Small time to avoid time wait during the close + { + Receive(&Buffer,sizeof(Buffer),Size); + if ((LastTcpError==0) && (Size>0)) + { + SendPacket(&Buffer,Size); + return LastTcpError==0; + } + else + return false; + } + else + return true; + }; +}; + +//--------------------------------------------------------------------------- +#endif // snap_tcpsrvr_h diff --git a/core/src/drivers/plugins/native/s7comm/snap7/sys/snap_threads.cpp b/core/src/drivers/plugins/native/s7comm/snap7/sys/snap_threads.cpp new file mode 100644 index 00000000..f1120b59 --- /dev/null +++ b/core/src/drivers/plugins/native/s7comm/snap7/sys/snap_threads.cpp @@ -0,0 +1,162 @@ +/*=============================================================================| +| PROJECT SNAP7 1.4.3 | +|==============================================================================| +| Copyright (C) 2013, 2025 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/ | +|=============================================================================*/ + +#include "snap_threads.h" +//--------------------------------------------------------------------------- +#ifdef OS_WINDOWS +DWORD WINAPI ThreadProc(LPVOID param) +#else + +void* ThreadProc(void* param) +#endif +{ + PSnapThread Thread; + // Unix but not Solaris +#if (defined(POSIX) || defined(OS_OSX)) && (!defined(OS_SOLARIS_NATIVE_THREADS)) + int last_type, last_state; + pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, &last_type); + pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &last_state); +#endif + Thread = PSnapThread(param); + + if (!Thread->Terminated) + try + { + Thread->Execute(); + } catch (...) + { + }; + Thread->Closed = true; + if (Thread->FreeOnTerminate) + { + delete Thread; + }; +#ifdef OS_WINDOWS + ExitThread(0); +#endif +#if defined(POSIX) && (!defined(OS_SOLARIS_NATIVE_THREADS)) + pthread_exit((void*)0); +#endif +#if defined(OS_OSX) + pthread_exit((void*)0); +#endif +#ifdef OS_SOLARIS_NATIVE_THREADS + thr_exit((void*)0); +#endif + return 0; // never reach, only to avoid compiler warning +} +//--------------------------------------------------------------------------- +TSnapThread::TSnapThread() +{ + Started = false; + Closed=false; + Terminated = false; + FreeOnTerminate = false; +} +//--------------------------------------------------------------------------- +TSnapThread::~TSnapThread() +{ + if (Started && !Closed) + { + Terminate(); + Join(); + }; +#ifdef OS_WINDOWS + if (Started) + CloseHandle(th); +#endif +} +//--------------------------------------------------------------------------- +void TSnapThread::ThreadCreate() +{ +#ifdef OS_WINDOWS + th = CreateThread(0, 0, ThreadProc, this, 0, 0); +#endif +#if defined(POSIX) && (!defined(OS_SOLARIS_NATIVE_THREADS)) + pthread_attr_t a; + pthread_attr_init(&a); + pthread_attr_setdetachstate(&a, PTHREAD_CREATE_DETACHED); + pthread_create(&th, &a, &ThreadProc, this); +#endif +#if defined(OS_OSX) + pthread_create(&th, 0, &ThreadProc, this); +#endif +#ifdef OS_SOLARIS_NATIVE_THREADS + thr_create(0, // default stack base + 0, // default stack size + &ThreadProc, // Thread routine + this, // argument + 0, + &th); +#endif +} +//--------------------------------------------------------------------------- +void TSnapThread::Start() +{ + if (!Started) + { + ThreadCreate(); + Started = true; + } +} +//--------------------------------------------------------------------------- +void TSnapThread::Terminate() +{ + Terminated = true; +} +//--------------------------------------------------------------------------- +void TSnapThread::Kill() +{ + if (Started && !Closed) + { + ThreadKill(); + Closed = true; + } +} +//--------------------------------------------------------------------------- +void TSnapThread::Join() +{ + if (Started && !Closed) + { + ThreadJoin(); + Closed = true; + } +} +//--------------------------------------------------------------------------- +longword TSnapThread::WaitFor(uint64_t Timeout) +{ + if (Started) + { + if (!Closed) + return ThreadWait(Timeout); + else + return WAIT_OBJECT_0; + } + else + return WAIT_OBJECT_0; +} +//--------------------------------------------------------------------------- + diff --git a/core/src/drivers/plugins/native/s7comm/snap7/sys/snap_threads.h b/core/src/drivers/plugins/native/s7comm/snap7/sys/snap_threads.h new file mode 100644 index 00000000..2d3458c3 --- /dev/null +++ b/core/src/drivers/plugins/native/s7comm/snap7/sys/snap_threads.h @@ -0,0 +1,45 @@ +/*=============================================================================| +| PROJECT SNAP7 1.4.3 | +|==============================================================================| +| Copyright (C) 2013, 2025 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/ | +|=============================================================================*/ +#ifndef snap_threads_h +#define snap_threads_h +//--------------------------------------------------------------------------- +#include "snap_platform.h" + +#ifdef OS_WINDOWS +# include "win_threads.h" +#endif +#if defined(POSIX) && (!defined(OS_SOLARIS_NATIVE_THREADS)) +# include "unix_threads.h" +#endif +#ifdef OS_SOLARIS_NATIVE_THREADS +# include "sol_threads.h" +#endif +#if defined(OS_OSX) +# include "unix_threads.h" +#endif + +//--------------------------------------------------------------------------- +#endif // snap_threads_h diff --git a/core/src/drivers/plugins/native/s7comm/snap7/sys/sol_threads.h b/core/src/drivers/plugins/native/s7comm/snap7/sys/sol_threads.h new file mode 100644 index 00000000..0bfa2374 --- /dev/null +++ b/core/src/drivers/plugins/native/s7comm/snap7/sys/sol_threads.h @@ -0,0 +1,208 @@ +/*=============================================================================| +| PROJECT SNAP7 1.4.3 | +|==============================================================================| +| Copyright (C) 2013, 2025 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/ | +|==============================================================================| +| | +| Solaris 11 Threads support | +| | +|=============================================================================*/ +#ifndef sol_threads_h +#define sol_threads_h +//--------------------------------------------------------------------------- +#include "snap_platform.h" +#include "snap_sysutils.h" +#include +#include +//--------------------------------------------------------------------------- + +class TSnapCriticalSection { +private: + mutex_t mx; + int result; +public: + + TSnapCriticalSection() { + mutex_init(&mx, USYNC_THREAD, 0); + }; + + ~TSnapCriticalSection() { + mutex_destroy(&mx); + }; + + void Enter() { + mutex_lock(&mx); + }; + + void Leave() { + mutex_unlock(&mx); + }; + + bool TryEnter() { + return mutex_trylock(&mx) == 0; + }; +}; +typedef TSnapCriticalSection *PSnapCriticalSection; + +//--------------------------------------------------------------------------- +const longword WAIT_OBJECT_0 = 0x00000000L; +const longword WAIT_ABANDONED = 0x00000080L; +const longword WAIT_TIMEOUT = 0x00000102L; +const longword WAIT_FAILED = 0xFFFFFFFFL; + +class TSnapEvent { +private: + cond_t CVariable; + mutex_t Mutex; + bool AutoReset; + bool State; +public: + + TSnapEvent(bool ManualReset) + { + AutoReset = !ManualReset; + cond_init(&CVariable, USYNC_THREAD, 0) == 0; + mutex_init(&Mutex, USYNC_THREAD, 0); + State = false; + } + + ~TSnapEvent() + { + cond_destroy(&CVariable); + mutex_destroy(&Mutex); + }; + + void Set() + { + mutex_lock(&Mutex); + State = true; + if (AutoReset) + cond_signal(&CVariable); + else + cond_broadcast(&CVariable); + mutex_unlock(&Mutex); + }; + + void Reset() + { + mutex_lock(&Mutex); + State = false; + mutex_unlock(&Mutex); + } + + longword WaitForever() + { + mutex_lock(&Mutex); + while (!State) // <-- to avoid spurious wakeups + cond_wait(&CVariable, &Mutex); + if (AutoReset) + State = false; + mutex_unlock(&Mutex); + return WAIT_OBJECT_0; + }; + + longword WaitFor(int64_t Timeout) + { + longword Result = WAIT_OBJECT_0; + if (Timeout == 0) + Timeout = 1; // 0 is not allowed + + if (Timeout > 0) { + mutex_lock(&Mutex); + if (!State) { + timespec ts; + timeval tv; + gettimeofday(&tv, NULL); + uint64_t nsecs = ((uint64_t) tv.tv_sec) * 1000000000 + + Timeout * 1000000 + + ((uint64_t) tv.tv_usec) * 1000; + ts.tv_sec = nsecs / 1000000000; + ts.tv_nsec = (nsecs - ((uint64_t) ts.tv_sec) * 1000000000); + do + { + Result = cond_timedwait(&CVariable, &Mutex, &ts); + if (Result == ETIMEDOUT) + Result = WAIT_TIMEOUT; + } + while (Result == 0 && !State); + } + else + if (AutoReset) // take the ownership + State = false; + mutex_unlock(&Mutex); + return Result; + } + else // Timeout<0 + return WaitForever(); + }; +}; +typedef TSnapEvent *PSnapEvent; +//--------------------------------------------------------------------------- + +class TSnapThread { +private: + thread_t th; + bool FCreateSuspended; + void ThreadCreate(); + + void ThreadJoin() + { + thr_join(th, 0, 0); + }; + + void ThreadKill() + { + thr_kill(th, 0); + }; + + longword ThreadWait(uint64_t Timeout) + { + longword Elapsed = SysGetTick(); + while (!Closed && !(DeltaTime(Elapsed) > Timeout)) + SysSleep(100); + if (Closed) + return WAIT_OBJECT_0; + else + return WAIT_TIMEOUT; + }; +protected: + bool Started; +public: + bool Terminated; + bool Closed; + bool FreeOnTerminate; + TSnapThread(); + virtual ~TSnapThread(); + + virtual void Execute() { + }; + void Start(); + void Terminate(); + void Kill(); + void Join(); + longword WaitFor(uint64_t Timeout); +}; +typedef TSnapThread *PSnapThread; + +//--------------------------------------------------------------------------- +#endif // sol_threads_h diff --git a/core/src/drivers/plugins/native/s7comm/snap7/sys/unix_threads.h b/core/src/drivers/plugins/native/s7comm/snap7/sys/unix_threads.h new file mode 100644 index 00000000..90ee88db --- /dev/null +++ b/core/src/drivers/plugins/native/s7comm/snap7/sys/unix_threads.h @@ -0,0 +1,228 @@ +/*=============================================================================| +| PROJECT SNAP7 1.4.3 | +|==============================================================================| +| Copyright (C) 2013, 2025 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/ | +|==============================================================================| +| | +| Posix Threads support (Linux, FreeBSD) | +| | +|=============================================================================*/ +#ifndef unix_threads_h +#define unix_threads_h +//--------------------------------------------------------------------------- +#include "snap_platform.h" +#include "snap_sysutils.h" +#include +#include +//--------------------------------------------------------------------------- + +class TSnapCriticalSection +{ +private: + pthread_mutex_t mx; +// int result; +public: + + TSnapCriticalSection() + { + /* + + This would be the best code, but very often it causes a segmentation fault in many + unix systems (the problem seems to be pthread_mutexattr_destroy()). + So, to avoid problems in future kernel/libc release, we use the "safe" default. + + pthread_mutexattr_t mxAttr; + pthread_mutexattr_settype(&mxAttr, PTHREAD_MUTEX_RECURSIVE); + pthread_mutex_init(&mx, &mxAttr); + pthread_mutexattr_destroy(&mxAttr); + + */ + pthread_mutex_init(&mx, 0); + }; + + ~TSnapCriticalSection() + { + pthread_mutex_destroy(&mx); + }; + + void Enter() + { + pthread_mutex_lock(&mx); + }; + + void Leave() + { + pthread_mutex_unlock(&mx); + }; + + bool TryEnter() + { + return pthread_mutex_trylock(&mx) == 0; + }; +}; +typedef TSnapCriticalSection *PSnapCriticalSection; + +//--------------------------------------------------------------------------- +const longword WAIT_OBJECT_0 = 0x00000000L; +const longword WAIT_ABANDONED = 0x00000080L; +const longword WAIT_TIMEOUT = 0x00000102L; +const longword WAIT_FAILED = 0xFFFFFFFFL; + +class TSnapEvent +{ +private: + pthread_cond_t CVariable; + pthread_mutex_t Mutex; + bool AutoReset; + bool State; +public: + + TSnapEvent(bool ManualReset) + { + AutoReset = !ManualReset; + if (pthread_cond_init(&CVariable, 0) == 0) + pthread_mutex_init(&Mutex, 0); + State = false; + } + + ~TSnapEvent() + { + pthread_cond_destroy(&CVariable); + pthread_mutex_destroy(&Mutex); + }; + + void Set() + { + pthread_mutex_lock(&Mutex); + State = true; + if (AutoReset) + pthread_cond_signal(&CVariable); + else + pthread_cond_broadcast(&CVariable); + pthread_mutex_unlock(&Mutex); + }; + + void Reset() + { + pthread_mutex_lock(&Mutex); + State = false; + pthread_mutex_unlock(&Mutex); + } + + longword WaitForever() + { + pthread_mutex_lock(&Mutex); + while (!State) // <-- to avoid spurious wakeups + pthread_cond_wait(&CVariable, &Mutex); + if (AutoReset) + State = false; + pthread_mutex_unlock(&Mutex); + return WAIT_OBJECT_0; + }; + + longword WaitFor(int64_t Timeout) + { + longword Result = WAIT_OBJECT_0; + if (Timeout == 0) + Timeout = 1; // 0 is not allowed + + if (Timeout > 0) + { + pthread_mutex_lock(&Mutex); + if (!State) + { + timespec ts; + timeval tv; + gettimeofday(&tv, NULL); + uint64_t nsecs = ((uint64_t) tv.tv_sec) * 1000000000 + + Timeout * 1000000 + + ((uint64_t) tv.tv_usec) * 1000; + ts.tv_sec = nsecs / 1000000000; + ts.tv_nsec = (nsecs - ((uint64_t) ts.tv_sec) * 1000000000); + do { + Result = pthread_cond_timedwait(&CVariable, &Mutex, &ts); + if (Result == ETIMEDOUT) + Result = WAIT_TIMEOUT; + } while (Result == 0 && !State); + } + else + if (AutoReset) // take the ownership + State = false; + pthread_mutex_unlock(&Mutex); + return Result; + } + else // Timeout<0 + return WaitForever(); + }; +}; +typedef TSnapEvent *PSnapEvent; +//--------------------------------------------------------------------------- +class TSnapThread +{ +private: + pthread_t th; + bool FCreateSuspended; + void ThreadCreate(); + + void ThreadJoin() + { + pthread_join(th, 0); + }; + + void ThreadKill() + { + pthread_cancel(th); + }; + + longword ThreadWait(uint64_t Timeout) + { + longword Elapsed = SysGetTick(); + while (!Closed && !(DeltaTime(Elapsed) > Timeout)) + SysSleep(100); + if (Closed) + return WAIT_OBJECT_0; + else + return WAIT_TIMEOUT; + }; +protected: + bool Started; +public: + bool Terminated; + bool Closed; + bool FreeOnTerminate; + TSnapThread(); + virtual ~TSnapThread(); + + virtual void Execute() + { + }; + void Start(); + void Terminate(); + void Kill(); + void Join(); + longword WaitFor(uint64_t Timeout); +}; +typedef TSnapThread *PSnapThread; + +//--------------------------------------------------------------------------- +#endif // unix_threads_h diff --git a/core/src/drivers/plugins/native/s7comm/snap7/sys/win_threads.h b/core/src/drivers/plugins/native/s7comm/snap7/sys/win_threads.h new file mode 100644 index 00000000..b57484ee --- /dev/null +++ b/core/src/drivers/plugins/native/s7comm/snap7/sys/win_threads.h @@ -0,0 +1,159 @@ +/*=============================================================================| +| PROJECT SNAP7 1.4.3 | +|==============================================================================| +| Copyright (C) 2013, 2025 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/ | +|==============================================================================| +| | +| Windows Threads support (Windows, ReactOS) | +| | +|=============================================================================*/ +#ifndef win_threads_h +#define win_threads_h +//--------------------------------------------------------------------------- +#include "snap_platform.h" +#include "snap_sysutils.h" +//--------------------------------------------------------------------------- + +class TSnapCriticalSection +{ +private: + CRITICAL_SECTION cs; +public: + + TSnapCriticalSection() + { + InitializeCriticalSection(&cs); + }; + + ~TSnapCriticalSection() + { + DeleteCriticalSection(&cs); + }; + + void Enter() + { + EnterCriticalSection(&cs); + }; + + void Leave() + { + LeaveCriticalSection(&cs); + }; + + bool TryEnter() + { + return (TryEnterCriticalSection(&cs) != 0); + }; +}; +typedef TSnapCriticalSection *PSnapCriticalSection; +//--------------------------------------------------------------------------- + +class TSnapEvent +{ +private: + HANDLE Event; +public: + + TSnapEvent(bool ManualReset) + { + Event = CreateEvent(0, ManualReset, false, 0); + }; + + ~TSnapEvent() + { + if (Event != 0) + CloseHandle(Event); + }; + + void Set() + { + if (Event != 0) + SetEvent(Event); + }; + + void Reset() + { + if (Event != 0) + ResetEvent(Event); + }; + + longword WaitForever() + { + if (Event != 0) + return WaitForSingleObject(Event, INFINITE); + else + return WAIT_FAILED; + }; + + longword WaitFor(int64_t Timeout) { + if (Event != 0) + return WaitForSingleObject(Event, DWORD(Timeout)); + else + return WAIT_FAILED; + }; +}; +typedef TSnapEvent *PSnapEvent; +//--------------------------------------------------------------------------- + +class TSnapThread { +private: + HANDLE th; + bool FCreateSuspended; + void ThreadCreate(); + + void ThreadJoin() + { + WaitForSingleObject(th, INFINITE); + }; + + void ThreadKill() + { + TerminateThread(th, 0); + }; + + longword ThreadWait(uint64_t Timeout) + { + return WaitForSingleObject(th, DWORD(Timeout)); + }; +protected: + bool Started; +public: + bool Terminated; + bool Closed; + bool FreeOnTerminate; + TSnapThread(); + virtual ~TSnapThread(); + + virtual void Execute() + { + }; + void Start(); + void Terminate(); + void Kill(); + void Join(); + longword WaitFor(uint64_t Timeout); +}; +typedef TSnapThread *PSnapThread; + +//--------------------------------------------------------------------------- +#endif // win_threads_h diff --git a/install.sh b/install.sh index 3a3ad460..918cfcf8 100755 --- a/install.sh +++ b/install.sh @@ -380,6 +380,107 @@ setup_plugin_venvs() { return 0 } +# Function to build native plugins that have CMakeLists.txt +build_native_plugins() { + local native_plugins_dir="$OPENPLC_DIR/core/src/drivers/plugins/native" + local plugins_output_dir="$OPENPLC_DIR/build/plugins" + + log_info "Scanning for native plugins to build..." + + # Check if native plugins directory exists + if [ ! -d "$native_plugins_dir" ]; then + log_warning "Native plugins directory not found: $native_plugins_dir" + return 0 + fi + + # Create plugins output directory + mkdir -p "$plugins_output_dir" + + # Find directories with CMakeLists.txt (indicates buildable plugin) + local plugins_found=0 + local plugins_built=0 + local plugins_failed=0 + + for plugin_dir in "$native_plugins_dir"/*/; do + # Skip if not a directory + [ -d "$plugin_dir" ] || continue + + local plugin_name=$(basename "$plugin_dir") + local cmake_file="$plugin_dir/CMakeLists.txt" + + # Skip if no CMakeLists.txt + if [ ! -f "$cmake_file" ]; then + continue + fi + + plugins_found=$((plugins_found + 1)) + log_info "Found native plugin: $plugin_name" + + # Create build directory for this plugin + local plugin_build_dir="$plugin_dir/build" + + # Clean existing build directory + if [ -d "$plugin_build_dir" ]; then + log_info "Cleaning existing build directory for $plugin_name..." + rm -rf "$plugin_build_dir" + fi + + mkdir -p "$plugin_build_dir" + + # Build the plugin + log_info "Building $plugin_name..." + ( + cd "$plugin_build_dir" || exit 1 + + # Configure with cmake, passing OpenPLC root directory + if ! cmake -DOPENPLC_ROOT="$OPENPLC_DIR" ..; then + log_error "CMake configuration failed for $plugin_name" + exit 1 + fi + + # Build with make + if ! make -j"$(nproc)"; then + log_error "Compilation failed for $plugin_name" + exit 1 + fi + ) + + if [ $? -eq 0 ]; then + # Copy built plugin to central plugins directory + local built_lib=$(find "$plugin_build_dir" -name "*.so" -type f 2>/dev/null | head -1) + if [ -n "$built_lib" ] && [ -f "$built_lib" ]; then + cp "$built_lib" "$plugins_output_dir/" + log_success "Built and installed: $plugin_name ($(basename "$built_lib"))" + plugins_built=$((plugins_built + 1)) + else + log_warning "No .so file found after building $plugin_name" + plugins_failed=$((plugins_failed + 1)) + fi + else + log_error "Failed to build $plugin_name" + plugins_failed=$((plugins_failed + 1)) + fi + done + + if [ $plugins_found -eq 0 ]; then + log_info "No native plugins with CMakeLists.txt found" + return 0 + fi + + log_info "Native plugin build summary: $plugins_built/$plugins_found succeeded" + + if [ $plugins_failed -gt 0 ]; then + log_warning "$plugins_failed plugin(s) failed to build" + # Don't fail installation if some plugins fail - they may be optional + fi + + if [ $plugins_built -gt 0 ]; then + log_success "Native plugins built and installed to: $plugins_output_dir" + fi + + return 0 +} + # Setup runtime directory (needed for both Linux and Docker) # On MSYS2, use /run/runtime which maps to the MSYS2 installation directory if is_msys2; then @@ -419,6 +520,10 @@ echo "Compiling OpenPLC..." if compile_plc; then echo "Build process completed successfully!" + # Build native plugins after main compilation + echo "Building native plugins..." + build_native_plugins + # Create installation marker (must be done before starting the service) touch "$OPENPLC_DIR/.installed" echo "Installation completed at $(date)" > "$OPENPLC_DIR/.installed" diff --git a/plugins.conf b/plugins.conf index ae0d73b0..d09403cb 100644 --- a/plugins.conf +++ b/plugins.conf @@ -1,2 +1,3 @@ modbus_slave,./core/src/drivers/plugins/python/modbus_slave/simple_modbus.py,0,0,./core/src/drivers/plugins/python/modbus_slave/modbus_slave_config.json,./venvs/modbus_slave modbus_master,./core/src/drivers/plugins/python/modbus_master/modbus_master_plugin.py,0,0,./core/src/drivers/plugins/python/modbus_master/modbus_master.json,./venvs/modbus_master +s7comm,./core/src/drivers/plugins/native/s7comm/build/plugins/libs7comm_plugin.so,0,1,./core/src/drivers/plugins/native/s7comm/s7comm_config.json, diff --git a/plugins_default.conf b/plugins_default.conf index 0c1bd5b0..5ea5e8d3 100644 --- a/plugins_default.conf +++ b/plugins_default.conf @@ -1,2 +1,3 @@ modbus_slave,./core/src/drivers/plugins/python/modbus_slave/simple_modbus.py,0,0,./core/src/drivers/plugins/python/modbus_slave/modbus_slave_config.json,./venvs/modbus_slave -modbus_master,./core/src/drivers/plugins/python/modbus_master/modbus_master_plugin.py,0,0,./core/src/drivers/plugins/python/modbus_master/modbus_master.json,./venvs/modbus_master \ No newline at end of file +modbus_master,./core/src/drivers/plugins/python/modbus_master/modbus_master_plugin.py,0,0,./core/src/drivers/plugins/python/modbus_master/modbus_master.json,./venvs/modbus_master +s7comm,./core/src/drivers/plugins/native/s7comm/build/plugins/libs7comm_plugin.so,0,1,./core/src/drivers/plugins/native/s7comm/s7comm_config.json, \ No newline at end of file