Skip to content

Add OPC-UA; Improvements on Plugin lifecycle#88

Merged
thiagoralves merged 109 commits into
mainfrom
development
Jan 23, 2026
Merged

Add OPC-UA; Improvements on Plugin lifecycle#88
thiagoralves merged 109 commits into
mainfrom
development

Conversation

@thiagoralves
Copy link
Copy Markdown
Contributor

This pull request introduces new utilities for plugin variable access in the OpenPLC core, improves plugin lifecycle handling, and adds documentation and structure for the OPC-UA Python plugin. The most significant changes are the addition of plugin_utils for variable access, enhancements to plugin stop and cleanup logic, and documentation for OPC-UA subscription support.

Plugin Variable Access Utilities:

  • Added new utility functions (get_var_list, get_var_size, get_var_count) in plugin_utils.c/plugin_utils.h to provide standardized access to PLC variables for plugins. These are now included in the plugin argument structure for use by all plugins. [1] [2] [3] [4] [5]
  • Included <stdint.h> in plugin_types.h to support the new variable access functions.

Plugin Lifecycle Improvements:

  • Updated plugin_driver_stop to skip disabled plugins when stopping, improving robustness and log clarity.
  • Refactored plugin stop and cleanup logic to consistently use local plugin pointers and check the enabled flag before cleaning up Python plugins. [1] [2]
  • Included plugin_utils.h in plugin_driver.c to make new utilities available.

OPC-UA Plugin Documentation and Structure:

  • Added a comprehensive implementation plan for OPC-UA subscription support in SUBSCRIPTION_IMPLEMENTATION.md, detailing current status, phases, and testing strategy.
  • Introduced a well-documented __init__.py for the OPC-UA Python plugin, outlining architecture and re-exporting the plugin interface for runtime compatibility.

marconetsf and others added 30 commits November 20, 2025 14:40
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* get_var_list available at runtime api arg

* adding get_var_list and var_size in python side

* adding read and write function with typecheck

* Update core/src/drivers/plugins/python/shared/python_plugin_types.py

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update core/src/drivers/plugins/python/shared/python_plugin_types.py

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* adding get_var_count to runtime api for python

* Update core/src/drivers/plugin_utils.c

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update core/src/drivers/plugins/python/shared/python_plugin_types.py

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update core/src/drivers/plugins/python/shared/python_plugin_types.py

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* refact safe buffer access

* Update core/src/drivers/plugins/python/shared/debug_utils.py

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* imports at top of the file accordingly with PEP 8

* deleting unecessary singleton logic for bufferType accessing

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
…ents

- Introduced `opcua_memory.py` for direct memory access functions.
- Created `opcua_types.py` for type definitions including `VariableNode` and `VariableMetadata`.
- Added utility functions in `opcua_utils.py` for type mapping and value conversion.
- Refactored `opcua_plugin.py` to utilize new memory access and utility functions.
marconetsf and others added 29 commits January 20, 2026 08:58
Add comprehensive unit tests for the new TIME type support:

Type conversion tests (test_type_conversions.py):
- TIME/TOD/DATE/DT type mappings to OPC-UA types
- TIME conversion from tuple to milliseconds
- TIME conversion from milliseconds to tuple
- TIME roundtrip conversion tests
- timespec_to_milliseconds helper function tests
- milliseconds_to_timespec helper function tests
- TIME_DATATYPES constant tests

Memory access tests (test_memory.py):
- IEC_TIMESPEC structure tests (size, fields, initialization)
- read_timespec_direct function tests
- write_timespec_direct function tests
- read_memory_direct with TIME datatype hint tests
- Roundtrip read/write tests for TIME values

All 127 tests pass.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- DATE: Now extracts only the date portion, setting time to 00:00:00
  (ignores HH:MM:SS from the IEC_TIMESPEC value)

- TOD: Now uses current date (today) + time from IEC_TIMESPEC
  (ignores YYYY-MM-DD, only uses HH:MM:SS)
  Changed mapping from Int64 to DateTime for better OPC-UA client compatibility

- DT: Unchanged - full DateTime conversion (both date and time)

- TIME: Unchanged - Int64 milliseconds representation

Updated tests to reflect the new behavior.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* feat: Add TIME type support for OPC-UA plugin

Add support for IEC 61131-3 TIME, DATE, TOD, and DT types in the OPC-UA plugin:

- Add IEC_TIMESPEC ctypes structure matching C definition (tv_sec, tv_nsec)
- Implement TIME type mapping to OPC-UA Int64 (milliseconds)
- Implement DATE/DT type mapping to OPC-UA DateTime
- Add timespec_to_milliseconds and milliseconds_to_timespec conversion functions
- Update convert_value_for_opcua/plc functions to handle TIME types
- Add read_timespec_direct and write_timespec_direct memory access functions
- Update synchronization to pass datatype hint for TIME handling
- Add datatype validation in config model with VALID_DATATYPES constant
- Add TIME variable examples to config template

TIME values are represented as Int64 milliseconds in OPC-UA, which provides
good compatibility with standard OPC-UA clients while maintaining reasonable
precision for PLC applications.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* test: Add unit tests for TIME type support

Add comprehensive unit tests for the new TIME type support:

Type conversion tests (test_type_conversions.py):
- TIME/TOD/DATE/DT type mappings to OPC-UA types
- TIME conversion from tuple to milliseconds
- TIME conversion from milliseconds to tuple
- TIME roundtrip conversion tests
- timespec_to_milliseconds helper function tests
- milliseconds_to_timespec helper function tests
- TIME_DATATYPES constant tests

Memory access tests (test_memory.py):
- IEC_TIMESPEC structure tests (size, fields, initialization)
- read_timespec_direct function tests
- write_timespec_direct function tests
- read_memory_direct with TIME datatype hint tests
- Roundtrip read/write tests for TIME values

All 127 tests pass.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix: Improve DATE and TOD type conversions for OPC-UA

- DATE: Now extracts only the date portion, setting time to 00:00:00
  (ignores HH:MM:SS from the IEC_TIMESPEC value)

- TOD: Now uses current date (today) + time from IEC_TIMESPEC
  (ignores YYYY-MM-DD, only uses HH:MM:SS)
  Changed mapping from Int64 to DateTime for better OPC-UA client compatibility

- DT: Unchanged - full DateTime conversion (both date and time)

- TIME: Unchanged - Int64 milliseconds representation

Updated tests to reflect the new behavior.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Update core/src/drivers/plugins/python/opcua/opcua_config_template.json

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update tests/pytest/plugins/opcua/test_type_conversions.py

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
)

* feat: Implement brute-force protection with rate limiting in user manager

* feat: Add OPC-UA authentication implementation report and error analysis documentation
psutil fails to install on MSYS2/Cygwin with "platform cygwin is not
supported" error. Use pip environment marker to skip psutil on cygwin
platforms while keeping it for Linux/macOS/native Windows.

The opcua_endpoints_config.py code already has socket-based fallbacks
when psutil is unavailable, so functionality is preserved on all
platforms.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
cryptography (required by asyncua) cannot be built from source on
MSYS2/Cygwin. This adds two changes to enable OPC-UA plugin on MSYS2:

1. Install python-cryptography via pacman in install_deps_msys2()
2. Create plugin venvs with --system-site-packages on MSYS2 so pip
   can use the pre-built system cryptography package

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
When existing venvs were created before the MSYS2 fix, they lack
--system-site-packages and pip still tries to build cryptography.

This adds detection to check pyvenv.cfg for system-site-packages flag
and automatically recreates the venv with proper flags on MSYS2.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
bcrypt is also a Rust-based package that cannot be built on MSYS2/Cygwin.
Install it via pacman alongside cryptography.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
python-bcrypt package doesn't exist in MSYS2 repos and pip can't build
it (Rust-based). The OPC-UA plugin already handles missing bcrypt
gracefully by disabling password authentication - users can use
certificate-based authentication instead.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Use the correct MSYS2 package name for bcrypt (mingw-w64-x86_64-python-bcrypt).
This enables password authentication on MSYS2 builds.

Also reverts making bcrypt optional since it's now available via pacman.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…ame issues (#86)

* fix: Auto-detect local IPs for OPC-UA certificate SAN entries

When running in Docker containers, the auto-generated OPC-UA server
certificate only included the container hostname in the Subject
Alternative Name (SAN). This caused BadCertificateHostNameInvalid
errors when clients connected via IP address.

Changes:
- Add get_local_ip_addresses() to auto-detect all local IPs
- Add generate_certificate_with_sans() for certificates with multiple
  DNS names and IP addresses in SANs
- Update certificate generation to include all detected local IPs

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix: Use PKCS8 format for private key to fix asyncua compatibility

The TraditionalOpenSSL format caused parsing errors when asyncua
tried to load the private key. PKCS8 format is required by asyncua.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix: Convert certificate to DER format for asyncua compatibility

asyncua's load_certificate was failing with PEM format. Convert both
certificate and private key to DER format before loading into the
server. Also improved error messages for better debugging.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix: Add CLIENT_AUTH to certificate Extended Key Usage for OPC-UA

OPC-UA certificates require both SERVER_AUTH and CLIENT_AUTH in the
Extended Key Usage extension. Missing CLIENT_AUTH caused
BadCertificateUseNotAllowed errors when clients connected.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix: Enable nonRepudiation (content_commitment) in certificate Key Usage

OPC-UA specification (OPC 10000-6 6.2.2) requires certificates to have
keyUsage including: digitalSignature, nonRepudiation, keyEncipherment,
and dataEncipherment. The missing nonRepudiation flag caused
BadCertificateUseNotAllowed errors.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix: Improve IP address detection code quality

- Use ipaddress.is_link_local for proper link-local address filtering
  instead of string prefix check (handles both IPv4 and IPv6)
- Add named constants for ioctl magic numbers (_SIOCGIFCONF,
  _SIZEOF_IFREQ, _MAX_INTERFACES) for better code readability

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix: Extend certificate validity to 10 years and auto-regenerate expired certs

- Change default certificate validity from 365 days to 3650 days (10 years)
- Add _is_certificate_valid() method to check if certificate is still valid
- Add _remove_certificate_files() method to remove expired certificate files
- Update _ensure_server_certificates() to check validity and regenerate if expired
- Update _setup_server_certificates_for_asyncua() with same validity check logic
- Remove unused iface_name variable to fix ruff lint warning

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix: Address Copilot PR review comments for certificate generation

- Remove async from generate_certificate_with_sans (no await operations)
- Remove deprecated default_backend() from all cryptography calls
- Remove redundant imports in _validate_certificate_format
- Add restricted permissions (0o600) for private key files
- Document 10-year validity rationale in docstring

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
…tibility

Rust/pyo3-based packages like bcrypt cannot be built on plain MSYS
environment due to fundamental linking issues with Python symbols.

Changes:
- requirements.txt: Add sys_platform marker to skip bcrypt on cygwin
- install.sh: Only install mingw-w64-x86_64-python-bcrypt on MINGW64

On plain MSYS, password authentication for OPC-UA will be disabled.
Users needing full functionality should use MINGW64 environment.
The user_manager.py already has graceful fallback when bcrypt is unavailable.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
bcrypt cannot be built on MSYS2/Cygwin due to Rust/pyo3 linking issues.
This adds PBKDF2-HMAC-SHA256 as a fallback using Python's stdlib hashlib.

Changes:
- requirements.txt: Skip bcrypt on cygwin platform
- user_manager.py: Add PBKDF2 hashing/verification functions
  - Automatically detects hash type (bcrypt vs PBKDF2)
  - Uses 600000 iterations per OWASP recommendation
  - Constant-time comparison to prevent timing attacks
- install.sh: Remove mingw-w64-x86_64-python-bcrypt (doesn't work on plain MSYS)

Behavior by platform:
- Linux/macOS: Uses bcrypt (preferred)
- MSYS2/Cygwin: Uses PBKDF2 (stdlib, no external dependencies)

Both hash formats are supported for verification, enabling cross-platform
password portability.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Fix validation error when adding Function Block instances to OPC-UA
address space. The previous validation only checked the first level
of fields, causing errors like "Invalid datatype 'TON'" for nested
FB instances.

Changes:
- Add recursive validate_field_datatypes() that only validates leaf
  fields (those without nested children)
- Complex types (FBs, nested structs) are skipped, only their leaf
  children are validated
- Add missing IEC 61131-3 base types to VALID_DATATYPES:
  SINT, USINT, UINT, UDINT, ULINT, LREAL, WORD, DWORD, LWORD

The validation now mirrors the recursive pattern used for index
collection (collect_field_indices).

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add null-check on datatype for defensive coding
- Include full field path in error messages for easier debugging
  (e.g., "TON0.ET" instead of just "ET")

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…-validation

fix: Add recursive validation for nested OPC-UA structures
Remove per-variable debug logging that would generate excessive output
during sync cycles. Removed messages for variable changes, array element
changes, and TIME variable writes. These debug messages add overhead to
the hot sync path and clutter logs during development/testing.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…y access

Security fixes:
- Fix username attribute mismatch: User object uses 'name' not 'username',
  causing audit logs to show "unknown" instead of actual username
- Change permission default from fail-open to fail-closed: nodes without
  configured permissions now deny writes instead of allowing them
- Add memory address validation to prevent segfaults from invalid addresses
  (NULL, negative, or reserved memory region)

The fail-open issue was a security vulnerability where unconfigured nodes
would allow writes from any authenticated user or anonymous client.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@thiagoralves thiagoralves merged commit 323fc25 into main Jan 23, 2026
0 of 2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants