1use std::fmt;
4use serde::{Serialize, Deserialize};
5use base64::{engine, Engine};
6use anyhow::{anyhow, Context, Result};
7use serde_json::Value;
8use tracing::warn;
9use crate::ToBytes;
10
11const BASE64_URL_SAFE_FORGIVING:
13 engine::general_purpose::GeneralPurpose =
14 engine::general_purpose::GeneralPurpose::new(
15 &base64::alphabet::URL_SAFE,
16 engine::general_purpose::GeneralPurposeConfig::new()
17 .with_decode_allow_trailing_bits(true)
18 .with_decode_padding_mode(engine::DecodePaddingMode::Indifferent),
19 );
20
21
22#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
23pub struct NagraPsshData {
24 pub content_id: String,
25 pub key_id: String,
26}
27
28impl fmt::Debug for NagraPsshData {
29 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
30 write!(f, "NagraPsshData<content_id: {}, key_id: {}>", self.content_id, self.key_id)
31 }
32}
33
34impl ToBytes for NagraPsshData {
35 fn to_bytes(&self) -> Vec<u8> {
36 let json = format!("{{\"contentId\":\"{}\",\"keyId\":\"{}\"}}",
38 self.content_id, self.key_id);
39 BASE64_URL_SAFE_FORGIVING.encode(json).into_bytes()
40 }
41}
42
43pub fn parse_pssh_data(buf: &[u8]) -> Result<NagraPsshData> {
45 let b64 = String::from_utf8(buf.to_vec())
46 .context("decoding UTF-8")?;
47 let json = BASE64_URL_SAFE_FORGIVING.decode(b64)
48 .context("decoding base64")?;
49 let parsed: Value = serde_json::from_slice(&json)
50 .context("parsing as JSON")?;
51 match parsed.as_object() {
52 Some(map) => {
53 if map.len() > 2 {
54 let keys: Vec<_> = map.keys().collect();
55 warn!("unknown key in Nagra PSSH data, {keys:?}");
56 }
57 let cid = map["contentId"].as_str()
58 .context("extracting contentId")?;
59 let kid = map["keyId"].as_str()
60 .context("extracting keyId")?;
61 Ok(NagraPsshData {
62 content_id: String::from(cid),
63 key_id: String::from(kid),
64 })
65 },
66 None => Err(anyhow!("parsing as JSON")),
67 }
68}