python-aidot-cameras 0.12.16__tar.gz → 0.13.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (159) hide show
  1. {python_aidot_cameras-0.12.16/python_aidot_cameras.egg-info → python_aidot_cameras-0.13.0}/PKG-INFO +4 -4
  2. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/README.md +2 -2
  3. python_aidot_cameras-0.13.0/aidot_cameras/_upstream.py +396 -0
  4. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/aidot_cameras/camera/client.py +253 -20
  5. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/aidot_cameras/camera/constants.py +8 -0
  6. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/aidot_cameras/camera/sdes_open.py +588 -24
  7. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/aidot_cameras/camera/webrtc_open.py +65 -30
  8. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/aidot_cameras/client.py +243 -65
  9. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/aidot_cameras/const.py +16 -3
  10. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/aidot_cameras/crypto.py +12 -6
  11. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/aidot_cameras/device_client.py +90 -24
  12. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/aidot_cameras/discover.py +38 -14
  13. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/pyproject.toml +17 -5
  14. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0/python_aidot_cameras.egg-info}/PKG-INFO +4 -4
  15. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/python_aidot_cameras.egg-info/SOURCES.txt +9 -1
  16. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/python_aidot_cameras.egg-info/requires.txt +1 -1
  17. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/tests/test_aioice_compat.py +2 -2
  18. python_aidot_cameras-0.13.0/tests/test_battery_detection.py +206 -0
  19. python_aidot_cameras-0.13.0/tests/test_constants_imports_resolve.py +62 -0
  20. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/tests/test_device_login_guard.py +3 -3
  21. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/tests/test_dtls_mux_terminal_write.py +2 -1
  22. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/tests/test_egress_guard.py +3 -3
  23. python_aidot_cameras-0.13.0/tests/test_ice_peer_reflexive.py +92 -0
  24. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/tests/test_light_active_color_mode.py +4 -6
  25. python_aidot_cameras-0.13.0/tests/test_live_stream_param.py +145 -0
  26. python_aidot_cameras-0.13.0/tests/test_live_validate_policy.py +281 -0
  27. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/tests/test_no_self_referential_go2rtc.py +2 -2
  28. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/tests/test_post_merge_hardening.py +37 -20
  29. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/tests/test_session_stats.py +3 -3
  30. python_aidot_cameras-0.13.0/tests/test_shared_house_seam.py +98 -0
  31. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/tests/test_token_refresh.py +34 -20
  32. python_aidot_cameras-0.13.0/tests/test_turn_create_permission.py +291 -0
  33. python_aidot_cameras-0.13.0/tests/test_upstream_compat.py +568 -0
  34. python_aidot_cameras-0.13.0/tests/test_wake_readiness_retry.py +167 -0
  35. python_aidot_cameras-0.12.16/tests/test_live_stream_param.py +0 -123
  36. python_aidot_cameras-0.12.16/tests/test_upstream_compat.py +0 -268
  37. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/LICENSE +0 -0
  38. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/MANIFEST.in +0 -0
  39. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/aidot_cameras/__init__.py +0 -0
  40. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/aidot_cameras/__main__.py +0 -0
  41. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/aidot_cameras/_vendor/__init__.py +0 -0
  42. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/aidot_cameras/_vendor/aiortc/__init__.py +0 -0
  43. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/aidot_cameras/_vendor/aiortc/clock.py +0 -0
  44. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/aidot_cameras/_vendor/aiortc/codecs/__init__.py +0 -0
  45. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/aidot_cameras/_vendor/aiortc/codecs/base.py +0 -0
  46. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/aidot_cameras/_vendor/aiortc/codecs/g711.py +0 -0
  47. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/aidot_cameras/_vendor/aiortc/codecs/g722.py +0 -0
  48. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/aidot_cameras/_vendor/aiortc/codecs/h264.py +0 -0
  49. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/aidot_cameras/_vendor/aiortc/codecs/opus.py +0 -0
  50. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/aidot_cameras/_vendor/aiortc/codecs/vpx.py +0 -0
  51. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/aidot_cameras/_vendor/aiortc/contrib/__init__.py +0 -0
  52. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/aidot_cameras/_vendor/aiortc/contrib/media.py +0 -0
  53. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/aidot_cameras/_vendor/aiortc/contrib/signaling.py +0 -0
  54. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/aidot_cameras/_vendor/aiortc/events.py +0 -0
  55. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/aidot_cameras/_vendor/aiortc/exceptions.py +0 -0
  56. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/aidot_cameras/_vendor/aiortc/jitterbuffer.py +0 -0
  57. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/aidot_cameras/_vendor/aiortc/mediastreams.py +0 -0
  58. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/aidot_cameras/_vendor/aiortc/rate.py +0 -0
  59. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/aidot_cameras/_vendor/aiortc/rtcconfiguration.py +0 -0
  60. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/aidot_cameras/_vendor/aiortc/rtcdatachannel.py +0 -0
  61. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/aidot_cameras/_vendor/aiortc/rtcdtlstransport.py +0 -0
  62. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/aidot_cameras/_vendor/aiortc/rtcicetransport.py +0 -0
  63. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/aidot_cameras/_vendor/aiortc/rtcpeerconnection.py +0 -0
  64. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/aidot_cameras/_vendor/aiortc/rtcrtpparameters.py +0 -0
  65. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/aidot_cameras/_vendor/aiortc/rtcrtpreceiver.py +0 -0
  66. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/aidot_cameras/_vendor/aiortc/rtcrtpsender.py +0 -0
  67. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/aidot_cameras/_vendor/aiortc/rtcrtptransceiver.py +0 -0
  68. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/aidot_cameras/_vendor/aiortc/rtcsctptransport.py +0 -0
  69. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/aidot_cameras/_vendor/aiortc/rtcsessiondescription.py +0 -0
  70. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/aidot_cameras/_vendor/aiortc/rtp.py +0 -0
  71. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/aidot_cameras/_vendor/aiortc/sdp.py +0 -0
  72. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/aidot_cameras/_vendor/aiortc/stats.py +0 -0
  73. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/aidot_cameras/_vendor/aiortc/utils.py +0 -0
  74. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/aidot_cameras/camera/__init__.py +0 -0
  75. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/aidot_cameras/camera/controls.py +0 -0
  76. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/aidot_cameras/camera/go2rtc.py +0 -0
  77. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/aidot_cameras/camera/lan_control.py +0 -0
  78. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/aidot_cameras/camera/models.py +0 -0
  79. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/aidot_cameras/camera/playback.py +0 -0
  80. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/aidot_cameras/camera/protocol.py +0 -0
  81. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/aidot_cameras/camera/sdes.py +0 -0
  82. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/aidot_cameras/camera/tutk.py +0 -0
  83. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/aidot_cameras/camera/webrtc.py +0 -0
  84. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/aidot_cameras/credentials.py +0 -0
  85. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/aidot_cameras/exceptions.py +0 -0
  86. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/aidot_cameras/g711.py +0 -0
  87. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/aidot_cameras/py.typed +0 -0
  88. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/python_aidot_cameras.egg-info/dependency_links.txt +0 -0
  89. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/python_aidot_cameras.egg-info/entry_points.txt +0 -0
  90. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/python_aidot_cameras.egg-info/top_level.txt +0 -0
  91. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/setup.cfg +0 -0
  92. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/tests/test_alarm_event.py +0 -0
  93. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/tests/test_backoff.py +0 -0
  94. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/tests/test_battery_skip_turn.py +0 -0
  95. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/tests/test_deferred_hardening.py +0 -0
  96. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/tests/test_device_user_info_cache.py +0 -0
  97. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/tests/test_dormant_camera_cleanup.py +0 -0
  98. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/tests/test_dtls_not_ready_burst.py +0 -0
  99. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/tests/test_dtls_pinning.py +0 -0
  100. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/tests/test_dtls_serve_decode_skip.py +0 -0
  101. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/tests/test_dtls_serve_open_timeout.py +0 -0
  102. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/tests/test_dtls_skip_signaling_wait.py +0 -0
  103. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/tests/test_dtls_slow_probe.py +0 -0
  104. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/tests/test_go2rtc.py +0 -0
  105. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/tests/test_go2rtc_cli.py +0 -0
  106. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/tests/test_h264_decode_rate_limit.py +0 -0
  107. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/tests/test_highport_nomination.py +0 -0
  108. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/tests/test_ice_config_cache.py +0 -0
  109. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/tests/test_idle_release_both_loops.py +0 -0
  110. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/tests/test_keepalive_renew.py +0 -0
  111. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/tests/test_key_restart_sdp.py +0 -0
  112. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/tests/test_keyframe_prompter.py +0 -0
  113. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/tests/test_lan_control.py +0 -0
  114. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/tests/test_logging_caps.py +0 -0
  115. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/tests/test_motion_poll.py +0 -0
  116. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/tests/test_mqtt_credential_refetch.py +0 -0
  117. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/tests/test_mqtt_credential_self_heal.py +0 -0
  118. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/tests/test_mqtt_password_not_persisted.py +0 -0
  119. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/tests/test_mqtt_session_reconnect.py +0 -0
  120. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/tests/test_narrow_pc_ice.py +0 -0
  121. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/tests/test_no_undefined_names.py +0 -0
  122. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/tests/test_offline_keepalive_pause.py +0 -0
  123. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/tests/test_open_fail_logger.py +0 -0
  124. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/tests/test_open_gate_delay.py +0 -0
  125. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/tests/test_peerid_reuse.py +0 -0
  126. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/tests/test_persistent_mqtt.py +0 -0
  127. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/tests/test_persistent_mqtt_robustness.py +0 -0
  128. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/tests/test_playback_tls.py +0 -0
  129. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/tests/test_retry_policy.py +0 -0
  130. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/tests/test_sdes_adaptive.py +0 -0
  131. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/tests/test_sdes_bridge_break_gate.py +0 -0
  132. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/tests/test_sdes_echo_wait_timeout.py +0 -0
  133. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/tests/test_sdes_fast_liveplay.py +0 -0
  134. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/tests/test_sdes_ffmpeg_stderr_log.py +0 -0
  135. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/tests/test_sdes_idle_release.py +0 -0
  136. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/tests/test_sdes_open_cleanup.py +0 -0
  137. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/tests/test_sdes_serve_audio.py +0 -0
  138. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/tests/test_sdes_serve_cmd.py +0 -0
  139. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/tests/test_sdes_sprop.py +0 -0
  140. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/tests/test_sdes_talk.py +0 -0
  141. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/tests/test_sdes_teardown_exit_log.py +0 -0
  142. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/tests/test_sdes_terminal_ack.py +0 -0
  143. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/tests/test_sdes_watchdog.py +0 -0
  144. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/tests/test_sdp_payload_type_narrowing.py +0 -0
  145. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/tests/test_security_hardening.py +0 -0
  146. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/tests/test_serializable_login_info.py +0 -0
  147. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/tests/test_serve_relay.py +0 -0
  148. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/tests/test_speak.py +0 -0
  149. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/tests/test_stream_cap.py +0 -0
  150. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/tests/test_stream_drain_ownership.py +0 -0
  151. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/tests/test_stream_idle.py +0 -0
  152. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/tests/test_stream_slot_cap.py +0 -0
  153. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/tests/test_stream_teardown.py +0 -0
  154. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/tests/test_talk.py +0 -0
  155. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/tests/test_terminal_ack.py +0 -0
  156. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/tests/test_token_file_robustness.py +0 -0
  157. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/tests/test_viewer_check_throttle.py +0 -0
  158. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/tests/test_viewer_presence.py +0 -0
  159. {python_aidot_cameras-0.12.16 → python_aidot_cameras-0.13.0}/tests/test_wait_or_event.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-aidot-cameras
3
- Version: 0.12.16
3
+ Version: 0.13.0
4
4
  Summary: Control AiDot/Leedarson WiFi lights and cameras (WebRTC streaming, two-way audio, PTZ, controls)
5
5
  Author-email: cbrightly <chris.brightly@gmail.com>
6
6
  License-Expression: MIT
@@ -11,7 +11,7 @@ Classifier: Operating System :: OS Independent
11
11
  Requires-Python: >=3.11
12
12
  Description-Content-Type: text/markdown
13
13
  License-File: LICENSE
14
- Requires-Dist: python-aidot==0.3.55
14
+ Requires-Dist: python-aidot<0.4,>=0.3.55
15
15
  Requires-Dist: aiohttp>=3.9
16
16
  Requires-Dist: paho-mqtt>=1.6.1
17
17
  Requires-Dist: cryptography>=42.0
@@ -171,12 +171,12 @@ audio, idle release, the sprop cache path) are documented in
171
171
  | `AIDOT_MAX_CONCURRENT_STREAMS` | Caps how many cameras stream at once. | `3` |
172
172
  | `AIDOT_FAST_CONNECT` | Enable LAN-direct "fast connect" (STUN-only, skips several cloud signaling waits) when truthy. On-LAN only - off-subnet/strict-NAT viewers must leave it off. | unset (off) |
173
173
  | `AIDOT_SDES_SKIP_TURN_PREALLOC` | Skip the SDES TURN relay pre-allocation (~2-3 s of cold-start latency) so signaling goes straight out with the host candidate. Faster on a LAN, at the cost of no relay fallback for a camera on a different segment / behind strict NAT. Experimental, opt-in (truthy = `1`/`true`/`yes`/`on`). | unset (off) |
174
- | `AIDOT_SDES_ADAPTIVE` | Adaptive fast-with-fallback for the SDES keepalive loop: try the fast path first and fall back to the full relay path if a fast attempt delivers no media. A per-device cache skips the fast attempt on later views once it has failed. Truthy value enables. | unset (off) |
174
+ | `AIDOT_SDES_ADAPTIVE` | Adaptive fast-with-fallback for the SDES keepalive loop: try the fast path first and fall back to the full relay path if a fast attempt delivers no media. A per-device cache skips the fast attempt on later views once it has failed. Truthy value enables. Ignored for battery cameras, where the fast path cannot win and its short grace truncates the cold start. | unset (off) |
175
175
  | `AIDOT_SDES_FAST_LIVEPLAY` | Don't block on the `livePlayResp` wait for eligible SDES cameras (~4.5 s faster cold start). Role-reversal models (A001064 PTZ) always excluded for correctness. **On by default**; set to `0`/`false`/`no`/`off` to disable. | enabled (on) |
176
176
  | `AIDOT_DTLS_FAST_LIVEPLAY` | The DTLS (A000088) analogue: skip the `livePlayReq`-echo and `livePlayResp` waits (the dominant LAN cold-start cost) while keeping the full ICE/TURN/DTLS handshake, so remote/relay viewing is unaffected. **On by default**; set to `0`/`false`/`no`/`off` to disable. | enabled (on) |
177
177
  | `AIDOT_PERSISTENT_MQTT` | Reuse ONE account-level persistent MQTT connection for commands, attribute fetches, and stream-open signaling (matching the official app) instead of connecting per operation. **On by default** (live soaks cut SDES NO_MEDIA from ~57% to ~11-19%); set to `0`/`false`/`no`/`off` to disable. | enabled (on) |
178
178
  | `AIDOT_SERVE_RELAY` | Hold the public stream port via an internal relay that proxies to ffmpeg, so the first (cold) view connects instead of failing while ffmpeg can't pre-bind the port. Set to `0` to serve ffmpeg directly. | `1` (enabled) |
179
- | `AIDOT_LIVESTREAM_PARAM` | Set to `0` to skip the cloud `liveStreamParam` pre-connect that provisions battery cameras' live-stream sessions before signaling (without it, battery cameras like the L2 models reject streaming with `-50019`). | `1` (enabled) |
179
+ | `AIDOT_LIVESTREAM_PARAM` | **No-op (ignored).** Formerly requested the cloud `liveStreamParam` pre-connect for battery cameras. That call provisions the camera toward AWS KVS, so it sends its media there instead of to this library and the live view negotiates and then shows nothing - and battery cameras were the only ones it applied to. Setting it (or `start_keepalive(live_stream_param=True)`) logs one warning and changes nothing. | ignored |
180
180
 
181
181
  ### Security hardening
182
182
 
@@ -140,12 +140,12 @@ audio, idle release, the sprop cache path) are documented in
140
140
  | `AIDOT_MAX_CONCURRENT_STREAMS` | Caps how many cameras stream at once. | `3` |
141
141
  | `AIDOT_FAST_CONNECT` | Enable LAN-direct "fast connect" (STUN-only, skips several cloud signaling waits) when truthy. On-LAN only - off-subnet/strict-NAT viewers must leave it off. | unset (off) |
142
142
  | `AIDOT_SDES_SKIP_TURN_PREALLOC` | Skip the SDES TURN relay pre-allocation (~2-3 s of cold-start latency) so signaling goes straight out with the host candidate. Faster on a LAN, at the cost of no relay fallback for a camera on a different segment / behind strict NAT. Experimental, opt-in (truthy = `1`/`true`/`yes`/`on`). | unset (off) |
143
- | `AIDOT_SDES_ADAPTIVE` | Adaptive fast-with-fallback for the SDES keepalive loop: try the fast path first and fall back to the full relay path if a fast attempt delivers no media. A per-device cache skips the fast attempt on later views once it has failed. Truthy value enables. | unset (off) |
143
+ | `AIDOT_SDES_ADAPTIVE` | Adaptive fast-with-fallback for the SDES keepalive loop: try the fast path first and fall back to the full relay path if a fast attempt delivers no media. A per-device cache skips the fast attempt on later views once it has failed. Truthy value enables. Ignored for battery cameras, where the fast path cannot win and its short grace truncates the cold start. | unset (off) |
144
144
  | `AIDOT_SDES_FAST_LIVEPLAY` | Don't block on the `livePlayResp` wait for eligible SDES cameras (~4.5 s faster cold start). Role-reversal models (A001064 PTZ) always excluded for correctness. **On by default**; set to `0`/`false`/`no`/`off` to disable. | enabled (on) |
145
145
  | `AIDOT_DTLS_FAST_LIVEPLAY` | The DTLS (A000088) analogue: skip the `livePlayReq`-echo and `livePlayResp` waits (the dominant LAN cold-start cost) while keeping the full ICE/TURN/DTLS handshake, so remote/relay viewing is unaffected. **On by default**; set to `0`/`false`/`no`/`off` to disable. | enabled (on) |
146
146
  | `AIDOT_PERSISTENT_MQTT` | Reuse ONE account-level persistent MQTT connection for commands, attribute fetches, and stream-open signaling (matching the official app) instead of connecting per operation. **On by default** (live soaks cut SDES NO_MEDIA from ~57% to ~11-19%); set to `0`/`false`/`no`/`off` to disable. | enabled (on) |
147
147
  | `AIDOT_SERVE_RELAY` | Hold the public stream port via an internal relay that proxies to ffmpeg, so the first (cold) view connects instead of failing while ffmpeg can't pre-bind the port. Set to `0` to serve ffmpeg directly. | `1` (enabled) |
148
- | `AIDOT_LIVESTREAM_PARAM` | Set to `0` to skip the cloud `liveStreamParam` pre-connect that provisions battery cameras' live-stream sessions before signaling (without it, battery cameras like the L2 models reject streaming with `-50019`). | `1` (enabled) |
148
+ | `AIDOT_LIVESTREAM_PARAM` | **No-op (ignored).** Formerly requested the cloud `liveStreamParam` pre-connect for battery cameras. That call provisions the camera toward AWS KVS, so it sends its media there instead of to this library and the live view negotiates and then shows nothing - and battery cameras were the only ones it applied to. Setting it (or `start_keepalive(live_stream_param=True)`) logs one warning and changes nothing. | ignored |
149
149
 
150
150
  ### Security hardening
151
151
 
@@ -0,0 +1,396 @@
1
+ """One place that knows which upstream ``python-aidot`` is installed.
2
+
3
+ Upstream ships two incompatible shapes of the same private API, and both are
4
+ live: Home Assistant core pins ``0.3.56`` while this library was written against
5
+ ``0.3.55``. They are not a forward/backward pair - ``0.3.56`` is a **revert** of
6
+ the refactor that ``0.3.54`` introduced and ``0.3.55`` carried, so the "old"
7
+ shape is also the current one:
8
+
9
+ 0.3.53 dict-based client, ``aes_utils``, ``login_const`` <- shape A
10
+ 0.3.54 typed dataclasses, ``api/``, ``utils/crypto`` <- shape B
11
+ 0.3.55 shape B
12
+ 0.3.56 shape A again (plus the ``models/`` package)
13
+
14
+ Because upstream has flip-flopped once, neither shape can be assumed permanent,
15
+ so both are supported rather than tracked. Every difference is resolved here,
16
+ once, at import time; no other module in this package may branch on the upstream
17
+ version. If you find yourself adding a second ``try: import aidot...`` anywhere
18
+ else, put it here instead.
19
+
20
+ The detection is by capability (does the name import?), never by parsing a
21
+ version string: upstream shipped shape A under both ``0.3.53`` and ``0.3.56``
22
+ with a five-day excursion in between, so a version comparison would encode the
23
+ excursion rather than the shape.
24
+ """
25
+
26
+ from enum import IntEnum
27
+ from typing import Any, Optional
28
+
29
+ # --------------------------------------------------------------------------- #
30
+ # Shape detection
31
+ # --------------------------------------------------------------------------- #
32
+
33
+ try: # shape B (0.3.54 - 0.3.55): typed account/device dataclasses + CloudApi
34
+ from aidot.models.auth_model import UserInformation as _UserInformation
35
+ HAS_TYPED_ACCOUNT = True
36
+ except ImportError: # shape A (<=0.3.53, >=0.3.56): plain dicts
37
+ _UserInformation = None
38
+ HAS_TYPED_ACCOUNT = False
39
+
40
+ #: True when upstream exposes the typed account layer (``user_info``,
41
+ #: ``_cloud_api``, ``UserInformation``). Prefer the named helpers below to
42
+ #: branching on this directly.
43
+ UPSTREAM_SHAPE = "typed" if HAS_TYPED_ACCOUNT else "dict"
44
+
45
+ #: Upstream's account record class, or ``None`` on shape A where the account is
46
+ #: a plain dict. Only used as a type annotation and an isinstance guard.
47
+ UserInformation = _UserInformation
48
+
49
+
50
+ # --------------------------------------------------------------------------- #
51
+ # Constants: four moved from ``aidot.const`` to ``aidot.login_const``
52
+ # --------------------------------------------------------------------------- #
53
+
54
+ try: # shape B
55
+ from aidot.const import (
56
+ API_URL_TEMPLATE,
57
+ APP_ID,
58
+ DEFAULT_REGION,
59
+ PUBLIC_KEY_PEM,
60
+ )
61
+ except ImportError: # shape A
62
+ from aidot.login_const import (
63
+ API_URL_TEMPLATE,
64
+ APP_ID,
65
+ DEFAULT_REGION,
66
+ PUBLIC_KEY_PEM,
67
+ )
68
+
69
+
70
+ # --------------------------------------------------------------------------- #
71
+ # Crypto: ``aidot.utils.crypto`` -> ``aidot.aes_utils``
72
+ # --------------------------------------------------------------------------- #
73
+
74
+ try: # shape B
75
+ from aidot.utils.crypto import (
76
+ aes_decrypt,
77
+ aes_decrypt_to_json,
78
+ aes_encrypt,
79
+ )
80
+ except ImportError: # shape A
81
+ from aidot.aes_utils import (
82
+ aes_decrypt,
83
+ aes_decrypt_to_json,
84
+ aes_encrypt,
85
+ )
86
+
87
+
88
+ def rsa_encrypt(message: str, public_key: Any = None) -> str:
89
+ """RSA/PKCS1v15 encrypt ``message``, returned base64.
90
+
91
+ Implemented here rather than re-exported. Shape B had
92
+ ``aidot.utils.crypto.rsa_encrypt(message, public_key)``; shape A replaced it
93
+ with ``aidot.client.rsa_password_encrypt(message)``, which bakes the key in
94
+ and takes one argument. Neither signature is available on both shapes, and
95
+ ``aidot_cameras.crypto.rsa_encrypt`` is public surface the integration repo
96
+ may import (see the back-compat rule in CLAUDE.md), so the two-argument form
97
+ is kept and satisfied locally. ``public_key`` defaults to upstream's
98
+ ``PUBLIC_KEY_PEM``, which is the only key any caller ever passed.
99
+ """
100
+ from cryptography.hazmat.backends import default_backend
101
+ from cryptography.hazmat.primitives import serialization
102
+ from cryptography.hazmat.primitives.asymmetric import padding
103
+ import base64
104
+
105
+ if public_key is None:
106
+ public_key = PUBLIC_KEY_PEM
107
+ loaded = serialization.load_pem_public_key(
108
+ public_key.encode() if isinstance(public_key, str) else public_key,
109
+ backend=default_backend(),
110
+ )
111
+ encrypted = loaded.encrypt(message.encode("utf-8"), padding.PKCS1v15())
112
+ return base64.b64encode(encrypted).decode("utf-8")
113
+
114
+
115
+ # --------------------------------------------------------------------------- #
116
+ # Device record: ``DeviceModel`` (shape B) / ``DeviceInformation`` (shape A)
117
+ # --------------------------------------------------------------------------- #
118
+
119
+ try: # shape B
120
+ from aidot.models.device_model import DeviceModel as DeviceRecord
121
+ except ImportError: # shape A
122
+ from aidot.models.device_model import DeviceInformation as DeviceRecord
123
+
124
+ #: The typed cloud device record. Both shapes expose ``from_json`` /
125
+ #: ``to_dict`` and the core field set this package reads (``id``, ``modelId``,
126
+ #: ``aesKey``, ``password``, ``product``); shape A drops ~20 fields shape B
127
+ #: carried and defaults everything to ``None`` rather than ``""``/empty, so
128
+ #: never rely on a nested default being present - check for ``None`` first.
129
+ DeviceModel = DeviceRecord
130
+
131
+
132
+ # --------------------------------------------------------------------------- #
133
+ # DeviceState: upstream's LAN session enum, deleted in shape A
134
+ # --------------------------------------------------------------------------- #
135
+
136
+ try: # shape B
137
+ from aidot.device_client import DeviceState as _DeviceState
138
+ DEVICE_STATE_IS_UPSTREAMS = True
139
+ except ImportError: # shape A: no state machine, just two booleans
140
+ DEVICE_STATE_IS_UPSTREAMS = False
141
+
142
+ class _DeviceState(IntEnum):
143
+ """Stand-in for upstream's deleted ``DeviceState``.
144
+
145
+ Shape A tracks the LAN session with ``_connecting`` /
146
+ ``_connect_and_login`` booleans instead of an enum. The enum is still
147
+ re-exported from ``aidot_cameras.device_client`` because the Home
148
+ Assistant integration annotates and compares against it, and dropping a
149
+ public name outright would break it at import.
150
+
151
+ Members and values are copied from 0.3.55 so a stored or hard-coded
152
+ comparison keeps its meaning. On shape A nothing upstream ever
153
+ *produces* one of these - use ``device_session_authenticated()`` rather
154
+ than comparing a client's private state against it.
155
+ """
156
+
157
+ IDLE = 0
158
+ INITIALIZING = 1
159
+ CONNECTING = 2
160
+ CONNECTED = 3
161
+ CONNECTION_FAILED = 4
162
+ AUTHENTICATING = 5
163
+ AUTHENTICATED = 6
164
+ AUTHENTICATION_FAILED = 7
165
+ DISCONNECTED = 8
166
+
167
+ DeviceState = _DeviceState
168
+
169
+
170
+ def device_session_authenticated(device_client: Any) -> bool:
171
+ """Whether a device client's LAN session is logged in, on either shape.
172
+
173
+ Shape B exposes ``_state == DeviceState.AUTHENTICATED``; shape A replaced
174
+ that with the ``connect_and_login`` property. Consumers that only want the
175
+ answer should call this instead of touching either private attribute.
176
+ """
177
+ if DEVICE_STATE_IS_UPSTREAMS:
178
+ return getattr(device_client, "_state", None) == DeviceState.AUTHENTICATED
179
+ return bool(getattr(device_client, "connect_and_login", False))
180
+
181
+
182
+ # --------------------------------------------------------------------------- #
183
+ # Reconnect timer handle: ``_reconnect_timer`` -> ``_reconnect_handle``
184
+ # --------------------------------------------------------------------------- #
185
+
186
+ #: Attribute names a pending reconnect may be parked under, newest first.
187
+ #: ``CameraClient.async_close`` cancels whichever exists so a closed account
188
+ #: cannot re-open a TCP connection ~45 s later (upstream cancels neither).
189
+ RECONNECT_HANDLE_ATTRS = ("_reconnect_handle", "_reconnect_timer")
190
+
191
+
192
+ def cancel_pending_reconnect(device_client: Any) -> bool:
193
+ """Cancel a device client's pending reconnect, whichever shape it uses.
194
+
195
+ Returns True if something was cancelled. Deliberately not a silent
196
+ ``getattr(x, "_reconnect_timer", None)``: that spelling exists only on shape
197
+ B, so on shape A it would find nothing, cancel nothing, and report success -
198
+ the leak fix would go quiet instead of failing. Callers that care should
199
+ log the False case.
200
+ """
201
+ for attr in RECONNECT_HANDLE_ATTRS:
202
+ handle = getattr(device_client, attr, None)
203
+ if handle is not None:
204
+ handle.cancel()
205
+ return True
206
+ return False
207
+
208
+
209
+ # --------------------------------------------------------------------------- #
210
+ # Account HTTP surface: ``CloudApi`` (shape B) -> client methods (shape A)
211
+ # --------------------------------------------------------------------------- #
212
+
213
+ async def api_get_houses(client: Any) -> Optional[list]:
214
+ """List the account's houses."""
215
+ if HAS_TYPED_ACCOUNT:
216
+ return await client._cloud_api.get_houses()
217
+ return await client.async_get_houses()
218
+
219
+
220
+ async def api_get_devices(client: Any, house_id: str) -> Optional[list]:
221
+ """List one house's devices."""
222
+ if HAS_TYPED_ACCOUNT:
223
+ return await client._cloud_api.get_devices(house_id)
224
+ return await client.async_get_devices(house_id)
225
+
226
+
227
+ async def api_get_products(client: Any, product_ids: str) -> Optional[list]:
228
+ """Fetch product metadata for a comma-joined id list."""
229
+ if HAS_TYPED_ACCOUNT:
230
+ return await client._cloud_api.get_products(product_ids)
231
+ return await client.async_get_products(product_ids)
232
+
233
+
234
+ async def api_refresh_token(client: Any) -> Any:
235
+ """Exchange the refresh token for a new access token.
236
+
237
+ Both shapes return the response body and raise ``AidotAuthFailed`` when the
238
+ refresh token itself is rejected; shape B additionally returns ``None`` on
239
+ some non-auth failures, so callers must treat a falsy return as "no token".
240
+ """
241
+ if HAS_TYPED_ACCOUNT:
242
+ return await client._cloud_api.refresh_token()
243
+ return await client.async_refresh_token()
244
+
245
+
246
+ # --------------------------------------------------------------------------- #
247
+ # Account fields: ``user_info`` dataclass (shape B) -> plain attrs (shape A)
248
+ # --------------------------------------------------------------------------- #
249
+
250
+ def account_region(client: Any) -> str:
251
+ """The account's API region ("us", "eu", ...)."""
252
+ if HAS_TYPED_ACCOUNT:
253
+ return getattr(client.user_info, "region", "") or ""
254
+ return getattr(client, "_region", "") or ""
255
+
256
+
257
+ def account_refresh_token(client: Any) -> str:
258
+ """The stored refresh token, or "" when there is none."""
259
+ if HAS_TYPED_ACCOUNT:
260
+ return getattr(client.user_info, "refreshToken", "") or ""
261
+ from .const import CONF_REFRESH_TOKEN
262
+ return (client.login_info or {}).get(CONF_REFRESH_TOKEN) or ""
263
+
264
+
265
+ def account_token_ttl(client: Any, default: int = 7200) -> int:
266
+ """Access-token lifetime in seconds, defaulting to the server's 7200."""
267
+ if HAS_TYPED_ACCOUNT:
268
+ raw = getattr(client.user_info, "expiresIn", 0)
269
+ else:
270
+ raw = (client.login_info or {}).get("expiresIn", 0)
271
+ try:
272
+ return int(raw or default)
273
+ except (TypeError, ValueError):
274
+ return default
275
+
276
+
277
+ def account_record(client: Any) -> Any:
278
+ """What to pass as a device client's second constructor argument.
279
+
280
+ Shape B wants the ``UserInformation`` dataclass; shape A wants the raw
281
+ ``login_info`` dict. See ``device_client_args``.
282
+ """
283
+ if HAS_TYPED_ACCOUNT:
284
+ return client.user_info
285
+ return client.login_info
286
+
287
+
288
+ def device_client_args(
289
+ device_record: Any,
290
+ raw_device: dict,
291
+ typed_account: Any,
292
+ login_info: Optional[dict],
293
+ ) -> tuple:
294
+ """The ``(device, user_info)`` pair upstream's ``DeviceClient`` expects.
295
+
296
+ Shape B: ``(DeviceModel, UserInformation)`` - typed dataclasses.
297
+ Shape A: ``(dict, dict)`` - the raw cloud records, natively.
298
+
299
+ Shape A taking raw dicts is why this package carried ``raw_device`` /
300
+ ``login_info`` alongside the typed models in the first place: the typed
301
+ round trip drops every camera-only field. On shape A the originals go
302
+ straight through and nothing is lost.
303
+
304
+ Takes the four values rather than the account client, because the only
305
+ caller (``CameraDeviceClient.__init__``) is handed them directly and has no
306
+ client reference. ``typed_account`` may be a ``UserInformation`` or, from
307
+ an older caller, already a dict - on shape A a dict ``login_info`` wins and
308
+ a dict ``typed_account`` is the fallback, so both call styles work.
309
+ """
310
+ if HAS_TYPED_ACCOUNT:
311
+ return (device_record, typed_account)
312
+ account = login_info if isinstance(login_info, dict) else None
313
+ if account is None:
314
+ account = typed_account if isinstance(typed_account, dict) else {}
315
+ if not isinstance(typed_account, dict) and hasattr(typed_account, "to_dict"):
316
+ account = typed_account.to_dict()
317
+ return (raw_device, account)
318
+
319
+
320
+ #: True when upstream's ``DeviceClient`` exposes a ``read_data`` seam - one
321
+ #: decrypted frame at a time, which this package hooks to recover camera-only
322
+ #: attribute keys that upstream's typed ``DeviceAttr`` model drops. Shape A
323
+ #: inlines the read into ``receive_data``, so there is no seam and the raw JSON
324
+ #: never escapes. See ``CameraDeviceClient._notify_status_update``.
325
+ try:
326
+ from aidot.device_client import DeviceClient as _DeviceClient
327
+ HAS_READ_DATA_SEAM = callable(getattr(_DeviceClient, "read_data", None))
328
+ except ImportError: # pragma: no cover - upstream is a hard dependency
329
+ HAS_READ_DATA_SEAM = False
330
+
331
+
332
+ # --------------------------------------------------------------------------- #
333
+ # Discovery: static ``Discover`` (shape B) -> instance ``Discover`` (shape A)
334
+ # --------------------------------------------------------------------------- #
335
+
336
+ #: True when upstream's ``Discover`` keeps discovered addresses in the
337
+ #: process-wide ``DISCOVERED_DEVICE`` class dict. Shape A made discovery
338
+ #: instance-scoped (``self._discover.discovered_device``), which is the shape
339
+ #: this package's ``CameraDiscover`` already presents - so on shape A the map
340
+ #: no longer has to be shared through the base class to reach upstream's
341
+ #: ``get_device_client``.
342
+ try:
343
+ from aidot.discover import Discover as _Discover
344
+ HAS_SHARED_DISCOVERY_MAP = isinstance(
345
+ getattr(_Discover, "DISCOVERED_DEVICE", None), dict
346
+ )
347
+ except ImportError: # pragma: no cover - upstream is a hard dependency
348
+ HAS_SHARED_DISCOVERY_MAP = False
349
+
350
+
351
+ def broadcast_protocol_args(callback: Any, user_id: str) -> tuple:
352
+ """Positional args for upstream's ``BroadcastProtocol.__init__``.
353
+
354
+ Shape B takes ``(callback)`` and has no notion of the account id; shape A
355
+ takes ``(callback, user_id)`` and uses it to build the request payload.
356
+ """
357
+ import inspect
358
+ from aidot.discover import BroadcastProtocol
359
+
360
+ params = list(inspect.signature(BroadcastProtocol.__init__).parameters)
361
+ return (callback, user_id) if "user_id" in params else (callback,)
362
+
363
+
364
+ #: Names this module intends as its public surface within the package.
365
+ __all__ = [
366
+ "API_URL_TEMPLATE",
367
+ "APP_ID",
368
+ "DEFAULT_REGION",
369
+ "DEVICE_STATE_IS_UPSTREAMS",
370
+ "HAS_READ_DATA_SEAM",
371
+ "HAS_SHARED_DISCOVERY_MAP",
372
+ "HAS_TYPED_ACCOUNT",
373
+ "PUBLIC_KEY_PEM",
374
+ "RECONNECT_HANDLE_ATTRS",
375
+ "UPSTREAM_SHAPE",
376
+ "DeviceModel",
377
+ "DeviceRecord",
378
+ "DeviceState",
379
+ "UserInformation",
380
+ "account_record",
381
+ "account_refresh_token",
382
+ "account_region",
383
+ "account_token_ttl",
384
+ "aes_decrypt",
385
+ "aes_decrypt_to_json",
386
+ "aes_encrypt",
387
+ "api_get_devices",
388
+ "api_get_houses",
389
+ "api_get_products",
390
+ "api_refresh_token",
391
+ "broadcast_protocol_args",
392
+ "cancel_pending_reconnect",
393
+ "device_client_args",
394
+ "device_session_authenticated",
395
+ "rsa_encrypt",
396
+ ]