use core::mem::MaybeUninit;
use crate::control::{InResponse, Recipient, Request, RequestType};
use crate::descriptor::capability_type;
use crate::driver::Driver;
use crate::{Builder, Handler};
const USB_CLASS_VENDOR: u8 = 0xff;
const USB_SUBCLASS_NONE: u8 = 0x00;
const USB_PROTOCOL_NONE: u8 = 0x00;
const WEB_USB_REQUEST_GET_URL: u16 = 0x02;
const WEB_USB_DESCRIPTOR_TYPE_URL: u8 = 0x03;
pub struct Url<'d>(&'d str, u8);
impl<'d> Url<'d> {
pub fn new(url: &'d str) -> Self {
let (prefix, stripped_url) = if let Some(stripped) = url.strip_prefix("https://") {
(1, stripped)
} else if let Some(stripped) = url.strip_prefix("http://") {
(0, stripped)
} else {
(255, url)
};
assert!(
stripped_url.len() <= 252,
"URL too long. ({} bytes). Maximum length is 252 bytes.",
stripped_url.len()
);
Self(stripped_url, prefix)
}
fn as_bytes(&self) -> &[u8] {
self.0.as_bytes()
}
fn scheme(&self) -> u8 {
self.1
}
}
pub struct Config<'d> {
pub max_packet_size: u16,
pub landing_url: Option<Url<'d>>,
pub vendor_code: u8,
}
struct Control<'d> {
ep_buf: [u8; 128],
vendor_code: u8,
landing_url: Option<&'d Url<'d>>,
}
impl<'d> Control<'d> {
fn new(config: &'d Config<'d>) -> Self {
Control {
ep_buf: [0u8; 128],
vendor_code: config.vendor_code,
landing_url: config.landing_url.as_ref(),
}
}
}
impl<'d> Handler for Control<'d> {
fn control_in(&mut self, req: Request, _data: &mut [u8]) -> Option<InResponse<'_>> {
let landing_value = if self.landing_url.is_some() { 1 } else { 0 };
if req.request_type == RequestType::Vendor
&& req.recipient == Recipient::Device
&& req.request == self.vendor_code
&& req.value == landing_value
&& req.index == WEB_USB_REQUEST_GET_URL
{
if let Some(url) = self.landing_url {
let url_bytes = url.as_bytes();
let len = url_bytes.len();
self.ep_buf[0] = len as u8 + 3;
self.ep_buf[1] = WEB_USB_DESCRIPTOR_TYPE_URL;
self.ep_buf[2] = url.scheme();
self.ep_buf[3..3 + len].copy_from_slice(url_bytes);
return Some(InResponse::Accepted(&self.ep_buf[..3 + len]));
}
}
None
}
}
pub struct State<'d> {
control: MaybeUninit<Control<'d>>,
}
impl<'d> Default for State<'d> {
fn default() -> Self {
Self::new()
}
}
impl<'d> State<'d> {
pub const fn new() -> Self {
State {
control: MaybeUninit::uninit(),
}
}
}
pub struct WebUsb<'d, D: Driver<'d>> {
_driver: core::marker::PhantomData<&'d D>,
}
impl<'d, D: Driver<'d>> WebUsb<'d, D> {
pub fn configure(builder: &mut Builder<'d, D>, state: &'d mut State<'d>, config: &'d Config<'d>) {
let mut func = builder.function(USB_CLASS_VENDOR, USB_SUBCLASS_NONE, USB_PROTOCOL_NONE);
let mut iface = func.interface();
let mut alt = iface.alt_setting(USB_CLASS_VENDOR, USB_SUBCLASS_NONE, USB_PROTOCOL_NONE, None);
alt.bos_capability(
capability_type::PLATFORM,
&[
0x0,
0x38,
0xb6,
0x08,
0x34,
0xa9,
0x09,
0xa0,
0x47,
0x8b,
0xfd,
0xa0,
0x76,
0x88,
0x15,
0xb6,
0x65,
0x00,
0x01,
config.vendor_code,
if config.landing_url.is_some() { 1 } else { 0 },
],
);
let control = state.control.write(Control::new(config));
drop(func);
builder.handler(control);
}
}