1#![allow(unsafe_code)]
12
13use std::fs::{File, OpenOptions};
14use std::io::{self, Read, Write};
15use std::os::unix::io::AsRawFd;
16use std::time::Duration;
17
18use crate::dataplane::{CiDataDevice, TS_PACKET_LEN};
19use crate::device::{CaDevice, SlotInfo};
20
21fn poll_readable(fd: libc::c_int, timeout: Duration) -> io::Result<bool> {
23 let mut pfd = libc::pollfd {
24 fd,
25 events: libc::POLLIN,
26 revents: 0,
27 };
28 let ms = i32::try_from(timeout.as_millis()).unwrap_or(i32::MAX);
29 let r = unsafe { libc::poll(&mut pfd as *mut libc::pollfd, 1, ms) };
31 if r < 0 {
32 Err(io::Error::last_os_error())
33 } else {
34 Ok(pfd.revents & libc::POLLIN != 0)
35 }
36}
37
38const IOC_NRBITS: u32 = 8;
40const IOC_TYPEBITS: u32 = 8;
41const IOC_SIZEBITS: u32 = 14;
42const IOC_NRSHIFT: u32 = 0;
43const IOC_TYPESHIFT: u32 = IOC_NRSHIFT + IOC_NRBITS;
44const IOC_SIZESHIFT: u32 = IOC_TYPESHIFT + IOC_TYPEBITS;
45const IOC_DIRSHIFT: u32 = IOC_SIZESHIFT + IOC_SIZEBITS;
46const IOC_NONE: u32 = 0;
47const IOC_READ: u32 = 2;
48
49const fn ioc(dir: u32, typ: u32, nr: u32, size: u32) -> u64 {
50 ((dir << IOC_DIRSHIFT) | (typ << IOC_TYPESHIFT) | (nr << IOC_NRSHIFT) | (size << IOC_SIZESHIFT))
51 as u64
52}
53
54const DVB_CA_MAGIC: u32 = b'o' as u32;
56const CA_RESET: u64 = ioc(IOC_NONE, DVB_CA_MAGIC, 128, 0);
57const CA_GET_SLOT_INFO: u64 = ioc(
58 IOC_READ,
59 DVB_CA_MAGIC,
60 130,
61 core::mem::size_of::<CaSlotInfo>() as u32,
62);
63const CA_CI_MODULE_READY: u32 = 1;
65
66#[repr(C)]
67struct CaSlotInfo {
68 num: i32,
69 typ: i32,
70 flags: u32,
71}
72
73const RESET_SETTLE: Duration = Duration::from_millis(3000);
79
80#[derive(Debug)]
87pub struct LinuxCaDevice {
88 file: File,
89 slot: u8,
90}
91
92impl LinuxCaDevice {
93 pub fn open(adapter: u32, ca: u32) -> io::Result<Self> {
95 let path = format!("/dev/dvb/adapter{adapter}/ca{ca}");
96 let file = OpenOptions::new().read(true).write(true).open(path)?;
97 Ok(Self { file, slot: 0 })
98 }
99
100 #[must_use]
102 pub fn from_file(file: File, slot: u8) -> Self {
103 Self { file, slot }
104 }
105
106 fn connection_id(tpdu: &[u8]) -> u8 {
109 dvb_ci::length::decode(tpdu.get(1..).unwrap_or(&[]))
110 .ok()
111 .and_then(|(_, hdr)| tpdu.get(1 + hdr).copied())
112 .unwrap_or(1)
113 }
114}
115
116impl CaDevice for LinuxCaDevice {
117 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
118 let mut frame = [0u8; 4096];
122 let n = match self.file.read(&mut frame) {
123 Ok(n) => n,
124 Err(e) if e.kind() == io::ErrorKind::WouldBlock => return Ok(0),
125 Err(e) => return Err(e),
126 };
127 let tpdu = frame.get(2..n).unwrap_or(&[]);
129 let copy = tpdu.len().min(buf.len());
130 buf[..copy].copy_from_slice(&tpdu[..copy]);
131 Ok(copy)
132 }
133
134 fn write(&mut self, buf: &[u8]) -> io::Result<()> {
135 let mut frame = Vec::with_capacity(buf.len() + 2);
137 frame.push(self.slot);
138 frame.push(Self::connection_id(buf));
139 frame.extend_from_slice(buf);
140 self.file.write_all(&frame)
141 }
142
143 fn reset(&mut self) -> io::Result<()> {
144 let r = unsafe { libc::ioctl(self.file.as_raw_fd(), CA_RESET as libc::c_ulong) };
146 if r < 0 {
147 return Err(io::Error::last_os_error());
148 }
149 std::thread::sleep(RESET_SETTLE);
151 Ok(())
152 }
153
154 fn slot_info(&mut self) -> io::Result<SlotInfo> {
155 let mut si = CaSlotInfo {
156 num: i32::from(self.slot),
157 typ: 0,
158 flags: 0,
159 };
160 let r = unsafe {
163 libc::ioctl(
164 self.file.as_raw_fd(),
165 CA_GET_SLOT_INFO as libc::c_ulong,
166 &mut si as *mut CaSlotInfo,
167 )
168 };
169 if r < 0 {
170 return Ok(SlotInfo {
173 num: self.slot,
174 module_ready: true,
175 });
176 }
177 Ok(SlotInfo {
178 num: si.num as u8,
179 module_ready: si.flags & CA_CI_MODULE_READY != 0,
180 })
181 }
182
183 fn poll(&mut self, timeout: Duration) -> io::Result<bool> {
184 poll_readable(self.file.as_raw_fd(), timeout)
185 }
186}
187
188#[derive(Debug)]
192pub struct LinuxCiDataDevice {
193 file: File,
194}
195
196impl LinuxCiDataDevice {
197 pub fn open(adapter: u32, ci: u32) -> io::Result<Self> {
199 let path = format!("/dev/dvb/adapter{adapter}/ci{ci}");
200 let file = OpenOptions::new().read(true).write(true).open(path)?;
201 Ok(Self { file })
202 }
203
204 #[must_use]
206 pub fn from_file(file: File) -> Self {
207 Self { file }
208 }
209}
210
211impl CiDataDevice for LinuxCiDataDevice {
212 fn write(&mut self, ts: &[u8]) -> io::Result<()> {
213 if ts.len() % TS_PACKET_LEN != 0 {
214 return Err(io::Error::new(
215 io::ErrorKind::InvalidInput,
216 "write not a multiple of 188 bytes",
217 ));
218 }
219 self.file.write_all(ts)
220 }
221
222 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
223 if buf.len() % TS_PACKET_LEN != 0 {
224 return Err(io::Error::new(
225 io::ErrorKind::InvalidInput,
226 "read buffer not a multiple of 188 bytes",
227 ));
228 }
229 match self.file.read(buf) {
230 Ok(n) => Ok(n),
231 Err(e) if e.kind() == io::ErrorKind::WouldBlock => Ok(0),
232 Err(e) => Err(e),
233 }
234 }
235
236 fn poll(&mut self, timeout: Duration) -> io::Result<bool> {
237 poll_readable(self.file.as_raw_fd(), timeout)
238 }
239}