TLS client management and other changes for clip upload in Push AV (#40840)
* Initial changes for getting TLS client management delegate to PAVST delegate
Author: Tushant Yadav <t.yadav@samsung.com>
Signed-off-by: Raveendra Karu <r.karu@samsung.com>
* - Added TLS certificates to Push AV transport manager.
- Updated zone management and triggers for PushAV.
- Updated zone ID validation logic.
- Implemented bandwidth limit API.
- Updated Push AV Stream Transport end event generation logic.
Co-author: Sambhavi <sambhavi.1@samsung.com>
Co-author: Tushant Yadav <t.yadav@samsung.com>
Signed-off-by: Raveendra Karu <r.karu@samsung.com>
* - Replace file-based TLS certificates with in-memory buffer management
- Add SetOnRecorderStartedCallback functionality across transport classes
- Generate PushTransportBeginEvent when recorder starts streaming
- Update PushAVUploader to support both path and buffer-based certificates
- Refactor certificate structures for better type safety
- Add certificate table accessor methods for improved encapsulation
Co-author: Tushant Yadav <t.yadav@samsung.com>
Co-author: Sambhavi <sambhavi.1@samsung.com>
Signed-off-by: Raveendra Karu <r.karu@samsung.com>
* Updated url handling and clip segment duration
Author: Tushant Yadav <t.yadav@samsung.com>
Signed-off-by: Raveendra Karu <r.karu@samsung.com>
* Updated certificate handling in push AV stream transport with improved credential and crypto support.
Author: Tushant Yadav <t.yadav@samsung.com>
Signed-off-by: Raveendra Karu <r.karu@samsung.com>
* Restyled by clang-format
* Add recorder callbacks and refactor TLS cert handling
Author: Tushant Yadav <t.yadav@samsung.com>
Signed-off-by: Raveendra Karu <r.karu@samsung.com>
* Improved Null Checks and Certificate Buffer Allocation
1. Add null check for in to prevent null pointer dereference.
2. Replace stack-allocated certificate buffers with heap-allocated ones to reduce stack usage and handle large certificate buffers efficiently.
Author: Tushant Yadav <t.yadav@samsung.com>
Signed-off-by: Raveendra Karu <r.karu@samsung.com>
* Add override keywords to SetOnRecorderStoppedCallback and SetOnRecorderStartedCallback functions in push-av-stream-manager.h to properly implement interface methods.
* Fix TLS Key handling in push AV stream transport
Author: Tushant Yadav <t.yadav@samsung.com>
Signed-off-by: Raveendra Karu <r.karu@samsung.com>
* resolved lint error
* Add intermediate certificates support to TLS handling in PushAV
Author: Tushant Yadav <t.yadav@samsung.com>
Signed-off-by: Raveendra Karu <r.karu@samsung.com>
* Updated log
* Update PushAV event generation logic to avoid dependency inversion violation + other minor fixes
Author: Tushant Yadav <t.yadav@samsung.com>
Signed-off-by: Raveendra Karu <r.karu@samsung.com>
* Build fail fix
Signed-off-by: Raveendra Karu <r.karu@samsung.com>
* Add GetAllocatedVideoStreams and GetAllocatedAudioStreams methods to CameraAVStreamManager class to provide direct access to allocated streams. Update PushAvStreamTransportManager to use the new delegate interface instead of accessing streams through HAL interface.
Author: Tushant Yadav <t.yadav@samsung.com>
Signed-off-by: Raveendra Karu <r.karu@samsung.com>
* Add getters for allocated AV streams in CameraAVStreamManager
Signed-off-by: Raveendra Karu <r.karu@samsung.com>
---------
Signed-off-by: Raveendra Karu <r.karu@samsung.com>
Co-authored-by: Restyled.io <commits@restyled.io>
Co-authored-by: Robert Szewczyk <szewczyk@google.com>
diff --git a/examples/all-clusters-app/all-clusters-common/include/camera-av-stream-delegate-impl.h b/examples/all-clusters-app/all-clusters-common/include/camera-av-stream-delegate-impl.h
index 509e8ed..43f337b 100644
--- a/examples/all-clusters-app/all-clusters-common/include/camera-av-stream-delegate-impl.h
+++ b/examples/all-clusters-app/all-clusters-common/include/camera-av-stream-delegate-impl.h
@@ -106,6 +106,10 @@
CHIP_ERROR OnTransportReleaseAudioVideoStreams(uint16_t audioStreamID, uint16_t videoStreamID) override;
+ const std::vector<chip::app::Clusters::CameraAvStreamManagement::VideoStreamStruct> & GetAllocatedVideoStreams() const override;
+
+ const std::vector<chip::app::Clusters::CameraAvStreamManagement::AudioStreamStruct> & GetAllocatedAudioStreams() const override;
+
void Init();
CameraAVStreamManager() = default;
@@ -114,10 +118,11 @@
// static inline CameraAVStreamManager & GetInstance() { return sCameraAVStreamMgrInstance; }
private:
- std::vector<VideoStream> videoStreams; // Vector to hold available video streams
- std::vector<AudioStream> audioStreams; // Vector to hold available audio streams
- std::vector<SnapshotStream> snapshotStreams; // Vector to hold available snapshot streams
-
+ std::vector<VideoStream> videoStreams; // Vector to hold available video streams
+ std::vector<AudioStream> audioStreams; // Vector to hold available audio streams
+ std::vector<SnapshotStream> snapshotStreams; // Vector to hold available snapshot streams
+ std::vector<VideoStreamStruct> videoStreamStructs; // Vector to hold allocated video streams
+ std::vector<AudioStreamStruct> audioStreamStructs; // Vector to hold allocated audio streams
void InitializeAvailableVideoStreams();
void InitializeAvailableAudioStreams();
void InitializeAvailableSnapshotStreams();
diff --git a/examples/all-clusters-app/all-clusters-common/include/push-av-stream-transport-delegate-impl.h b/examples/all-clusters-app/all-clusters-common/include/push-av-stream-transport-delegate-impl.h
index 563f39f..05b16a0 100644
--- a/examples/all-clusters-app/all-clusters-common/include/push-av-stream-transport-delegate-impl.h
+++ b/examples/all-clusters-app/all-clusters-common/include/push-av-stream-transport-delegate-impl.h
@@ -86,6 +86,16 @@
CHIP_ERROR PersistentAttributesLoadedCallback() override;
+ void SetTLSCerts(Tls::CertificateTable::BufferedClientCert & clientCertEntry,
+ Tls::CertificateTable::BufferedRootCert & rootCertEntry) override
+ {
+ // Handle TLS certificates if needed for implementation
+ }
+ void SetPushAvStreamTransportServer(PushAvStreamTransportServerLogic * serverLogic) override
+ {
+ // Store pointer to server logic if needed for implementation
+ }
+
void Init();
PushAvStreamTransportManager() = default;
~PushAvStreamTransportManager() = default;
diff --git a/examples/all-clusters-app/all-clusters-common/src/camera-av-stream-delegate-impl.cpp b/examples/all-clusters-app/all-clusters-common/src/camera-av-stream-delegate-impl.cpp
index e587eea..00c4513 100644
--- a/examples/all-clusters-app/all-clusters-common/src/camera-av-stream-delegate-impl.cpp
+++ b/examples/all-clusters-app/all-clusters-common/src/camera-av-stream-delegate-impl.cpp
@@ -304,6 +304,16 @@
return CHIP_NO_ERROR;
}
+const std::vector<VideoStreamStruct> & CameraAVStreamManager::GetAllocatedVideoStreams() const
+{
+ return videoStreamStructs;
+}
+
+const std::vector<AudioStreamStruct> & CameraAVStreamManager::GetAllocatedAudioStreams() const
+{
+ return audioStreamStructs;
+}
+
void CameraAVStreamManager::InitializeAvailableVideoStreams()
{
// Example initialization with different codecs
diff --git a/examples/camera-app/camera-common/src/camera-app.cpp b/examples/camera-app/camera-common/src/camera-app.cpp
index 0b2fcae..96d4e0a 100644
--- a/examples/camera-app/camera-common/src/camera-app.cpp
+++ b/examples/camera-app/camera-common/src/camera-app.cpp
@@ -16,6 +16,7 @@
* limitations under the License.
*/
#include "camera-app.h"
+#include "tls-certificate-management-instance.h"
#include "tls-client-management-instance.h"
#include <app/clusters/push-av-stream-transport-server/CodegenIntegration.h>
@@ -50,6 +51,8 @@
Clusters::PushAvStreamTransport::SetTLSClientManagementDelegate(mEndpoint,
&Clusters::TlsClientManagementCommandDelegate::GetInstance());
+ Clusters::PushAvStreamTransport::SetTlsCertificateManagementDelegate(
+ mEndpoint, &Clusters::TlsCertificateManagementCommandDelegate::getInstance());
// Fetch all initialization parameters for CameraAVStreamMgmt Server
BitFlags<CameraAvStreamManagement::Feature> avsmFeatures;
BitFlags<CameraAvStreamManagement::OptionalAttribute> avsmOptionalAttrs;
diff --git a/examples/camera-app/linux/include/clusters/camera-avstream-mgmt/camera-av-stream-manager.h b/examples/camera-app/linux/include/clusters/camera-avstream-mgmt/camera-av-stream-manager.h
index 25d04b4..4d4cae2 100644
--- a/examples/camera-app/linux/include/clusters/camera-avstream-mgmt/camera-av-stream-manager.h
+++ b/examples/camera-app/linux/include/clusters/camera-avstream-mgmt/camera-av-stream-manager.h
@@ -98,6 +98,10 @@
CHIP_ERROR OnTransportReleaseAudioVideoStreams(uint16_t audioStreamID, uint16_t videoStreamID) override;
+ const std::vector<chip::app::Clusters::CameraAvStreamManagement::VideoStreamStruct> & GetAllocatedVideoStreams() const override;
+
+ const std::vector<chip::app::Clusters::CameraAvStreamManagement::AudioStreamStruct> & GetAllocatedAudioStreams() const override;
+
CameraAVStreamManager() = default;
~CameraAVStreamManager() = default;
diff --git a/examples/camera-app/linux/include/clusters/push-av-stream-transport/push-av-stream-manager.h b/examples/camera-app/linux/include/clusters/push-av-stream-transport/push-av-stream-manager.h
index b4f9e2a..a47adeb 100644
--- a/examples/camera-app/linux/include/clusters/push-av-stream-transport/push-av-stream-manager.h
+++ b/examples/camera-app/linux/include/clusters/push-av-stream-transport/push-av-stream-manager.h
@@ -19,11 +19,15 @@
#pragma once
#include <app-common/zap-generated/cluster-enums.h>
#include <app/clusters/push-av-stream-transport-server/push-av-stream-transport-cluster.h>
-
+#include <app/clusters/push-av-stream-transport-server/push-av-stream-transport-logic.h>
+#include <app/clusters/tls-certificate-management-server/tls-certificate-management-server.h>
#include <camera-device-interface.h>
+#include <credentials/CHIPCert.h>
+#include <crypto/CHIPCryptoPAL.h>
+#include <functional>
+#include <iomanip>
#include <media-controller.h>
#include <pushav-transport.h>
-
#include <unordered_map>
#include <vector>
@@ -52,6 +56,7 @@
void Init();
void SetMediaController(MediaController * mediaController);
void SetCameraDevice(CameraDeviceInterface * cameraDevice);
+ void SetPushAvStreamTransportServer(PushAvStreamTransportServerLogic * server) override;
// Add missing override keywords and fix signatures
Protocols::InteractionModel::Status AllocatePushTransport(const TransportOptionsStruct & transportOptions,
@@ -69,6 +74,9 @@
const uint16_t connectionID, TriggerActivationReasonEnum activationReason,
const Optional<Structs::TransportMotionTriggerTimeControlStruct::DecodableType> & timeControl) override;
+ void SetTLSCerts(Tls::CertificateTable::BufferedClientCert & clientCertEntry,
+ Tls::CertificateTable::BufferedRootCert & rootCertEntry) override;
+
bool ValidateUrl(const std::string & url) override;
bool ValidateStreamUsage(StreamUsageEnum streamUsage) override;
@@ -99,15 +107,34 @@
CHIP_ERROR PersistentAttributesLoadedCallback() override;
+ void OnZoneTriggeredEvent(uint16_t zoneId);
+
private:
std::vector<PushAvStream> pushavStreams;
- MediaController * mMediaController = nullptr;
- CameraDeviceInterface * mCameraDevice = nullptr;
+ MediaController * mMediaController = nullptr;
+ CameraDeviceInterface * mCameraDevice = nullptr;
+ PushAvStreamTransportServerLogic * mPushAvStreamTransportServer = nullptr;
AudioStreamStruct mAudioStreamParams;
VideoStreamStruct mVideoStreamParams;
std::unordered_map<uint16_t, std::unique_ptr<PushAVTransport>> mTransportMap; // map for the transport objects
std::unordered_map<uint16_t, TransportOptionsStruct> mTransportOptionsMap; // map for the transport options
+
+ double mTotalUsedBandwidthMbps = 0.0; // Tracks the total bandwidth used by all active transports
+
+ std::vector<uint8_t> mBufferRootCert;
+ std::vector<uint8_t> mBufferClientCert;
+ std::vector<uint8_t> mBufferClientCertKey;
+ std::vector<std::vector<uint8_t>> mBufferIntermediateCerts;
+
+ /**
+ * @brief Calculates the total bandwidth in Mbps for the given video and audio stream IDs.
+ * @param videoStreamId Optional nullable video stream ID.
+ * @param audioStreamId Optional nullable audio stream ID.
+ * @param outBandwidthMbps Output parameter for the calculated bandwidth in Mbps.
+ */
+ void GetBandwidthForStreams(const Optional<DataModel::Nullable<uint16_t>> & videoStreamId,
+ const Optional<DataModel::Nullable<uint16_t>> & audioStreamId, double & outBandwidthMbps);
};
} // namespace PushAvStreamTransport
diff --git a/examples/camera-app/linux/include/pushav-clip-recorder.h b/examples/camera-app/linux/include/pushav-clip-recorder.h
index a1ab9b8..d5221fc 100644
--- a/examples/camera-app/linux/include/pushav-clip-recorder.h
+++ b/examples/camera-app/linux/include/pushav-clip-recorder.h
@@ -18,10 +18,12 @@
#pragma once
#include "pushav-uploader.h"
+#include <app/clusters/push-av-stream-transport-server/push-av-stream-transport-logic.h>
#include <algorithm>
#include <atomic>
#include <condition_variable>
+#include <functional>
#include <memory>
#include <mutex>
#include <queue>
@@ -70,7 +72,8 @@
uint32_t mMaxClipDuration; ///< Maximum clip duration in seconds
uint16_t mInitialDuration; ///< Initial clip duration in seconds
uint16_t mAugmentationDuration; ///< Duration increment on motion detect
- uint16_t mChunkDuration; ///< Segment duration in seconds
+ uint16_t mChunkDuration; ///< Chunk duration milliseconds
+ uint16_t mSegmentDuration; ///< Segment duration in milliseconds
uint16_t mBlindDuration; ///< Duration without recording after motion stop
uint16_t mPreRollLength; ///< Pre-roll length in seconds
std::string mRecorderId; ///< Unique recorder identifier
@@ -138,6 +141,22 @@
*/
void PushPacket(const char * data, size_t size, bool isVideo);
+ void SetOnStopCallback(std::function<void()> cb) { mOnStopCallback = std::move(cb); }
+
+ // Set the cluster server reference for direct API calls
+ void SetPushAvStreamTransportServer(chip::app::Clusters::PushAvStreamTransportServerLogic * server)
+ {
+ mPushAvStreamTransportServer = server;
+ }
+
+ void SetConnectionInfo(uint16_t connectionID, chip::app::Clusters::PushAvStreamTransport::TransportTriggerTypeEnum triggerType,
+ chip::Optional<chip::app::Clusters::PushAvStreamTransport::TriggerActivationReasonEnum> reasonType)
+ {
+ mConnectionID = connectionID;
+ mTriggerType = triggerType;
+ mReasonType = reasonType;
+ }
+
std::atomic<bool> mDeinitializeRecorder{ false }; ///< Deinitialization flag
ClipInfoStruct mClipInfo; ///< Clip configuration parameters
void SetRecorderStatus(bool status); ///< Sets the recorder status
@@ -153,6 +172,8 @@
VideoInfoStruct mVideoInfo; ///< Video stream parameters
/// @}
+ std::function<void()> mOnStopCallback;
+
AVFormatContext * mFormatContext;
AVFormatContext * mInputFormatContext;
AVStream * mVideoStream;
@@ -176,6 +197,12 @@
PushAVUploader * mUploader;
+ // Cluster server reference for direct API calls
+ chip::app::Clusters::PushAvStreamTransportServerLogic * mPushAvStreamTransportServer = nullptr;
+ uint16_t mConnectionID = 0;
+ chip::app::Clusters::PushAvStreamTransport::TransportTriggerTypeEnum mTriggerType;
+ chip ::Optional<chip::app::Clusters::PushAvStreamTransport::TriggerActivationReasonEnum> mReasonType;
+
/// @name Internal Methods
/// @{
bool FileExists(const std::string & path);
diff --git a/examples/camera-app/linux/include/pushav-transport/pushav-transport.h b/examples/camera-app/linux/include/pushav-transport/pushav-transport.h
index a685d93..68646fe 100644
--- a/examples/camera-app/linux/include/pushav-transport/pushav-transport.h
+++ b/examples/camera-app/linux/include/pushav-transport/pushav-transport.h
@@ -22,7 +22,6 @@
#include "pushav-clip-recorder.h"
#include "transport.h"
#include "uploader/pushav-uploader.h"
-
#include <app-common/zap-generated/attributes/Accessors.h>
#include <app-common/zap-generated/cluster-enums.h>
#include <app-common/zap-generated/cluster-objects.h>
@@ -31,6 +30,8 @@
#include <app/AttributeAccessInterface.h>
#include <app/CommandHandlerInterface.h>
#include <app/clusters/push-av-stream-transport-server/constants.h>
+#include <app/clusters/push-av-stream-transport-server/push-av-stream-transport-logic.h>
+#include <functional>
#include <memory>
#include <protocols/interaction_model/StatusCode.h>
#include <thread>
@@ -68,8 +69,8 @@
// Set Transport status
void SetTransportStatus(chip::app::Clusters::PushAvStreamTransport::TransportStatusEnum status);
- void TriggerTransport(chip::app::Clusters::PushAvStreamTransport::TriggerActivationReasonEnum activationReason);
-
+ void TriggerTransport(chip::app::Clusters::PushAvStreamTransport::TriggerActivationReasonEnum activationReason, int zoneId = -1,
+ int sensitivity = 5);
// Get Transport status
bool GetTransportStatus()
{
@@ -84,22 +85,54 @@
bool HandleTriggerDetected();
void InitializeRecorder();
+
bool CanSendPacketsToRecorder();
+
void readFromFile(char * filename, uint8_t ** videoBuffer, size_t * videoBufferBytes);
+
void SetTLSCertPath(std::string rootCert, std::string devCert, std::string devKey);
+ void SetTLSCert(std::vector<uint8_t> bufferRootCert, std::vector<uint8_t> bufferClientCert,
+ std::vector<uint8_t> bufferClientCertKey, std::vector<std::vector<uint8_t>> bufferIntermediateCerts);
+
+ void SetZoneSensitivityList(std::vector<std::pair<uint16_t, uint8_t>> zoneSensitivityList)
+ {
+ mZoneSensitivityList = zoneSensitivityList;
+ }
+
+ void SetCurrentlyUsedBandwidthMbps(double currentlyUsedBandwidthMbps)
+ {
+ mCurrentlyUsedBandwidthMbps = currentlyUsedBandwidthMbps;
+ }
+
+ double GetCurrentlyUsedBandwidthMbps() { return mCurrentlyUsedBandwidthMbps; }
+
+ // Set the cluster server reference for direct API calls
+ void SetPushAvStreamTransportServer(chip::app::Clusters::PushAvStreamTransportServerLogic * server)
+ {
+ mPushAvStreamTransportServer = server;
+ }
+
+ void ConfigureRecorderTimeSetting(
+ const chip::app::Clusters::PushAvStreamTransport::Structs::TransportMotionTriggerTimeControlStruct::DecodableType &
+ timeControl);
+
private:
- bool mHasAugmented = false;
- bool mStreaming = false;
- std::unique_ptr<PushAVClipRecorder> mRecorder = nullptr;
- std::unique_ptr<PushAVUploader> mUploader = nullptr;
+ bool mHasAugmented = false;
+ bool mStreaming = false;
+ std::unique_ptr<PushAVClipRecorder> mRecorder = nullptr;
+ std::unique_ptr<PushAVUploader> mUploader = nullptr;
+ chip::app::Clusters::PushAvStreamTransportServerLogic * mPushAvStreamTransportServer = nullptr;
+
std::chrono::steady_clock::time_point mBlindStartTime;
PushAVClipRecorder::ClipInfoStruct mClipInfo;
PushAVClipRecorder::AudioInfoStruct mAudioInfo;
PushAVClipRecorder::VideoInfoStruct mVideoInfo;
PushAVUploader::PushAVCertPath mCertPath;
+ PushAVUploader::CertificatesInfo mCertBuffer;
AudioStreamStruct mAudioStreamParams;
VideoStreamStruct mVideoStreamParams;
+ std::vector<std::pair<uint16_t, uint8_t>> mZoneSensitivityList;
// Dummy implementation to indicate if video can be sent
bool mCanSendVideo = false;
@@ -107,8 +140,8 @@
// Dummy implementation to indicate if audio can be sent
bool mCanSendAudio = false;
- // Enum indicating the type of trigger used to start the transport
chip::app::Clusters::PushAvStreamTransport::TransportStatusEnum mTransportStatus;
chip::app::Clusters::PushAvStreamTransport::TransportTriggerTypeEnum mTransportTriggerType;
uint16_t mConnectionID;
+ double mCurrentlyUsedBandwidthMbps = 0.0;
};
diff --git a/examples/camera-app/linux/include/uploader/pushav-uploader.h b/examples/camera-app/linux/include/uploader/pushav-uploader.h
index 55ca37f..512e6ff 100644
--- a/examples/camera-app/linux/include/uploader/pushav-uploader.h
+++ b/examples/camera-app/linux/include/uploader/pushav-uploader.h
@@ -35,14 +35,22 @@
class PushAVUploader
{
public:
- typedef struct CertificatesInfo
+ typedef struct CertificatesPathInfo
{
std::string mRootCert;
std::string mDevCert;
std::string mDevKey;
} PushAVCertPath;
- PushAVUploader(PushAVCertPath certPath);
+ typedef struct CertificatesInfo
+ {
+ std::vector<uint8_t> mRootCertBuffer;
+ std::vector<uint8_t> mClientCertBuffer;
+ std::vector<uint8_t> mClientKeyBuffer;
+ std::vector<std::vector<uint8_t>> mIntermediateCertBuffer;
+ } PushAVCertBuffer;
+
+ PushAVUploader();
~PushAVUploader();
void Start();
@@ -54,10 +62,14 @@
return mAvData.size();
}
+ void setCertificateBuffer(const PushAVCertBuffer & certBuffer) { mCertBuffer = certBuffer; }
+ void setCertificatePath(const PushAVCertPath & certPath) { mCertPath = certPath; }
+
private:
void ProcessQueue();
void UploadData(std::pair<std::string, std::string> data);
PushAVCertPath mCertPath;
+ PushAVCertBuffer mCertBuffer;
std::queue<std::pair<std::string, std::string>> mAvData;
std::mutex mQueueMutex;
std::atomic<bool> mIsRunning;
diff --git a/examples/camera-app/linux/src/camera-device.cpp b/examples/camera-app/linux/src/camera-device.cpp
index 00f70cd..94cb133 100644
--- a/examples/camera-app/linux/src/camera-device.cpp
+++ b/examples/camera-app/linux/src/camera-device.cpp
@@ -1376,6 +1376,7 @@
void CameraDevice::HandleSimulatedZoneTriggeredEvent(uint16_t zoneID)
{
mZoneManager.OnZoneTriggeredEvent(zoneID, ZoneEventTriggeredReasonEnum::kMotion);
+ mPushAVTransportManager.OnZoneTriggeredEvent(zoneID);
}
void CameraDevice::HandleSimulatedZoneStoppedEvent(uint16_t zoneID)
diff --git a/examples/camera-app/linux/src/clusters/camera-avstream-mgmt/camera-av-stream-manager.cpp b/examples/camera-app/linux/src/clusters/camera-avstream-mgmt/camera-av-stream-manager.cpp
index 9372f6a..c576894 100644
--- a/examples/camera-app/linux/src/clusters/camera-avstream-mgmt/camera-av-stream-manager.cpp
+++ b/examples/camera-app/linux/src/clusters/camera-avstream-mgmt/camera-av-stream-manager.cpp
@@ -144,6 +144,18 @@
return CHIP_NO_ERROR;
}
+const std::vector<chip::app::Clusters::CameraAvStreamManagement::VideoStreamStruct> &
+CameraAVStreamManager::GetAllocatedVideoStreams() const
+{
+ return GetCameraAVStreamMgmtServer()->GetAllocatedVideoStreams();
+}
+
+const std::vector<chip::app::Clusters::CameraAvStreamManagement::AudioStreamStruct> &
+CameraAVStreamManager::GetAllocatedAudioStreams() const
+{
+ return GetCameraAVStreamMgmtServer()->GetAllocatedAudioStreams();
+}
+
CHIP_ERROR CameraAVStreamManager::ValidateVideoStreamID(uint16_t videoStreamId)
{
const std::vector<VideoStreamStruct> & allocatedVideoStreams = GetCameraAVStreamMgmtServer()->GetAllocatedVideoStreams();
diff --git a/examples/camera-app/linux/src/clusters/push-av-stream-transport/push-av-stream-manager.cpp b/examples/camera-app/linux/src/clusters/push-av-stream-transport/push-av-stream-manager.cpp
index 1cc4228..ef2880b 100644
--- a/examples/camera-app/linux/src/clusters/push-av-stream-transport/push-av-stream-manager.cpp
+++ b/examples/camera-app/linux/src/clusters/push-av-stream-transport/push-av-stream-manager.cpp
@@ -63,16 +63,27 @@
mCameraDevice = aCameraDevice;
}
+void PushAvStreamTransportManager::SetPushAvStreamTransportServer(PushAvStreamTransportServerLogic * server)
+{
+ mPushAvStreamTransportServer = server;
+}
+
Protocols::InteractionModel::Status
PushAvStreamTransportManager::AllocatePushTransport(const TransportOptionsStruct & transportOptions, const uint16_t connectionID)
{
-
+ if (mCameraDevice == nullptr)
+ {
+ ChipLogError(Camera, "CameraDeviceInterface not initialized for AllocatePushTransport");
+ return Status::Failure;
+ }
mTransportOptionsMap[connectionID] = transportOptions;
ChipLogProgress(Camera, "PushAvStreamTransportManager, Create PushAV Transport for Connection: [%u]", connectionID);
mTransportMap[connectionID] =
std::make_unique<PushAVTransport>(transportOptions, connectionID, mAudioStreamParams, mVideoStreamParams);
+ mTransportMap[connectionID]->SetPushAvStreamTransportServer(mPushAvStreamTransportServer);
+
if (mMediaController == nullptr)
{
ChipLogError(Camera, "PushAvStreamTransportManager: MediaController is not set");
@@ -82,63 +93,49 @@
mMediaController->RegisterTransport(mTransportMap[connectionID].get(), transportOptions.videoStreamID.Value().Value(),
transportOptions.audioStreamID.Value().Value());
-
mMediaController->SetPreRollLength(mTransportMap[connectionID].get(), mTransportMap[connectionID].get()->GetPreRollLength());
-#ifdef TLS_CLUSTER_ENABLED
- // TODO: get TLS endpointId from PAVST cluster
- auto & tlsClientManager = mCameraDevice->GetTLSClientMgmtDelegate();
- auto & tlsCertManager = mCameraDevice->GetTLSCertMgmtDelegate();
- if (!tlsClientManager || !tlsClientManager)
- {
- ChipLogError(Camera, "Failed to get TLS cluster handlers");
- return;
- }
+ double newTransportBandwidthMbps = 0.0;
+ GetBandwidthForStreams(transportOptions.videoStreamID, transportOptions.audioStreamID, newTransportBandwidthMbps);
- // Get TLS endpoint information
- TLSClientManagement::Commands::FindEndpointResponse::Type endpointResponse;
- CHIP_ERROR err = tlsClientManager->FindEndpoint(mConnectionID, endpointResponse);
- if (err != CHIP_NO_ERROR)
- {
- ChipLogError(Camera, "Failed to find TLS endpoint for connection %u: %" CHIP_ERROR_FORMAT, mConnectionID, err.Format());
- return;
- }
- auto endpoint = endpointResponse.endpoint;
+ mTransportMap[connectionID].get()->SetCurrentlyUsedBandwidthMbps(newTransportBandwidthMbps);
+ mTotalUsedBandwidthMbps += newTransportBandwidthMbps;
+ ChipLogDetail(Camera,
+ "AllocatePushTransport: Transport for connection %u allocated successfully. "
+ "New transport bandwidth: %.2f Mbps. Total used bandwidth: %.2f Mbps.",
+ connectionID, newTransportBandwidthMbps, mTotalUsedBandwidthMbps);
- // Get root certificate
- TLSCertificateManagement::Commands::FindRootCertificateResponse::Type rootCertResponse;
- rootCertResponse.CertificateDetails = DataModel::List<TLSCertificateManagement::Structs::TLSCertStruct::Type>();
- err = tlsCertManager->FindRootCertificate(endpoint.CAID, rootCertResponse);
- if (err != CHIP_NO_ERROR)
+ if (transportOptions.triggerOptions.triggerType == TransportTriggerTypeEnum::kMotion &&
+ transportOptions.triggerOptions.motionZones.HasValue())
{
- ChipLogError(Camera, "Failed to find root certificate for CAID %u: %" CHIP_ERROR_FORMAT, endpoint.CAID, err.Format());
- return;
- }
- auto rootCert = rootCertResponse.CertificateDetails[0].Certificate;
+ std::vector<std::pair<uint16_t, uint8_t>> zoneSensitivityList;
- // Get client certificate details if configured
- if (endpoint.CCDID != NullOptional)
- {
- chip::app::Clusters::TLSCertificateManagement::Commands::FindClientCertificateResponse::Type clientCertResponse;
- err = tlsCertManager->FindClientCertificate(endpoint.CCDID.Value(), clientCertResponse);
- if (err != CHIP_NO_ERROR)
+ auto motionZones = transportOptions.triggerOptions.motionZones.Value().Value();
+ for (const auto & zoneOption : motionZones)
{
- ChipLogError(Camera, "Failed to find client certificate for CCDID %u: %" CHIP_ERROR_FORMAT, endpoint.CCDID.Value(),
- err.Format());
- return;
+ if (zoneOption.sensitivity.HasValue())
+ {
+ zoneSensitivityList.push_back({ zoneOption.zone.Value(), zoneOption.sensitivity.Value() });
+ }
+ else
+ {
+ zoneSensitivityList.push_back(
+ { zoneOption.zone.Value(), transportOptions.triggerOptions.motionSensitivity.Value().Value() });
+ }
}
- auto clientCert = clientCertResponse.CertificateDetails[0].ClientCertificate;
- // Get private key from secure storage
- auto mDevKey = GetPrivateKeyFromSecureStorage(endpoint.CCDID.Value());
- if (mCertPath.mDevKey.empty())
+ if (!zoneSensitivityList.empty())
{
- ChipLogError(Camera, "Failed to get private key for CCDID %u", endpoint.CCDID.Value());
- return;
+ mTransportMap[connectionID].get()->SetZoneSensitivityList(zoneSensitivityList);
}
}
- mTransportMap[connectionID].get()->SetTLSCertPath(rootCert, clientCert, mDevKey);
+
+#ifndef TLS_CLUSTER_NOT_ENABLED
+ ChipLogDetail(Camera, "PushAvStreamTransportManager: TLS Cluster enabled, using default certs");
+ mTransportMap[connectionID].get()->SetTLSCert(mBufferRootCert, mBufferClientCert, mBufferClientCertKey,
+ mBufferIntermediateCerts);
#else
+ // TODO: The else block is for testing purpose. It should be removed once the TLS cluster integration is stable.
mTransportMap[connectionID].get()->SetTLSCertPath("/tmp/pavstest/certs/server/root.pem", "/tmp/pavstest/certs/device/dev.pem",
"/tmp/pavstest/certs/device/dev.key");
#endif
@@ -152,6 +149,7 @@
ChipLogError(Camera, "PushAvStreamTransportManager, failed to find Connection :[%u]", connectionID);
return Status::NotFound;
}
+ mTotalUsedBandwidthMbps -= mTransportMap[connectionID].get()->GetCurrentlyUsedBandwidthMbps();
mMediaController->UnregisterTransport(mTransportMap[connectionID].get());
mTransportMap.erase(connectionID);
mTransportOptionsMap.erase(connectionID);
@@ -168,6 +166,19 @@
return Status::NotFound;
}
+ double newTransportBandwidthMbps = 0.0;
+ GetBandwidthForStreams(transportOptions.videoStreamID, transportOptions.audioStreamID, newTransportBandwidthMbps);
+
+ mTotalUsedBandwidthMbps -= mTransportMap[connectionID].get()->GetCurrentlyUsedBandwidthMbps();
+
+ mTransportMap[connectionID].get()->SetCurrentlyUsedBandwidthMbps(newTransportBandwidthMbps);
+ mTotalUsedBandwidthMbps += newTransportBandwidthMbps;
+
+ ChipLogDetail(Camera,
+ "ModifyPushTransport: Transport for connection %u allocated successfully. "
+ "New transport bandwidth: %.2f Mbps. Total used bandwidth: %.2f Mbps.",
+ connectionID, newTransportBandwidthMbps, mTotalUsedBandwidthMbps);
+
mTransportOptionsMap[connectionID] = transportOptions;
mTransportMap[connectionID].get()->ModifyPushTransport(transportOptions);
ChipLogProgress(Camera, "PushAvStreamTransportManager, success to modify Connection :[%u]", connectionID);
@@ -241,18 +252,96 @@
}
ChipLogProgress(Camera, "PushAvStreamTransportManager, Trigger PushAV Transport for Connection: [%u]", connectionID);
+ if (timeControl.HasValue())
+ {
+ mTransportMap[connectionID]->ConfigureRecorderTimeSetting(timeControl.Value());
+ }
mTransportMap[connectionID]->TriggerTransport(activationReason);
return Status::Success;
}
+void PushAvStreamTransportManager::GetBandwidthForStreams(const Optional<DataModel::Nullable<uint16_t>> & videoStreamId,
+ const Optional<DataModel::Nullable<uint16_t>> & audioStreamId,
+ double & outBandwidthMbps)
+{
+ outBandwidthMbps = 0.0;
+
+ if (videoStreamId.HasValue() && !videoStreamId.Value().IsNull())
+ {
+ uint16_t vStreamId = videoStreamId.Value().Value();
+
+ auto & availableVideoStreams = mCameraDevice->GetCameraAVStreamMgmtDelegate().GetAllocatedVideoStreams();
+ for (const chip::app::Clusters::CameraAvStreamManagement::Structs::VideoStreamStruct::Type & stream : availableVideoStreams)
+ {
+ if (stream.videoStreamID == vStreamId)
+ {
+ outBandwidthMbps += (stream.maxBitRate / 1000000.0);
+ ChipLogProgress(Camera, "GetBandwidthForStreams: VideoStream %u maxBitRate: %u bps (%.2f Mbps)", vStreamId,
+ stream.maxBitRate, (stream.maxBitRate / 1000000.0));
+ break;
+ }
+ }
+ }
+
+ if (audioStreamId.HasValue() && !audioStreamId.Value().IsNull())
+ {
+ uint16_t aStreamId = audioStreamId.Value().Value();
+
+ auto & availableAudioStreams = mCameraDevice->GetCameraAVStreamMgmtDelegate().GetAllocatedAudioStreams();
+ for (const chip::app::Clusters::CameraAvStreamManagement::Structs::AudioStreamStruct::Type & stream : availableAudioStreams)
+ {
+ if (stream.audioStreamID == aStreamId)
+ {
+ outBandwidthMbps += (stream.bitRate / 1000000.0);
+ ChipLogProgress(Camera, "GetBandwidthForStreams: AudioStream %u bitRate: %u bps (%.2f Mbps)", aStreamId,
+ stream.bitRate, (stream.bitRate / 1000000.0));
+ break;
+ }
+ }
+ }
+
+ return;
+}
+
Protocols::InteractionModel::Status
PushAvStreamTransportManager::ValidateBandwidthLimit(StreamUsageEnum streamUsage,
const Optional<DataModel::Nullable<uint16_t>> & videoStreamId,
const Optional<DataModel::Nullable<uint16_t>> & audioStreamId)
{
- // TODO: Validates the requested stream usage against the camera's resource management.
- // Returning Status::Success to pass through checks in the Server Implementation.
+ if (mCameraDevice == nullptr)
+ {
+ ChipLogError(Camera, "CameraDeviceInterface not initialized for ValidateBandwidthLimit");
+ return Status::Failure;
+ }
+
+ double newStreamBandwidthMbps = 0.0;
+ GetBandwidthForStreams(videoStreamId, audioStreamId, newStreamBandwidthMbps);
+ uint32_t maxNetworkBandwidthMbps = mCameraDevice->GetCameraHALInterface().GetMaxNetworkBandwidth();
+
+ double projectedTotalBandwidthMbps = mTotalUsedBandwidthMbps + newStreamBandwidthMbps;
+
+ ChipLogProgress(Camera,
+ "ValidateBandwidthLimit: For streamUsage %u. New stream bandwidth: %.2f Mbps. "
+ "Currently used bandwidth: %.2f Mbps. Projected total: %.2f Mbps. Max allowed: %u Mbps.",
+ static_cast<uint16_t>(streamUsage), newStreamBandwidthMbps, mTotalUsedBandwidthMbps,
+ projectedTotalBandwidthMbps, maxNetworkBandwidthMbps);
+
+ if (projectedTotalBandwidthMbps > maxNetworkBandwidthMbps)
+ {
+ ChipLogError(Camera,
+ "ValidateBandwidthLimit: ResourceExhausted for streamUsage %u. "
+ "Projected total bandwidth (%.2f Mbps) would exceed maximum network bandwidth (%u Mbps). "
+ "New stream requires %.2f Mbps, currently %.2f Mbps is in use.",
+ static_cast<uint16_t>(streamUsage), projectedTotalBandwidthMbps, maxNetworkBandwidthMbps,
+ newStreamBandwidthMbps, mTotalUsedBandwidthMbps);
+ return Status::ResourceExhausted;
+ }
+
+ ChipLogProgress(Camera,
+ "ValidateBandwidthLimit: Success for streamUsage %u. "
+ "Allocating this stream would keep bandwidth usage within limits.",
+ static_cast<uint16_t>(streamUsage));
return Status::Success;
}
@@ -447,3 +536,82 @@
return CHIP_NO_ERROR;
}
+
+void PushAvStreamTransportManager::OnZoneTriggeredEvent(uint16_t zoneId)
+{
+ for (auto & pavst : mTransportMap)
+ {
+ int connectionId = pavst.first;
+ ChipLogError(Camera, "PushAV sending trigger to connection ID %d", connectionId);
+
+ if (mTransportOptionsMap[connectionId].triggerOptions.triggerType == TransportTriggerTypeEnum::kMotion)
+ {
+ pavst.second->TriggerTransport(TriggerActivationReasonEnum::kAutomation, zoneId, 10);
+ }
+ }
+}
+
+void PushAvStreamTransportManager::SetTLSCerts(Tls::CertificateTable::BufferedClientCert & clientCertEntry,
+ Tls::CertificateTable::BufferedRootCert & rootCertEntry)
+{
+ auto rootSpan = rootCertEntry.GetCert().certificate.Value();
+ mBufferRootCert.assign(rootSpan.data(), rootSpan.data() + rootSpan.size());
+
+ auto clientSpan = clientCertEntry.GetCert().clientCertificate.Value().Value();
+ mBufferClientCert.assign(clientSpan.data(), clientSpan.data() + clientSpan.size());
+
+ mBufferIntermediateCerts.clear();
+ if (clientCertEntry.mCertWithKey.detail.intermediateCertificates.HasValue())
+ {
+ auto intermediateList = clientCertEntry.mCertWithKey.detail.intermediateCertificates.Value();
+ auto iter = intermediateList.begin();
+ while (iter.Next())
+ {
+ auto certSpan = iter.GetValue();
+ std::vector<uint8_t> intermediateCert;
+ intermediateCert.assign(certSpan.data(), certSpan.data() + certSpan.size());
+ mBufferIntermediateCerts.push_back(intermediateCert);
+ }
+ if (iter.GetStatus() != CHIP_NO_ERROR)
+ {
+ ChipLogError(Camera, "Error iterating intermediate certificates: %" CHIP_ERROR_FORMAT, iter.GetStatus().Format());
+ mBufferIntermediateCerts.clear();
+ }
+ else
+ {
+ ChipLogProgress(Camera, "Intermediate certificates fetched and stored. Size: %ld", mBufferIntermediateCerts.size());
+ }
+ }
+ else
+ {
+ ChipLogProgress(Camera, "No intermediate certificates found.");
+ }
+
+ const ByteSpan rawKeySpan = clientCertEntry.mCertWithKey.key.Span();
+ if (rawKeySpan.size() != Crypto::kP256_PublicKey_Length + Crypto::kP256_PrivateKey_Length)
+ {
+ ChipLogError(Camera, "Raw key pair has incorrect size: %ld (expected %ld)", rawKeySpan.size(),
+ static_cast<size_t>(Crypto::kP256_PublicKey_Length + Crypto::kP256_PrivateKey_Length));
+ return;
+ }
+
+ Crypto::P256SerializedKeypair rawSerializedKeypair;
+ if (rawSerializedKeypair.SetLength(rawKeySpan.size()) != CHIP_NO_ERROR)
+ {
+ ChipLogError(Camera, "Failed to set length for serialized keypair");
+ return;
+ }
+ memcpy(rawSerializedKeypair.Bytes(), rawKeySpan.data(), rawKeySpan.size());
+
+ uint8_t derBuffer[Credentials::kP256ECPrivateKeyDERLength];
+ MutableByteSpan keypairDer(derBuffer);
+
+ CHIP_ERROR err = Credentials::ConvertECDSAKeypairRawToDER(rawSerializedKeypair, keypairDer);
+ if (err != CHIP_NO_ERROR)
+ {
+ ChipLogError(Camera, "Failed to convert raw keypair to DER: %" CHIP_ERROR_FORMAT, err.Format());
+ return;
+ }
+
+ mBufferClientCertKey.assign(keypairDer.data(), keypairDer.data() + keypairDer.size());
+}
diff --git a/examples/camera-app/linux/src/pushav-clip-recorder.cpp b/examples/camera-app/linux/src/pushav-clip-recorder.cpp
index 192b379..616553d 100644
--- a/examples/camera-app/linux/src/pushav-clip-recorder.cpp
+++ b/examples/camera-app/linux/src/pushav-clip-recorder.cpp
@@ -19,6 +19,7 @@
#include "pushav-clip-recorder.h"
#include <cstring>
#include <lib/support/logging/CHIPLogging.h>
+#include <platform/PlatformManager.h>
#include <sys/stat.h>
extern "C" {
@@ -237,6 +238,30 @@
{
if (GetRecorderStatus())
{
+ // Call the cluster server's NotifyTransportStopped method asynchronously to prevent blocking
+ if (mPushAvStreamTransportServer != nullptr)
+ {
+ ChipLogProgress(Camera, "PushAVClipRecorder::Stop - Scheduling async cluster server API call for connection %u",
+ mConnectionID);
+
+ uint16_t connectionID = mConnectionID;
+ auto triggerType = mTriggerType;
+ auto reasonType = mReasonType;
+ auto * server = mPushAvStreamTransportServer;
+
+ std::thread([server, connectionID, triggerType, reasonType]() {
+ ChipLogProgress(Camera, "Async thread: Calling NotifyTransportStopped for connection %u", connectionID);
+ chip::DeviceLayer::PlatformMgr().LockChipStack();
+ server->NotifyTransportStopped(connectionID, triggerType, reasonType);
+ chip::DeviceLayer::PlatformMgr().UnlockChipStack();
+ ChipLogProgress(Camera, "Async thread: NotifyTransportStopped completed for connection %u", connectionID);
+ }).detach();
+ }
+ else
+ {
+ ChipLogError(Camera, "PushAVClipRecorder::Stop - Cluster server reference is null for connection %u", mConnectionID);
+ }
+
SetRecorderStatus(false);
mCondition.notify_one();
while (!mVideoQueue.empty())
@@ -305,11 +330,13 @@
{
ChipLogError(Camera, "ERROR: Output context is null");
}
+ double segSeconds = static_cast<double>(mClipInfo.mSegmentDuration) / 1000.0;
// Set DASH/CMAF options
av_opt_set(mFormatContext->priv_data, "increment_tc", "1", 0);
av_opt_set(mFormatContext->priv_data, "use_timeline", "1", 0);
av_opt_set(mFormatContext->priv_data, "movflags", "+cmaf+dash+delay_moov+skip_sidx+skip_trailer+frag_custom", 0);
- av_opt_set(mFormatContext->priv_data, "seg_duration", std::to_string(mClipInfo.mChunkDuration).c_str(), 0);
+ av_opt_set(mFormatContext->priv_data, "seg_duration", std::to_string(segSeconds).c_str(), 0);
+ av_opt_set(mFormatContext->priv_data, "frag_duration", std::to_string(mClipInfo.mChunkDuration).c_str(), 0);
av_opt_set(mFormatContext->priv_data, "init_seg_name", initSegPattern.c_str(), 0);
av_opt_set(mFormatContext->priv_data, "media_seg_name", mediaSegPattern.c_str(), 0);
av_opt_set_int(mFormatContext->priv_data, "use_template", 1, 0);
diff --git a/examples/camera-app/linux/src/pushav-transport/pushav-transport.cpp b/examples/camera-app/linux/src/pushav-transport/pushav-transport.cpp
index dd4259c..74871ba 100644
--- a/examples/camera-app/linux/src/pushav-transport/pushav-transport.cpp
+++ b/examples/camera-app/linux/src/pushav-transport/pushav-transport.cpp
@@ -61,7 +61,8 @@
ChipLogProgress(Camera, "Initial Duration: %d sec", clipInfo.mInitialDuration);
ChipLogProgress(Camera, "Augmentation Duration: %d sec", clipInfo.mAugmentationDuration);
ChipLogProgress(Camera, "Max Clip Duration: %d sec", clipInfo.mMaxClipDuration);
- ChipLogProgress(Camera, "Chunk Duration: %d sec", clipInfo.mChunkDuration);
+ ChipLogProgress(Camera, "Chunk Duration: %d ms", clipInfo.mChunkDuration);
+ ChipLogProgress(Camera, "Segment Duration: %d ms", clipInfo.mSegmentDuration);
ChipLogProgress(Camera, "Blind Duration: %d sec", clipInfo.mBlindDuration);
ChipLogProgress(Camera, "PreRoll Length: %d ", clipInfo.mPreRollLength);
ChipLogProgress(Camera, "URL: %s", clipInfo.mUrl.c_str());
@@ -87,6 +88,24 @@
ChipLogProgress(Camera, "Bit Rate: %d bps", videoInfo.mBitRate);
}
+void PushAVTransport::ConfigureRecorderTimeSetting(
+ const chip::app::Clusters::PushAvStreamTransport::Structs::TransportMotionTriggerTimeControlStruct::DecodableType & timeControl)
+{
+ mClipInfo.mInitialDuration = timeControl.initialDuration;
+ mClipInfo.mAugmentationDuration = timeControl.augmentationDuration;
+ mClipInfo.mMaxClipDuration = timeControl.maxDuration;
+ mClipInfo.mBlindDuration = timeControl.blindDuration;
+ ChipLogDetail(Camera, "PushAVTransport ConfigureRecorderTimeSetting done");
+ ChipLogDetail(Camera, "Initial Duration: %d sec", mClipInfo.mInitialDuration);
+ ChipLogDetail(Camera, "Augmentation Duration: %d sec", mClipInfo.mAugmentationDuration);
+ ChipLogDetail(Camera, "Max Clip Duration: %d sec", mClipInfo.mMaxClipDuration);
+ ChipLogDetail(Camera, "Blind Duration: %d sec", mClipInfo.mBlindDuration);
+ if (mRecorder.get() != nullptr)
+ {
+ mRecorder->mClipInfo = mClipInfo;
+ }
+}
+
void PushAVTransport::ConfigureRecorderSettings(const TransportOptionsStruct & transportOptions,
AudioStreamStruct & audioStreamParams, VideoStreamStruct & videoStreamParams)
{
@@ -98,7 +117,8 @@
mClipInfo.mAugmentationDuration = 10;
mClipInfo.mBlindDuration = 5;
mClipInfo.mMaxClipDuration = 30;
- mClipInfo.mChunkDuration = 5;
+ mClipInfo.mChunkDuration = 1000;
+ mClipInfo.mSegmentDuration = 4000;
mClipInfo.mTriggerType = 0;
mClipInfo.mPreRollLength = 0;
mClipInfo.mUrl = "https://localhost:1234/streams/1/";
@@ -107,7 +127,7 @@
{
mClipInfo.mHasAudio = true;
mClipInfo.mHasVideo = true;
- mClipInfo.mUrl = std::string(transportOptions.url.begin(), transportOptions.url.end());
+ mClipInfo.mUrl = std::string(transportOptions.url.data(), transportOptions.url.size());
mClipInfo.mTriggerType = static_cast<int>(transportOptions.triggerOptions.triggerType);
if (transportOptions.triggerOptions.maxPreRollLen.HasValue())
{
@@ -119,14 +139,12 @@
}
if (transportOptions.triggerOptions.motionTimeControl.HasValue())
{
- mClipInfo.mInitialDuration = transportOptions.triggerOptions.motionTimeControl.Value().initialDuration;
- mClipInfo.mAugmentationDuration = transportOptions.triggerOptions.motionTimeControl.Value().augmentationDuration;
- mClipInfo.mMaxClipDuration = transportOptions.triggerOptions.motionTimeControl.Value().maxDuration;
- mClipInfo.mBlindDuration = transportOptions.triggerOptions.motionTimeControl.Value().blindDuration;
+ ConfigureRecorderTimeSetting(transportOptions.triggerOptions.motionTimeControl.Value());
}
if (transportOptions.containerOptions.CMAFContainerOptions.HasValue())
{
- mClipInfo.mChunkDuration = transportOptions.containerOptions.CMAFContainerOptions.Value().chunkDuration;
+ mClipInfo.mChunkDuration = transportOptions.containerOptions.CMAFContainerOptions.Value().chunkDuration;
+ mClipInfo.mSegmentDuration = transportOptions.containerOptions.CMAFContainerOptions.Value().segmentDuration;
}
}
@@ -245,16 +263,20 @@
bool PushAVTransport::HandleTriggerDetected()
{
- int64_t elapsed;
- auto now = std::chrono::steady_clock::now();
+ int64_t elapsed = 0;
+ auto now = std::chrono::steady_clock::now();
if (InBlindPeriod(mBlindStartTime, mRecorder->mClipInfo.mBlindDuration))
{
return false;
}
- elapsed = std::chrono::duration_cast<std::chrono::seconds>(now - mRecorder->mClipInfo.activationTime).count();
- ChipLogError(Camera, "PushAVTransport HandleTriggerDetected elapsed: %ld", elapsed);
+ if (mRecorder->mClipInfo.activationTime != std::chrono::steady_clock::time_point())
+ {
+ elapsed = std::chrono::duration_cast<std::chrono::seconds>(now - mRecorder->mClipInfo.activationTime).count();
+ }
+
+ ChipLogDetail(Camera, "PushAVTransport HandleTriggerDetected elapsed: %ld", elapsed);
if (!mRecorder->GetRecorderStatus())
{
@@ -262,6 +284,10 @@
ChipLogError(Camera, "PushAVTransport starting new recording");
mHasAugmented = false;
mRecorder->mClipInfo.activationTime = std::chrono::steady_clock::now();
+ // Set the cluster server reference and connection info for direct API calls
+ mRecorder->SetPushAvStreamTransportServer(mPushAvStreamTransportServer);
+ mRecorder->SetConnectionInfo(mConnectionID, mTransportTriggerType,
+ chip::Optional<chip::app::Clusters::PushAvStreamTransport::TriggerActivationReasonEnum>());
mRecorder->Start();
mStreaming = true;
}
@@ -287,43 +313,74 @@
return true;
}
-void PushAVTransport::TriggerTransport(TriggerActivationReasonEnum activationReason)
+void PushAVTransport::TriggerTransport(TriggerActivationReasonEnum activationReason, int zoneId, int sensitivity)
{
- ChipLogProgress(Camera, "PushAVTransport trigger transport, activation reason: [%u]", (uint16_t) activationReason);
-
+ ChipLogProgress(Camera, "PushAVTransport trigger transport, activation reason: [%u], ZoneId: [%d], Sensitivity: [%d]",
+ (uint16_t) activationReason, zoneId, sensitivity);
if (mTransportTriggerType == TransportTriggerTypeEnum::kCommand)
{
+ mRecorder->SetConnectionInfo(mConnectionID, mTransportTriggerType, chip::MakeOptional(activationReason));
if (HandleTriggerDetected())
{
ChipLogError(Camera, "PushAVTransport command/motion transport trigger received. Clip duration [%d seconds]",
mRecorder->mClipInfo.mInitialDuration);
+ // Begin event already generated at cluster server
}
else
{
- ChipLogError(
- Camera,
- "PushAVTransport command/motion transport trigger received but ignored due to blind period. Clip duration. "
- "Clip duration [%d seconds]",
- mRecorder->mClipInfo.mInitialDuration);
+ ChipLogError(Camera,
+ "PushAVTransport command/motion transport trigger received but ignored due to blind period. Clip duration "
+ "[%d seconds]",
+ mRecorder->mClipInfo.mInitialDuration);
}
}
else if (mTransportTriggerType == TransportTriggerTypeEnum::kMotion)
{
- if (HandleTriggerDetected())
+ bool zoneFound = false; // Zone found flag
+ for (auto zone : mZoneSensitivityList)
{
- ChipLogError(Camera, "PushAVTransport command/motion transport trigger received. Clip duration [%d seconds]",
- mRecorder->mClipInfo.mInitialDuration);
+ if (zone.first == zoneId)
+ {
+ zoneFound = true;
+ if (zone.second > sensitivity)
+ {
+ ChipLogProgress(Camera, "PushAVTransport motion transport trigger received but ignored due to sensitivity");
+ }
+ else
+ {
+ if (HandleTriggerDetected())
+ {
+ ChipLogError(Camera,
+ "PushAVTransport command/motion transport trigger received. Clip duration [%d seconds]",
+ mRecorder->mClipInfo.mInitialDuration);
+ if (mPushAvStreamTransportServer != nullptr)
+ {
+ mPushAvStreamTransportServer->NotifyTransportStarted(
+ mConnectionID, mTransportTriggerType,
+ chip::Optional<chip::app::Clusters::PushAvStreamTransport::TriggerActivationReasonEnum>());
+ }
+ else
+ {
+ ChipLogError(Camera, "PushAvStreamTransportServer is null for connection %u", mConnectionID);
+ }
+ }
+ else
+ {
+ ChipLogError(Camera,
+ "PushAVTransport command/motion transport trigger received but ignored due to blind period. "
+ "Clip duration. "
+ "Clip duration [%d seconds]",
+ mRecorder->mClipInfo.mInitialDuration);
+ }
+ }
+ break;
+ }
}
- else
+ if (!zoneFound)
{
- ChipLogError(
- Camera,
- "PushAVTransport command/motion transport trigger received but ignored due to blind period. Clip duration. "
- "Clip duration [%d seconds]",
- mRecorder->mClipInfo.mInitialDuration);
+ ChipLogProgress(Camera, "PushAVTransport motion transport trigger received but ignored due to unknown zone id");
}
}
-
else if (mTransportTriggerType == TransportTriggerTypeEnum::kContinuous)
{
ChipLogProgress(Camera, "PushAVTransport continuous transport trigger received. No action needed");
@@ -337,6 +394,17 @@
mCertPath.mDevCert = devCert;
mCertPath.mDevKey = devKey;
}
+
+void PushAVTransport::SetTLSCert(std::vector<uint8_t> bufferRootCert, std::vector<uint8_t> bufferClientCert,
+ std::vector<uint8_t> bufferClientCertKey,
+ std::vector<std::vector<uint8_t>> bufferIntermediateCerts)
+{
+ mCertBuffer.mRootCertBuffer = bufferRootCert;
+ mCertBuffer.mClientCertBuffer = bufferClientCert;
+ mCertBuffer.mClientKeyBuffer = bufferClientCertKey;
+ mCertBuffer.mIntermediateCertBuffer = bufferIntermediateCerts;
+}
+
void PushAVTransport::SetTransportStatus(TransportStatusEnum status)
{
if (mTransportStatus == status)
@@ -350,17 +418,33 @@
{
ChipLogProgress(Camera, "PushAVTransport transport status changed to active");
- mUploader = std::make_unique<PushAVUploader>(mCertPath);
+ mUploader = std::make_unique<PushAVUploader>();
+ mUploader->setCertificateBuffer(mCertBuffer);
+ mUploader->setCertificatePath(mCertPath);
mUploader->Start();
InitializeRecorder();
if (mTransportTriggerType == TransportTriggerTypeEnum::kContinuous)
{
+ // Set the cluster server reference and connection info for direct API calls
+ mRecorder->SetPushAvStreamTransportServer(mPushAvStreamTransportServer);
+ mRecorder->SetConnectionInfo(mConnectionID, mTransportTriggerType,
+ chip::Optional<chip::app::Clusters::PushAvStreamTransport::TriggerActivationReasonEnum>());
mRecorder->Start();
mStreaming = true;
if (IsStreaming())
{
ChipLogProgress(Camera, "Ready to stream");
+ if (mPushAvStreamTransportServer != nullptr)
+ {
+ mPushAvStreamTransportServer->NotifyTransportStarted(
+ mConnectionID, mTransportTriggerType,
+ chip::Optional<chip::app::Clusters::PushAvStreamTransport::TriggerActivationReasonEnum>());
+ }
+ else
+ {
+ ChipLogError(Camera, "PushAvStreamTransportServer is null for connection %u", mConnectionID);
+ }
}
}
else
@@ -428,9 +512,10 @@
{
return false;
}
- if (mRecorder->mDeinitializeRecorder.load()) // Current clip is completed, Next clip will start on trigger
+ if (mRecorder->mDeinitializeRecorder.load())
{
- mRecorder.reset();
+ ChipLogProgress(Camera, "Current clip is completed, Next clip will start on trigger");
+ mRecorder.reset(); // Redundant cleanup to make sure no dangling pointer left
InitializeRecorder();
mStreaming = false;
return false;
diff --git a/examples/camera-app/linux/src/uploader/pushav-uploader.cpp b/examples/camera-app/linux/src/uploader/pushav-uploader.cpp
index 26d9d90..4aba1ee 100644
--- a/examples/camera-app/linux/src/uploader/pushav-uploader.cpp
+++ b/examples/camera-app/linux/src/uploader/pushav-uploader.cpp
@@ -17,17 +17,161 @@
*/
#include "pushav-uploader.h"
+#include <cstring>
#include <fstream>
+#include <iomanip>
#include <iostream>
#include <lib/support/logging/CHIPLogging.h>
+#include <openssl/bio.h>
+#include <openssl/pem.h>
+#include <openssl/x509.h>
+#include <sys/stat.h>
+#include <vector>
-PushAVUploader::PushAVUploader(PushAVCertPath certPath) : mCertPath(certPath), mIsRunning(false) {}
+PushAVUploader::PushAVUploader() : mIsRunning(false) {}
PushAVUploader::~PushAVUploader()
{
Stop();
}
+// Helper function to convert certificate from DER format to PEM format
+std::string DerCertToPem(const std::vector<uint8_t> & derData)
+{
+ const unsigned char * p = derData.data();
+ X509 * cert = d2i_X509(nullptr, &p, derData.size());
+ if (!cert)
+ {
+ ChipLogError(Camera, "Failed to parse DER certificate of size: %ld", derData.size());
+ return "";
+ }
+
+ BIO * bio = BIO_new(BIO_s_mem());
+ if (!bio)
+ {
+ X509_free(cert);
+ ChipLogError(Camera, "Failed to allocate BIO");
+ return "";
+ }
+
+ if (!PEM_write_bio_X509(bio, cert))
+ {
+ BIO_free(bio);
+ X509_free(cert);
+ ChipLogError(Camera, "Failed to write PEM certificate");
+ return "";
+ }
+
+ BUF_MEM * bptr;
+ BIO_get_mem_ptr(bio, &bptr);
+ std::string pem(bptr->data, bptr->length);
+
+ BIO_free(bio);
+ X509_free(cert);
+
+ return pem;
+}
+
+// Helper function to convert vector of bytes to hex string representation
+std::string vectorToHexString(const std::vector<uint8_t> & vec)
+{
+ std::ostringstream oss;
+ for (size_t i = 0; i < vec.size(); ++i)
+ {
+ oss << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(vec[i]);
+ if ((i + 1) % 16 == 0)
+ {
+ oss << "\n";
+ }
+ else if (i != vec.size() - 1)
+ {
+ oss << " ";
+ }
+ }
+ return oss.str();
+}
+
+// Helper function to convert ECDSA private key from DER format to PEM format
+std::string ConvertECDSAPrivateKey_DER_to_PEM(const std::vector<uint8_t> & derData)
+{
+ const unsigned char * p = derData.data();
+
+ EVP_PKEY * pkey = d2i_AutoPrivateKey(nullptr, &p, derData.size());
+ if (!pkey)
+ {
+ ChipLogError(Camera, "Failed to parse DER ECDSA private key of size: %ld", derData.size());
+ return "";
+ }
+
+ // Write PEM to memory BIO
+ BIO * bio = BIO_new(BIO_s_mem());
+ if (!bio)
+ {
+ EVP_PKEY_free(pkey);
+ ChipLogError(Camera, "Failed to allocate BIO");
+ return "";
+ }
+
+ // Use PEM_write_bio_PrivateKey to write the EVP_PKEY in PEM format.
+ // This function handles the key type automatically.
+ if (!PEM_write_bio_PrivateKey(bio, pkey, nullptr, nullptr, 0, nullptr, nullptr))
+ {
+ BIO_free(bio);
+ EVP_PKEY_free(pkey);
+ ChipLogError(Camera, "Failed to convert ECDSA key to PEM format");
+ return "";
+ }
+
+ // Extract PEM string
+ char * pemData = nullptr;
+ long pemLen = BIO_get_mem_data(bio, &pemData);
+ std::string pemStr(pemData, pemLen);
+
+ BIO_free(bio);
+ EVP_PKEY_free(pkey);
+
+ return pemStr;
+}
+
+// Helper function to read and print file content to log
+std::string readAndPrintFile(const std::string & filePath)
+{
+ std::ifstream file(filePath, std::ios::binary);
+ if (!file.is_open())
+ {
+ ChipLogDetail(Camera, "Failed to open file: %s", filePath.c_str());
+ return "";
+ }
+
+ // Read the entire file content
+ std::string fileContent((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
+
+ // Print the file content
+ ChipLogDetail(Camera, "File:%s", filePath.c_str());
+ ChipLogDetail(Camera, "Content:%s", fileContent.c_str());
+
+ file.close();
+ return fileContent;
+}
+
+void SaveCertToFile(const std::string & certData, const std::string & filePath)
+{
+ std::ofstream out(filePath, std::ios::binary);
+ if (!out)
+ {
+ ChipLogDetail(Camera, "Failed to open file: %s", filePath.c_str());
+ return;
+ }
+ out.write(certData.data(), certData.size());
+ out.close();
+
+ // Set file permissions to read/write for owner only (0600)
+ if (chmod(filePath.c_str(), S_IRUSR | S_IWUSR) != 0)
+ {
+ ChipLogError(Camera, "Failed to set permissions for file: %s", filePath.c_str());
+ }
+}
+
void PushAVUploader::ProcessQueue()
{
while (mIsRunning)
@@ -160,9 +304,46 @@
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, true);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2L);
curl_easy_setopt(curl, CURLOPT_INFILESIZE_LARGE, static_cast<curl_off_t>(size));
+#ifndef TLS_CLUSTER_NOT_ENABLED
+
+ // TODO: The logic to provide DER-formatted certificates and keys in memory (blob) format to curl is currently unstable. As a
+ // temporary workaround, PEM-format files are being provided as input to curl.
+
+ auto rootCertPEM = DerCertToPem(mCertBuffer.mRootCertBuffer);
+ auto clientCertPEM = DerCertToPem(mCertBuffer.mClientCertBuffer);
+ if (!mCertBuffer.mIntermediateCertBuffer.empty())
+ {
+ clientCertPEM.append("\n"); // Add newline separator between certs in PEM format
+ }
+ for (size_t i = 0; i < mCertBuffer.mIntermediateCertBuffer.size(); ++i)
+ {
+ clientCertPEM.append(DerCertToPem(mCertBuffer.mIntermediateCertBuffer[i]) + "\n");
+ }
+ std::string derKeyToPemstr = ConvertECDSAPrivateKey_DER_to_PEM(mCertBuffer.mClientKeyBuffer);
+
+ SaveCertToFile(rootCertPEM, "/tmp/root.pem");
+ SaveCertToFile(clientCertPEM, "/tmp/dev.pem");
+
+ // Logic to save PEM format to file
+ SaveCertToFile(derKeyToPemstr, "/tmp/dev.key");
+
+ curl_easy_setopt(curl, CURLOPT_CAINFO, "/tmp/root.pem");
+ curl_easy_setopt(curl, CURLOPT_SSLCERT, "/tmp/dev.pem");
+ curl_easy_setopt(curl, CURLOPT_SSLKEY, "/tmp/dev.key");
+
+ // curl_blob rootBlob = { mCertBuffer.mRootCertBuffer.data(), mCertBuffer.mRootCertBuffer.size(), CURL_BLOB_COPY };
+ // curl_blob clientBlob = { mCertBuffer.mClientCertBuffer.data(), mCertBuffer.mClientCertBuffer.size(), CURL_BLOB_COPY };
+ // curl_blob keyBlob = { mCertBuffer.mClientKeyBuffer.data(), mCertBuffer.mClientKeyBuffer.size(), CURL_BLOB_COPY };
+
+ // curl_easy_setopt(curl, CURLOPT_CAINFO_BLOB, &rootBlob);
+ // curl_easy_setopt(curl, CURLOPT_SSLCERT_BLOB, &clientBlob);
+ // curl_easy_setopt(curl, CURLOPT_SSLKEY_BLOB, &keyBlob);
+#else
+ // TODO: The else block is for testing purpose. It should be removed once the TLS cluster integration is stable.
curl_easy_setopt(curl, CURLOPT_CAINFO, mCertPath.mRootCert.c_str());
curl_easy_setopt(curl, CURLOPT_SSLCERT, mCertPath.mDevCert.c_str());
curl_easy_setopt(curl, CURLOPT_SSLKEY, mCertPath.mDevKey.c_str());
+#endif
curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);
curl_easy_setopt(curl, CURLOPT_READFUNCTION, PushAvUploadCb);
curl_easy_setopt(curl, CURLOPT_READDATA, &upload);
diff --git a/src/app/clusters/camera-av-stream-management-server/camera-av-stream-management-server.h b/src/app/clusters/camera-av-stream-management-server/camera-av-stream-management-server.h
index 5b8f38c..e3b42c6 100644
--- a/src/app/clusters/camera-av-stream-management-server/camera-av-stream-management-server.h
+++ b/src/app/clusters/camera-av-stream-management-server/camera-av-stream-management-server.h
@@ -297,6 +297,26 @@
*/
virtual CHIP_ERROR OnTransportReleaseAudioVideoStreams(uint16_t audioStreamID, uint16_t videoStreamID) = 0;
+ /**
+ * @brief Provides read-only access to the list of currently allocated video streams.
+ * This allows other components (like PushAVStreamTransportManager) to query
+ * allocated stream parameters (e.g., for bandwidth calculation) without directly
+ * accessing the CameraAVStreamMgmtServer instance.
+ *
+ * @return A const reference to the vector of allocated video stream structures.
+ */
+ virtual const std::vector<VideoStreamStruct> & GetAllocatedVideoStreams() const = 0;
+
+ /**
+ * @brief Provides read-only access to the list of currently allocated audio streams.
+ * This allows other components (like PushAVStreamTransportManager) to query
+ * allocated stream parameters (e.g., for bandwidth calculation) without directly
+ * accessing the CameraAVStreamMgmtServer instance.
+ *
+ * @return A const reference to the vector of allocated audio stream structures.
+ */
+ virtual const std::vector<AudioStreamStruct> & GetAllocatedAudioStreams() const = 0;
+
private:
friend class CameraAVStreamMgmtServer;
diff --git a/src/app/clusters/push-av-stream-transport-server/BUILD.gn b/src/app/clusters/push-av-stream-transport-server/BUILD.gn
index 69f15cb..b7322b2 100644
--- a/src/app/clusters/push-av-stream-transport-server/BUILD.gn
+++ b/src/app/clusters/push-av-stream-transport-server/BUILD.gn
@@ -26,6 +26,7 @@
public_deps = [
"${chip_root}/src/app/",
+ "${chip_root}/src/app/clusters/tls-certificate-management-server",
"${chip_root}/src/app/clusters/tls-client-management-server",
"${chip_root}/src/app/server-cluster",
"${chip_root}/src/lib/core:types",
diff --git a/src/app/clusters/push-av-stream-transport-server/CodegenIntegration.cpp b/src/app/clusters/push-av-stream-transport-server/CodegenIntegration.cpp
index d2cd0aa..1b34315 100644
--- a/src/app/clusters/push-av-stream-transport-server/CodegenIntegration.cpp
+++ b/src/app/clusters/push-av-stream-transport-server/CodegenIntegration.cpp
@@ -134,6 +134,23 @@
gServers[arrayIndex].Cluster().SetTLSClientManagementDelegate(delegate);
}
+void SetTlsCertificateManagementDelegate(EndpointId endpointId, TlsCertificateManagementDelegate * delegate)
+{
+ ChipLogProgress(AppServer, "Setting TLS Certificate Management delegate on endpoint %u", endpointId);
+ uint16_t arrayIndex =
+ emberAfGetClusterServerEndpointIndex(endpointId, PushAvStreamTransport::Id, kPushAvStreamTransportFixedClusterCount);
+ if (arrayIndex >= kPushAvStreamTransportMaxClusterCount)
+ {
+ return;
+ }
+
+ if (!gServers[arrayIndex].IsConstructed())
+ {
+ ChipLogError(AppServer, "Push AV Stream transport is NOT yet constructed. Cannot set TLS Certificate Management delegate");
+ return;
+ }
+ gServers[arrayIndex].Cluster().SetTlsCertificateManagementDelegate(delegate);
+}
} // namespace PushAvStreamTransport
} // namespace Clusters
} // namespace app
diff --git a/src/app/clusters/push-av-stream-transport-server/CodegenIntegration.h b/src/app/clusters/push-av-stream-transport-server/CodegenIntegration.h
index 99278e2..fd8d918 100644
--- a/src/app/clusters/push-av-stream-transport-server/CodegenIntegration.h
+++ b/src/app/clusters/push-av-stream-transport-server/CodegenIntegration.h
@@ -19,6 +19,7 @@
#pragma once
#include "push-av-stream-transport-delegate.h"
+#include <app/clusters/tls-certificate-management-server/tls-certificate-management-server.h>
#include <app/clusters/tls-client-management-server/tls-client-management-server.h>
namespace chip {
@@ -32,6 +33,9 @@
/// Sets the given TLS Client Management delegate on an endpoint configured via code-generation
void SetTLSClientManagementDelegate(chip::EndpointId endpointId, TlsClientManagementDelegate * delegate);
+/// Sets the given TLS Certificate Management delegate on an endpoint configured via code-generation
+void SetTlsCertificateManagementDelegate(chip::EndpointId endpointId, TlsCertificateManagementDelegate * delegate);
+
} // namespace PushAvStreamTransport
} // namespace Clusters
} // namespace app
diff --git a/src/app/clusters/push-av-stream-transport-server/push-av-stream-transport-cluster.h b/src/app/clusters/push-av-stream-transport-server/push-av-stream-transport-cluster.h
index f2d00ae..05c4592 100644
--- a/src/app/clusters/push-av-stream-transport-server/push-av-stream-transport-cluster.h
+++ b/src/app/clusters/push-av-stream-transport-server/push-av-stream-transport-cluster.h
@@ -54,6 +54,11 @@
void SetTLSClientManagementDelegate(TlsClientManagementDelegate * delegate) { mLogic.SetTLSClientManagementDelegate(delegate); }
+ void SetTlsCertificateManagementDelegate(TlsCertificateManagementDelegate * delegate)
+ {
+ mLogic.SetTlsCertificateManagementDelegate(delegate);
+ }
+
CHIP_ERROR Init() { return mLogic.Init(); }
void Shutdown() override
diff --git a/src/app/clusters/push-av-stream-transport-server/push-av-stream-transport-delegate.h b/src/app/clusters/push-av-stream-transport-server/push-av-stream-transport-delegate.h
index 62cfeb4..9835649 100644
--- a/src/app/clusters/push-av-stream-transport-server/push-av-stream-transport-delegate.h
+++ b/src/app/clusters/push-av-stream-transport-server/push-av-stream-transport-delegate.h
@@ -21,6 +21,9 @@
#include <app-common/zap-generated/cluster-objects.h>
#include <app/clusters/push-av-stream-transport-server/constants.h>
#include <app/clusters/push-av-stream-transport-server/push-av-stream-transport-storage.h>
+#include <app/clusters/tls-certificate-management-server/tls-certificate-management-server.h>
+#include <app/clusters/tls-client-management-server/tls-client-management-server.h>
+#include <functional>
#include <protocols/interaction_model/StatusCode.h>
#include <vector>
@@ -28,6 +31,9 @@
namespace app {
namespace Clusters {
+// Forward declaration
+class PushAvStreamTransportServerLogic;
+
/**
* @brief Defines interfaces for implementing application-specific logic for the PushAvStreamTransport Delegate.
*
@@ -259,6 +265,20 @@
* @return CHIP_ERROR indicating success or failure
*/
virtual CHIP_ERROR PersistentAttributesLoadedCallback() = 0;
+
+ virtual void SetTLSCerts(Tls::CertificateTable::BufferedClientCert & clientCertEntry,
+ Tls::CertificateTable::BufferedRootCert & rootCertEntry) = 0;
+
+ /**
+ * @brief Sets the PushAvStreamTransportServerLogic instance for the delegate.
+ *
+ * This method is called by the PushAvStreamTransportServerLogic to provide
+ * the delegate with a pointer to the server logic instance. This allows the
+ * delegate to interact with the server logic, for example, to generate events.
+ *
+ * @param serverLogic A pointer to the PushAvStreamTransportServerLogic instance.
+ */
+ virtual void SetPushAvStreamTransportServer(PushAvStreamTransportServerLogic * serverLogic) = 0;
};
} // namespace Clusters
} // namespace app
diff --git a/src/app/clusters/push-av-stream-transport-server/push-av-stream-transport-logic.cpp b/src/app/clusters/push-av-stream-transport-server/push-av-stream-transport-logic.cpp
index 1d8e6cf..5f0fe7f 100644
--- a/src/app/clusters/push-av-stream-transport-server/push-av-stream-transport-logic.cpp
+++ b/src/app/clusters/push-av-stream-transport-server/push-av-stream-transport-logic.cpp
@@ -596,6 +596,32 @@
handler.AddClusterSpecificFailure(commandPath, status);
return std::nullopt;
});
+ // Use heap allocation for large certificate buffers to reduce stack usage
+ auto rootCertBuffer = std::make_unique<PersistentStore<CHIP_CONFIG_TLS_PERSISTED_ROOT_CERT_BYTES>>();
+ auto clientCertBuffer = std::make_unique<PersistentStore<CHIP_CONFIG_TLS_PERSISTED_CLIENT_CERT_BYTES>>();
+
+ if (!rootCertBuffer || !clientCertBuffer)
+ {
+ ChipLogError(Zcl, "HandleAllocatePushTransport[ep=%d]: Memory allocation failed for certificate buffers", mEndpointId);
+ handler.AddStatus(commandPath, Status::ResourceExhausted);
+ return std::nullopt;
+ }
+
+ Tls::CertificateTable::BufferedClientCert clientCertEntry(*clientCertBuffer);
+ Tls::CertificateTable::BufferedRootCert rootCertEntry(*rootCertBuffer);
+
+ if (mTlsCertificateManagementDelegate != nullptr)
+ {
+ auto & table = mTlsCertificateManagementDelegate->GetCertificateTable();
+ table.GetClientCertificateEntry(handler.GetAccessingFabricIndex(), TLSEndpoint.ccdid.Value(), clientCertEntry);
+ table.GetRootCertificateEntry(handler.GetAccessingFabricIndex(), TLSEndpoint.caid, rootCertEntry);
+ mDelegate->SetTLSCerts(clientCertEntry, rootCertEntry);
+ }
+ else
+ {
+ // For tests, create empty cert entries
+ mDelegate->SetTLSCerts(clientCertEntry, rootCertEntry);
+ }
}
else
{
@@ -1165,6 +1191,42 @@
return Status::Success;
}
+Status PushAvStreamTransportServerLogic::NotifyTransportStarted(uint16_t connectionID, TransportTriggerTypeEnum triggerType,
+ Optional<TriggerActivationReasonEnum> activationReason)
+{
+ ChipLogProgress(Zcl, "NotifyTransportStarted called for connectionID %u with triggerType %u", connectionID,
+ to_underlying(triggerType));
+
+ // Validate that the connection exists
+ TransportConfigurationStorage * transportConfig = FindStreamTransportConnection(connectionID);
+ if (transportConfig == nullptr)
+ {
+ ChipLogError(Zcl, "NotifyTransportStarted: ConnectionID %u not found", connectionID);
+ return Status::NotFound;
+ }
+
+ // Generate the PushTransportBegin event
+ return GeneratePushTransportBeginEvent(connectionID, triggerType, activationReason);
+}
+
+Status PushAvStreamTransportServerLogic::NotifyTransportStopped(uint16_t connectionID, TransportTriggerTypeEnum triggerType,
+ Optional<TriggerActivationReasonEnum> activationReason)
+{
+ ChipLogProgress(Zcl, "NotifyTransportStopped called for connectionID %u with triggerType %u", connectionID,
+ to_underlying(triggerType));
+
+ // Validate that the connection exists
+ TransportConfigurationStorage * transportConfig = FindStreamTransportConnection(connectionID);
+ if (transportConfig == nullptr)
+ {
+ ChipLogError(Zcl, "NotifyTransportStopped: ConnectionID %u not found", connectionID);
+ return Status::NotFound;
+ }
+
+ // Generate the PushTransportEnd event
+ return GeneratePushTransportEndEvent(connectionID, triggerType, activationReason);
+}
+
} // namespace Clusters
} // namespace app
} // namespace chip
diff --git a/src/app/clusters/push-av-stream-transport-server/push-av-stream-transport-logic.h b/src/app/clusters/push-av-stream-transport-server/push-av-stream-transport-logic.h
index 7d51b0d..7e23fd6 100644
--- a/src/app/clusters/push-av-stream-transport-server/push-av-stream-transport-logic.h
+++ b/src/app/clusters/push-av-stream-transport-server/push-av-stream-transport-logic.h
@@ -6,8 +6,10 @@
#include <app/clusters/push-av-stream-transport-server/constants.h>
#include <app/clusters/push-av-stream-transport-server/push-av-stream-transport-delegate.h>
#include <app/clusters/push-av-stream-transport-server/push-av-stream-transport-storage.h>
+#include <app/clusters/tls-certificate-management-server/tls-certificate-management-server.h>
#include <app/clusters/tls-client-management-server/tls-client-management-server.h>
#include <app/server-cluster/DefaultServerCluster.h>
+#include <functional>
#include <protocols/interaction_model/StatusCode.h>
#include <vector>
@@ -29,8 +31,41 @@
ChipLogError(Zcl, "Push AV Stream Transport : Trying to set delegate to null");
return;
}
+ mDelegate->SetPushAvStreamTransportServer(this);
}
+ /**
+ * @brief API for application layer to notify when transport has started
+ *
+ * This should be called by the application layer when a transport begins streaming.
+ * It will generate the appropriate PushTransportBegin event.
+ *
+ * @param connectionID The connection ID of the transport that started
+ * @param triggerType The type of trigger that started the transport
+ * @param activationReason Optional reason for the activation
+ * @return Status::Success if event was generated successfully, failure otherwise
+ */
+ Protocols::InteractionModel::Status
+ NotifyTransportStarted(uint16_t connectionID, PushAvStreamTransport::TransportTriggerTypeEnum triggerType,
+ Optional<PushAvStreamTransport::TriggerActivationReasonEnum> activationReason =
+ Optional<PushAvStreamTransport::TriggerActivationReasonEnum>());
+
+ /**
+ * @brief API for application layer to notify when transport has stopped
+ *
+ * This should be called by the application layer when a transport stops streaming.
+ * It will generate the appropriate PushTransportEnd event.
+ *
+ * @param connectionID The connection ID of the transport that stopped
+ * @param triggerType The type of trigger that started the transport
+ * @param activationReason Optional reason for the deactivation
+ * @return Status::Success if event was generated successfully, failure otherwise
+ */
+ Protocols::InteractionModel::Status
+ NotifyTransportStopped(uint16_t connectionID, PushAvStreamTransport::TransportTriggerTypeEnum triggerType,
+ Optional<PushAvStreamTransport::TriggerActivationReasonEnum> activationReason =
+ Optional<PushAvStreamTransport::TriggerActivationReasonEnum>());
+
void SetTLSClientManagementDelegate(TlsClientManagementDelegate * delegate)
{
mTLSClientManagementDelegate = delegate;
@@ -41,6 +76,15 @@
}
}
+ void SetTlsCertificateManagementDelegate(TlsCertificateManagementDelegate * delegate)
+ {
+ mTlsCertificateManagementDelegate = delegate;
+ if (mTlsCertificateManagementDelegate == nullptr)
+ {
+ ChipLogError(Zcl, "Push AV Stream Transport: Trying to set TLS Certificate Management delegate to null");
+ return;
+ }
+ }
enum class UpsertResultEnum : uint8_t
{
kInserted = 0x00,
@@ -105,8 +149,10 @@
const Optional<PushAvStreamTransport::TriggerActivationReasonEnum> activationReason);
private:
- PushAvStreamTransportDelegate * mDelegate = nullptr;
- TlsClientManagementDelegate * mTLSClientManagementDelegate = nullptr;
+ PushAvStreamTransportDelegate * mDelegate = nullptr;
+ TlsClientManagementDelegate * mTLSClientManagementDelegate = nullptr;
+ TlsCertificateManagementDelegate * mTlsCertificateManagementDelegate = nullptr;
+
/// Convenience method that returns if the internal delegate is null and will log
/// an error if the check returns true
bool IsNullDelegateWithLogging(EndpointId endpointIdForLogging);
diff --git a/src/app/clusters/push-av-stream-transport-server/tests/TestPushAVStreamTransportCluster.cpp b/src/app/clusters/push-av-stream-transport-server/tests/TestPushAVStreamTransportCluster.cpp
index 9efdd45..99560cf 100644
--- a/src/app/clusters/push-av-stream-transport-server/tests/TestPushAVStreamTransportCluster.cpp
+++ b/src/app/clusters/push-av-stream-transport-server/tests/TestPushAVStreamTransportCluster.cpp
@@ -371,6 +371,17 @@
return CHIP_NO_ERROR;
}
+ void SetTLSCerts(Tls::CertificateTable::BufferedClientCert & clientCertEntry,
+ Tls::CertificateTable::BufferedRootCert & rootCertEntry) override
+ {
+ // No-op implementation for tests
+ }
+
+ void SetPushAvStreamTransportServer(PushAvStreamTransportServerLogic * serverLogic) override
+ {
+ // No-op implementation for tests
+ }
+
private:
std::vector<Clusters::PushAvStreamTransport::PushAvStream> pushavStreams;
};
diff --git a/src/app/clusters/tls-certificate-management-server/tls-certificate-management-server.h b/src/app/clusters/tls-certificate-management-server/tls-certificate-management-server.h
index d3366db..8d7a51e 100644
--- a/src/app/clusters/tls-certificate-management-server/tls-certificate-management-server.h
+++ b/src/app/clusters/tls-certificate-management-server/tls-certificate-management-server.h
@@ -81,6 +81,8 @@
*/
EndpointId GetEndpointId() { return AttributeAccessInterface::GetEndpointId().Value(); }
+ Tls::CertificateTable & GetCertificateTable() { return mCertificateTable; }
+
private:
TlsCertificateManagementDelegate & mDelegate;
Tls::CertificateTable & mCertificateTable;
@@ -331,6 +333,8 @@
virtual Protocols::InteractionModel::Status RemoveClientCert(EndpointId matterEndpoint, FabricIndex fabric,
Tls::TLSCAID id) = 0;
+ Tls::CertificateTable & GetCertificateTable() { return mTlsCertificateManagementServer->GetCertificateTable(); }
+
protected:
friend class TlsCertificateManagementServer;