init ws connection + put ttt and chat behind features

This commit is contained in:
Arkitu 2025-07-14 10:41:42 +02:00
parent 6c57c3aaaf
commit b50300fbbb
12 changed files with 220 additions and 85 deletions

View File

@ -1,8 +1,8 @@
[target.'cfg(all(target_arch = "arm", target_os = "none"))'] [target.'cfg(all(target_arch = "arm", target_os = "none"))']
runner = "elf2uf2-rs -d -s -t" runner = "sudo /home/arkitu/.cargo/bin/elf2uf2-rs -d -s"
[build] [build]
target = "thumbv6m-none-eabi" # Cortex-M0 and Cortex-M0+ target = "thumbv6m-none-eabi" # Cortex-M0 and Cortex-M0+
[env] [env]
DEFMT_LOG = "debug" DEFMT_LOG = "debug"

19
Cargo.lock generated
View File

@ -68,6 +68,12 @@ dependencies = [
"rustc_version", "rustc_version",
] ]
[[package]]
name = "base64"
version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]] [[package]]
name = "bit-set" name = "bit-set"
version = "0.8.0" version = "0.8.0"
@ -1098,6 +1104,7 @@ checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315"
name = "pico-website" name = "pico-website"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"base64",
"cortex-m", "cortex-m",
"cortex-m-rt", "cortex-m-rt",
"cyw43", "cyw43",
@ -1122,6 +1129,7 @@ dependencies = [
"ringbuffer", "ringbuffer",
"serde", "serde",
"serde-json-core", "serde-json-core",
"sha1",
"static_cell", "static_cell",
] ]
@ -1420,6 +1428,17 @@ dependencies = [
"syn 2.0.100", "syn 2.0.100",
] ]
[[package]]
name = "sha1"
version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
dependencies = [
"cfg-if",
"cpufeatures",
"digest",
]
[[package]] [[package]]
name = "sha2-const-stable" name = "sha2-const-stable"
version = "0.1.0" version = "0.1.0"

View File

@ -7,9 +7,11 @@ edition = "2024"
wifi-connect = [ wifi-connect = [
"dep:serde-json-core", "dep:serde-json-core",
"dep:serde", "dep:serde",
] # you need to add a wifi.conf file for this to work ] # you need to add a wifi.json file for this to work
dhcp = ["dep:dhcparse"] dhcp = ["dep:dhcparse"]
dns = ["dep:dnsparse"] dns = ["dep:dnsparse"]
chat = ["dep:ringbuffer"]
ttt = []
default = ["dhcp", "dns"] default = ["dhcp", "dns"]
[dependencies] [dependencies]
@ -58,5 +60,7 @@ serde = { version = "*", optional = true, default-features = false, features = [
] } ] }
dhcparse = { version = "*", default-features = false, optional = true } dhcparse = { version = "*", default-features = false, optional = true }
dnsparse = { version = "*", optional = true } dnsparse = { version = "*", optional = true }
ringbuffer = { version = "*", default-features = false } ringbuffer = { version = "*", default-features = false, optional = true }
percent-encoding = { version = "*", default-features = false } percent-encoding = { version = "*", default-features = false }
sha1 = { version = "*", default-features = false }
base64 = { version = "*", default-features = false }

View File

@ -19,12 +19,12 @@ const MSG_SIZE: usize = 128;
static MESSAGES: Mutex<ThreadModeRawMutex, Messages> = Mutex::new(Messages::new()); static MESSAGES: Mutex<ThreadModeRawMutex, Messages> = Mutex::new(Messages::new());
pub struct ChatApp { pub struct ChatApp {
res_buf: Vec<u8, 1100>, res_buf: String<1100>,
} }
impl ChatApp { impl ChatApp {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
res_buf: Vec::new(), res_buf: String::new(),
} }
} }
} }
@ -34,13 +34,14 @@ impl App for ChatApp {
} }
async fn handle_request<'a>( async fn handle_request<'a>(
&'a mut self, &'a mut self,
_host: &str,
path: &str, path: &str,
req_type: HttpRequestType, req_type: HttpRequestType,
content: &str, content: &str,
) -> (HttpResCode, &'static str, &'a [u8]) { ) -> (HttpResCode, &'static str, &'a str) {
match (req_type, path) { match (req_type, path) {
(HttpRequestType::Get, "/" | "/index" | "/index.html" | "/chat" | "/chat.html") => { (HttpRequestType::Get, "/" | "/index" | "/index.html" | "/chat" | "/chat.html") => {
(HttpResCode::Ok, "html", include_bytes!("./chat.html")) (HttpResCode::Ok, "html", include_str!("./chat.html"))
} }
(_, path) => { (_, path) => {
let (path, args) = path.split_once('?').unwrap_or((path, "")); let (path, args) = path.split_once('?').unwrap_or((path, ""));
@ -53,7 +54,7 @@ impl App for ChatApp {
Some(("load", n)) => { Some(("load", n)) => {
let n: u16 = match n.parse() { let n: u16 = match n.parse() {
Ok(v) => v, Ok(v) => v,
Err(_) => return (HttpResCode::BadRequest, "", &[]), Err(_) => return (HttpResCode::BadRequest, "", ""),
}; };
if n > 0 { if n > 0 {
load = Some(n); load = Some(n);
@ -63,11 +64,11 @@ impl App for ChatApp {
let mut name = String::<USERNAME_SIZE>::new(); let mut name = String::<USERNAME_SIZE>::new();
for c in percent_decode_str(u) { for c in percent_decode_str(u) {
if let Err(_) = name.push(c as char) { if let Err(_) = name.push(c as char) {
return (HttpResCode::BadRequest, "", &[]); return (HttpResCode::BadRequest, "", "");
} }
} }
if u.len() < USERNAME_MIN_SIZE { if u.len() < USERNAME_MIN_SIZE {
return (HttpResCode::BadRequest, "", &[]); return (HttpResCode::BadRequest, "", "");
} }
username = Some(name); username = Some(name);
} }
@ -83,7 +84,7 @@ impl App for ChatApp {
Some(Ok(c)) => c, Some(Ok(c)) => c,
_ => { _ => {
warn!("Invalid percent encoding of msg argument"); warn!("Invalid percent encoding of msg argument");
return (HttpResCode::BadRequest, "", &[]); return (HttpResCode::BadRequest, "", "");
} }
}; };
i += 2; i += 2;
@ -92,7 +93,7 @@ impl App for ChatApp {
m.as_bytes()[i] m.as_bytes()[i]
}; };
if let Err(_) = msg.push(c) { if let Err(_) = msg.push(c) {
return (HttpResCode::BadRequest, "", &[]); return (HttpResCode::BadRequest, "", "");
} }
i += 1; i += 1;
} }
@ -101,7 +102,7 @@ impl App for ChatApp {
Ok(msg) => msg, Ok(msg) => msg,
Err(_) => { Err(_) => {
warn!("Invalid utf8 msg argument"); warn!("Invalid utf8 msg argument");
return (HttpResCode::BadRequest, "", &[]); return (HttpResCode::BadRequest, "", "");
} }
}); });
} }
@ -168,17 +169,17 @@ impl App for ChatApp {
} else if path.starts_with("/chat/message/") && path.len() > 14 { } else if path.starts_with("/chat/message/") && path.len() > 14 {
let msg_id: u16 = match path[14..].parse() { let msg_id: u16 = match path[14..].parse() {
Ok(n) => n, Ok(n) => n,
Err(_) => return (HttpResCode::BadRequest, "", &[]), Err(_) => return (HttpResCode::BadRequest, "", ""),
}; };
let msgs = MESSAGES.lock().await; let msgs = MESSAGES.lock().await;
if msg_id > msgs.next { if msg_id > msgs.next {
return (HttpResCode::BadRequest, "", &[]); return (HttpResCode::BadRequest, "", "");
} }
self.res_buf.clear(); self.res_buf.clear();
unwrap(write!(&mut self.res_buf, "<div class=\"message\"")).await; unwrap(write!(&mut self.res_buf, "<div class=\"message\"")).await;
if msg_id == msgs.next { if msg_id == msgs.next {
if poll { if poll {
return (HttpResCode::NoContent, "", &[]); return (HttpResCode::NoContent, "", "");
} }
unwrap(write!( unwrap(write!(
&mut self.res_buf, &mut self.res_buf,
@ -231,7 +232,7 @@ impl App for ChatApp {
.await; .await;
} }
} else { } else {
return (HttpResCode::NoContent, "", &[]); return (HttpResCode::NoContent, "", "");
} }
} }
}; };
@ -239,7 +240,7 @@ impl App for ChatApp {
return (HttpResCode::Ok, "html", &self.res_buf); return (HttpResCode::Ok, "html", &self.res_buf);
} else { } else {
(HttpResCode::NotFound, "", &[]) (HttpResCode::NotFound, "", "")
} }
} }
} }

View File

@ -1,5 +1,9 @@
<!doctype html> <!doctype html>
<head> </head> <head>
<script>
var ws = new WebSocket("/chat");
</script>
</head>
<html> <html>
<body> <body>
<h1>Apps</h1> <h1>Apps</h1>

View File

@ -9,15 +9,16 @@ impl App for IndexApp {
} }
async fn handle_request<'a>( async fn handle_request<'a>(
&'a mut self, &'a mut self,
_host: &str,
path: &str, path: &str,
_req_type: HttpRequestType, _req_type: HttpRequestType,
_content: &str, _content: &str,
) -> (HttpResCode, &'static str, &'a [u8]) { ) -> (HttpResCode, &'static str, &'a str) {
match path { match path {
"/" | "/index" | "/index.html" => { "/" | "/index" | "/index.html" => {
(HttpResCode::Ok, "html", include_bytes!("./index.html")) (HttpResCode::Ok, "html", include_str!("./index.html"))
} }
_ => (HttpResCode::NotFound, "", &[]), _ => (HttpResCode::NotFound, "", ""),
} }
} }
} }

View File

@ -1,15 +1,18 @@
use crate::socket::{HttpRequestType, HttpResCode}; use crate::socket::{HttpRequestType, HttpResCode};
#[cfg(feature="chat")]
pub mod chat; pub mod chat;
pub mod index; pub mod index;
#[cfg(feature = "ttt")]
pub mod ttt; pub mod ttt;
pub trait App { pub trait App {
fn socket_name(&self) -> &'static str; fn socket_name(&self) -> &'static str;
async fn handle_request<'a>( async fn handle_request<'a>(
&'a mut self, &'a mut self,
host: &str,
path: &str, path: &str,
req_type: HttpRequestType, req_type: HttpRequestType,
content: &str, content: &str,
) -> (HttpResCode, &'static str, &'a [u8]); ) -> (HttpResCode, &'static str, &'a str);
} }

View File

@ -1,7 +1,7 @@
use core::fmt::Write; use core::fmt::Write;
use core::{ops::Not, sync::atomic::Ordering}; use core::{ops::Not, sync::atomic::Ordering};
use embassy_time::{Duration, Instant}; use embassy_time::{Duration, Instant};
use heapless::Vec; use heapless::String;
use pico_website::unwrap; use pico_website::unwrap;
use portable_atomic::{AtomicBool, AtomicU32}; use portable_atomic::{AtomicBool, AtomicU32};
@ -14,7 +14,7 @@ static TURN: AtomicBool = AtomicBool::new(false);
static BOARD: AtomicU32 = AtomicU32::new(0); static BOARD: AtomicU32 = AtomicU32::new(0);
pub struct TttApp { pub struct TttApp {
res_buf: Vec<u8, 2048>, res_buf: String<2048>,
/// State of the board last time it has been sent /// State of the board last time it has been sent
last_board: u32, last_board: u32,
team: Team, team: Team,
@ -23,7 +23,7 @@ pub struct TttApp {
impl TttApp { impl TttApp {
pub fn new(team: Team) -> Self { pub fn new(team: Team) -> Self {
Self { Self {
res_buf: Vec::new(), res_buf: String::new(),
last_board: 0, last_board: 0,
team, team,
end: None, end: None,
@ -73,11 +73,11 @@ impl TttApp {
board: u32, board: u32,
turn: Team, turn: Team,
outer_html: bool, outer_html: bool,
) -> &'a [u8] { ) -> &'a str {
self.res_buf.clear(); self.res_buf.clear();
if outer_html { if outer_html {
unwrap(self.res_buf.extend_from_slice( unwrap(self.res_buf.push_str(
b"<div \ "<div \
id=\"game\" \ id=\"game\" \
hx-get=\"/ttt/game\" \ hx-get=\"/ttt/game\" \
hx-swap=\"innerHTML\" \ hx-swap=\"innerHTML\" \
@ -107,7 +107,7 @@ impl TttApp {
Some((_, None)) => unwrap(write!(self.res_buf, "<br><h3>Draw!</h3><br>",)).await, Some((_, None)) => unwrap(write!(self.res_buf, "<br><h3>Draw!</h3><br>",)).await,
None => {} None => {}
} }
unwrap(self.res_buf.extend_from_slice(b"<div id=\"grid\">")).await; unwrap(self.res_buf.push_str("<div id=\"grid\">")).await;
for c in 0..=8 { for c in 0..=8 {
let picked_by = if board & (1 << c) != 0 { let picked_by = if board & (1 << c) != 0 {
Some(Team::Zero) Some(Team::Zero)
@ -133,18 +133,14 @@ impl TttApp {
c c
)).await; )).await;
} else { } else {
unwrap( unwrap(self.res_buf.push_str("<div class=\"cell\"></div>")).await;
self.res_buf
.extend_from_slice(b"<div class=\"cell\"></div>"),
)
.await;
} }
} }
}; };
} }
unwrap(self.res_buf.extend_from_slice(b"</div>")).await; unwrap(self.res_buf.push_str("</div>")).await;
if outer_html { if outer_html {
unwrap(self.res_buf.extend_from_slice(b"</div>")).await; unwrap(self.res_buf.push_str("</div>")).await;
} }
&self.res_buf &self.res_buf
} }
@ -156,13 +152,14 @@ impl App for TttApp {
} }
async fn handle_request<'a>( async fn handle_request<'a>(
&'a mut self, &'a mut self,
_host: &str,
path: &str, path: &str,
_req_type: HttpRequestType, _req_type: HttpRequestType,
_content: &str, _content: &str,
) -> (HttpResCode, &'static str, &'a [u8]) { ) -> (HttpResCode, &'static str, &'a str) {
match path { match path {
"/" | "/index" | "/index.html" | "/ttt" | "/ttt.html" => { "/" | "/index" | "/index.html" | "/ttt" | "/ttt.html" => {
(HttpResCode::Ok, "html", include_bytes!("./ttt.html")) (HttpResCode::Ok, "html", include_str!("./ttt.html"))
} }
"/ttt/initial_game" => { "/ttt/initial_game" => {
let board = BOARD.load(Ordering::Acquire); let board = BOARD.load(Ordering::Acquire);
@ -184,11 +181,11 @@ impl App for TttApp {
unwrap(path.chars().nth(9).ok_or("no 9th char")).await, unwrap(path.chars().nth(9).ok_or("no 9th char")).await,
) { ) {
Ok(c) => c, Ok(c) => c,
Err(_) => return (HttpResCode::NotFound, "", &[]), Err(_) => return (HttpResCode::NotFound, "", ""),
}; };
if board & ((1 << (clicked_c as u32)) + (1 << (9 + clicked_c as u32))) != 0 if board & ((1 << (clicked_c as u32)) + (1 << (9 + clicked_c as u32))) != 0
{ {
return (HttpResCode::Forbidden, "", &[]); return (HttpResCode::Forbidden, "", "");
} }
board = board | (1 << ((self.team as u32 * 9) + clicked_c as u32)); board = board | (1 << ((self.team as u32 * 9) + clicked_c as u32));
turn = (!self.team).into(); turn = (!self.team).into();
@ -204,10 +201,10 @@ impl App for TttApp {
self.generate_board_res(board, turn.into(), false).await, self.generate_board_res(board, turn.into(), false).await,
) )
} else { } else {
(HttpResCode::NoContent, "", &[]) (HttpResCode::NoContent, "", "")
} }
} else { } else {
(HttpResCode::NotFound, "", &[]) (HttpResCode::NotFound, "", "")
} }
} }
} }

View File

@ -27,6 +27,7 @@ pub async fn dns_server(stack: Stack<'static>) {
info!("Starting DNS server"); info!("Starting DNS server");
loop { loop {
Timer::after_secs(0).await;
let (n, meta) = unwrap(socket.recv_from(&mut buf).await).await; let (n, meta) = unwrap(socket.recv_from(&mut buf).await).await;
let msg = match dnsparse::Message::parse(&mut buf[..n]) { let msg = match dnsparse::Message::parse(&mut buf[..n]) {
@ -42,7 +43,6 @@ pub async fn dns_server(stack: Stack<'static>) {
"Dns: Received unknown dns opcode ({:?}), ignoring", "Dns: Received unknown dns opcode ({:?}), ignoring",
msg.header().opcode() msg.header().opcode()
); );
Timer::after_micros(10).await;
continue; continue;
} }
let mut res = Message::builder(&mut res_buf) let mut res = Message::builder(&mut res_buf)

View File

@ -3,6 +3,10 @@
#![allow(async_fn_in_trait)] #![allow(async_fn_in_trait)]
#![feature(impl_trait_in_assoc_type)] #![feature(impl_trait_in_assoc_type)]
#![feature(slice_split_once)] #![feature(slice_split_once)]
#![feature(try_blocks)]
#[cfg(feature = "wifi-connect")]
use core::net::Ipv4Addr;
use cyw43_pio::{DEFAULT_CLOCK_DIVIDER, PioSpi}; use cyw43_pio::{DEFAULT_CLOCK_DIVIDER, PioSpi};
use embassy_executor::Spawner; use embassy_executor::Spawner;
@ -18,6 +22,8 @@ use pico_website::unwrap;
use rand_core::RngCore; use rand_core::RngCore;
use static_cell::StaticCell; use static_cell::StaticCell;
use {defmt_rtt as _, panic_probe as _}; use {defmt_rtt as _, panic_probe as _};
use log::info;
use embassy_time::Timer;
#[cfg(feature = "dhcp")] #[cfg(feature = "dhcp")]
mod dhcp; mod dhcp;
@ -57,6 +63,13 @@ async fn main(spawner: Spawner) {
spawner.spawn(logger_task(driver)).unwrap(); spawner.spawn(logger_task(driver)).unwrap();
let mut rng = RoscRng; let mut rng = RoscRng;
// let mut i = 0;
// loop {
// info!("test{}", i);
// Timer::after_secs(1).await;
// i += 1;
// }
let fw = include_bytes!("../cyw43-firmware/43439A0.bin"); let fw = include_bytes!("../cyw43-firmware/43439A0.bin");
let clm = include_bytes!("../cyw43-firmware/43439A0_clm.bin"); let clm = include_bytes!("../cyw43-firmware/43439A0_clm.bin");
let pwr = Output::new(p.PIN_23, Level::Low); let pwr = Output::new(p.PIN_23, Level::Low);
@ -106,7 +119,7 @@ async fn main(spawner: Spawner) {
dns_servers: heapless::Vec::new(), dns_servers: heapless::Vec::new(),
gateway: None, gateway: None,
}), }),
None => Config::dhcpv4(DhcpConfig::default()), None => Config::dhcpv4(embassy_net::DhcpConfig::default()),
}; };
(wifi_conf, config) (wifi_conf, config)
}; };
@ -170,11 +183,16 @@ async fn main(spawner: Spawner) {
unwrap(spawner.spawn(dns::dns_server(stack))).await; unwrap(spawner.spawn(dns::dns_server(stack))).await;
unwrap(spawner.spawn(socket::index_listen_task(stack, 80))).await; unwrap(spawner.spawn(socket::index_listen_task(stack, 80))).await;
unwrap(spawner.spawn(socket::ttt_listen_task(stack, apps::ttt::Team::Zero, 8080))).await; #[cfg(feature = "ttt")]
unwrap(spawner.spawn(socket::ttt_listen_task(stack, apps::ttt::Team::One, 8081))).await; {
unwrap(spawner.spawn(socket::ttt_listen_task(stack, apps::ttt::Team::Zero, 8080))).await;
unwrap(spawner.spawn(socket::ttt_listen_task(stack, apps::ttt::Team::One, 8081))).await;
}
#[cfg(feature = "chat")]
for _ in 0..4 { for _ in 0..4 {
unwrap(spawner.spawn(socket::chat_listen_task(stack, 8082))).await; unwrap(spawner.spawn(socket::chat_listen_task(stack, 8082))).await;
} }
info!("All apps lauched!");
} }
#[cfg(feature = "wifi-connect")] #[cfg(feature = "wifi-connect")]

View File

@ -1,29 +1,36 @@
use base64::{EncodeSliceError, prelude::*};
use core::fmt::Write; use core::fmt::Write;
use core::str::from_utf8; use core::str::from_utf8;
use defmt::dbg;
use embassy_net::tcp::TcpSocket; use embassy_net::tcp::TcpSocket;
use embassy_time::{Duration, Timer}; use embassy_time::{Duration, Timer};
use embedded_io_async::Write as _; use embedded_io_async::Write as _;
use heapless::Vec; use heapless::{String, Vec};
use log::{info, warn}; use log::{info, warn};
use sha1::{Digest, Sha1};
use crate::apps::{App, chat, index::IndexApp, ttt}; use crate::apps;
pub mod ws;
#[cfg(feature = "ttt")]
#[embassy_executor::task(pool_size = 2)] #[embassy_executor::task(pool_size = 2)]
pub async fn ttt_listen_task(stack: embassy_net::Stack<'static>, team: ttt::Team, port: u16) { pub async fn ttt_listen_task(stack: embassy_net::Stack<'static>, team: apps::ttt::Team, port: u16) {
listen_task(stack, ttt::TttApp::new(team), port).await listen_task(stack, apps::ttt::TttApp::new(team), port).await
} }
#[embassy_executor::task(pool_size = 2)] #[embassy_executor::task(pool_size = 2)]
pub async fn index_listen_task(stack: embassy_net::Stack<'static>, port: u16) { pub async fn index_listen_task(stack: embassy_net::Stack<'static>, port: u16) {
listen_task(stack, IndexApp, port).await listen_task(stack, apps::index::IndexApp, port).await
} }
#[cfg(feature = "chat")]
#[embassy_executor::task(pool_size = 4)] #[embassy_executor::task(pool_size = 4)]
pub async fn chat_listen_task(stack: embassy_net::Stack<'static>, port: u16) { pub async fn chat_listen_task(stack: embassy_net::Stack<'static>, port: u16) {
listen_task(stack, chat::ChatApp::new(), port).await listen_task(stack, apps::chat::ChatApp::new(), port).await
} }
pub async fn listen_task(stack: embassy_net::Stack<'static>, mut app: impl App, port: u16) { pub async fn listen_task(stack: embassy_net::Stack<'static>, mut app: impl apps::App, port: u16) {
// loop { // loop {
// info!("team:{:?}", team); // info!("team:{:?}", team);
// Timer::after_millis(0).await; // Timer::after_millis(0).await;
@ -31,7 +38,7 @@ pub async fn listen_task(stack: embassy_net::Stack<'static>, mut app: impl App,
let mut rx_buffer = [0; 1024]; let mut rx_buffer = [0; 1024];
let mut tx_buffer = [0; 2048]; let mut tx_buffer = [0; 2048];
let mut buf = [0; 1024]; let mut buf = [0; 1024];
let mut res_head_buf = Vec::<u8, 128>::new(); let mut res_head_buf = Vec::<u8, 256>::new();
loop { loop {
Timer::after_secs(0).await; Timer::after_secs(0).await;
@ -75,8 +82,10 @@ pub async fn listen_task(stack: embassy_net::Stack<'static>, mut app: impl App,
} }
}; };
// let mut headers = headers.split(|x| *x == b'\n'); info!("\n{:?}\n", headers);
let (request_type, path) = match headers.lines().next() {
let mut hl = headers.lines();
let (request_type, path) = match hl.next() {
None => { None => {
warn!("Empty request"); warn!("Empty request");
break; break;
@ -106,40 +115,100 @@ pub async fn listen_task(stack: embassy_net::Stack<'static>, mut app: impl App,
) )
} }
}; };
let mut host = None;
let mut ws_handshake = false;
let mut ws_key = None;
for h in hl {
let Some((name, val)) = h.split_once(':') else {
continue;
};
let name = name.trim();
let val = val.trim();
match (name, val) {
("Host", _) => host = Some(val),
("Upgrade", "websocket") => ws_handshake = true,
("Sec-WebSocket-Key", _) => ws_key = Some(val),
_ => {}
}
}
let Some(host) = host else {
warn!("No host");
break;
};
info!( info!(
"Socket {}: {:?} request for {}", "Socket {}: {:?}{} request for {}{}",
app.socket_name(), app.socket_name(),
request_type, request_type,
path if ws_handshake { " websocket" } else { "" },
host,
path,
); );
Timer::after_secs(0).await; Timer::after_secs(0).await;
let (code, res_type, res_content): (HttpResCode, &str, &[u8]) = match path { res_head_buf.clear();
"/htmx.js" => ( let res_content: Result<&str, core::fmt::Error> = try {
HttpResCode::Ok, if ws_handshake {
"javascript", let Some(key) = ws_key else {
#[cfg(debug_assertions)] warn!("No ws key!");
include_bytes!("../static/htmx.js"), break;
#[cfg(not(debug_assertions))] };
include_bytes!("../static/htmx.min.js"), let accept = match compute_ws_accept(key).await {
), Ok(a) => a,
p => app.handle_request(p, request_type, content).await, Err(e) => {
warn!("compute ws accept error : {:?}", e);
break;
}
};
write!(
&mut res_head_buf,
"{}\r\n\
Upgrade: websocket\r\n\
Connection: Upgrade\r\n\
Sec-WebSocket-Accept: {}\r\n\r\n",
// Sec-WebSocket-Protocol: chat\r\n
Into::<&str>::into(HttpResCode::SwitchingProtocols),
accept
)?;
""
} else {
let (code, res_type, res_content): (HttpResCode, &str, &str) = match path {
"/htmx.js" => (
HttpResCode::Ok,
"javascript",
#[cfg(debug_assertions)]
include_str!("../static/htmx.js"),
#[cfg(not(debug_assertions))]
include_bytes!("../static/htmx.min.js"),
),
_ => app.handle_request(host, path, request_type, content).await,
};
write!(&mut res_head_buf, "{}", Into::<&str>::into(code))?;
if res_type.len() > 0 {
write!(
&mut res_head_buf,
"\r\n\
Content-Type: text/{}\r\n\
Content-Length: {}\r\n",
res_type,
res_content.len()
)?;
}
write!(&mut res_head_buf, "\r\n\r\n")?;
res_content
}
}; };
res_head_buf.clear(); let res_content = match res_content {
if let Err(e) = write!( Ok(rc) => rc,
&mut res_head_buf, Err(e) => {
"{}\r\n\ warn!("res buffer write error: {:?}", e);
Content-Type: text/{}\r\n\ break;
Content-Length: {}\r\n\r\n", }
Into::<&str>::into(code), };
res_type,
res_content.len() info!("\n{}\n", from_utf8(&res_head_buf).unwrap());
) {
warn!("res buffer write error: {:?}", e);
break;
}
match socket.write_all(&res_head_buf).await { match socket.write_all(&res_head_buf).await {
Ok(()) => {} Ok(()) => {}
@ -148,7 +217,7 @@ pub async fn listen_task(stack: embassy_net::Stack<'static>, mut app: impl App,
break; break;
} }
}; };
match socket.write_all(&res_content).await { match socket.write_all(res_content.as_bytes()).await {
Ok(()) => {} Ok(()) => {}
Err(e) => { Err(e) => {
warn!("write error: {:?}", e); warn!("write error: {:?}", e);
@ -175,6 +244,7 @@ impl Into<&str> for HttpRequestType {
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub enum HttpResCode { pub enum HttpResCode {
SwitchingProtocols,
Ok, Ok,
NoContent, NoContent,
BadRequest, BadRequest,
@ -184,6 +254,7 @@ pub enum HttpResCode {
impl Into<&str> for HttpResCode { impl Into<&str> for HttpResCode {
fn into(self) -> &'static str { fn into(self) -> &'static str {
match self { match self {
HttpResCode::SwitchingProtocols => "HTTP/1.1 101 Switching Protocols",
HttpResCode::Ok => "HTTP/1.1 200 OK", HttpResCode::Ok => "HTTP/1.1 200 OK",
HttpResCode::NoContent => "HTTP/1.1 204 NO CONTENT", HttpResCode::NoContent => "HTTP/1.1 204 NO CONTENT",
HttpResCode::BadRequest => "HTTP/1.1 400 BAD REQUEST", HttpResCode::BadRequest => "HTTP/1.1 400 BAD REQUEST",
@ -192,3 +263,14 @@ impl Into<&str> for HttpResCode {
} }
} }
} }
async fn compute_ws_accept(key: &str) -> Result<String<28>, EncodeSliceError> {
let mut res = Vec::<u8, 28>::new();
res.extend_from_slice(&[0; 28]).unwrap();
let mut hasher = Sha1::new();
hasher.update(key.as_bytes());
hasher.update(b"258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
let hash = hasher.finalize();
BASE64_STANDARD.encode_slice(hash, &mut res)?;
Ok(String::from_utf8(res).unwrap())
}

6
src/socket/ws.rs Normal file
View File

@ -0,0 +1,6 @@
// pub struct Ws {
// }
// impl Ws {
// pub fn handshake
// }