1use std::collections::VecDeque;
18use std::sync::Arc;
19use std::time::{Duration, SystemTime, UNIX_EPOCH};
20
21use crate::types::{
22 OsdSyncContent, OsdSyncEntry, OsdSyncImage, OsdSyncRequest, OsdSyncSlot, OsdSyncValue,
23 OSD_SYNC_FONT_MAX, OSD_SYNC_IMAGE_HEIGHT_MAX, OSD_SYNC_IMAGE_MAX, OSD_SYNC_IMAGE_WIDTH_MAX,
24 OSD_SYNC_PERCENT_MAX, OSD_SYNC_RECORDS_MAX, OSD_SYNC_SLOTS, OSD_SYNC_TEXT_MAX,
25};
26use crate::wire::{
27 build_osd_setting, build_osd_sync, crc32, op, Addressee, DeviceFeature, DeviceUid, OsdSyncWhat,
28};
29
30use super::control::ControlError;
31use super::logo_server::{Images, LogoServer};
32use super::{lock, Remote, Shared, SHUTDOWN_POLL};
33
34const SEQS_KEPT: usize = 64;
38
39const SETTINGS_PER_PASS: usize = 4;
42const PASS_GAP: Duration = Duration::from_millis(14);
43
44const PNG_SIGNATURE: &[u8] = b"\x89PNG\r\n\x1a\n";
46const PNG_IHDR_AT: usize = 12;
47
48pub(super) struct OsdSender {
50 held: Option<Held>,
51 next_seq: u16,
52 queue: VecDeque<Pending>,
53 server: Option<LogoServer>,
54}
55
56struct Held {
58 what: OsdSyncWhat,
60 entries: Arc<[OsdSyncEntry]>,
61 slots: [OsdSyncSlot; OSD_SYNC_SLOTS],
62 http_port: u16,
63 seqs: VecDeque<u16>,
65}
66
67#[derive(Clone, Copy, PartialEq, Eq)]
69struct Pending {
70 unit: DeviceUid,
71 seq: u16,
72 index: u16,
73}
74
75impl OsdSender {
76 pub(super) fn new() -> Self {
80 let ms = SystemTime::now()
81 .duration_since(UNIX_EPOCH)
82 .map_or(0, |d| d.as_millis());
83 Self {
84 held: None,
85 next_seq: (ms as u16).max(1),
86 queue: VecDeque::new(),
87 server: None,
88 }
89 }
90
91 pub(super) fn holds(&self) -> bool {
93 self.held.is_some()
94 }
95
96 fn sync_frame(&mut self, target: DeviceUid, what: OsdSyncWhat) -> Option<(u16, Vec<u8>)> {
100 let seq = self.next_seq;
101 self.next_seq = seq.wrapping_add(1).max(1);
102 let held = self.held.as_mut()?;
103 if held.seqs.len() == SEQS_KEPT {
104 held.seqs.pop_front();
105 }
106 held.seqs.push_back(seq);
107 let records = held.entries.len() as u16;
108 let frame = build_osd_sync(seq, target, what, held.http_port, records, &held.slots);
109 Some((seq, frame))
110 }
111
112 fn queue_missing(&mut self, unit: DeviceUid, request: &OsdSyncRequest) {
115 let Some(held) = &self.held else {
116 return;
117 };
118 self.queue.retain(|p| p.unit != unit);
119 for index in 0..held.entries.len() as u16 {
120 if request.is_missing(index) {
121 self.queue.push_back(Pending {
122 unit,
123 seq: request.seq,
124 index,
125 });
126 }
127 }
128 }
129
130 fn next_pass(&mut self) -> Vec<(DeviceUid, Vec<u8>)> {
136 let mut pass = Vec::new();
137 while pass.len() < SETTINGS_PER_PASS {
138 let Some(p) = self.queue.pop_front() else {
139 break;
140 };
141 let (theirs, others): (VecDeque<_>, VecDeque<_>) =
142 self.queue.drain(..).partition(|q| q.unit == p.unit);
143 self.queue = others;
144 self.queue.extend(theirs);
145 let Some(held) = &self.held else {
146 break;
147 };
148 let Some(entry) = held.entries.get(usize::from(p.index)) else {
151 continue;
152 };
153 if !held.seqs.contains(&p.seq) {
154 continue;
155 }
156 let records = held.entries.len() as u16;
157 pass.push((
158 p.unit,
159 build_osd_setting(p.seq, p.unit, p.index, records, entry),
160 ));
161 }
162 pass
163 }
164}
165
166impl Shared {
167 fn addressee(&self, unit: DeviceUid) -> Option<Addressee> {
168 self.read(|state| state.device(unit).map(|d| Addressee::device(d)))
169 }
170
171 pub(super) fn answer_osd_requests(&self, requests: Vec<OsdSyncRequest>) {
179 for request in requests {
180 let unit = request.target;
181 let Some(to) = self.addressee(unit) else {
182 continue;
183 };
184 let sync = {
185 let mut osd = lock(&self.osd);
186 let Some(held) = &osd.held else {
187 return;
188 };
189 let (held_what, current) = (held.what, held.seqs.contains(&request.seq));
190 if request.seq != 0 && current {
191 osd.queue_missing(unit, &request);
192 self.osd_queued.notify_one();
193 None
194 } else {
195 let what = request.what & held_what;
198 if what.is_empty() {
199 continue;
200 }
201 osd.sync_frame(unit, what)
202 }
203 };
204 if let Some((_, frame)) = sync {
205 let _ = self.send(&to, op::V2IP_OSD_SYNC, &frame);
206 }
207 }
208 }
209
210 pub(super) fn osd_pass(&self) -> usize {
212 let pass = lock(&self.osd).next_pass();
213 for (unit, frame) in &pass {
214 if let Some(to) = self.addressee(*unit) {
215 let _ = self.send(&to, op::V2IP_OSD_SYNC, frame);
216 }
217 }
218 pass.len()
219 }
220
221 pub(super) fn osd_loop(&self) {
223 while !self.closing.load(std::sync::atomic::Ordering::SeqCst) {
224 if self.osd_pass() > 0 {
225 std::thread::sleep(PASS_GAP);
226 continue;
227 }
228 let osd = lock(&self.osd);
229 if osd.queue.is_empty() {
230 let _ = self.osd_queued.wait_timeout(osd, SHUTDOWN_POLL);
231 }
232 }
233 }
234
235 pub(super) fn drop_osd_sync(&self) -> bool {
237 let (held, server) = {
238 let mut osd = lock(&self.osd);
239 (osd.held.take().is_some(), osd.server.take())
240 };
241 drop(server);
244 held
245 }
246}
247
248impl Remote {
249 pub fn send_osd_sync(&self, content: OsdSyncContent) -> Result<u16, ControlError> {
272 validate(&content)?;
273 self.hold_osd_sync(content)
274 .inspect_err(|_| self.clear_osd_sync())
275 }
276
277 fn hold_osd_sync(&self, content: OsdSyncContent) -> Result<u16, ControlError> {
278 let images = content.images.as_ref();
279 let mut what =
280 OsdSyncWhat::from_bits(content.what.bits() & !OsdSyncWhat::LOGO_SLOTS.bits());
281 if images.is_some() {
282 what |= OsdSyncWhat::LOGO_SLOTS;
283 }
284 let slots = images.map_or_else(Default::default, |images| {
285 core::array::from_fn(|i| OsdSyncSlot {
286 bytes: images[i].png.len() as u32,
287 crc32: crc32(&images[i].png),
288 percent: images[i].percent,
289 })
290 });
291 let (was_held, sync) = {
292 let mut osd = lock(&self.shared.osd);
293 let http_port = match images {
294 Some(images) => serve(&mut osd.server, self.shared.logo_port, images)?,
295 None => 0,
296 };
297 let was_held = osd.held.is_some();
298 osd.held = Some(Held {
299 what,
300 entries: content.entries.into(),
301 slots,
302 http_port,
303 seqs: VecDeque::new(),
304 });
305 (was_held, osd.sync_frame(DeviceUid::ZERO, what))
306 };
307 let (seq, frame) = sync.ok_or(ControlError::InvalidRequest("no overlay sync is held"))?;
308 if !was_held {
311 self.shared.announce();
312 }
313 self.shared
314 .send(&Addressee::Broadcast, op::V2IP_OSD_SYNC, &frame)?;
315 Ok(seq)
316 }
317
318 pub fn resend_osd_sync(&self, device: DeviceUid) -> Result<u16, ControlError> {
325 let to = self
326 .shared
327 .addressee(device)
328 .ok_or(ControlError::UnknownDevice(device))?;
329 let takes = self.shared.read(|state| {
330 state
331 .device(device)
332 .is_some_and(|d| d.hello.features.has(DeviceFeature::OSD_SYNC))
333 });
334 if !takes {
335 return Err(ControlError::Unsupported("the unit takes no overlay sync"));
336 }
337 let (seq, frame) = {
338 let mut osd = lock(&self.shared.osd);
339 let what = osd.held.as_ref().map(|h| h.what);
340 what.and_then(|what| osd.sync_frame(device, what))
341 .ok_or(ControlError::InvalidRequest("no overlay sync is held"))?
342 };
343 self.shared.send(&to, op::V2IP_OSD_SYNC, &frame)?;
344 Ok(seq)
345 }
346
347 pub fn clear_osd_sync(&self) {
353 if self.shared.drop_osd_sync() {
354 self.shared.announce();
355 }
356 }
357}
358
359fn serve(
362 server: &mut Option<LogoServer>,
363 port: Option<u16>,
364 images: &[OsdSyncImage; OSD_SYNC_SLOTS],
365) -> Result<u16, ControlError> {
366 let running = match server.take() {
367 Some(running) => running,
368 None => LogoServer::start(port.unwrap_or(0))
369 .map_err(|e| ControlError::Send(crate::wire::SendError::Io(e)))?,
370 };
371 let images: Images = core::array::from_fn(|i| Arc::from(images[i].png.as_slice()));
372 running.set_images(images);
373 let port = running.port();
374 *server = Some(running);
375 Ok(port)
376}
377
378fn validate(content: &OsdSyncContent) -> Result<(), ControlError> {
379 let invalid = |what| Err(ControlError::InvalidRequest(what));
380 let copies = OsdSyncWhat::from_bits(content.what.bits() & !OsdSyncWhat::LOGO_SLOTS.bits());
381 if copies.is_empty() && content.images.is_none() {
382 return invalid("an overlay sync copying something");
383 }
384 if content.entries.len() > OSD_SYNC_RECORDS_MAX {
385 return invalid("an overlay sync of at most 128 settings");
386 }
387 for entry in &content.entries {
388 match &entry.value {
389 OsdSyncValue::Caption(c) if c.font.len() > OSD_SYNC_FONT_MAX => {
390 return invalid("a caption font of at most 31 bytes");
391 }
392 OsdSyncValue::Caption(c) if c.text.len() > OSD_SYNC_TEXT_MAX => {
393 return invalid("a caption text of at most 360 bytes");
394 }
395 OsdSyncValue::Logo(l) if !(-1..=4).contains(&l.logo) => {
396 return invalid("a logo of -1 to 4");
397 }
398 _ => {}
399 }
400 }
401 for image in content.images.iter().flatten() {
402 if image.percent > OSD_SYNC_PERCENT_MAX {
403 return invalid("a logo size of 0 to 100 percent");
404 }
405 if !image.png.is_empty() && !fits(&image.png) {
406 return invalid("a PNG of at most 512 KB and 1920 by 1080 pixels");
407 }
408 }
409 Ok(())
410}
411
412fn fits(png: &[u8]) -> bool {
414 let be32 = |at: usize| {
415 png.get(at..at + 4)
416 .map(|b| u32::from_be_bytes([b[0], b[1], b[2], b[3]]))
417 };
418 let (Some(width), Some(height)) = (be32(PNG_IHDR_AT + 4), be32(PNG_IHDR_AT + 8)) else {
419 return false;
420 };
421 png.len() <= OSD_SYNC_IMAGE_MAX
422 && png.starts_with(PNG_SIGNATURE)
423 && png.get(PNG_IHDR_AT..PNG_IHDR_AT + 4) == Some(b"IHDR")
424 && (1..=OSD_SYNC_IMAGE_WIDTH_MAX).contains(&width)
425 && (1..=OSD_SYNC_IMAGE_HEIGHT_MAX).contains(&height)
426}
427
428#[cfg(test)]
429#[path = "osd_tests.rs"]
430mod tests;