Compare commits

..

3 Commits

Author SHA1 Message Date
dailz
8b04893ceb fix(security): remove error details from HTTP 500 response (#12)
The 500 error response previously included the raw error message {e}
in the body, potentially leaking internal implementation details (SDP
parse errors, ICE candidate info) to clients.

The detailed error is already logged server-side via tracing::error!,
so the response body is now a fixed generic string with a proper
HTTP/1.1 status line.
2026-06-06 21:22:57 +08:00
dailz
1beaea8088 fix(webrtc): use MediaAdded event to discover video mid instead of hardcoded iteration (closes #11) 2026-06-06 21:16:55 +08:00
dailz
fc4733ffe8 fix: return Ok(true) on ICE Disconnected to prevent resource leak
poll_rtc() always returned Ok(false), preventing WebRtcState from
clearing self.inner on disconnect. This leaked the UDP socket, Rtc
instance, and 65KB buffer permanently if the client never reconnected.

Closes #10
2026-06-06 20:57:25 +08:00

View File

@@ -210,7 +210,7 @@ impl WebRtcState {
} }
Err(e) => { Err(e) => {
tracing::error!("SDP offer handling failed: {e}"); tracing::error!("SDP offer handling failed: {e}");
let resp = format!("HTTP/1.1 500 Error\r\nConnection: close\r\n\r\n{e}"); let resp = "HTTP/1.1 500 Internal Server Error\r\nConnection: close\r\n\r\n";
let _ = stream.write_all(resp.as_bytes()); let _ = stream.write_all(resp.as_bytes());
} }
} }
@@ -311,18 +311,14 @@ impl WebRtcInner {
} }
fn discover_video_params(&mut self) { fn discover_video_params(&mut self) {
for s in ["0", "1", "2", "3"] { let mid = match self.video_mid {
let mid: Mid = s.into(); Some(m) => m,
if let Some(media) = self.rtc.media(mid) { None => {
if media.kind() == MediaKind::Video { tracing::warn!("discover_video_params: no video_mid yet");
tracing::info!("Found video media: mid={mid}"); return;
self.video_mid = Some(mid);
break;
} }
} };
} self.video_pt = None;
if let Some(mid) = self.video_mid {
if let Some(writer) = self.rtc.writer(mid) { if let Some(writer) = self.rtc.writer(mid) {
for pp in writer.payload_params() { for pp in writer.payload_params() {
tracing::debug!("Codec: pt={:?} spec={:?}", pp.pt(), pp.spec()); tracing::debug!("Codec: pt={:?} spec={:?}", pp.pt(), pp.spec());
@@ -333,6 +329,8 @@ impl WebRtcInner {
} }
} }
} }
if self.video_pt.is_none() {
tracing::warn!("discover_video_params: no H.264 codec found for mid={mid}");
} }
} }
@@ -357,9 +355,21 @@ impl WebRtcInner {
Event::IceConnectionStateChange(IceConnectionState::Disconnected) => { Event::IceConnectionStateChange(IceConnectionState::Disconnected) => {
tracing::warn!("WebRTC disconnected"); tracing::warn!("WebRTC disconnected");
self.connected = false; self.connected = false;
return Ok(true);
} }
Event::MediaAdded(ma) => { Event::MediaAdded(ma) => {
tracing::info!("Media added: mid={:?}", ma.mid); tracing::info!("Media added: mid={} kind={:?}", ma.mid, ma.kind);
if ma.kind == MediaKind::Video {
if let Some(media) = self.rtc.media(ma.mid) {
if media.direction().is_sending()
&& self.video_mid.is_none()
{
self.video_mid = Some(ma.mid);
tracing::info!("Captured video mid: {}", ma.mid);
self.discover_video_params();
}
}
}
} }
_ => { _ => {
tracing::debug!("WebRTC event: {:?}", e); tracing::debug!("WebRTC event: {:?}", e);