From 60e8c44abbd0fc624a7a71aab027d09b77fa3c43 Mon Sep 17 00:00:00 2001 From: olivierbabasse Date: Fri, 8 Dec 2023 16:48:03 +0100 Subject: [PATCH] rtsp server: allow custom authentication Enables subclassing gst_rtsp_server::RTSPAuth and overriding its authenticate/check/generate_authenticate_header methods Also add new methods in RTSPContext to retrieve RTSP request/response, and to get/replace tokens. Additionally, added RTSPMessage with methods to add an authentication header to a request / retrieve authentication parameters from a response. Part-of: --- Cargo.lock | 7 + examples/Cargo.toml | 5 + examples/src/bin/rtsp-server-custom-auth.rs | 219 ++++++++++++++++++ gstreamer-rtsp-server/src/rtsp_client.rs | 19 +- gstreamer-rtsp-server/src/rtsp_context.rs | 87 ++++++- gstreamer-rtsp-server/src/subclass/mod.rs | 2 + .../src/subclass/rtsp_auth.rs | 116 ++++++++++ gstreamer-rtsp/Gir.toml | 5 + gstreamer-rtsp/src/auto/mod.rs | 3 + .../src/auto/rtsp_auth_credential.rs | 17 ++ gstreamer-rtsp/src/lib.rs | 3 + gstreamer-rtsp/src/rtsp_auth_credential.rs | 26 +++ gstreamer-rtsp/src/rtsp_auth_param.rs | 25 ++ gstreamer-rtsp/src/rtsp_message.rs | 58 +++++ 14 files changed, 588 insertions(+), 4 deletions(-) create mode 100644 examples/src/bin/rtsp-server-custom-auth.rs create mode 100644 gstreamer-rtsp-server/src/subclass/rtsp_auth.rs create mode 100644 gstreamer-rtsp/src/auto/rtsp_auth_credential.rs create mode 100644 gstreamer-rtsp/src/rtsp_auth_credential.rs create mode 100644 gstreamer-rtsp/src/rtsp_auth_param.rs create mode 100644 gstreamer-rtsp/src/rtsp_message.rs diff --git a/Cargo.lock b/Cargo.lock index 90f5e7871..5199b4aaf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -388,6 +388,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96a6ac251f4a2aca6b3f91340350eab87ae57c3f127ffeb585e92bd336717991" +[[package]] +name = "data-encoding" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5" + [[package]] name = "derive_more" version = "0.99.17" @@ -474,6 +480,7 @@ dependencies = [ "byte-slice-cast", "cairo-rs", "cocoa", + "data-encoding", "derive_more", "futures", "gio", diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 5254f9df9..960d9a088 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -41,6 +41,7 @@ raw-window-handle = { version = "0.5", optional = true } uds = { version = "0.4", optional = true } winit = { version = "0.29", optional = true, default-features = false, features = ["rwh_05"] } atomic_refcell = "0.1" +data-encoding = "2.0" [target.'cfg(windows)'.dependencies] windows = { version = "0.52", features=["Win32_Graphics_Direct3D11", @@ -133,6 +134,10 @@ required-features = ["rtsp-server"] name = "rtsp-server-subclass" required-features = ["rtsp-server"] +[[bin]] +name = "rtsp-server-custom-auth" +required-features = ["rtsp-server", "gst-rtsp-server/v1_22"] + [[bin]] name = "tagsetter" diff --git a/examples/src/bin/rtsp-server-custom-auth.rs b/examples/src/bin/rtsp-server-custom-auth.rs new file mode 100644 index 000000000..8df20453e --- /dev/null +++ b/examples/src/bin/rtsp-server-custom-auth.rs @@ -0,0 +1,219 @@ +// This example demonstrates how to set up a rtsp server using GStreamer +// and extending the default auth module behaviour by subclassing RTSPAuth +// For this, the example creates a videotestsrc pipeline manually to be used +// by the RTSP server for providing data +#![allow(clippy::non_send_fields_in_send_ty)] + +use anyhow::Error; +use derive_more::{Display, Error}; +use gst_rtsp_server::prelude::*; + +#[path = "../examples-common.rs"] +mod examples_common; + +#[derive(Debug, Display, Error)] +#[display(fmt = "Could not get mount points")] +struct NoMountPoints; + +fn main_loop() -> Result<(), Error> { + let main_loop = glib::MainLoop::new(None, false); + let server = gst_rtsp_server::RTSPServer::new(); + + // We create our custom auth module. + // The job of the auth module is to authenticate users and authorize + // factories access/construction. + let auth = auth::Auth::default(); + server.set_auth(Some(&auth)); + + // Much like HTTP servers, RTSP servers have multiple endpoints that + // provide different streams. Here, we ask our server to give + // us a reference to his list of endpoints, so we can add our + // test endpoint, providing the pipeline from the cli. + let mounts = server.mount_points().ok_or(NoMountPoints)?; + + // Next, we create a factory for the endpoint we want to create. + // The job of the factory is to create a new pipeline for each client that + // connects, or (if configured to do so) to reuse an existing pipeline. + let factory = gst_rtsp_server::RTSPMediaFactory::new(); + // Here we tell the media factory the media we want to serve. + // This is done in the launch syntax. When the first client connects, + // the factory will use this syntax to create a new pipeline instance. + factory.set_launch("( videotestsrc ! vp8enc ! rtpvp8pay name=pay0 )"); + // This setting specifies whether each connecting client gets the output + // of a new instance of the pipeline, or whether all connected clients share + // the output of the same pipeline. + // If you want to stream a fixed video you have stored on the server to any + // client, you would not set this to shared here (since every client wants + // to start at the beginning of the video). But if you want to distribute + // a live source, you will probably want to set this to shared, to save + // computing and memory capacity on the server. + factory.set_shared(true); + + // Now we add a new mount-point and tell the RTSP server to serve the content + // provided by the factory we configured above, when a client connects to + // this specific path. + mounts.add_factory("/test", factory); + + // Attach the server to our main context. + // A main context is the thing where other stuff is registering itself for its + // events (e.g. sockets, GStreamer bus, ...) and the main loop is something that + // polls the main context for its events and dispatches them to whoever is + // interested in them. In this example, we only do have one, so we can + // leave the context parameter empty, it will automatically select + // the default one. + let id = server.attach(None)?; + + println!( + "Stream ready at rtsp://127.0.0.1:{}/test", + server.bound_port() + ); + println!("user admin/password can access stream"); + println!("user demo/demo passes authentication but receives 404"); + println!("other users do not pass pass authentication and receive 401"); + + // Start the mainloop. From this point on, the server will start to serve + // our quality content to connecting clients. + main_loop.run(); + + id.remove(); + + Ok(()) +} + +// Our custom auth module +mod auth { + // In the imp submodule we include the actual implementation + mod imp { + use gst_rtsp::{RTSPHeaderField, RTSPStatusCode}; + use gst_rtsp_server::{prelude::*, subclass::prelude::*, RTSPContext}; + + // This is the private data of our auth + #[derive(Default)] + pub struct Auth; + + impl Auth { + // Simulate external auth validation and user extraction + // authorized users are admin/password and demo/demo + fn external_auth(&self, auth: &str) -> Option { + if let Ok(decoded) = data_encoding::BASE64.decode(auth.as_bytes()) { + if let Ok(decoded) = std::str::from_utf8(&decoded) { + let tokens = decoded.split(':').collect::>(); + if tokens == vec!["admin", "password"] || tokens == vec!["demo", "demo"] { + return Some(tokens[0].into()); + } + } + } + None + } + + // Simulate external role check + // admin user can construct and access media factory + fn external_access_check(&self, user: &str) -> bool { + user == "admin" + } + } + + // This trait registers our type with the GObject object system and + // provides the entry points for creating a new instance and setting + // up the class data + #[glib::object_subclass] + impl ObjectSubclass for Auth { + const NAME: &'static str = "RsRTSPAuth"; + type Type = super::Auth; + type ParentType = gst_rtsp_server::RTSPAuth; + } + + // Implementation of glib::Object virtual methods + impl ObjectImpl for Auth {} + + // Implementation of gst_rtsp_server::RTSPAuth virtual methods + impl RTSPAuthImpl for Auth { + fn authenticate(&self, ctx: &RTSPContext) -> bool { + // authenticate should always be called with a valid context request + let req = ctx + .request() + .expect("Context without request. Should not happen !"); + + if let Some(auth_credentials) = req.parse_auth_credentials().get(0) { + if let Some(authorization) = auth_credentials.authorization() { + if let Some(user) = self.external_auth(authorization) { + // Update context token with authenticated username + ctx.set_token(gst_rtsp_server::RTSPToken::new(&[("user", &user)])); + return true; + } + } + } + + false + } + + fn check(&self, ctx: &RTSPContext, role: &glib::GString) -> bool { + // We only check media factory access + if !role.starts_with("auth.check.media.factory") { + return true; + } + + if ctx.token().is_none() { + // If we do not have a context token yet, check if there are any auth credentials in request + if !self.authenticate(ctx) { + // If there were no credentials, send a "401 Unauthorized" response + if let Some(resp) = ctx.response() { + resp.init_response(RTSPStatusCode::Unauthorized, ctx.request()); + resp.add_header( + RTSPHeaderField::WwwAuthenticate, + "Basic realm=\"CustomRealm\"", + ); + if let Some(client) = ctx.client() { + client.send_message(resp, ctx.session()); + } + } + return false; + } + } + + if let Some(token) = ctx.token() { + // If we already have a user token... + if self.external_access_check(&token.string("user").unwrap_or_default()) { + // grant access if user may access factory + return true; + } else { + // send a "404 Not Found" response if user may not access factory + if let Some(resp) = ctx.response() { + resp.init_response(RTSPStatusCode::NotFound, ctx.request()); + if let Some(client) = ctx.client() { + client.send_message(resp, ctx.session()); + } + } + } + } + + false + } + } + } + + // This here defines the public interface of our auth and implements + // the corresponding traits so that it behaves like any other RTSPAuth + glib::wrapper! { + pub struct Auth(ObjectSubclass) @extends gst_rtsp_server::RTSPAuth; + } + + impl Default for Auth { + // Creates a new instance of our auth + fn default() -> Self { + glib::Object::new() + } + } +} + +fn example_main() -> Result<(), Error> { + gst::init()?; + main_loop() +} + +fn main() { + match examples_common::run(example_main) { + Ok(r) => r, + Err(e) => eprintln!("Error! {e}"), + } +} diff --git a/gstreamer-rtsp-server/src/rtsp_client.rs b/gstreamer-rtsp-server/src/rtsp_client.rs index 25168d454..f5b6f2398 100644 --- a/gstreamer-rtsp-server/src/rtsp_client.rs +++ b/gstreamer-rtsp-server/src/rtsp_client.rs @@ -1,8 +1,8 @@ // Take a look at the license at the top of the repository in the LICENSE file. +use crate::{RTSPClient, RTSPSession}; use glib::{prelude::*, source::SourceId, translate::*}; - -use crate::RTSPClient; +use gst_rtsp::rtsp_message::RTSPMessage; mod sealed { pub trait Sealed {} @@ -19,6 +19,21 @@ pub trait RTSPClientExtManual: sealed::Sealed + IsA + 'static { )) } } + + #[doc(alias = "gst_rtsp_client_send_message")] + fn send_message( + &self, + message: &RTSPMessage, + session: Option<&RTSPSession>, + ) -> gst_rtsp::RTSPResult { + unsafe { + from_glib(ffi::gst_rtsp_client_send_message( + self.as_ref().to_glib_none().0, + session.to_glib_none().0, + message.to_glib_none().0, + )) + } + } } impl> RTSPClientExtManual for O {} diff --git a/gstreamer-rtsp-server/src/rtsp_context.rs b/gstreamer-rtsp-server/src/rtsp_context.rs index 3be5b399d..d51cd8a84 100644 --- a/gstreamer-rtsp-server/src/rtsp_context.rs +++ b/gstreamer-rtsp-server/src/rtsp_context.rs @@ -5,8 +5,10 @@ use std::{ ptr::{self, addr_of}, }; -use glib::translate::*; -use gst_rtsp::RTSPUrl; +use glib::{translate::*, ObjectType}; +use gst_rtsp::{rtsp_message::RTSPMessage, RTSPUrl}; + +use crate::{RTSPClient, RTSPSession, RTSPToken}; #[derive(Debug, PartialEq, Eq)] #[doc(alias = "GstRTSPContext")] @@ -42,6 +44,87 @@ impl RTSPContext { } } + #[inline] + pub fn client(&self) -> Option<&RTSPClient> { + unsafe { + let ptr = self.0.as_ptr(); + if (*ptr).client.is_null() { + None + } else { + let client = RTSPClient::from_glib_ptr_borrow( + addr_of!((*ptr).client) as *const *const ffi::GstRTSPClient + ); + Some(client) + } + } + } + + #[inline] + pub fn request(&self) -> Option<&RTSPMessage> { + unsafe { + let ptr = self.0.as_ptr(); + if (*ptr).request.is_null() { + None + } else { + let msg = RTSPMessage::from_glib_ptr_borrow( + addr_of!((*ptr).request) as *const *const gst_rtsp::ffi::GstRTSPMessage + ); + Some(msg) + } + } + } + + #[inline] + pub fn response(&self) -> Option<&RTSPMessage> { + unsafe { + let ptr = self.0.as_ptr(); + if (*ptr).response.is_null() { + None + } else { + let msg = RTSPMessage::from_glib_ptr_borrow( + addr_of!((*ptr).response) as *const *const gst_rtsp::ffi::GstRTSPMessage + ); + Some(msg) + } + } + } + + #[inline] + pub fn session(&self) -> Option<&RTSPSession> { + unsafe { + let ptr = self.0.as_ptr(); + if (*ptr).session.is_null() { + None + } else { + let sess = RTSPSession::from_glib_ptr_borrow( + addr_of!((*ptr).session) as *const *const ffi::GstRTSPSession + ); + Some(sess) + } + } + } + + #[inline] + pub fn token(&self) -> Option { + unsafe { + let ptr = self.0.as_ptr(); + if (*ptr).token.is_null() { + None + } else { + let token = RTSPToken::from_glib_none((*ptr).token as *const ffi::GstRTSPToken); + Some(token) + } + } + } + + #[cfg(feature = "v1_22")] + #[doc(alias = "gst_rtsp_context_set_token")] + pub fn set_token(&self, token: RTSPToken) { + unsafe { + ffi::gst_rtsp_context_set_token(self.0.as_ptr(), token.into_glib_ptr()); + } + } + // TODO: Add additional getters for all the contained fields as needed } diff --git a/gstreamer-rtsp-server/src/subclass/mod.rs b/gstreamer-rtsp-server/src/subclass/mod.rs index 6a9ee7a7d..89a232a97 100644 --- a/gstreamer-rtsp-server/src/subclass/mod.rs +++ b/gstreamer-rtsp-server/src/subclass/mod.rs @@ -2,6 +2,7 @@ #![allow(clippy::cast_ptr_alignment)] +mod rtsp_auth; mod rtsp_client; mod rtsp_media; mod rtsp_media_factory; @@ -20,6 +21,7 @@ pub mod prelude { pub use gst::subclass::prelude::*; pub use super::{ + rtsp_auth::{RTSPAuthImpl, RTSPAuthImplExt}, rtsp_client::{RTSPClientImpl, RTSPClientImplExt}, rtsp_media::{RTSPMediaImpl, RTSPMediaImplExt}, rtsp_media_factory::{RTSPMediaFactoryImpl, RTSPMediaFactoryImplExt}, diff --git a/gstreamer-rtsp-server/src/subclass/rtsp_auth.rs b/gstreamer-rtsp-server/src/subclass/rtsp_auth.rs new file mode 100644 index 000000000..db42888fb --- /dev/null +++ b/gstreamer-rtsp-server/src/subclass/rtsp_auth.rs @@ -0,0 +1,116 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use crate::{RTSPAuth, RTSPContext}; +use glib::{prelude::*, subclass::prelude::*, translate::*}; +use libc::c_char; + +pub trait RTSPAuthImpl: RTSPAuthImplExt + ObjectImpl + Send + Sync { + fn authenticate(&self, ctx: &RTSPContext) -> bool { + self.parent_authenticate(ctx) + } + + fn check(&self, ctx: &RTSPContext, check: &glib::GString) -> bool { + self.parent_check(ctx, check) + } + + fn generate_authenticate_header(&self, ctx: &RTSPContext) { + self.parent_generate_authenticate_header(ctx); + } +} + +mod sealed { + pub trait Sealed {} + impl Sealed for T {} +} + +pub trait RTSPAuthImplExt: sealed::Sealed + ObjectSubclass { + fn parent_authenticate(&self, ctx: &RTSPContext) -> bool { + unsafe { + let data = Self::type_data(); + let parent_class = data.as_ref().parent_class() as *mut ffi::GstRTSPAuthClass; + (*parent_class) + .authenticate + .map(|f| { + from_glib(f( + self.obj().unsafe_cast_ref::().to_glib_none().0, + ctx.to_glib_none().0, + )) + }) + .unwrap_or(false) + } + } + + fn parent_check(&self, ctx: &RTSPContext, check: &glib::GString) -> bool { + unsafe { + let data = Self::type_data(); + let parent_class = data.as_ref().parent_class() as *mut ffi::GstRTSPAuthClass; + (*parent_class) + .check + .map(|f| { + from_glib(f( + self.obj().unsafe_cast_ref::().to_glib_none().0, + ctx.to_glib_none().0, + check.to_glib_none().0, + )) + }) + .unwrap_or(false) + } + } + + fn parent_generate_authenticate_header(&self, ctx: &RTSPContext) { + unsafe { + let data = Self::type_data(); + let parent_class = data.as_ref().parent_class() as *mut ffi::GstRTSPAuthClass; + if let Some(f) = (*parent_class).generate_authenticate_header { + f( + self.obj().unsafe_cast_ref::().to_glib_none().0, + ctx.to_glib_none().0, + ) + } + } + } +} + +impl RTSPAuthImplExt for T {} + +unsafe impl IsSubclassable for RTSPAuth { + fn class_init(klass: &mut glib::Class) { + Self::parent_class_init::(klass); + let klass = klass.as_mut(); + klass.authenticate = Some(rtsp_auth_authenticate::); + klass.check = Some(rtsp_auth_check::); + klass.generate_authenticate_header = Some(rtsp_auth_generate_authenticate_header::); + } +} + +unsafe extern "C" fn rtsp_auth_authenticate( + ptr: *mut ffi::GstRTSPAuth, + ctx: *mut ffi::GstRTSPContext, +) -> glib::ffi::gboolean { + let instance = &*(ptr as *mut T::Instance); + let imp = instance.imp(); + + imp.authenticate(&from_glib_borrow(ctx)).into_glib() +} + +unsafe extern "C" fn rtsp_auth_check( + ptr: *mut ffi::GstRTSPAuth, + ctx: *mut ffi::GstRTSPContext, + check: *const c_char, +) -> glib::ffi::gboolean { + let instance = &*(ptr as *mut T::Instance); + let imp = instance.imp(); + + imp.check(&from_glib_borrow(ctx), &from_glib_borrow(check)) + .into_glib() +} + +unsafe extern "C" fn rtsp_auth_generate_authenticate_header( + ptr: *mut ffi::GstRTSPAuth, + ctx: *mut ffi::GstRTSPContext, +) { + let instance = &*(ptr as *mut T::Instance); + let imp = instance.imp(); + + imp.generate_authenticate_header(&from_glib_borrow(ctx)); +} diff --git a/gstreamer-rtsp/Gir.toml b/gstreamer-rtsp/Gir.toml index 537e7811c..0f37ae7e0 100644 --- a/gstreamer-rtsp/Gir.toml +++ b/gstreamer-rtsp/Gir.toml @@ -49,6 +49,11 @@ name = "Gst.Structure" status = "manual" ref_mode = "ref" +[[object]] +name = "GstRtsp.RTSPAuthCredential" +status = "generate" +concurrency = "send" + [[object]] name = "GstRtsp.RTSPAuthParam" status = "generate" diff --git a/gstreamer-rtsp/src/auto/mod.rs b/gstreamer-rtsp/src/auto/mod.rs index fab99d331..a991be6af 100644 --- a/gstreamer-rtsp/src/auto/mod.rs +++ b/gstreamer-rtsp/src/auto/mod.rs @@ -3,6 +3,9 @@ // from gst-gir-files (https://gitlab.freedesktop.org/gstreamer/gir-files-rs.git) // DO NOT EDIT +mod rtsp_auth_credential; +pub use self::rtsp_auth_credential::RTSPAuthCredential; + mod rtsp_auth_param; pub use self::rtsp_auth_param::RTSPAuthParam; diff --git a/gstreamer-rtsp/src/auto/rtsp_auth_credential.rs b/gstreamer-rtsp/src/auto/rtsp_auth_credential.rs new file mode 100644 index 000000000..86f2d80db --- /dev/null +++ b/gstreamer-rtsp/src/auto/rtsp_auth_credential.rs @@ -0,0 +1,17 @@ +// This file was generated by gir (https://github.com/gtk-rs/gir) +// from gir-files (https://github.com/gtk-rs/gir-files) +// from gst-gir-files (https://gitlab.freedesktop.org/gstreamer/gir-files-rs.git) +// DO NOT EDIT + +glib::wrapper! { + #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] + pub struct RTSPAuthCredential(Boxed); + + match fn { + copy => |ptr| glib::gobject_ffi::g_boxed_copy(ffi::gst_rtsp_auth_credential_get_type(), ptr as *mut _) as *mut ffi::GstRTSPAuthCredential, + free => |ptr| glib::gobject_ffi::g_boxed_free(ffi::gst_rtsp_auth_credential_get_type(), ptr as *mut _), + type_ => || ffi::gst_rtsp_auth_credential_get_type(), + } +} + +unsafe impl Send for RTSPAuthCredential {} diff --git a/gstreamer-rtsp/src/lib.rs b/gstreamer-rtsp/src/lib.rs index 436598e04..25047a36c 100644 --- a/gstreamer-rtsp/src/lib.rs +++ b/gstreamer-rtsp/src/lib.rs @@ -27,6 +27,9 @@ pub use crate::auto::*; #[cfg(feature = "serde")] mod flag_serde; +pub mod rtsp_auth_credential; +pub mod rtsp_message; + // Re-export all the traits in a prelude module, so that applications // can always "use gst_rtsp::prelude::*" without getting conflicts pub mod prelude { diff --git a/gstreamer-rtsp/src/rtsp_auth_credential.rs b/gstreamer-rtsp/src/rtsp_auth_credential.rs new file mode 100644 index 000000000..97c3e14f2 --- /dev/null +++ b/gstreamer-rtsp/src/rtsp_auth_credential.rs @@ -0,0 +1,26 @@ +use crate::{RTSPAuthCredential, RTSPAuthMethod, RTSPAuthParam}; +use ffi::GstRTSPAuthCredential; +use glib::translate::*; + +impl RTSPAuthCredential { + pub fn scheme(&self) -> RTSPAuthMethod { + let ptr: *mut GstRTSPAuthCredential = self.to_glib_none().0; + unsafe { from_glib((*ptr).scheme) } + } + + pub fn authorization(&self) -> Option<&str> { + let ptr: *mut GstRTSPAuthCredential = self.to_glib_none().0; + unsafe { + if (*ptr).authorization.is_null() { + None + } else { + std::ffi::CStr::from_ptr((*ptr).authorization).to_str().ok() + } + } + } + + pub fn params(&self) -> glib::collections::PtrSlice { + let ptr: *mut GstRTSPAuthCredential = self.to_glib_none().0; + unsafe { FromGlibPtrContainer::from_glib_none((*ptr).params) } + } +} diff --git a/gstreamer-rtsp/src/rtsp_auth_param.rs b/gstreamer-rtsp/src/rtsp_auth_param.rs new file mode 100644 index 000000000..5adf19796 --- /dev/null +++ b/gstreamer-rtsp/src/rtsp_auth_param.rs @@ -0,0 +1,25 @@ +use glib::translate::*; + +impl RTSPAuthParam { + pub fn name(&self) -> Option<&str> { + let ptr: *mut GstRTSPAuthParam = self.to_glib_none().0; + unsafe { + if (*ptr).name.is_null() { + None + } else { + std::ffi::CStr::from_ptr((*ptr).name).to_str().ok() + } + } + } + + pub fn value(&self) -> Option<&str> { + let ptr: *mut GstRTSPAuthParam = self.to_glib_none().0; + unsafe { + if (*ptr).value.is_null() { + None + } else { + std::ffi::CStr::from_ptr((*ptr).value).to_str().ok() + } + } + } +} diff --git a/gstreamer-rtsp/src/rtsp_message.rs b/gstreamer-rtsp/src/rtsp_message.rs new file mode 100644 index 000000000..a46df6bd8 --- /dev/null +++ b/gstreamer-rtsp/src/rtsp_message.rs @@ -0,0 +1,58 @@ +use crate::{RTSPAuthCredential, RTSPHeaderField, RTSPStatusCode}; +use glib::translate::*; + +glib::wrapper! { + #[derive(PartialEq, Eq, PartialOrd, Ord, Hash)] + #[doc(alias = "GstRTSPMessage")] + pub struct RTSPMessage(Boxed); + + match fn { + copy => |ptr| { + let mut copy = std::ptr::null_mut(); + let res = ffi::gst_rtsp_message_copy(ptr, &mut copy); + debug_assert_eq!(res, ffi::GST_RTSP_OK); + copy + }, + free => |ptr| { + let res = ffi::gst_rtsp_message_free(ptr); + debug_assert_eq!(res, ffi::GST_RTSP_OK); + }, + type_ => || ffi::gst_rtsp_msg_get_type(), + } +} + +impl RTSPMessage { + pub const NONE: Option<&'static RTSPMessage> = None; + + #[doc(alias = "gst_rtsp_message_add_header")] + pub fn add_header(&self, header: RTSPHeaderField, value: &str) { + let ptr = self.to_glib_none().0; + unsafe { + ffi::gst_rtsp_message_add_header(ptr, header.into_glib(), value.to_glib_none().0); + } + } + + #[doc(alias = "gst_rtsp_message_init_response")] + pub fn init_response(&self, code: RTSPStatusCode, request: Option<&RTSPMessage>) { + let ptr = self.to_glib_none().0; + unsafe { + ffi::gst_rtsp_message_init_response( + ptr, + code.into_glib(), + ffi::gst_rtsp_status_as_text(code.into_glib()), + request.to_glib_none().0, + ); + } + } + + #[doc(alias = "gst_rtsp_message_parse_auth_credentials")] + pub fn parse_auth_credentials(&self) -> glib::collections::PtrSlice { + unsafe { + let credentials = ffi::gst_rtsp_message_parse_auth_credentials( + self.to_glib_none().0, + ffi::GST_RTSP_HDR_AUTHORIZATION, + ); + FromGlibPtrContainer::from_glib_full(credentials) + } + } +}