Skip to content

v0.2.2 — 2025-10-31

Choose a tag to compare

@kaburagisec kaburagisec released this 31 Oct 00:49
· 33 commits to main since this release
Immutable release. Only release title and notes can be modified.

PyPI

https://pypi.org/project/onvif-python/0.2.2 (18959263050)

Feat

  • [Client] Add ONVIFParser plugin for extracting XML elements from SOAP responses (4d1b634)
  • [Client] Add support for user-provided plugins in ONVIFClient initialization (11163e1)
  • [Client] Add lazy loading for security service capabilities in ONVIFClient (8e307af)
  • [Client] Add lazy loading for JWT service in ONVIFClient (735a38c)
  • [CLI] Enhance service availability checks for Security and JWT services (8f139f3)

Fix/Refactor

  • [Client] Update service_path in AdvancedSecurity to use device_service (18fc581)
  • [Client] Improve handling of wrapper elements and attribute parsing in ZeepPatcher (83600fe)

Docs

  • [Examples] Add media profile creation example with video source and encoder configurations (5f12aa5)
  • [Examples] Replace XMLCapturePlugin with ONVIFParser for topic extraction in pull_live_events.py (99e7e8d)
  • [Release] Bump version 0.2.1 → 0.2.2 (29d1ffb)

Full Changelog: v0.2.1...v0.2.2

Technical Details

1. ONVIFParser: Solving Zeep's Text Element Limitation (4d1b634) (11163e1)

Problem Statement:

Zeep has a known limitation when parsing XML text elements that contain attributes, specifically when extracting Topic information from the PullPoint (Event) service responses. After extensive debugging and attempts to modify the ZeepPatcher class (originally implemented in v0.0.8 to enhance XML parsing for xsd:any fields), no viable solution was found within Zeep's architecture.

Issue Example:

Below is a comparison showing how Zeep fails to parse the Topic text value:

Zeep Parsed Object:

{
    'CurrentTime': datetime.datetime(2025, 10, 30, 22, 16, 42, tzinfo=<isodate.tzinfo.Utc>),
    'TerminationTime': datetime.datetime(2025, 10, 30, 22, 26, 47, tzinfo=<isodate.tzinfo.Utc>),
    'NotificationMessage': [
        {
            'SubscriptionReference': None,
            'Topic': {
                '_value_1': None,  # ❌ Topic text is missing!
                'Dialect': 'http://www.onvif.org/ver10/tev/topicExpression/ConcreteSet',
                '_attr_1': {}
            },
            'ProducerReference': None,
            'Message': {
                '_value_1': <Element {http://www.onvif.org/ver10/schema}Message at 0x1ea5df2bd00>
            }
        }
        # ... more notifications
    ]
}

Expected Raw XML:

<env:Header>
    <wsa:Action>http://www.onvif.org/ver10/events/wsdl/PullPointSubscription/PullMessagesResponse</wsa:Action>
</env:Header>
<env:Body>
    <tev:PullMessagesResponse>
        <tev:CurrentTime>2025-10-30T22:16:42Z</tev:CurrentTime>
        <tev:TerminationTime>2025-10-30T22:26:47Z</tev:TerminationTime>
        <wsnt:NotificationMessage>
            <wsnt:Topic Dialect="http://www.onvif.org/ver10/tev/topicExpression/ConcreteSet">
                tns1:RuleEngine/CellMotionDetector/Motion
            </wsnt:Topic>
            <wsnt:Message>
                <tt:Message UtcTime="2025-10-30T22:16:41Z" PropertyOperation="Initialized">
                    <tt:Source>
                        <tt:SimpleItem Name="VideoSourceConfigurationToken" Value="VideoSourceToken"/>
                        <tt:SimpleItem Name="VideoAnalyticsConfigurationToken" Value="VideoAnalyticsToken"/>
                        <tt:SimpleItem Name="Rule" Value="MyMotionDetectorRule"/>
                    </tt:Source>
                    <tt:Data>
                        <tt:SimpleItem Name="IsMotion" Value="false"/>
                    </tt:Data>
                </tt:Message>
            </wsnt:Message>
        </wsnt:NotificationMessage>
        <!-- ... more notifications -->
    </tev:PullMessagesResponse>
</env:Body>

The Issue:

The _value_1 field should contain the topic string "tns1:RuleEngine/CellMotionDetector/Motion", but Zeep fails to extract it when the <wsnt:Topic> element has attributes (in this case, the Dialect attribute).

Solution: ONVIFParser

To work around this limitation, a new utility class ONVIFParser was introduced that directly parses raw XML using XPath expressions before Zeep processes it. This provides reliable access to element text content regardless of attributes.

Implementation Changes:

  • New Class: onvif.utils.ONVIFParser - Generic XML extraction plugin using Zeep's ingress() hook
  • Updated Example: examples/pull_live_events.py now uses ONVIFParser instead of XMLCapturePlugin
  • Removed in example: XMLCapturePlugin usage in production examples (this plugin is only suitable for development/testing as it stores all request/response XML in memory indefinitely)

Usage Example:

from onvif import ONVIFClient, ONVIFParser

# Create parser with XPath for topic extraction
parser = ONVIFParser(extract_xpaths={
    'topic': './/{http://docs.oasis-open.org/wsn/b-2}Topic'
})

# Pass parser to client
client = ONVIFClient(host, port, username, password, plugins=[parser])

# Use parsed data
event_service = client.pullpoint(...)
response = event_service.PullMessages(...)
topics = parser.get_extracted_texts('topic', len(response.NotificationMessage))

2. Advanced Security Service Discovery (18fc581) (8e307af) (735a38c)

Problem Statement:

The XAddr endpoint for the Security service (now called "Advanced Security" by the ONVIF consortium) was not discoverable through the standard 3-tier service discovery mechanism (GetServicesGetCapabilities → default URL). Extensive testing across multiple devices showed that neither "Security" nor "AdvancedSecurity" keys appeared in response structures, even within Extension or Extension.Extensions fields.

Investigation:

After thorough review of the ONVIF Advanced Security Service specification, the solution was discovered through experimentation: calling GetServiceCapabilities() on the Security service using the Device service's default endpoint (/onvif/device_service).

Discovery Response:

{
    'KeystoreCapabilities': None,
    'TLSServerCapabilities': None,
    'Dot1XCapabilities': None,
    'AuthorizationServer': None,
    'MediaSigning': None,
    '_value_1': [
        <Element {http://www.onvif.org/ver10/device/wsdl}Network at 0x1ddc2c13640>,
        <Element {http://www.onvif.org/ver10/device/wsdl}Security at 0x1ddc2c13680>,
        <Element {http://www.onvif.org/ver10/device/wsdl}System at 0x1ddc2c136c0>
    ],
    '_attr_1': None,
    'Network': {
        'IPFilter': False,
        'ZeroConfiguration': True,
        'IPVersion6': False,
        'DHCPv6': False,
        'DynDNS': False,
        'Dot11Configuration': False,
        'Dot1XConfigurations': 0,
        'HostnameFromDHCP': True,
        'NTP': 1
    },
    'Security': {
        'TLS1.0': True,
        'TLS1.1': True,
        'TLS1.2': True,
        'OnboardKeyGeneration': False,
        'AccessPolicyConfig': False,
        'DefaultAccessPolicy': True,
        'Dot1X': False,
        'RemoteUserHandling': False,
        'X.509Token': False,
        'SAMLToken': False,
        'KerberosToken': False,
        'UsernameToken': True,
        'HttpDigest': True,
        'RELToken': False,
        'SupportedEAPMethods': 0,
        'MaxUsers': 32,
        'MaxUserNameLength': 32,
        'MaxPasswordLength': 16
    },
    'System': {
        'DiscoveryResolve': False,
        'DiscoveryBye': True,
        'RemoteDiscovery': True,
        'SystemBackup': False,
        'SystemLogging': False,
        'FirmwareUpgrade': True,
        'HttpFirmwareUpgrade': True,
        'HttpSystemBackup': False,
        'HttpSystemLogging': False,
        'HttpSupportInformation': False,
        'StorageConfiguration': False,
        'MaxStorageConfigurations': 0
    }
}

Key Findings:

  1. Endpoint Location: The Advanced Security service is accessible at /onvif/device_service (which is the endpoint of Device service)
  2. Sub-service Detection: Security sub-services (Keystore, TLS Server, Dot1X, etc.) can be detected by checking if their capability objects are None or contain data
  3. Capability Response: The GetServiceCapabilities() call returns comprehensive information about Network, Security, and System capabilities

Implementation:

  • Service Endpoint: Security service XAddr is constructed as {protocol}://{host}:{port}/onvif/device_service
  • Capability Caching: Added client-level caching to prevent repeated GetServiceCapabilities() calls
  • Sub-service Detection: Automatically detect available sub-services (keystore, tlsserver, dot1x, authorizationserver, mediasigning) based on capability presence