Compare commits

...

3 Commits

Author SHA1 Message Date
23d03920ae works! 2025-08-09 15:09:49 +02:00
1d7eaa028b working win game 2025-08-09 14:43:33 +02:00
5a9ac5c436 ttt backend api kinda works 2025-08-09 13:53:33 +02:00
10 changed files with 315 additions and 131 deletions

View File

@ -1,3 +1,6 @@
use heapless::Vec;
use pico_website::unwrap;
use crate::{ use crate::{
apps::Content, apps::Content,
socket::{HttpRequestType, HttpResCode}, socket::{HttpRequestType, HttpResCode},
@ -22,17 +25,50 @@ impl App for IndexApp {
"html", "html",
Some(include_str!("./index.html").into()), Some(include_str!("./index.html").into()),
), ),
"/ttt" => ( path => {
HttpResCode::Ok, let (path, args) = path.split_once('?').unwrap_or((path, ""));
"html", let mut team = None;
Some(include_str!("ttt.html").into()), for arg in args.split('&') {
), match arg.split_once('=') {
"/ttt.js" => ( Some(("team", "0")) => team = Some("0"),
HttpResCode::Ok, Some(("team", "1")) => team = Some("1"),
"javascript", _ => {}
Some(include_str!("ttt.js").into()), }
), }
_ => (HttpResCode::NotFound, "", None), if path == "/ttt" {
let Some(team) = team else {
return (HttpResCode::BadRequest, "", None);
};
let html = include_str!("ttt.html");
let mut content = Vec::new();
let r: Result<(), &str> = try {
let (html1, html2) = html.split_once("/ttt.js").ok_or("")?;
content.push(html1)?;
content.push("/ttt.js?team=")?;
content.push(team)?;
content.push(html2)?;
};
unwrap(r).await;
(HttpResCode::Ok, "html", Some(Content(content)))
} else if path == "/ttt.js" {
let Some(team) = team else {
return (HttpResCode::BadRequest, "", None);
};
let mut content = Vec::new();
let r: Result<(), &str> = try {
content.push("const team = ")?;
content.push(team)?;
content.push(";\n")?;
content.push(include_str!("ttt.js"))?;
};
unwrap(r).await;
(HttpResCode::Ok, "javascript", Some(Content(content)))
} else {
(HttpResCode::NotFound, "", None)
}
}
} }
} }
} }

View File

@ -27,18 +27,7 @@
<body> <body>
<h1>TicTacToe</h1> <h1>TicTacToe</h1>
<h3 id="team"></h3> <h3 id="team"></h3>
<!-- <div class="cell" style="background-color:"></div> --> <h3 id="winner"></h3>
<div id="grid"> <div id="grid"></div>
<div class="cell" id="cell0"></div>
<div class="cell" id="cell1"></div>
<div class="cell" id="cell2"></div>
<div class="cell" id="cell3"></div>
<div class="cell" id="cell4"></div>
<div class="cell" id="cell5"></div>
<div class="cell" id="cell6"></div>
<div class="cell" id="cell7"></div>
<div class="cell" id="cell8"></div>
<div class="cell" id="cell9"></div>
</div>
</body> </body>
</html> </html>

View File

@ -1,12 +1,30 @@
const team = 0; //const team = 0;
const teamName = "blue"; if (team != 0 && team != 1) {
const color = "dodgerblue"; throw "team is not 0 or 1! team=" + team;
const otherColor = "firebrick"; }
const teams = [
{
name: "blue",
color: "dodgerblue",
port: "8080",
},
{
name: "red",
color: "firebrick",
port: "8081",
},
];
document.getElementById("team").innerHTML = document.getElementById("team").innerHTML =
'Team : <span style="color:' + color + '">' + teamName + "</span>"; 'Team : <span style="color:' +
teams[team].color +
'">' +
teams[team].name +
"</span>";
const ws = new WebSocket("ws://192.254.0.2:8080/" + teamName); const ws = new WebSocket(
"ws://192.254.0.2:" + teams[team].port + "/" + teams[team].name,
);
ws.onmessage = (event) => { ws.onmessage = (event) => {
console.log(event.data); console.log(event.data);
@ -14,12 +32,7 @@ ws.onmessage = (event) => {
let msg = JSON.parse(event.data); let msg = JSON.parse(event.data);
let cells = []; let cells = [];
for (let i = 0; i < 9; i++) { for (let i = 0; i < 9; i++) {
let owner = null; let owner = msg.board[i];
if (((msg.board >> (17 - i)) & 1) == 1) {
owner = 0;
} else if (((msg.board >> (8 - i)) & 1) == 1) {
owner = 1;
}
let tagName; let tagName;
if (msg.turn == team && owner === null) { if (msg.turn == team && owner === null) {
@ -48,5 +61,19 @@ ws.onmessage = (event) => {
cells.push(cell); cells.push(cell);
} }
document.getElementById("grid").replaceChildren(...cells); document.getElementById("grid").replaceChildren(...cells);
if (msg.turn == null) {
if (msg.winner == null) {
document.getElementById("winner").innerHTML = "Draw!";
} else {
document.getElementById("winner").innerHTML =
'Winner : <span style="color:' +
teams[msg.winner].color +
'">' +
teams[msg.winner].name +
"</span>";
}
} else {
document.getElementById("winner").innerHTML = "";
}
} }
}; };

View File

@ -1,10 +1,11 @@
use core::fmt::Write; use core::str::from_utf8_unchecked;
use core::{ops::Not, sync::atomic::Ordering}; use core::{ops::Not, sync::atomic::Ordering};
use embassy_sync::blocking_mutex::raw::ThreadModeRawMutex;
use embassy_sync::mutex::Mutex;
use embassy_time::{Duration, Instant, Timer}; use embassy_time::{Duration, Instant, Timer};
use heapless::{String, Vec}; use heapless::{String, Vec};
use log::info; use log::{info, warn};
use pico_website::unwrap; use pico_website::{unwrap, unwrap_opt};
use portable_atomic::{AtomicBool, AtomicU32};
use serde::Serialize; use serde::Serialize;
use crate::apps::Content; use crate::apps::Content;
@ -13,61 +14,112 @@ use crate::socket::{HttpRequestType, HttpResCode};
use super::App; use super::App;
static TURN: AtomicBool = AtomicBool::new(false);
// bits [0; 8] : player zero board / bits [9; 17] : player one board / is_ended [18] / is_draw [19] / winner [20]: 0=blue 1=green / current_turn [21]: 0=blue 1=green // bits [0; 8] : player zero board / bits [9; 17] : player one board / is_ended [18] / is_draw [19] / winner [20]: 0=blue 1=green / current_turn [21]: 0=blue 1=green
static BOARD: AtomicU32 = AtomicU32::new(0b01000000_001000000);
#[derive(Debug, Serialize, Clone, PartialEq, Eq)]
struct Game {
board: [Option<Team>; 9],
turn: Option<Team>,
winner: Option<Team>,
}
impl Game {
const fn default() -> Self {
Game {
board: [None; 9],
turn: Some(Team::Zero),
winner: None,
}
}
fn check_end(&mut self) -> bool {
for [a, b, c] in [
[0, 1, 2],
[3, 4, 5],
[6, 7, 8],
[0, 3, 6],
[1, 4, 7],
[2, 5, 8],
[0, 4, 8],
[2, 4, 6],
] {
if let Some(t) = self.board[a] {
if self.board[b] == Some(t) && self.board[c] == Some(t) {
self.winner = Some(t);
self.turn = None;
return true;
}
}
}
if self.board.iter().all(|c| c.is_some()) {
self.winner = None;
self.turn = None;
return true;
}
false
}
}
static GAME: Mutex<ThreadModeRawMutex, Game> = Mutex::new(Game::default());
// {"board"=[null,null,null,null,null,null,null,null,null],"turn"=null,"winner":null}
pub struct TttApp { pub struct TttApp {
team: Team, team: Team,
last_board: u32, last_game: Game,
end: Option<(Instant, Option<Team>)>, /// Only one socket manages the end, this can be None even when it's the end
end: Option<Instant>,
json_buf: [u8; 128],
} }
impl TttApp { impl TttApp {
pub fn new(team: Team) -> Self { pub fn new(team: Team) -> Self {
Self { Self {
team, team,
last_board: 0, last_game: Game {
board: [None; 9],
turn: None,
winner: None,
},
end: None, end: None,
json_buf: [0; 128],
} }
} }
pub fn is_ended(&self, board: u32) -> (bool, Option<Team>) { // pub fn is_ended(&self, board: u32) -> (bool, Option<Team>) {
if let Some((_, t)) = self.end { // if let Some((_, t)) = self.end {
return (true, t); // return (true, t);
} // }
for (t, m) in [(Team::Zero, 0), (Team::One, 9)] { // for (t, m) in [(Team::Zero, 0), (Team::One, 9)] {
for w in [ // for w in [
0b111000000, // 0b111000000,
0b000111000, // 0b000111000,
0b000000111, // 0b000000111,
0b100100100, // 0b100100100,
0b010010010, // 0b010010010,
0b001001001, // 0b001001001,
0b100010001, // 0b100010001,
0b001010100, // 0b001010100,
] { // ] {
if board & (w << m) == (w << m) { // if board & (w << m) == (w << m) {
return (true, Some(t)); // return (true, Some(t));
} // }
} // }
} // }
if ((board | (board >> 9)) & 0b111111111) == 0b111111111 { // if ((board | (board >> 9)) & 0b111111111) == 0b111111111 {
return (true, None); // return (true, None);
} // }
(false, None) // (false, None)
} // }
pub fn update_end_state(&mut self, board: &mut u32) { // pub fn update_end_state(&mut self, board: &mut u32) {
if let Some((i, _)) = self.end { // if let Some((i, _)) = self.end {
if i + Duration::from_secs(7) < Instant::now() { // if i + Duration::from_secs(7) < Instant::now() {
self.end = None; // self.end = None;
BOARD.store(0, Ordering::Release); // // BOARD.store(0, Ordering::Release);
*board = 0; // *board = 0;
} // }
} else { // } else {
if let (true, t) = self.is_ended(*board) { // if let (true, t) = self.is_ended(*board) {
self.end = Some((Instant::now(), t)); // self.end = Some((Instant::now(), t));
} // }
} // }
} // }
} }
impl App for TttApp { impl App for TttApp {
@ -97,38 +149,73 @@ impl App for TttApp {
_path: &str, _path: &str,
mut ws: Ws<'a, BUF_SIZE, RES_HEAD_BUF_SIZE>, mut ws: Ws<'a, BUF_SIZE, RES_HEAD_BUF_SIZE>,
) { ) {
Timer::after_millis(500).await;
let r: Result<(), ()> = try { let r: Result<(), ()> = try {
loop { loop {
let board = BOARD.load(Ordering::Acquire); Timer::after_millis(1).await;
ws.send(WsMsg::Text( let Ok(mut game) = GAME.try_lock() else {
&serde_json_core::to_string::<_, 40>(&ServerMsg { info!("locked");
board, continue;
turn: Some(Team::Zero), };
winner: None, // match GAME.try_lock() ;
}) if self.last_game != *game {
.unwrap(), // let json = unwrap(serde_json_core::to_string::<Game, 128>(&game)).await;
)) let n = unwrap(serde_json_core::to_slice(&(*game), &mut self.json_buf)).await;
.await?; let json =
unsafe { from_utf8_unchecked(&unwrap_opt(self.json_buf.get(..n)).await) };
info!("{:?}", json);
ws.send(WsMsg::Text(json)).await?;
self.last_game = game.clone();
}
if ws.last_msg.elapsed() >= Duration::from_secs(5) {
ws.send(WsMsg::Ping(&[])).await?;
info!("ping");
}
if self.end.map(|e| e.elapsed()).unwrap_or_default() > Duration::from_secs(5) {
self.end = None;
*game = Game {
turn: Some(!game.winner.unwrap_or_default()),
..Game::default()
};
}
while let Some(r) = ws.rcv().await? { while let Some(r) = ws.rcv().await? {
info!("{:?}", r); info!("{:?}", r);
if let WsMsg::Bytes([c]) = r {
let c = *c as usize;
info!("c={}", c);
if c >= game.board.len() {
warn!("Cell played is too big!");
return;
}
if game.board[c].is_some() {
warn!("Cell is already taken!");
return;
}
if game.turn == Some(self.team) {
game.board[c] = Some(self.team);
game.turn = Some(!self.team);
if game.check_end() {
self.end = Some(Instant::now());
}
} else {
warn!("It's not your turn!");
return;
}
info!("{:#?}", game);
}
} }
Timer::after_secs(1).await; // Timer::after_secs(1).await;
} }
}; };
info!("{:?}", r); warn!("error: {:?}", r);
Timer::after_micros(100).await;
} }
} }
#[derive(Debug, Serialize)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
struct ServerMsg {
board: u32,
turn: Option<Team>,
winner: Option<Team>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Team { pub enum Team {
#[default]
Zero = 0, Zero = 0,
One = 1, One = 1,
} }

View File

@ -11,7 +11,7 @@ use embassy_net::{
use embassy_time::Timer; use embassy_time::Timer;
use heapless::Vec; use heapless::Vec;
use log::{info, warn}; use log::{info, warn};
use pico_website::unwrap; use pico_website::{unwrap, unwrap_opt};
#[embassy_executor::task(pool_size = 1)] #[embassy_executor::task(pool_size = 1)]
pub async fn dhcp_server(stack: Stack<'static>) { pub async fn dhcp_server(stack: Stack<'static>) {
@ -38,7 +38,8 @@ pub async fn dhcp_server(stack: Stack<'static>) {
loop { loop {
let (n, _) = unwrap(socket.recv_from(&mut buf).await).await; let (n, _) = unwrap(socket.recv_from(&mut buf).await).await;
let msg = unwrap(dhcpv4::Message::new(&buf[..n])).await; let msg = unwrap_opt(buf.get(..n)).await;
let msg = unwrap(dhcpv4::Message::new(&msg)).await;
let msg_type = unwrap(v4_options!(msg; MessageType required)).await; let msg_type = unwrap(v4_options!(msg; MessageType required)).await;
let mut rapid_commit = false; let mut rapid_commit = false;

View File

@ -5,7 +5,7 @@ use embassy_net::{
}; };
use embassy_time::Timer; use embassy_time::Timer;
use log::{info, warn}; use log::{info, warn};
use pico_website::unwrap; use pico_website::{unwrap, unwrap_opt};
#[embassy_executor::task(pool_size = 1)] #[embassy_executor::task(pool_size = 1)]
pub async fn dns_server(stack: Stack<'static>) { pub async fn dns_server(stack: Stack<'static>) {
@ -30,7 +30,8 @@ pub async fn dns_server(stack: Stack<'static>) {
Timer::after_secs(0).await; 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 = unwrap_opt(buf.get_mut(..n)).await;
let msg = match dnsparse::Message::parse(msg) {
Ok(msg) => msg, Ok(msg) => msg,
Err(e) => { Err(e) => {
warn!("Dns: Error while parsing DNS message : {:#?}", e); warn!("Dns: Error while parsing DNS message : {:#?}", e);

View File

@ -1,6 +1,6 @@
#![no_std] #![no_std]
use core::fmt::Debug; use core::{fmt::Debug, panic::PanicInfo};
use embassy_time::Timer; use embassy_time::Timer;
use log::info; use log::info;
@ -13,3 +13,20 @@ pub async fn unwrap<T, E: Debug>(res: Result<T, E>) -> T {
}, },
} }
} }
pub async fn unwrap_opt<T>(opt: Option<T>) -> T {
unwrap(opt.ok_or(())).await
}
pub async fn assert(condition: bool) {
if !condition {
let err: Result<(), &str> = Err("assert failed");
unwrap(err).await;
}
}
// Doesn't work
#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
info!("PANIC: {}", info);
loop {}
}

View File

@ -1,6 +1,12 @@
#![no_std] #![no_std]
#![no_main] #![no_main]
#![allow(async_fn_in_trait)] #![allow(async_fn_in_trait)]
#![deny(
// clippy::unwrap_used,
// clippy::expect_used,
clippy::panic,
clippy::indexing_slicing
)]
#![feature(impl_trait_in_assoc_type)] #![feature(impl_trait_in_assoc_type)]
#![feature(slice_split_once)] #![feature(slice_split_once)]
#![feature(try_blocks)] #![feature(try_blocks)]
@ -10,6 +16,8 @@
use core::net::Ipv4Addr; use core::net::Ipv4Addr;
use cyw43_pio::{DEFAULT_CLOCK_DIVIDER, PioSpi}; use cyw43_pio::{DEFAULT_CLOCK_DIVIDER, PioSpi};
use defmt::warn;
use defmt_rtt as _;
use embassy_executor::Spawner; use embassy_executor::Spawner;
use embassy_net::{Config, StackResources}; use embassy_net::{Config, StackResources};
use embassy_rp::bind_interrupts; use embassy_rp::bind_interrupts;
@ -23,7 +31,6 @@ use log::info;
use pico_website::unwrap; 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 _};
#[cfg(feature = "dhcp")] #[cfg(feature = "dhcp")]
mod dhcp; mod dhcp;
@ -175,6 +182,12 @@ async fn main(spawner: Spawner) {
.address .address
) )
} }
// embassy_time::Timer::after_secs(5).await;
// info!("test0");
// embassy_time::Timer::after_secs(3).await;
// panic!("test");
#[cfg(feature = "dhcp")] #[cfg(feature = "dhcp")]
unwrap(spawner.spawn(dhcp::dhcp_server(stack))).await; unwrap(spawner.spawn(dhcp::dhcp_server(stack))).await;

View File

@ -6,6 +6,7 @@ use embassy_time::{Duration, Timer};
use embedded_io_async::Write as _; use embedded_io_async::Write as _;
use heapless::{String, Vec}; use heapless::{String, Vec};
use log::{info, warn}; use log::{info, warn};
use pico_website::{unwrap, unwrap_opt};
use sha1::{Digest, Sha1}; use sha1::{Digest, Sha1};
use crate::apps::Content; use crate::apps::Content;
@ -71,7 +72,8 @@ pub async fn listen_task(stack: embassy_net::Stack<'static>, mut app: impl apps:
} }
}; };
head_buf.clear(); head_buf.clear();
let (headers, content) = match from_utf8(&buf[..n]) { let msg = unwrap_opt(buf.get(..n)).await;
let (headers, content) = match from_utf8(msg) {
Ok(b) => match b.split_once("\r\n\r\n") { Ok(b) => match b.split_once("\r\n\r\n") {
Some(t) => t, Some(t) => t,
None => (b, ""), None => (b, ""),
@ -82,7 +84,8 @@ pub async fn listen_task(stack: embassy_net::Stack<'static>, mut app: impl apps:
} }
}; };
info!("\n{:?}\n", headers); // info!("\n{:?}\n", headers);
// Timer::after_micros(100).await;
let mut hl = headers.lines(); let mut hl = headers.lines();
let (request_type, path) = match hl.next() { let (request_type, path) = match hl.next() {
@ -144,12 +147,13 @@ pub async fn listen_task(stack: embassy_net::Stack<'static>, mut app: impl apps:
host, host,
path, path,
); );
Timer::after_secs(0).await; Timer::after_micros(100).await;
head_buf.clear(); head_buf.clear();
let res_content: Result<Option<Content>, core::fmt::Error> = try { let res_content: Result<Option<Content>, core::fmt::Error> = try {
if ws_handshake { if ws_handshake {
if !app.accept_ws(path) { if !app.accept_ws(path) {
warn!("No ws there!");
write!( write!(
&mut head_buf, &mut head_buf,
"{}\r\n\r\n", "{}\r\n\r\n",
@ -206,7 +210,7 @@ pub async fn listen_task(stack: embassy_net::Stack<'static>, mut app: impl apps:
Content-Length: {}\r\n", Content-Length: {}\r\n",
res_type, res_type,
c.len() c.len()
)? )?;
} }
write!(&mut head_buf, "\r\n\r\n")?; write!(&mut head_buf, "\r\n\r\n")?;
res_content res_content
@ -221,7 +225,8 @@ pub async fn listen_task(stack: embassy_net::Stack<'static>, mut app: impl apps:
} }
}; };
info!("\n{}\n", from_utf8(&head_buf).unwrap()); info!("\n{}\n", unwrap(from_utf8(&head_buf)).await);
Timer::after_micros(1000).await;
let w: Result<(), embassy_net::tcp::Error> = try { let w: Result<(), embassy_net::tcp::Error> = try {
socket.write_all(&head_buf).await?; socket.write_all(&head_buf).await?;
@ -229,7 +234,6 @@ pub async fn listen_task(stack: embassy_net::Stack<'static>, mut app: impl apps:
for s in c.0.iter() { for s in c.0.iter() {
socket.write_all(s.as_bytes()).await?; socket.write_all(s.as_bytes()).await?;
} }
} else {
} }
}; };
@ -239,11 +243,13 @@ pub async fn listen_task(stack: embassy_net::Stack<'static>, mut app: impl apps:
}; };
if ws_handshake { if ws_handshake {
ws_path = Some(String::from_str(path).unwrap()); ws_path = Some(unwrap(String::from_str(path)).await);
break; break;
} }
} }
if let Some(path) = ws_path { if let Some(path) = ws_path {
info!("handle ws");
Timer::after_micros(200).await;
app.handle_ws( app.handle_ws(
&path, &path,
Ws::new(&mut socket, &mut buf, &mut head_buf, app.socket_name()), Ws::new(&mut socket, &mut buf, &mut head_buf, app.socket_name()),
@ -291,11 +297,12 @@ impl Into<&str> for HttpResCode {
async fn compute_ws_accept(key: &str) -> Result<String<28>, EncodeSliceError> { async fn compute_ws_accept(key: &str) -> Result<String<28>, EncodeSliceError> {
let mut res = Vec::<u8, 28>::new(); let mut res = Vec::<u8, 28>::new();
Timer::after_micros(10).await;
res.extend_from_slice(&[0; 28]).unwrap(); res.extend_from_slice(&[0; 28]).unwrap();
let mut hasher = Sha1::new(); let mut hasher = Sha1::new();
hasher.update(key.as_bytes()); hasher.update(key.as_bytes());
hasher.update(b"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"); hasher.update(b"258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
let hash = hasher.finalize(); let hash = hasher.finalize();
BASE64_STANDARD.encode_slice(hash, &mut res)?; BASE64_STANDARD.encode_slice(hash, &mut res)?;
Ok(String::from_utf8(res).unwrap()) Ok(unwrap(String::from_utf8(res)).await)
} }

View File

@ -5,6 +5,7 @@ use embassy_time::{Instant, Timer};
use embedded_io_async::{ErrorType, ReadReady, Write}; use embedded_io_async::{ErrorType, ReadReady, Write};
use heapless::Vec; use heapless::Vec;
use log::{info, warn}; use log::{info, warn};
use pico_website::{assert, unwrap, unwrap_opt};
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub enum WsMsg<'a> { pub enum WsMsg<'a> {
@ -38,7 +39,6 @@ impl WsMsg<'_> {
struct WsRx<'a, const BUF_SIZE: usize> { struct WsRx<'a, const BUF_SIZE: usize> {
socket: TcpReader<'a>, socket: TcpReader<'a>,
buf: &'a mut [u8; BUF_SIZE], buf: &'a mut [u8; BUF_SIZE],
last_msg: Instant,
msg_in_buf: Option<(usize, usize)>, // (start, length) msg_in_buf: Option<(usize, usize)>, // (start, length)
} }
struct WsTx<'a, const HEAD_BUF_SIZE: usize> { struct WsTx<'a, const HEAD_BUF_SIZE: usize> {
@ -48,15 +48,17 @@ struct WsTx<'a, const HEAD_BUF_SIZE: usize> {
impl<'a, const HEAD_BUF_SIZE: usize> WsTx<'a, HEAD_BUF_SIZE> { impl<'a, const HEAD_BUF_SIZE: usize> WsTx<'a, HEAD_BUF_SIZE> {
pub async fn send<'m>(&mut self, msg: WsMsg<'m>) -> Result<(), ()> { pub async fn send<'m>(&mut self, msg: WsMsg<'m>) -> Result<(), ()> {
self.head_buf.clear(); self.head_buf.clear();
self.head_buf.push(0b1000_0000 | msg.code()).unwrap(); unwrap(self.head_buf.push(0b1000_0000 | msg.code())).await;
if msg.len() < 126 { if msg.len() < 126 {
self.head_buf.push(msg.len() as u8).unwrap(); unwrap(self.head_buf.push(msg.len() as u8)).await;
} else { } else {
self.head_buf.push(0b0111_1110).unwrap(); unwrap(self.head_buf.push(0b0111_1110)).await;
self.head_buf unwrap(
.extend_from_slice(&(msg.len() as u16).to_le_bytes()) self.head_buf
.unwrap(); .extend_from_slice(&(msg.len() as u16).to_le_bytes()),
self.head_buf.extend_from_slice(msg.as_bytes()).unwrap(); )
.await;
// self.head_buf.extend_from_slice(msg.as_bytes()).unwrap();
} }
let w: Result<(), <TcpSocket<'_> as ErrorType>::Error> = try { let w: Result<(), <TcpSocket<'_> as ErrorType>::Error> = try {
self.socket.write_all(&self.head_buf).await?; self.socket.write_all(&self.head_buf).await?;
@ -72,6 +74,7 @@ impl<'a, const HEAD_BUF_SIZE: usize> WsTx<'a, HEAD_BUF_SIZE> {
pub struct Ws<'a, const BUF_SIZE: usize = 1024, const RES_HEAD_BUF_SIZE: usize = 256> { pub struct Ws<'a, const BUF_SIZE: usize = 1024, const RES_HEAD_BUF_SIZE: usize = 256> {
rx: WsRx<'a, BUF_SIZE>, rx: WsRx<'a, BUF_SIZE>,
tx: WsTx<'a, RES_HEAD_BUF_SIZE>, tx: WsTx<'a, RES_HEAD_BUF_SIZE>,
pub last_msg: Instant,
name: &'a str, name: &'a str,
} }
impl<'a, const BUF_SIZE: usize, const HEAD_BUF_SIZE: usize> Ws<'a, BUF_SIZE, HEAD_BUF_SIZE> { impl<'a, const BUF_SIZE: usize, const HEAD_BUF_SIZE: usize> Ws<'a, BUF_SIZE, HEAD_BUF_SIZE> {
@ -86,13 +89,13 @@ impl<'a, const BUF_SIZE: usize, const HEAD_BUF_SIZE: usize> Ws<'a, BUF_SIZE, HEA
rx: WsRx { rx: WsRx {
socket: rx, socket: rx,
buf, buf,
last_msg: Instant::MIN,
msg_in_buf: None, msg_in_buf: None,
}, },
tx: WsTx { tx: WsTx {
socket: tx, socket: tx,
head_buf, head_buf,
}, },
last_msg: Instant::MIN,
name, name,
} }
} }
@ -100,8 +103,9 @@ impl<'a, const BUF_SIZE: usize, const HEAD_BUF_SIZE: usize> Ws<'a, BUF_SIZE, HEA
pub async fn rcv(&mut self) -> Result<Option<WsMsg>, ()> { pub async fn rcv(&mut self) -> Result<Option<WsMsg>, ()> {
let n = match self.rx.msg_in_buf.take() { let n = match self.rx.msg_in_buf.take() {
Some(n) => { Some(n) => {
assert(n.0 + n.1 <= self.rx.buf.len()).await;
self.rx.buf.copy_within(n.0..n.0 + n.1, 0); self.rx.buf.copy_within(n.0..n.0 + n.1, 0);
if self.rx.socket.read_ready().unwrap() { if unwrap(self.rx.socket.read_ready()).await {
let n_rcv = match self.rx.socket.read(&mut self.rx.buf[n.1..]).await { let n_rcv = match self.rx.socket.read(&mut self.rx.buf[n.1..]).await {
Ok(0) => { Ok(0) => {
info!("read EOF"); info!("read EOF");
@ -119,7 +123,7 @@ impl<'a, const BUF_SIZE: usize, const HEAD_BUF_SIZE: usize> Ws<'a, BUF_SIZE, HEA
} }
} }
None => { None => {
if self.rx.socket.read_ready().unwrap() { if unwrap(self.rx.socket.read_ready()).await {
match self.rx.socket.read(self.rx.buf).await { match self.rx.socket.read(self.rx.buf).await {
Ok(0) => { Ok(0) => {
info!("read EOF"); info!("read EOF");
@ -177,7 +181,7 @@ impl<'a, const BUF_SIZE: usize, const HEAD_BUF_SIZE: usize> Ws<'a, BUF_SIZE, HEA
.iter_mut() .iter_mut()
.enumerate() .enumerate()
{ {
*x ^= mask_key[i & 0xff]; *x ^= unwrap_opt(mask_key.get(i & 0xff)).await;
} }
if n_after_length + 4 + (length as usize) < n { if n_after_length + 4 + (length as usize) < n {
self.rx.msg_in_buf = Some(( self.rx.msg_in_buf = Some((
@ -199,7 +203,7 @@ impl<'a, const BUF_SIZE: usize, const HEAD_BUF_SIZE: usize> Ws<'a, BUF_SIZE, HEA
} }
&self.rx.buf[n_after_length..n_after_length + length as usize] &self.rx.buf[n_after_length..n_after_length + length as usize]
}; };
self.rx.last_msg = Instant::now(); self.last_msg = Instant::now();
match self.rx.buf[0] & 0b0000_1111 { match self.rx.buf[0] & 0b0000_1111 {
// Text message // Text message
1 => { 1 => {
@ -222,6 +226,8 @@ impl<'a, const BUF_SIZE: usize, const HEAD_BUF_SIZE: usize> Ws<'a, BUF_SIZE, HEA
} }
} }
pub async fn send(&mut self, msg: WsMsg<'_>) -> Result<(), ()> { pub async fn send(&mut self, msg: WsMsg<'_>) -> Result<(), ()> {
self.tx.send(msg).await self.tx.send(msg).await?;
self.last_msg = Instant::now();
Ok(())
} }
} }