Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 90 additions & 5 deletions src/projects/modules/dtls_srtp/srtp_transport.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
//==============================================================================
#include "srtp_transport.h"
#include "dtls_transport.h"
#include <modules/rtsp/rtsp_data.h>

#define OV_LOG_TAG "SRTP"

Expand All @@ -33,6 +34,15 @@ bool SrtpTransport::Stop()
_recv_session->Release();
}

for (auto &[channel_id, session] : _channel_recv_sessions)
{
if (session != nullptr)
{
session->Release();
}
}
_channel_recv_sessions.clear();

return Node::Stop();
}

Expand Down Expand Up @@ -80,14 +90,49 @@ bool SrtpTransport::OnDataReceivedFromNextNode(NodeType from_node, const std::sh
return false;
}

if(_recv_session == nullptr)
if(data->GetLength() < 4)
{
// Invalid RTP or RTCP packet
return false;
}

if(data->GetLength() < 4)
// Determine which recv session to use.
// If per-channel sessions exist (RTSP SDES-SRTP), look up by channel ID.
// Otherwise fall back to the single _recv_session (WebRTC DTLS-SRTP).
SrtpAdapter *recv_adapter = nullptr;

if (!_channel_recv_sessions.empty())
{
// Try to extract the interleaved channel ID from RtspData
auto rtsp_data = std::dynamic_pointer_cast<const RtspData>(data);
if (rtsp_data != nullptr)
{
// Map both RTP (even) and RTCP (odd) channels to the same session
// The session is keyed by the RTP channel (even)
uint8_t rtp_channel = rtsp_data->GetChannelId() & ~1;
auto it = _channel_recv_sessions.find(rtp_channel);
if (it != _channel_recv_sessions.end())
{
recv_adapter = it->second.get();
}
else
{
logte("No SRTP session found for interleaved channel %u", rtsp_data->GetChannelId());
return false;
}
}
else
{
logte("Per-channel SRTP is configured but received non-RtspData");
return false;
}
}
else if (_recv_session != nullptr)
{
recv_adapter = _recv_session.get();
}
else
{
// Invalid RTP or RTCP packet
return false;
}

Expand All @@ -101,7 +146,7 @@ bool SrtpTransport::OnDataReceivedFromNextNode(NodeType from_node, const std::sh
// RTCP
if(payload_type >= 192 && payload_type <= 223)
{
if(!_recv_session->UnprotectRtcp(decode_data))
if(!recv_adapter->UnprotectRtcp(decode_data))
{
logtt("RTCP unprotected fail");
return false;
Expand All @@ -112,7 +157,7 @@ bool SrtpTransport::OnDataReceivedFromNextNode(NodeType from_node, const std::sh
// RTP
else
{
if(!_recv_session->UnprotectRtp(decode_data))
if(!recv_adapter->UnprotectRtp(decode_data))
{
logtt("RTP unprotected fail");
return false;
Expand All @@ -121,6 +166,15 @@ bool SrtpTransport::OnDataReceivedFromNextNode(NodeType from_node, const std::sh
node_type = NodeType::Srtp;
}

// If the original data was RtspData, preserve the channel ID through SRTP decryption
// so that RtpRtcp can use channel-based track lookup
auto rtsp_data = std::dynamic_pointer_cast<const RtspData>(data);
if (rtsp_data != nullptr)
{
auto rtsp_decode_data = std::make_shared<RtspData>(rtsp_data->GetChannelId(), decode_data);
return SendDataToPrevNode(node_type, rtsp_decode_data);
}

// To RTP_RTCP
return SendDataToPrevNode(node_type, decode_data);
}
Expand Down Expand Up @@ -160,5 +214,36 @@ bool SrtpTransport::SetKeyMaterial(uint64_t crypto_suite, std::shared_ptr<ov::Da
return false;
}

return true;
}

bool SrtpTransport::AddChannelKeyMaterial(uint8_t rtp_channel_id, uint64_t crypto_suite, std::shared_ptr<ov::Data> key)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It creates only the recv session and does not set up _send_session. Since OnDataReceivedFromPrevNode() immediately returns false when _send_session is null, all RTCP Receiver Reports sent from OME to the camera are dropped. In SDES-SRTP, key negotiation for OME's outgoing stream and initialization of _send_session are required.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, no input from my side on this one:

Copilot response:

That's a significant oversight, thanks for the detailed explanation. You're absolutely right, without send sessions all outgoing RTCP gets silently dropped since [OnDataReceivedFromPrevNode()] bails out on the null check. I've fixed this by having [AddChannelKeyMaterial()] create both inbound and outbound sessions per channel using the same key (as SDES-SRTP uses symmetric keying). The outgoing path now looks up the correct per-channel send session using an SSRC to channel mapping learned from incoming RTP.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in commit: e8c3498

{
// Ensure the channel ID is even (RTP channel)
rtp_channel_id = rtp_channel_id & ~1;

if (_channel_recv_sessions.find(rtp_channel_id) != _channel_recv_sessions.end())
{
logte("SRTP session already exists for channel %u", rtp_channel_id);
return false;
}

auto recv_session = std::make_shared<SrtpAdapter>();
if (recv_session == nullptr)
{
logte("Failed to create SRTP adapter for channel %u", rtp_channel_id);
return false;
}

if (!recv_session->SetKey(ssrc_any_inbound, crypto_suite, key))
{
logte("Failed to set SRTP key for channel %u", rtp_channel_id);
return false;
}

_channel_recv_sessions[rtp_channel_id] = recv_session;

logtd("Added per-channel SRTP session for interleaved channel %u", rtp_channel_id);

return true;
}
11 changes: 11 additions & 0 deletions src/projects/modules/dtls_srtp/srtp_transport.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,20 @@ class SrtpTransport : public ov::Node
bool OnDataReceivedFromPrevNode(NodeType from_node, const std::shared_ptr<ov::Data> &data) override;
bool OnDataReceivedFromNextNode(NodeType from_node, const std::shared_ptr<const ov::Data> &data) override;

// Single key for all channels (used by WebRTC / DTLS-SRTP)
bool SetKeyMaterial(uint64_t crypto_suite, std::shared_ptr<ov::Data> server_key, std::shared_ptr<ov::Data> client_key);

// Per-channel keying for RTSP SDES-SRTP (RFC 4568).
// Each interleaved channel pair (rtp_channel, rtp_channel+1) gets its own SRTP session.
bool AddChannelKeyMaterial(uint8_t rtp_channel_id, uint64_t crypto_suite, std::shared_ptr<ov::Data> key);

private:
// Single-key mode (WebRTC)
std::shared_ptr<SrtpAdapter> _send_session = nullptr;
std::shared_ptr<SrtpAdapter> _recv_session = nullptr;

// Per-channel mode (RTSP SDES-SRTP)
// Maps RTP interleaved channel ID -> recv SrtpAdapter
// Odd channel IDs (RTCP) are resolved to even channel ID (RTP) via (channel_id & ~1)
std::map<uint8_t, std::shared_ptr<SrtpAdapter>> _channel_recv_sessions;
};
55 changes: 37 additions & 18 deletions src/projects/modules/rtp_rtcp/rtp_rtcp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -444,21 +444,34 @@ bool RtpRtcp::OnRtpReceived(NodeType from_node, const std::shared_ptr<const ov::
std::optional<uint32_t> track_id_opt = GetTrackId(packet->Ssrc());
if (track_id_opt.has_value() == false)
{
if(from_node == NodeType::Rtsp)
// For RTSP sources (direct or via SRTP), use channel ID for track lookup
if(from_node == NodeType::Rtsp || from_node == NodeType::Srtp)
{
auto rtsp_data = std::static_pointer_cast<const RtspData>(data);
if(rtsp_data == nullptr)
auto rtsp_data = std::dynamic_pointer_cast<const RtspData>(data);
if(rtsp_data != nullptr)
{
// RTSP Node uses channelID as trackID
track_id_opt = FindTrackId(rtsp_data->GetChannelId());
if (track_id_opt.has_value() == false)
{
logte("Could not find track ID for RTSP channel ID %u", rtsp_data->GetChannelId());
return false;
}
}
else if(from_node == NodeType::Rtsp)
{
logte("Could not convert to RtspData");
return false;
}

// RTSP Node uses channelID as trackID
track_id_opt = FindTrackId(rtsp_data->GetChannelId());
if (track_id_opt.has_value() == false)
else
{
logte("Could not find track ID for RTSP channel ID %u", rtsp_data->GetChannelId());
return false;
// SRTP without RtspData (e.g. WebRTC) - fall through to generic lookup
track_id_opt = FindTrackId(packet);
if (track_id_opt.has_value() == false)
{
logte("Could not find track ID for SSRC %u", packet->Ssrc());
return false;
}
}
}
else
Expand All @@ -474,10 +487,14 @@ bool RtpRtcp::OnRtpReceived(NodeType from_node, const std::shared_ptr<const ov::
ConnectSsrcToTrack(packet->Ssrc(), track_id_opt.value());
}

if (from_node == NodeType::Rtsp)
if (from_node == NodeType::Rtsp || from_node == NodeType::Srtp)
{
// RTSP Node uses channelID as trackID
packet->SetRtspChannel(track_id_opt.value());
auto rtsp_data = std::dynamic_pointer_cast<const RtspData>(data);
if (rtsp_data != nullptr)
{
// RTSP Node uses channelID as trackID
packet->SetRtspChannel(track_id_opt.value());
}
}

auto track_id = track_id_opt.value();
Expand Down Expand Up @@ -651,17 +668,19 @@ bool RtpRtcp::OnRtcpReceived(NodeType from_node, const std::shared_ptr<const ov:
}

uint32_t rtsp_channel = 0;
if(from_node == NodeType::Rtsp)
if(from_node == NodeType::Rtsp || from_node == NodeType::Srtcp)
{
auto rtsp_data = std::static_pointer_cast<const RtspData>(data);
if(rtsp_data == nullptr)
auto rtsp_data = std::dynamic_pointer_cast<const RtspData>(data);
if(rtsp_data != nullptr)
{
// RTSP Node uses channelID as trackID
rtsp_channel = rtsp_data->GetChannelId();
}
else if(from_node == NodeType::Rtsp)
{
logte("Could not convert to RtspData");
return false;
}

// RTSP Node uses channelID as trackID
rtsp_channel = rtsp_data->GetChannelId();
}

while(receiver.HasAvailableRtcpInfo())
Expand Down
60 changes: 60 additions & 0 deletions src/projects/modules/sdp/media_description.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -607,6 +607,30 @@ bool MediaDescription::ParsingMediaLine(char type, std::string content)
id,
match.GetGroupAt(2).GetValue());
}
else if (content.compare(0, 7, "crypto:") == 0)
{
// a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:base64key [session-params]
// Regex: ^crypto:(\d+) ([\w_]+) inline:([\w\+/=]+)(?:\|.*)?(?:\s+(.*))?$
// Groups: 0=full match, 1=tag, 2=crypto_suite, 3=key_params, 4=session_params (optional)
// PCRE2 does not count trailing optional groups that didn't match,
// so group count is 4 (without session params) or 5 (with session params).
constexpr size_t CRYPTO_MIN_GROUP_COUNT = 4; // groups 0-3 required
auto match = SDPRegexPattern::GetInstance()->MatchCrypto(content.c_str());
if (match.GetGroupCount() >= CRYPTO_MIN_GROUP_COUNT)
{
ov::String session_params;
if (match.GetGroupCount() >= 5)
{
session_params = match.GetGroupAt(4).GetValue();
}

AddCrypto(
ov::Converter::ToUInt32(match.GetGroupAt(1).GetValue().CStr()),
match.GetGroupAt(2).GetValue(),
match.GetGroupAt(3).GetValue(),
session_params);
}
Comment thread
getroot marked this conversation as resolved.
}
else if (ParsingCommonAttrLine(type, content))
{
}
Expand Down Expand Up @@ -1122,6 +1146,42 @@ bool MediaDescription::FindExtmapItem(const ov::String &keyword, uint8_t &id, ov
return false;
}

void MediaDescription::AddCrypto(uint32_t tag, const ov::String &crypto_suite, const ov::String &key_params, const ov::String &session_params)
{
CryptoAttr crypto;
crypto.tag = tag;
crypto.crypto_suite = crypto_suite;
crypto.key_params = key_params;
crypto.session_params = session_params;
_crypto_list.push_back(crypto);
}

const std::vector<MediaDescription::CryptoAttr>& MediaDescription::GetCryptoList() const
{
return _crypto_list;
}

std::optional<MediaDescription::CryptoAttr> MediaDescription::GetCrypto(uint32_t tag) const
{
for (const auto &crypto : _crypto_list)
{
if (crypto.tag == tag)
{
return crypto;
}
}
return std::nullopt;
}

std::optional<MediaDescription::CryptoAttr> MediaDescription::GetFirstCrypto() const
{
if (!_crypto_list.empty())
{
return _crypto_list.front();
}
return std::nullopt;
}

// a=rtpmap:96 VP8/50000
bool MediaDescription::AddRtpmap(uint8_t payload_type, const ov::String &codec,
uint32_t rate, const ov::String &parameters)
Expand Down
16 changes: 16 additions & 0 deletions src/projects/modules/sdp/media_description.h
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,20 @@ class MediaDescription : public SdpBase, public CommonAttr
std::optional<uint32_t> GetRtxSsrc() const;
std::optional<ov::String> GetCname() const;

// a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:base64key
struct CryptoAttr
{
uint32_t tag;
ov::String crypto_suite;
ov::String key_params;
ov::String session_params;
};

void AddCrypto(uint32_t tag, const ov::String &crypto_suite, const ov::String &key_params, const ov::String &session_params = "");
const std::vector<CryptoAttr>& GetCryptoList() const;
std::optional<CryptoAttr> GetCrypto(uint32_t tag) const;
std::optional<CryptoAttr> GetFirstCrypto() const;

// a=extmap:1 urn:ietf:params:rtp-hdrext:framemarking
void AddExtmap(uint8_t id, ov::String attribute);
std::map<uint8_t, ov::String> GetExtmap() const;
Expand Down Expand Up @@ -172,6 +186,8 @@ class MediaDescription : public SdpBase, public CommonAttr

std::map<uint8_t, ov::String> _extmap;

std::vector<CryptoAttr> _crypto_list;

std::vector<std::shared_ptr<PayloadAttr>> _payload_list;
std::vector<std::shared_ptr<RidAttr>> _rid_list;
std::vector<std::shared_ptr<SimulcastLayer>> _send_layers;
Expand Down
3 changes: 3 additions & 0 deletions src/projects/modules/sdp/sdp_regex_pattern.h
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ class SDPRegexPattern : public ov::Singleton<SDPRegexPattern>

RegisterPattern(_rid_pattern, R"(^rid:([a-zA-Z0-9\-_]+)\s+(send|recv)(?:\s+(.*))?)");
RegisterPattern(_simulcast_pattern, R"(^simulcast:\s*(send|recv)\s+([a-zA-Z0-9\-_\,;]+)(?:\s*(send|recv)\s+([a-zA-Z0-9\-_\,;]+))?)");
RegisterPattern(_crypto_pattern, R"(^crypto:(\d+) ([\w_]+) inline:([\w\+/=]+)(?:\|.*)?(?:\s+(.*))?$)");
_built = true;

return true;
Expand Down Expand Up @@ -119,6 +120,7 @@ class SDPRegexPattern : public ov::Singleton<SDPRegexPattern>

RegisterMatchFunction(_rid_pattern, MatchRid)
RegisterMatchFunction(_simulcast_pattern, MatchSimulcast)
RegisterMatchFunction(_crypto_pattern, MatchCrypto)

private:
bool _built = false;
Expand Down Expand Up @@ -160,4 +162,5 @@ class SDPRegexPattern : public ov::Singleton<SDPRegexPattern>

ov::Regex _rid_pattern; // a=rid:1 send pt=97,98;max-width=1280;max-height=720
ov::Regex _simulcast_pattern; // a=simulcast:send 1;2,3
ov::Regex _crypto_pattern; // a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:base64key
};
4 changes: 4 additions & 0 deletions src/projects/orchestrator/orchestrator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -944,6 +944,10 @@ namespace ocst
{
type = ProviderType::RtspPull;
}
else if (lower_scheme == "rtsps")
{
type = ProviderType::RtspPull;
}
Comment on lines 969 to +975

Copilot AI Mar 30, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The rtsps scheme handling is functionally identical to the rtsp branch and duplicates the assignment. This can be simplified to a single condition (e.g., (lower_scheme == "rtsp" || lower_scheme == "rtsps")) to reduce branching and keep scheme mappings easier to maintain.

Copilot uses AI. Check for mistakes.
else if (lower_scheme == "ovt")
{
type = ProviderType::Ovt;
Expand Down
Loading