#![allow(unsafe_code)]
use std::fs::{File, OpenOptions};
use std::io::{self, Read, Write};
use std::os::unix::io::AsRawFd;
use std::time::Duration;
use crate::dataplane::{CiDataDevice, TS_PACKET_LEN};
use crate::device::{CaDevice, SlotInfo};
fn poll_readable(fd: libc::c_int, timeout: Duration) -> io::Result<bool> {
let mut pfd = libc::pollfd {
fd,
events: libc::POLLIN,
revents: 0,
};
let ms = i32::try_from(timeout.as_millis()).unwrap_or(i32::MAX);
let r = unsafe { libc::poll(&mut pfd as *mut libc::pollfd, 1, ms) };
if r < 0 {
Err(io::Error::last_os_error())
} else {
Ok(pfd.revents & libc::POLLIN != 0)
}
}
const IOC_NRBITS: u32 = 8;
const IOC_TYPEBITS: u32 = 8;
const IOC_SIZEBITS: u32 = 14;
const IOC_NRSHIFT: u32 = 0;
const IOC_TYPESHIFT: u32 = IOC_NRSHIFT + IOC_NRBITS;
const IOC_SIZESHIFT: u32 = IOC_TYPESHIFT + IOC_TYPEBITS;
const IOC_DIRSHIFT: u32 = IOC_SIZESHIFT + IOC_SIZEBITS;
const IOC_NONE: u32 = 0;
const IOC_READ: u32 = 2;
const fn ioc(dir: u32, typ: u32, nr: u32, size: u32) -> u64 {
((dir << IOC_DIRSHIFT) | (typ << IOC_TYPESHIFT) | (nr << IOC_NRSHIFT) | (size << IOC_SIZESHIFT))
as u64
}
const DVB_CA_MAGIC: u32 = b'o' as u32;
const CA_RESET: u64 = ioc(IOC_NONE, DVB_CA_MAGIC, 128, 0);
const CA_GET_SLOT_INFO: u64 = ioc(
IOC_READ,
DVB_CA_MAGIC,
130,
core::mem::size_of::<CaSlotInfo>() as u32,
);
const CA_CI_MODULE_READY: u32 = 1;
#[repr(C)]
struct CaSlotInfo {
num: i32,
typ: i32,
flags: u32,
}
const RESET_SETTLE: Duration = Duration::from_millis(2000);
#[derive(Debug)]
pub struct LinuxCaDevice {
file: File,
slot: u8,
}
impl LinuxCaDevice {
pub fn open(adapter: u32, ca: u32) -> io::Result<Self> {
let path = format!("/dev/dvb/adapter{adapter}/ca{ca}");
let file = OpenOptions::new().read(true).write(true).open(path)?;
Ok(Self { file, slot: 0 })
}
#[must_use]
pub fn from_file(file: File, slot: u8) -> Self {
Self { file, slot }
}
fn connection_id(tpdu: &[u8]) -> u8 {
dvb_ci::length::decode(tpdu.get(1..).unwrap_or(&[]))
.ok()
.and_then(|(_, hdr)| tpdu.get(1 + hdr).copied())
.unwrap_or(1)
}
}
impl CaDevice for LinuxCaDevice {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
let mut frame = [0u8; 4096];
let n = match self.file.read(&mut frame) {
Ok(n) => n,
Err(e) if e.kind() == io::ErrorKind::WouldBlock => return Ok(0),
Err(e) => return Err(e),
};
let tpdu = frame.get(2..n).unwrap_or(&[]);
let copy = tpdu.len().min(buf.len());
buf[..copy].copy_from_slice(&tpdu[..copy]);
Ok(copy)
}
fn write(&mut self, buf: &[u8]) -> io::Result<()> {
let mut frame = Vec::with_capacity(buf.len() + 2);
frame.push(self.slot);
frame.push(Self::connection_id(buf));
frame.extend_from_slice(buf);
self.file.write_all(&frame)
}
fn reset(&mut self) -> io::Result<()> {
let r = unsafe { libc::ioctl(self.file.as_raw_fd(), CA_RESET as libc::c_ulong) };
if r < 0 {
return Err(io::Error::last_os_error());
}
std::thread::sleep(RESET_SETTLE);
Ok(())
}
fn slot_info(&mut self) -> io::Result<SlotInfo> {
let mut si = CaSlotInfo {
num: i32::from(self.slot),
typ: 0,
flags: 0,
};
let r = unsafe {
libc::ioctl(
self.file.as_raw_fd(),
CA_GET_SLOT_INFO as libc::c_ulong,
&mut si as *mut CaSlotInfo,
)
};
if r < 0 {
return Ok(SlotInfo {
num: self.slot,
module_ready: true,
});
}
Ok(SlotInfo {
num: si.num as u8,
module_ready: si.flags & CA_CI_MODULE_READY != 0,
})
}
fn poll(&mut self, timeout: Duration) -> io::Result<bool> {
poll_readable(self.file.as_raw_fd(), timeout)
}
}
#[derive(Debug)]
pub struct LinuxCiDataDevice {
file: File,
}
impl LinuxCiDataDevice {
pub fn open(adapter: u32, ci: u32) -> io::Result<Self> {
let path = format!("/dev/dvb/adapter{adapter}/ci{ci}");
let file = OpenOptions::new().read(true).write(true).open(path)?;
Ok(Self { file })
}
#[must_use]
pub fn from_file(file: File) -> Self {
Self { file }
}
}
impl CiDataDevice for LinuxCiDataDevice {
fn write(&mut self, ts: &[u8]) -> io::Result<()> {
if ts.len() % TS_PACKET_LEN != 0 {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"write not a multiple of 188 bytes",
));
}
self.file.write_all(ts)
}
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
if buf.len() % TS_PACKET_LEN != 0 {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"read buffer not a multiple of 188 bytes",
));
}
match self.file.read(buf) {
Ok(n) => Ok(n),
Err(e) if e.kind() == io::ErrorKind::WouldBlock => Ok(0),
Err(e) => Err(e),
}
}
fn poll(&mut self, timeout: Duration) -> io::Result<bool> {
poll_readable(self.file.as_raw_fd(), timeout)
}
}