use alloc::{boxed::Box, string::String, sync::Arc, vec::Vec};
use core::sync::atomic::{AtomicI64, Ordering};
use rt::sync::SpinLock;
use crate::content::{ContentFs, DocHost, DocInfo};
use crate::vfs::{Provider, Stream};
use crate::{CLIP_HTML, CLIP_IMAGE, CLIP_PNG, CLIP_TEXT, FO_READ, FO_RW, FO_WRITE};
const CALL_QUERY: u32 = 1;
const CALL_CHILDREN: u32 = 2;
const CALL_CREATE: u32 = 3;
const CALL_DELETE: u32 = 4;
const CALL_RENAME: u32 = 5;
const CALL_OPEN: u32 = 6;
const CALL_CLIP_GET: u32 = 10;
const CALL_CLIP_SET: u32 = 11;
const CALL_CLIP_HAS: u32 = 12;
const CALL_CLIP_STAMP: u32 = 13;
const CALL_CLIP_OURS: u32 = 14;
const CALL_GRANTS: u32 = 20;
const CALL_RELEASE: u32 = 21;
pub(crate) const CALL_SECRET_AVAILABLE: u32 = 30;
pub(crate) const CALL_SECRET_GET: u32 = 31;
pub(crate) const CALL_SECRET_SET: u32 = 32;
pub(crate) const CALL_SECRET_DELETE: u32 = 33;
unsafe extern "C" {
fn jfgui_host_call(kind: u32, ptr: *const u8, len: usize) -> i32;
}
#[link(name = "c")]
unsafe extern "C" {
fn close(fd: i32) -> i32;
fn read(fd: i32, buf: *mut u8, n: usize) -> isize;
fn write(fd: i32, buf: *const u8, n: usize) -> isize;
fn pread(fd: i32, buf: *mut u8, n: usize, off: i64) -> isize;
fn pwrite(fd: i32, buf: *const u8, n: usize, off: i64) -> isize;
fn lseek(fd: i32, off: i64, whence: i32) -> i64;
fn fsync(fd: i32) -> i32;
fn ftruncate(fd: i32, len: i64) -> i32;
}
static REPLY: SpinLock<Vec<u8>> = SpinLock::new(Vec::new());
#[unsafe(no_mangle)]
pub extern "C" fn jfgui_reply(len: u32) -> usize {
let mut r = REPLY.lock();
r.clear();
if r.try_reserve_exact(len as usize).is_err() {
return 0;
}
r.resize(len as usize, 0);
r.as_mut_ptr() as usize
}
pub(crate) fn call(kind: u32, payload: &[u8]) -> Option<Vec<u8>> {
if (CALL_QUERY..=CALL_OPEN).contains(&kind) {
let uri = core::str::from_utf8(payload).ok().filter(|u| u.starts_with("content:")).unwrap_or("content://");
if !crate::policy::user_doc_allowed(uri) {
return None;
}
}
REPLY.lock().clear();
let n = unsafe { jfgui_host_call(kind, payload.as_ptr(), payload.len()) };
let mut r = core::mem::take(&mut *REPLY.lock());
let n = usize::try_from(n).ok()?;
r.truncate(n);
(r.len() == n).then_some(r)
}
#[derive(Default)]
pub(crate) struct W(pub(crate) Vec<u8>);
impl W {
fn u32(mut self, v: u32) -> W {
self.0.extend_from_slice(&v.to_le_bytes());
self
}
pub(crate) fn bytes(mut self, b: &[u8]) -> W {
self.0.extend_from_slice(&(b.len() as u32).to_le_bytes());
self.0.extend_from_slice(b);
self
}
pub(crate) fn str(self, s: &str) -> W {
self.bytes(s.as_bytes())
}
}
pub(crate) struct R<'a>(pub(crate) &'a [u8]);
impl<'a> R<'a> {
fn take(&mut self, n: usize) -> Option<&'a [u8]> {
let (a, b) = (self.0.get(..n)?, self.0.get(n..)?);
self.0 = b;
Some(a)
}
pub(crate) fn u32(&mut self) -> Option<u32> {
Some(u32::from_le_bytes(self.take(4)?.try_into().ok()?))
}
fn u64(&mut self) -> Option<u64> {
Some(u64::from_le_bytes(self.take(8)?.try_into().ok()?))
}
pub(crate) fn bytes(&mut self) -> Option<&'a [u8]> {
let n = self.u32()? as usize;
self.take(n)
}
fn str(&mut self) -> Option<String> {
Some(String::from(core::str::from_utf8(self.bytes()?).ok()?))
}
fn info(&mut self) -> Option<DocInfo> {
let uri = self.str()?;
let name = self.str()?;
let dir = self.u32()? != 0;
let size = self.u64()?;
let mtime_ms = self.u64()? as i64;
let flags = self.u32()?;
Some(DocInfo { uri, name, dir, size: (size != u64::MAX).then_some(size), mtime_ns: mtime_ms.saturating_mul(1_000_000), flags })
}
}
pub struct AndroidDocs;
impl DocHost for AndroidDocs {
fn query(&self, uri: &str) -> Option<DocInfo> {
R(&call(CALL_QUERY, uri.as_bytes())?).info()
}
fn children(&self, uri: &str) -> Option<Vec<DocInfo>> {
let a = call(CALL_CHILDREN, uri.as_bytes())?;
let mut r = R(&a);
let n = r.u32()?;
(0..n).map(|_| r.info()).collect()
}
fn create(&self, parent: &str, name: &str, dir: bool) -> Option<DocInfo> {
R(&call(CALL_CREATE, &W::default().u32(dir as u32).str(parent).str(name).0)?).info()
}
fn delete(&self, uri: &str) -> bool {
call(CALL_DELETE, uri.as_bytes()).is_some_and(|a| R(&a).u32() == Some(1))
}
fn rename(&self, uri: &str, name: &str) -> Option<DocInfo> {
R(&call(CALL_RENAME, &W::default().str(uri).str(name).0)?).info()
}
fn open(&self, uri: &str, mode: u32) -> Option<Box<dyn Stream>> {
let a = call(CALL_OPEN, &W::default().u32(mode).str(uri).0)?;
let fd = R(&a).u32()? as i32;
(fd >= 0).then(|| FdStream::new(fd, mode)).flatten().map(|s| Box::new(s) as Box<dyn Stream>)
}
fn generation(&self) -> u64 {
CHANGES.load(Ordering::Relaxed)
}
}
static CHANGES: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
#[unsafe(no_mangle)]
pub extern "C" fn jfgui_content_changed() {
CHANGES.fetch_add(1, Ordering::Relaxed);
}
pub fn content_fs() -> Arc<dyn Provider> {
let mut fs = ContentFs::new(AndroidDocs);
if let Some(d) = crate::vfs::app_dir(crate::vfs::APP_DATA) {
fs = fs.with_journal(&alloc::format!("{d}/.content-journal"));
}
fs.recover();
Arc::new(fs)
}
pub fn content_grants() -> Vec<(String, bool, bool)> {
let Some(a) = call(CALL_GRANTS, &[]) else { return Vec::new() };
let mut r = R(&a);
let Some(n) = r.u32() else { return Vec::new() };
let mut out = Vec::new();
for _ in 0..n {
let (Some(u), Some(rd), Some(wr)) = (r.str(), r.u32(), r.u32()) else { break };
out.push((u, rd != 0, wr != 0));
}
out
}
pub fn content_release(uri: &str) -> bool {
call(CALL_RELEASE, uri.as_bytes()).is_some_and(|a| R(&a).u32() == Some(1))
}
struct FdStream {
fd: i32,
writable: bool,
pipe: Option<SpinLock<(Vec<u8>, u64)>>,
}
impl FdStream {
fn new(fd: i32, mode: u32) -> Option<FdStream> {
let writable = matches!(mode, FO_WRITE | FO_RW);
if unsafe { lseek(fd, 0, 1) } >= 0 {
return Some(FdStream { fd, writable, pipe: None });
}
if mode != FO_READ {
return Some(FdStream { fd, writable, pipe: Some(SpinLock::new((Vec::new(), 0))) });
}
let mut all = Vec::new();
let mut buf = alloc::vec![0u8; 64 * 1024];
loop {
let n = unsafe { read(fd, buf.as_mut_ptr(), buf.len()) };
if n < 0 {
unsafe { close(fd) };
return None;
}
if n == 0 {
break;
}
all.extend_from_slice(&buf[..n as usize]);
}
Some(FdStream { fd, writable, pipe: Some(SpinLock::new((all, 0))) })
}
}
impl Stream for FdStream {
fn read_at(&self, off: u64, buf: &mut [u8]) -> Option<usize> {
if let Some(p) = &self.pipe {
let g = p.lock();
let off = usize::try_from(off).unwrap_or(usize::MAX).min(g.0.len());
let n = (g.0.len() - off).min(buf.len());
buf[..n].copy_from_slice(&g.0[off..off + n]);
return Some(n);
}
let n = unsafe { pread(self.fd, buf.as_mut_ptr(), buf.len(), i64::try_from(off).ok()?) };
usize::try_from(n).ok()
}
fn write_at(&self, off: u64, buf: &[u8]) -> Option<usize> {
if !self.writable {
return None;
}
let mut done = 0;
if let Some(p) = &self.pipe {
let mut g = p.lock();
if off != g.1 {
return None; }
while done < buf.len() {
let n = unsafe { write(self.fd, buf[done..].as_ptr(), buf.len() - done) };
if n <= 0 {
break;
}
done += n as usize;
}
g.1 += done as u64;
return (done > 0 || buf.is_empty()).then_some(done);
}
let off = i64::try_from(off).ok()?;
while done < buf.len() {
let n = unsafe { pwrite(self.fd, buf[done..].as_ptr(), buf.len() - done, off + done as i64) };
if n <= 0 {
return (done > 0).then_some(done);
}
done += n as usize;
}
Some(done)
}
fn size(&self) -> Option<u64> {
if let Some(p) = &self.pipe {
let g = p.lock();
return Some(if self.writable { g.1 } else { g.0.len() as u64 });
}
u64::try_from(unsafe { lseek(self.fd, 0, 2) }).ok()
}
fn set_len(&self, len: u64) -> bool {
self.writable && self.pipe.is_none() && i64::try_from(len).is_ok_and(|l| unsafe { ftruncate(self.fd, l) } == 0)
}
fn sync(&self) -> bool {
!self.writable || self.pipe.is_some() || unsafe { fsync(self.fd) } == 0
}
}
impl Drop for FdStream {
fn drop(&mut self) {
unsafe { close(self.fd) };
}
}
static OWN: AtomicI64 = AtomicI64::new(0);
fn stamp() -> i64 {
call(CALL_CLIP_STAMP, &[]).and_then(|a| R(&a).u64()).map_or(0, |v| v as i64)
}
pub(super) fn clip_own(local_text: Option<&[u8]>, local_image: bool) -> bool {
let own = OWN.load(Ordering::Relaxed);
let now = stamp();
if now == 0 {
return false; }
if own != 0 {
return now == own;
}
fn canon(b: &[u8]) -> impl Iterator<Item = u8> + '_ {
b.iter().copied().enumerate().filter_map(move |(i, c)| (c != b'\r' || b.get(i + 1) != Some(&b'\n')).then_some(c))
}
let ours = match local_text {
Some(t) => clip_read(CLIP_TEXT).is_some_and(|s| canon(&s).eq(canon(t))),
None => local_image && call(CALL_CLIP_OURS, &[]).is_some_and(|a| R(&a).u32() == Some(1)),
};
if ours {
OWN.store(now, Ordering::Relaxed);
}
ours
}
pub(super) fn clip_publish(text: &[u8], html: &[u8], png: &[u8]) -> bool {
let w = W::default().bytes(text).bytes(html).bytes(png);
match call(CALL_CLIP_SET, &w.0).and_then(|a| R(&a).u64()) {
Some(s) => {
OWN.store(s as i64, Ordering::Relaxed);
true
}
None => {
OWN.store(0, Ordering::Relaxed);
false
}
}
}
pub(super) fn clip_read(format: u32) -> Option<Vec<u8>> {
let f = match format {
CLIP_TEXT | CLIP_HTML => format,
CLIP_IMAGE | CLIP_PNG => CLIP_IMAGE,
_ => return None,
};
let a = call(CALL_CLIP_GET, &f.to_le_bytes())?;
let mut r = R(&a);
(r.u32()? != 0).then(|| r.bytes().map(<[u8]>::to_vec)).flatten()
}
pub(super) fn clip_offers(format: u32) -> bool {
let f = match format {
CLIP_TEXT | CLIP_HTML => format,
CLIP_IMAGE | CLIP_PNG => CLIP_IMAGE,
_ => return false,
};
call(CALL_CLIP_HAS, &f.to_le_bytes()).is_some_and(|a| R(&a).u32() == Some(1))
}