Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
186 changes: 171 additions & 15 deletions src/projects/modules/dtls_srtp/srtp_transport.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
//==============================================================================
#include "srtp_transport.h"
#include "dtls_transport.h"
#include <modules/rtsp/rtsp_data.h>
#include <base/ovlibrary/byte_io.h>

#define OV_LOG_TAG "SRTP"

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

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

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

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.

SrtpTransport::Stop() clears the per-channel session maps but leaves _ssrc_to_channel populated. This mapping can persist across restarts and grow unnecessarily; clear it during Stop() alongside the other per-stream state.

Suggested change
_channel_send_sessions.clear();
_channel_send_sessions.clear();
_ssrc_to_channel.clear();

Copilot uses AI. Check for mistakes.

return Node::Stop();
}

Expand All @@ -44,32 +64,69 @@ bool SrtpTransport::OnDataReceivedFromPrevNode(NodeType from_node, const std::sh
return false;
}

if(!_send_session)
// Determine which send session to use
SrtpAdapter *send_adapter = nullptr;

if (_send_session)
{
// Single-key mode (WebRTC DTLS-SRTP)
send_adapter = _send_session.get();
}
else if (!_channel_send_sessions.empty())
{
// Per-channel mode (RTSP SDES-SRTP)
// Try to determine the channel from the RTCP packet's report block SSRC
if (from_node == NodeType::Rtcp && data->GetLength() >= 12)
{
auto ptr = data->GetDataAs<uint8_t>();
uint8_t rc = ptr[0] & 0x1F; // Report count
if (rc > 0)
{
// First report block SSRC (bytes 8-11) is the remote SSRC we're reporting on
uint32_t report_ssrc = ByteReader<uint32_t>::ReadBigEndian(&ptr[8]);
auto ch_it = _ssrc_to_channel.find(report_ssrc);
if (ch_it != _ssrc_to_channel.end())
{
auto sess_it = _channel_send_sessions.find(ch_it->second);
if (sess_it != _channel_send_sessions.end())
{
send_adapter = sess_it->second.get();
}
}
}
}

// Fallback: use the first available send session
if (send_adapter == nullptr)
{
send_adapter = _channel_send_sessions.begin()->second.get();
}
}
else
{
return false;
}

if(from_node == NodeType::Rtp)
{
if(!_send_session->ProtectRtp(data))
if(!send_adapter->ProtectRtp(data))
{
return false;
}

return SendDataToNextNode(NodeType::Srtp, data);
}
else if(from_node == NodeType::Rtcp)
{
if(!_send_session->ProtectRtcp(data))
if(!send_adapter->ProtectRtcp(data))
{
return false;
}
}
else
{
return false;

return SendDataToNextNode(NodeType::Srtcp, data);
}

// To DTLS transport
return SendDataToNextNode(data);
return false;
}

bool SrtpTransport::OnDataReceivedFromNextNode(NodeType from_node, const std::shared_ptr<const ov::Data> &data)
Expand All @@ -80,14 +137,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 +193,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 +204,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 +213,23 @@ 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)
{
// Learn SSRC -> channel mapping from incoming RTP for outgoing RTCP routing
if (node_type == NodeType::Srtp && decode_data->GetLength() >= 12)
{
uint32_t ssrc = ByteReader<uint32_t>::ReadBigEndian(&decode_data->GetDataAs<uint8_t>()[8]);
uint8_t rtp_channel = rtsp_data->GetChannelId() & ~1;
_ssrc_to_channel[ssrc] = rtp_channel;
}

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 +269,52 @@ 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;
}

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

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

// Create send (outbound) session with the same key (SDES-SRTP uses same key for both directions)
auto send_session = std::make_shared<SrtpAdapter>();
if (send_session == nullptr)
{
logte("Failed to create SRTP send adapter for channel %u", rtp_channel_id);
return false;
}

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

_channel_recv_sessions[rtp_channel_id] = recv_session;
_channel_send_sessions[rtp_channel_id] = send_session;

logtd("Added per-channel SRTP recv/send sessions for interleaved channel %u", rtp_channel_id);

return true;
}
15 changes: 15 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,24 @@ 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 -> 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;
std::map<uint8_t, std::shared_ptr<SrtpAdapter>> _channel_send_sessions;

// SSRC -> RTP channel ID mapping (learned from incoming RTP for outgoing RTCP routing)
std::map<uint32_t, uint8_t> _ssrc_to_channel;
};
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 @@ -680,17 +697,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
Loading
Loading