redbear-iwlwifi: port network.wlan0 scheme to redox-scheme SchemeSync

bridge/scheme.rs was a legacy redox_syscall Packet-based scheme server
(syscall::Packet / syscall::open / SYS_OPEN / EVENT_READ-from-libredox) whose
ABI no longer exists -> the driver failed to compile. Rewrote the redox path
onto the current redox-scheme SchemeSync API, mirroring the canonical NIC
scheme (base drivers/net/driver-network) so smolnetd's network.* consumer works
unchanged: root dir; open "" -> Data (raw eth frames), open "mac" -> 6-byte
positioned MAC; read pops one RX frame (EAGAIN when empty), write does
eth->802.11->C TX submit; blocked readers woken via post_fevent(EVENT_READ).
Kept the self-contained non-blocking 1ms poll loop (also pumps bridge TX). Also
made the two extern "C" blocks (callback.rs, scheme.rs FFI)
for Rust 2024, and added redox-scheme as a redox-target dep. Compile-checked;
not yet hardware-validated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-28 16:33:46 +09:00
parent 304b0d4977
commit 85feac909b
5 changed files with 321 additions and 227 deletions
+64
View File
@@ -1855,3 +1855,67 @@ Config comparison:
## ANTI-PATTERNS (COMMIT POLICY)
- **DO NOT** include AI attribution in commit messages — no "Ultraworked with [Sisyphus]", "Co-authored-by: Sisyphus", or similar AI agent footers. Commits belong to the human author only.
## LIBRARY-ONLY RED BEAR CRATES
The following Red Bear crates exist as **library-only dependencies** — they provide shared
types, protocols, or utility code consumed by other Red Bear daemons and binaries. They are
**not recipes in any config TOML**, have no init service, and are never cooked as standalone
packages by the build system. They are consumed exclusively via Cargo `path` dependencies.
| Crate | Location | Purpose | Consumers |
|-------|----------|---------|-----------|
| `redbear-hid-core` | `local/recipes/system/redbear-hid-core/source/` | Shared HID protocol types, report descriptors, and device classification enums | `redbear-inputd`, `redbear-bthid`, future HID daemons |
| `redbear-login-protocol` | `local/recipes/system/redbear-login-protocol/source/` | Shared types and wire format for the greeter-auth-session handshake: login request/response, session token, PAM conversation messages | `redbear-greeter`, `redbear-authd`, `redbear-session-launch` |
| `redbear-tui-theme` | `local/recipes/system/redbear-tui-theme/source/` | (When wired as lib): shared ratatui color palettes, text styles, and layout constants for consistent TUI theming | `cub`, `tlc`, `redbear-power`, other ratatui-based TUI tools |
| `redbear-passwd` | `local/recipes/system/redbear-passwd/source/` | `passwd` file parsing, user/group lookup, shadow entry validation, and credential verification types | `redbear-authd`, `redbear-polkit`, `redbear-greeter`, `redbear-session-launch` |
### Consumption Pattern
These crates are used via **path dependencies** in other Red Bear daemon Cargo.toml files:
```toml
[dependencies]
redbear-hid-core = { path = "../../../system/redbear-hid-core/source" }
redbear-login-protocol = { path = "../../../system/redbear-login-protocol/source" }
redbear-passwd = { path = "../../../system/redbear-passwd/source" }
```
The path is relative to the consuming crate's `Cargo.toml` location.
### Why Library-Only
These crates are **intentionally excluded from all config TOMLs** (`redbear-full.toml`,
`redbear-mini.toml`, `redbear-grub.toml`) and have no `[package]` / `installs` in their
recipe. Reasons:
1. **No standalone binary** — They produce only a `lib` target, never a `bin`.
2. **No init service** — They are not daemons and do not register a scheme or D-Bus name.
3. **Config presence would be misleading** — Including them as a package suggests they
contribute runtime artifacts to the image, which they do not.
4. **Linking is handled by the consumer** — The consumer daemon's `Cargo.toml` pulls them
in; the build system compiles them transitively during the consumer's `repo cook`.
### Host Testing
Since these are pure-logic crates with no Redox-specific syscalls (no `libredox`, no
`redox_syscall` dep, no scheme I/O), their unit tests are **host-runnable**:
```bash
cargo test --manifest-path local/recipes/system/redbear-hid-core/source/Cargo.toml
cargo test --manifest-path local/recipes/system/redbear-login-protocol/source/Cargo.toml
cargo test --manifest-path local/recipes/system/redbear-passwd/source/Cargo.toml
```
They are included in the CI `cargo test host` step.
### Finding All Library-Only Crates
```bash
# List crates not in any config TOML
for crate in redbear-hid-core redbear-login-protocol redbear-passwd; do
if ! grep -rq "\"$crate\"" config/redbear-*.toml; then
echo "$crate: library-only (not in config)"
fi
done
```
@@ -21,6 +21,7 @@ redox-driver-sys = { path = "../../redox-driver-sys/source", features = ["redox"
pcid_interface = { path = "../../../../sources/base/drivers/pcid", package = "pcid" }
libredox = { path = "../../../../../local/sources/libredox" }
syscall = { package = "redox_syscall", path = "../../../../../local/sources/syscall", features = ["std"] }
redox-scheme = { path = "../../../../../local/sources/redox-scheme" }
[patch.crates-io]
redox-driver-sys = { path = "../../redox-driver-sys/source" }
@@ -44,7 +44,7 @@ pub struct SkBuff {
}
// Forward declaration of kfree_skb from linux-kpi
extern "C" {
unsafe extern "C" {
fn kfree_skb(skb: *mut SkBuff);
}
@@ -35,7 +35,9 @@ struct HandleMap {
impl HandleMap {
fn new() -> Self {
Self { next: 0, map: std::collections::HashMap::new() }
// Start handle ids at 1; 0 is avoided so a zero-initialised id is never
// mistaken for a valid handle.
Self { next: 1, map: std::collections::HashMap::new() }
}
fn insert(&mut self, kind: HandleKind) -> usize {
@@ -77,158 +79,135 @@ pub fn run_event_loop(
}
// —— Redox implementation ——————————————————————————————————————
//
// Ported from the legacy redox_syscall Packet-based scheme to the current
// redox-scheme SchemeSync API. The legacy `syscall::Packet` / `syscall::open`
// / `SYS_OPEN` scheme-server ABI no longer exists; this mirrors the canonical
// NIC scheme (base drivers/net/driver-network) so smolnetd's `network.*`
// consumer works unchanged:
// * the scheme root is a directory;
// * opening "" yields a Data handle for raw Ethernet frame read/write;
// * opening "mac" yields a 6-byte, positioned MAC-address handle.
// Read on Data pops one RX frame (EAGAIN when empty on a non-blocking handle);
// write on Data converts Ethernet -> 802.11 and submits via the C TX path.
// Blocked readers are woken with post_fevent(EVENT_READ) whenever RX frames
// become available. The daemon uses a non-blocking socket and a 1 ms poll loop
// (matching the legacy behaviour) so it can also pump the bridge TX path and
// post readiness without a dedicated event fd.
#[cfg(target_os = "redox")]
mod inner {
use super::*;
use libredox::flag::{EVENT_READ, O_NONBLOCK};
use std::cmp;
use super::{ethernet_to_wifi, HandleKind, HandleMap, WifiLinkBridge};
use std::io;
use syscall::flag;
use syscall::{
Error, Result as SysResult, EBADF, EAGAIN, EINVAL, EWOULDBLOCK, ERANGE, MODE_FILE, Packet,
};
use std::sync::{Arc, Mutex};
pub struct SchemeInner {
use redox_scheme::scheme::{register_sync_scheme, SchemeState, SchemeSync};
use redox_scheme::{
CallerCtx, OpenResult, RequestKind, Response, SignalBehavior, Socket,
};
use syscall::error::{
Error, Result as SysResult, EACCES, EAGAIN, EBADF, EINVAL, EIO, EWOULDBLOCK,
};
use syscall::flag::{EventFlags, MODE_DIR, MODE_FILE, O_NONBLOCK};
use syscall::schemev2::NewFdFlags;
use syscall::Stat;
// FFI: TX submission — called to send a Wi-Fi frame down the C driver path.
unsafe extern "C" {
fn rb_iwlwifi_bridge_tx_submit(data: *const u8, len: usize) -> i32;
}
pub struct WlanScheme {
name: String,
handles: HandleMap,
fd: usize,
bridge: Arc<Mutex<WifiLinkBridge>>,
mac: [u8; 6],
bssid: [u8; 6],
}
const SYS_OPEN: usize = syscall::number::SYS_OPEN;
const SYS_READ: usize = syscall::number::SYS_READ;
const SYS_WRITE: usize = syscall::number::SYS_WRITE;
const SYS_FPATH: usize = syscall::number::SYS_FPATH;
const SYS_FSTAT: usize = syscall::number::SYS_FSTAT;
const SYS_FSYNC: usize = syscall::number::SYS_FSYNC;
const SYS_CLOSE: usize = syscall::number::SYS_CLOSE;
const SYS_FEVENT: usize = syscall::number::SYS_FEVENT;
impl SchemeInner {
impl WlanScheme {
pub fn new(
name: &str,
bridge: Arc<Mutex<WifiLinkBridge>>,
mac: [u8; 6],
bssid: [u8; 6],
) -> io::Result<Self> {
let path = format!(":{}", name);
let fd = syscall::open(&path, flag::O_RDWR | flag::O_NONBLOCK | flag::O_CREAT)
.map_err(|e| io::Error::new(io::ErrorKind::Other, format!("scheme socket {}: {:?}", path, e)))?;
log::info!("bridge::scheme: registered {}", name);
Ok(Self {
) -> Self {
Self {
name: name.to_string(),
handles: HandleMap::new(),
fd,
bridge,
mac,
bssid,
})
}
pub fn fd(&self) -> usize { self.fd }
pub fn tick(&mut self) -> io::Result<bool> {
let mut pkt = Packet::default();
match syscall::read(self.fd, &mut pkt) {
Ok(_) => { self.dispatch(pkt)?; Ok(true) }
Err(err) if err.errno == syscall::EAGAIN => Ok(false),
Err(err) => Err(io::Error::from_raw_os_error(err.errno)),
}
}
pub fn notify_readers(&self) {
for id in self.handles.data_handles() {
let p = Packet {
id: 0,
a: SYS_FEVENT,
b: id,
c: EVENT_READ.bits(),
d: 0,
};
let _ = syscall::write(self.fd, &p);
}
fn kind(&self, id: usize) -> SysResult<&HandleKind> {
self.handles.get(id).ok_or(Error::new(EBADF))
}
fn dispatch(&mut self, pkt: Packet) -> io::Result<()> {
let result = match pkt.a {
SYS_OPEN => self.do_open(&pkt),
SYS_READ => self.do_read(&pkt),
SYS_WRITE => self.do_write(&pkt),
SYS_FPATH => self.do_fpath(&pkt),
SYS_FSTAT => self.do_fstat(&pkt),
SYS_FSYNC => self.do_fsync(&pkt),
SYS_CLOSE => { self.handles.remove(pkt.b); Ok(None) }
_ => Err(Error::new(syscall::ENOSYS)),
/// Ids of all currently-open Data handles, for readiness notification.
pub fn data_handle_ids(&self) -> Vec<usize> {
self.handles.data_handles()
}
}
impl SchemeSync for WlanScheme {
fn scheme_root(&mut self) -> SysResult<usize> {
Ok(self.handles.insert(HandleKind::Root))
}
fn openat(
&mut self,
dirfd: usize,
path: &str,
_flags: usize,
_fcntl_flags: u32,
ctx: &CallerCtx,
) -> SysResult<OpenResult> {
if !matches!(self.kind(dirfd)?, HandleKind::Root) {
return Err(Error::new(EACCES));
}
if ctx.uid != 0 {
return Err(Error::new(EACCES));
}
let (kind, flags) = match path.trim_matches('/') {
"" => (HandleKind::Data, NewFdFlags::empty()),
"mac" => (HandleKind::Mac, NewFdFlags::POSITIONED),
_ => return Err(Error::new(EINVAL)),
};
let id = pkt.id;
match result {
Ok(Some(mut r)) => { r.id = id; syscall::write(self.fd, &r).ok(); }
Ok(None) => {}
Err(err) => {
let mut r = Packet::default();
r.id = id;
r.a = err.errno;
if err.errno != EAGAIN && err.errno != EWOULDBLOCK {
syscall::write(self.fd, &r).ok();
}
}
}
Ok(())
let number = self.handles.insert(kind);
Ok(OpenResult::ThisScheme { number, flags })
}
fn do_open(&mut self, _pkt: &Packet) -> SysResult<Option<Packet>> {
let id = self.handles.insert(HandleKind::Data);
let mut r = Packet::default();
r.a = 0;
r.b = id;
Ok(Some(r))
}
fn do_read(&mut self, pkt: &Packet) -> SysResult<Option<Packet>> {
let hid = pkt.b;
let max = pkt.c;
let fl = pkt.d as u32;
let kind = self.handles.get(hid).ok_or(Error::new(EBADF))?;
match kind {
fn read(
&mut self,
id: usize,
buf: &mut [u8],
offset: u64,
fcntl_flags: u32,
_ctx: &CallerCtx,
) -> SysResult<usize> {
match self.kind(id)? {
HandleKind::Mac => {
let off = pkt.d as usize;
let off = offset as usize;
let mac = self.mac;
if off >= 6 {
return Err(Error::new(ERANGE));
if off >= mac.len() {
return Ok(0);
}
let n = cmp::min(max, 6 - off);
let mut r = Packet::default();
r.a = 0;
r.b = n;
// SAFETY: caller must verify the safety contract for this operation
unsafe {
std::ptr::copy_nonoverlapping(mac[off..].as_ptr(), &mut r.c as *mut usize as *mut u8, n);
}
Ok(Some(r))
let n = core::cmp::min(buf.len(), mac.len() - off);
buf[..n].copy_from_slice(&mac[off..off + n]);
Ok(n)
}
HandleKind::Data => {
let mut bridge = self.bridge.lock().unwrap();
match bridge.pop_rx() {
let frame = self.bridge.lock().unwrap().pop_rx();
match frame {
Some(frame) => {
let n = cmp::min(frame.len(), max);
let mut r = Packet::default();
r.a = 0;
r.b = n;
// SAFETY: caller must verify the safety contract for this operation
unsafe {
std::ptr::copy_nonoverlapping(frame.as_ptr(), &mut r.c as *mut usize as *mut u8, n);
}
Ok(Some(r))
let n = core::cmp::min(frame.len(), buf.len());
buf[..n].copy_from_slice(&frame[..n]);
Ok(n)
}
None => {
if fl & O_NONBLOCK as u32 != 0 {
if fcntl_flags & O_NONBLOCK as u32 != 0 {
Err(Error::new(EAGAIN))
} else {
Err(Error::new(EWOULDBLOCK))
@@ -236,84 +215,180 @@ mod inner {
}
}
}
_ => Err(Error::new(EBADF)),
HandleKind::Root => Err(Error::new(EBADF)),
}
}
fn do_write(&mut self, pkt: &Packet) -> SysResult<Option<Packet>> {
let hid = pkt.b;
let len = pkt.c;
let kind = self.handles.get(hid).ok_or(Error::new(EBADF))?;
if !matches!(kind, HandleKind::Data) {
fn write(
&mut self,
id: usize,
buf: &[u8],
_offset: u64,
_fcntl_flags: u32,
_ctx: &CallerCtx,
) -> SysResult<usize> {
if !matches!(self.kind(id)?, HandleKind::Data) {
return Err(Error::new(EINVAL));
}
// Extract data from the packet's inline area
let mut buf = vec![0u8; cmp::min(len, std::mem::size_of::<usize>() * 4)];
unsafe {
let src = &pkt.c as *const usize as *const u8;
std::ptr::copy_nonoverlapping(src, buf.as_mut_ptr(), buf.len());
}
buf.truncate(len);
// Convert Ethernet → 802.11 and submit
let bssid = self.bssid;
let mac = self.mac;
if let Some(wifi) = ethernet_to_wifi(&buf, &bssid, &mac) {
// Try submitting via C TX path
let submitted = unsafe { rb_iwlwifi_bridge_tx_submit(wifi.as_ptr(), wifi.len()) };
if submitted != 0 {
self.bridge.lock().unwrap().stats.inc_tx_dropped();
return Err(Error::new(syscall::EIO));
match ethernet_to_wifi(buf, &bssid, &mac) {
Some(wifi) => {
// SAFETY: `wifi` is a live, contiguous byte buffer; the C
// side only reads `len` bytes from `data` and does not
// retain the pointer past the call.
let rc = unsafe { rb_iwlwifi_bridge_tx_submit(wifi.as_ptr(), wifi.len()) };
if rc != 0 {
self.bridge.lock().unwrap().stats.inc_tx_dropped();
return Err(Error::new(EIO));
}
self.bridge.lock().unwrap().stats.inc_tx_frame(buf.len());
Ok(buf.len())
}
self.bridge.lock().unwrap().stats.inc_tx_frame(buf.len());
None => {
self.bridge.lock().unwrap().stats.inc_tx_dropped();
Err(Error::new(EINVAL))
}
}
}
let mut r = Packet::default();
r.a = 0;
r.b = len;
Ok(Some(r))
fn fevent(
&mut self,
id: usize,
_flags: EventFlags,
_ctx: &CallerCtx,
) -> SysResult<EventFlags> {
let _ = self.kind(id)?;
Ok(EventFlags::empty())
}
fn fpath(&mut self, id: usize, buf: &mut [u8], _ctx: &CallerCtx) -> SysResult<usize> {
let leaf = match self.kind(id)? {
HandleKind::Root | HandleKind::Data => "",
HandleKind::Mac => "mac",
};
let path = if leaf.is_empty() {
format!("{}:", self.name)
} else {
self.bridge.lock().unwrap().stats.inc_tx_dropped();
Err(Error::new(EINVAL))
}
format!("{}:{}", self.name, leaf)
};
let bytes = path.as_bytes();
let n = core::cmp::min(buf.len(), bytes.len());
buf[..n].copy_from_slice(&bytes[..n]);
Ok(n)
}
fn do_fpath(&mut self, _pkt: &Packet) -> SysResult<Option<Packet>> // SAFETY: caller must verify the safety contract for this operation
{
let name = self.name.clone();
let mut r = Packet::default();
r.a = 0;
r.b = name.len();
unsafe {
std::ptr::copy_nonoverlapping(name.as_ptr(), &mut r.c as *mut usize as *mut u8, name.len());
fn fstat(&mut self, id: usize, stat: &mut Stat, _ctx: &CallerCtx) -> SysResult<()> {
match self.kind(id)? {
HandleKind::Root => {
stat.st_mode = MODE_DIR | 0o500;
}
HandleKind::Data => {
stat.st_mode = MODE_FILE | 0o700;
}
HandleKind::Mac => {
stat.st_mode = MODE_FILE | 0o400;
stat.st_size = 6;
}
}
Ok(Some(r))
Ok(())
}
fn do_fstat(&mut self, pkt: &Packet) -> SysResult<Option<Packet>> {
let hid = pkt.b;
let kind = self.handles.get(hid).ok_or(Error::new(EBADF))?;
let mut r = Packet::default();
r.a = 0;
match kind {
HandleKind::Data => { r.c = MODE_FILE | 0o700; }
HandleKind::Mac => { r.c = MODE_FILE | 0o400; r.d = 6; }
HandleKind::Root => { r.c = MODE_FILE | 0o500; }
}
Ok(Some(r))
fn fsync(&mut self, id: usize, _ctx: &CallerCtx) -> SysResult<()> {
let _ = self.kind(id)?;
Ok(())
}
fn do_fsync(&mut self, _pkt: &Packet) -> SysResult<Option<Packet>> {
let mut r = Packet::default();
r.a = 0;
Ok(Some(r))
fn on_close(&mut self, id: usize) {
self.handles.remove(id);
}
}
// FFI: TX submission — called by bridge to send a Wi-Fi frame
extern "C" {
fn rb_iwlwifi_bridge_tx_submit(data: *const u8, len: usize) -> i32;
/// Register the `network.<name>` scheme and service it until the socket
/// closes. Non-blocking: also pumps the bridge TX path and posts read
/// readiness on each poll tick.
pub fn run(
scheme_name: &str,
bridge: Arc<Mutex<WifiLinkBridge>>,
mac: [u8; 6],
bssid: [u8; 6],
) -> io::Result<()> {
let socket = Socket::nonblock().map_err(|e| {
io::Error::new(io::ErrorKind::Other, format!("create scheme socket: {e}"))
})?;
let mut scheme = WlanScheme::new(scheme_name, Arc::clone(&bridge), mac, bssid);
register_sync_scheme(&socket, scheme_name, &mut scheme).map_err(|e| {
io::Error::new(
io::ErrorKind::Other,
format!("register scheme {scheme_name}: {e}"),
)
})?;
let mut state = SchemeState::new();
log::info!("bridge::scheme: registered {} (redox-scheme)", scheme_name);
let read_flag = EventFlags::EVENT_READ.bits();
loop {
// Drain all currently-pending scheme requests.
loop {
match socket.next_request(SignalBehavior::Restart) {
Ok(Some(request)) => match request.kind() {
RequestKind::Call(call) => {
let response = call.handle_sync(&mut scheme, &mut state);
if let Err(e) =
socket.write_response(response, SignalBehavior::Restart)
{
log::error!("bridge::scheme: write_response: {e}");
}
}
RequestKind::OnClose { id } => scheme.on_close(id),
_ => {}
},
Ok(None) => {
log::info!("bridge::scheme: socket closed, exiting");
return Ok(());
}
Err(e) if e.errno == EAGAIN => break,
Err(e) => {
log::error!("bridge::scheme: next_request: {e}");
break;
}
}
}
// Pump a pending Ethernet TX frame (submit_tx path) to the C driver.
let tx_frame = bridge.lock().unwrap().take_tx();
if let Some(eth) = tx_frame {
let (bssid_copy, mac_copy) = {
let b = bridge.lock().unwrap();
(b.bssid, b.mac)
};
if let Some(wifi) = ethernet_to_wifi(&eth, &bssid_copy, &mac_copy) {
// SAFETY: `wifi` is live for the duration of the call; the C
// side copies out `len` bytes and does not retain the pointer.
let rc = unsafe { rb_iwlwifi_bridge_tx_submit(wifi.as_ptr(), wifi.len()) };
if rc != 0 {
bridge.lock().unwrap().stats.inc_tx_dropped();
} else {
bridge.lock().unwrap().stats.inc_tx_frame(eth.len());
}
} else {
bridge.lock().unwrap().stats.inc_tx_dropped();
}
}
// Wake blocked readers when RX frames are available.
if bridge.lock().unwrap().available_for_read() > 0 {
for id in scheme.data_handle_ids() {
let _ = socket.write_response(
Response::post_fevent(id, read_flag),
SignalBehavior::Restart,
);
}
}
std::thread::sleep(std::time::Duration::from_millis(1));
}
}
}
@@ -326,62 +401,16 @@ pub fn run_event_loop(
mac: [u8; 6],
bssid: [u8; 6],
) -> ! {
use inner::SchemeInner;
bridge.lock().unwrap().active.store(true, Ordering::Release);
let mut scheme = match SchemeInner::new(scheme_name, Arc::clone(&bridge), mac, bssid) {
Ok(s) => s,
Err(e) => {
log::error!("bridge::scheme: failed to create scheme: {}", e);
std::process::exit(1);
}
};
log::info!("bridge::scheme: event loop running on {}", scheme_name);
loop {
// Process incoming scheme requests
match scheme.tick() {
Ok(true) => { /* handled one request */ }
Ok(false) => { /* no work */ }
Err(e) => {
log::error!("bridge::scheme: tick error: {}", e);
// Continue — don't crash on transient errors
}
match inner::run(scheme_name, bridge, mac, bssid) {
Ok(()) => std::process::exit(0),
Err(e) => {
log::error!("bridge::scheme: event loop error: {}", e);
std::process::exit(1);
}
// Drain pending TX
let tx_frame = {
let mut b = bridge.lock().unwrap();
b.take_tx()
};
if let Some(eth) = tx_frame {
let (bssid_copy, mac_copy) = {
let b = bridge.lock().unwrap();
(b.bssid, b.mac)
};
if let Some(wifi) = ethernet_to_wifi(&eth, &bssid_copy, &mac_copy) {
unsafe {
let rc = inner::rb_iwlwifi_bridge_tx_submit(wifi.as_ptr(), wifi.len());
if rc != 0 {
bridge.lock().unwrap().stats.inc_tx_dropped();
} else {
bridge.lock().unwrap().stats.inc_tx_frame(eth.len());
}
}
} else {
bridge.lock().unwrap().stats.inc_tx_dropped();
}
}
// Notify readers if data is available
if bridge.lock().unwrap().available_for_read() > 0 {
scheme.notify_readers();
}
// Small yield to avoid busy-looping when idle
std::thread::sleep(std::time::Duration::from_millis(1));
}
}
+2 -2
View File
@@ -169,7 +169,7 @@ dependencies = [
[[package]]
name = "redbear-authd"
version = "0.3.0"
version = "0.3.1"
dependencies = [
"redbear-login-protocol",
"rust-argon2",
@@ -180,7 +180,7 @@ dependencies = [
[[package]]
name = "redbear-login-protocol"
version = "0.3.0"
version = "0.3.1"
dependencies = [
"serde",
"serde_json",