init ws connection + put ttt and chat behind features
This commit is contained in:
parent
6c57c3aaaf
commit
b50300fbbb
@ -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
19
Cargo.lock
generated
@ -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"
|
||||||
|
10
Cargo.toml
10
Cargo.toml
@ -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 }
|
||||||
|
@ -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, "", "")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
|
@ -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, "", ""),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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, "", "")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
24
src/main.rs
24
src/main.rs
@ -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")]
|
||||||
|
154
src/socket.rs
154
src/socket.rs
@ -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
6
src/socket/ws.rs
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
// pub struct Ws {
|
||||||
|
|
||||||
|
// }
|
||||||
|
// impl Ws {
|
||||||
|
// pub fn handshake
|
||||||
|
// }
|
Loading…
Reference in New Issue
Block a user