playready_ffi/
lib.rs

1use playready::{Cdm, Device, Pssh, cdm::Session, pssh::WrmHeader};
2use safer_ffi::prelude::*;
3
4#[derive_ReprC(rename = "playready_cdm")]
5#[repr(opaque)]
6pub struct FfiCdm {
7    inner: Cdm,
8}
9
10#[derive_ReprC(rename = "playready_session")]
11#[repr(opaque)]
12pub struct FfiSession {
13    inner: Session,
14}
15
16#[derive_ReprC(rename = "playready_pssh")]
17#[repr(opaque)]
18pub struct FfiPssh {
19    inner: Pssh,
20}
21
22#[derive_ReprC(rename = "playready_wrm_header")]
23#[repr(opaque)]
24pub struct FfiWrmHeader {
25    inner: WrmHeader,
26}
27
28impl From<WrmHeader> for FfiWrmHeader {
29    fn from(value: WrmHeader) -> Self {
30        Self { inner: value }
31    }
32}
33
34#[derive_ReprC(rename = "playready_kid_ck")]
35#[repr(C)]
36pub struct FfiKidCk {
37    pub kid: [u8; 16],
38    pub ck: repr_c::Box<[u8]>,
39}
40
41/// Creates new instance of [`Cdm`] from path to `.prd` file.
42///
43/// When successful function returns pointer to [`Cdm`] which needs to be deallocated by [`playready_cdm_free()`].
44/// Otherwise it returns `NULL` and pointer to `error_msg` that needs to be deallocated by [`playready_error_message_free()`].
45#[ffi_export]
46pub fn playready_cdm_create_from_prd(
47    prd_path: char_p::Ref<'_>,
48    error_msg: Out<'_, Option<char_p::Box>>,
49) -> Option<repr_c::Box<FfiCdm>> {
50    let device = match Device::from_prd(prd_path.to_str()) {
51        Ok(device) => device,
52        Err(err) => {
53            error_msg.write(Some(char_p::new(format!("{err:?}"))));
54            return None;
55        }
56    };
57
58    Some(
59        Box::new(FfiCdm {
60            inner: Cdm::from_device(device),
61        })
62        .into(),
63    )
64}
65
66/// Creates new [`Session`].
67///
68/// Returned pointer should be deallocated by [`playready_session_free()`].
69#[ffi_export]
70pub fn playready_cdm_open_session(cdm: &FfiCdm) -> repr_c::Box<FfiSession> {
71    Box::new(FfiSession {
72        inner: cdm.inner.open_session(),
73    })
74    .into()
75}
76
77/// Creates new instance of [`Pssh`] from bytes.
78///
79/// When successful it returns a pointer to [`Pssh`] which needs to be deallocated by [`playready_pssh_free()`].
80/// If not successful it will return `NULL` and pointer to `error_msg` which needs to be deallocated by [`playready_error_message_free()`].
81#[ffi_export]
82pub fn playready_pssh_from_bytes(
83    bytes: c_slice::Ref<'_, u8>,
84    error_msg: Out<'_, Option<char_p::Box>>,
85) -> Option<repr_c::Box<FfiPssh>> {
86    match Pssh::from_bytes(bytes.as_slice()) {
87        Ok(inner) => Some(Box::new(FfiPssh { inner }).into()),
88        Err(err) => {
89            error_msg.write(Some(char_p::new(format!("{err:?}"))));
90            None
91        }
92    }
93}
94
95/// Creates new instance of [`Pssh`] from Base64 string.
96///
97/// When successful it returns a pointer to [`Pssh`] which needs to be deallocated by [`playready_pssh_free()`].
98/// If not successful it will return `NULL` and pointer to `error_msg` which needs to be deallocated by [`playready_error_message_free()`].
99#[ffi_export]
100pub fn playready_pssh_from_b64(
101    b64: char_p::Ref<'_>,
102    error_msg: Out<'_, Option<char_p::Box>>,
103) -> Option<repr_c::Box<FfiPssh>> {
104    match Pssh::from_b64(b64.to_bytes()) {
105        Ok(inner) => Some(Box::new(FfiPssh { inner }).into()),
106        Err(err) => {
107            error_msg.write(Some(char_p::new(format!("{err:?}"))));
108            None
109        }
110    }
111}
112
113/// Extracts first wrm header from [`Pssh`].
114///
115/// Returned pointer should be passed to [`playready_session_get_license_challenge()`] where it will be consumed (and deallocated).
116#[ffi_export]
117pub fn playready_pssh_get_first_wrm_header(pssh: &FfiPssh) -> Option<repr_c::Box<FfiWrmHeader>> {
118    let wrm_headers = pssh.inner.wrm_headers();
119    let wrm_header: FfiWrmHeader = wrm_headers.into_iter().next()?.into();
120    Some(Box::new(wrm_header).into())
121}
122
123/// Returns license challenge.
124///
125/// Function consumes and deallocates `wrm_header`. It's recommended to set `wrm_header` to `NULL` after calling this function.
126/// If successful returned pointer should be freed by [`playready_license_challenge_free()`].
127/// Otherwise it returns `error_msg` which needs to deallocated by [`playready_error_message_free()`].
128#[ffi_export]
129pub fn playready_session_get_license_challenge(
130    session: &FfiSession,
131    wrm_header: repr_c::Box<FfiWrmHeader>,
132    error_msg: Out<'_, Option<char_p::Box>>,
133) -> Option<char_p::Box> {
134    let wrm_header = wrm_header.into();
135
136    match session.inner.get_license_challenge(wrm_header.inner) {
137        Ok(s) => Some(char_p::new(s)),
138        Err(err) => {
139            error_msg.write(Some(char_p::new(format!("{err:?}"))));
140            None
141        }
142    }
143}
144
145/// Returns keys from license response.
146///
147/// If successful (`error_msg` != `NULL`) return value needs to be deallocated by [`playready_keys_free()`].
148/// Otherwise it return `error_msg` which needs to be deallocated by [`playready_error_message_free()`].
149#[ffi_export]
150pub fn playready_session_get_keys_from_challenge_response(
151    session: &FfiSession,
152    response: char_p::Ref<'_>,
153    error_msg: Out<'_, Option<char_p::Box>>,
154) -> Option<repr_c::Vec<FfiKidCk>> {
155    match session
156        .inner
157        .get_keys_from_challenge_response(response.to_str())
158    {
159        Ok(keys) => Some(
160            keys.into_iter()
161                .map(|kid_ck| FfiKidCk {
162                    kid: kid_ck.0.into(),
163                    ck: Box::<[u8]>::from(kid_ck.1).into(),
164                })
165                .collect::<Vec<_>>()
166                .into(),
167        ),
168        Err(err) => {
169            error_msg.write(Some(char_p::new(format!("{err:?}"))));
170            None
171        }
172    }
173}
174
175/// Deallocates `cdm`. If `NULL` is passed function will do nothing.
176#[ffi_export]
177pub fn playready_cdm_free(cdm: Option<repr_c::Box<FfiCdm>>) {
178    drop(cdm)
179}
180
181/// Deallocates `session`. If `NULL` is passed function will do nothing.
182#[ffi_export]
183pub fn playready_session_free(session: Option<repr_c::Box<FfiSession>>) {
184    drop(session)
185}
186
187/// Deallocates `pssh`. If `NULL` is passed function will do nothing.
188#[ffi_export]
189pub fn playready_pssh_free(pssh: Option<repr_c::Box<FfiPssh>>) {
190    drop(pssh)
191}
192
193/// Deallocates `challenge`. If `NULL` is passed function will do nothing.
194#[ffi_export]
195pub fn playready_license_challenge_free(challenge: Option<char_p::Box>) {
196    drop(challenge)
197}
198
199/// Deallocates `keys`. If `NULL` is passed function will do nothing.
200#[ffi_export]
201pub fn playready_keys_free(keys: repr_c::Vec<FfiKidCk>) {
202    drop(keys)
203}
204
205/// Deallocates `error_msg`. If `NULL` is passed function will do nothing.
206#[ffi_export]
207pub fn playready_error_message_free(error_msg: Option<char_p::Box>) {
208    drop(error_msg)
209}
210
211pub fn generate_headers() -> std::io::Result<()> {
212    safer_ffi::headers::builder()
213        .to_file("playready.h")?
214        .generate()
215}