Skip to main content

bramley_engine/
lib.rs

1mod audio;
2mod buffer;
3mod cdm;
4mod eme;
5mod http_cache;
6mod media;
7mod mp4;
8mod net;
9mod player;
10mod runtime;
11
12use std::path::PathBuf;
13
14#[cfg(feature = "diagnostics")]
15pub use player::Diagnostics;
16pub use player::{
17    COMMAND_WAIT, Command, PlayState, Player, PlayerConfig, PlayerError, QUEUE_SHOWN, Queue,
18    QueueItem, Repeat, STOREFRONT_BACKOFF, STOREFRONT_DEADLINE, Snapshot, Tokens,
19};
20pub use runtime::Engine;
21
22#[cfg(feature = "sabotage")]
23#[used]
24static SABOTAGE_MARKER: &str = "bramley-unshipped-feature:engine/sabotage\0";
25#[cfg(feature = "diagnostics")]
26#[used]
27static DIAGNOSTICS_MARKER: &str = "bramley-unshipped-feature:engine/diagnostics\0";
28
29#[cfg(feature = "trace")]
30#[used]
31static TRACE_MARKER: &str = "bramley-unshipped-feature:engine/trace\0";
32
33#[used]
34static LINKED_MARKER: &str = "bramley-linked:engine\0";
35
36pub fn user_agent() -> String {
37    format!(
38        "Bramley/{} ({})",
39        env!("CARGO_PKG_VERSION"),
40        std::env::consts::OS
41    )
42}
43
44#[derive(Clone)]
45#[non_exhaustive]
46pub struct EngineConfig {
47    pub cdm_path: PathBuf,
48    pub audio_sink_name: String,
49    pub output_device: Option<String>,
50    pub redact: Vec<String>,
51}
52
53impl EngineConfig {
54    pub fn default_cdm_path() -> PathBuf {
55        if cfg!(target_os = "linux") {
56            PathBuf::from(
57                "/opt/google/chrome/WidevineCdm/_platform_specific/linux_x64/libwidevinecdm.so",
58            )
59        } else {
60            PathBuf::from(format!(
61                "{}widevinecdm{}",
62                std::env::consts::DLL_PREFIX,
63                std::env::consts::DLL_SUFFIX
64            ))
65        }
66    }
67}
68
69impl std::fmt::Debug for EngineConfig {
70    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
71        f.debug_struct("EngineConfig")
72            .field("cdm_path", &self.cdm_path)
73            .field("audio_sink_name", &self.audio_sink_name)
74            .field("output_device", &self.output_device)
75            .finish_non_exhaustive()
76    }
77}
78
79impl Default for EngineConfig {
80    fn default() -> Self {
81        EngineConfig {
82            cdm_path: Self::default_cdm_path(),
83            audio_sink_name: "engine".into(),
84            output_device: None,
85            redact: Vec::new(),
86        }
87    }
88}
89
90pub fn redact(text: &str, secrets: &[String]) -> String {
91    let mut out = text.to_string();
92    for secret in secrets.iter().filter(|s| !s.is_empty()) {
93        let encoded = secret
94            .replace('+', "%2B")
95            .replace('/', "%2F")
96            .replace('=', "%3D");
97        mask_from_prefix(&mut out, &[secret, &encoded]);
98    }
99    let jwt_char = |c: char| c.is_ascii_alphanumeric() || matches!(c, '-' | '_' | '.');
100    let mut from = 0;
101    while let Some(i) = out[from..].find("eyJ").map(|i| i + from) {
102        let end = out[i..].find(|c| !jwt_char(c)).map_or(out.len(), |e| i + e);
103        if out[i..end].matches('.').count() >= 2 {
104            out.replace_range(i..end, "<jwt>");
105            from = i + 5;
106        } else {
107            from = end;
108        }
109    }
110    out
111}
112
113fn mask_from_prefix(text: &mut String, forms: &[&str]) {
114    const MIN_PREFIX: usize = 16;
115    for head in forms.iter().map(|f| f.get(..MIN_PREFIX).unwrap_or(f)) {
116        let mut from = 0;
117        while let Some(i) = text[from..].find(head).map(|i| i + from) {
118            let len = forms
119                .iter()
120                .map(|f| {
121                    text[i..]
122                        .bytes()
123                        .zip(f.bytes())
124                        .take_while(|(a, b)| a == b)
125                        .count()
126                })
127                .max()
128                .unwrap_or(0);
129            text.replace_range(i..i + len, "<redacted>");
130            from = i + "<redacted>".len();
131        }
132    }
133}
134
135#[cfg(test)]
136mod tests;