Skip to main content

bramley_engine/
lib.rs

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