blob: 6ef1df871e35341605c737599d196848399837ac [file] [log] [blame]
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use std::sync::{Arc, Mutex};
use std::{collections::HashMap, io::Cursor, net::TcpStream};
use http::Request;
use log::{error, info, warn};
use netsim_proto::common::ChipKind;
use tungstenite::{protocol::Role, Message, WebSocket};
use crate::devices::chip;
use crate::devices::devices_handler::{add_chip, remove_chip};
use crate::echip;
use crate::echip::packet::{register_transport, unregister_transport, Response};
use crate::http_server::server_response::ResponseWritable;
use super::h4;
// This feature is enabled only for CMake builds
#[cfg(feature = "local_ssl")]
use crate::openssl;
/// Generate Sec-Websocket-Accept value from given Sec-Websocket-Key value
fn generate_websocket_accept(websocket_key: String) -> String {
let concat = websocket_key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
let hashed = openssl::sha::sha1(concat.as_bytes());
data_encoding::BASE64.encode(&hashed)
}
/// Handler for websocket server connection
pub fn handle_websocket(request: &Request<Vec<u8>>, param: &str, writer: ResponseWritable) {
if param != "bt" {
writer.put_error(404, "netsim websocket currently supports bt, uri=/v1/websocket/bt");
}
let websocket_accept = match request.headers().get("Sec-Websocket-Key") {
Some(key) => match key.to_str() {
Ok(key_str) => generate_websocket_accept(key_str.to_string()),
Err(_) => {
writer.put_error(
404,
"The HeaderValue of Sec-Websocket-Key cannot be converted to str",
);
return;
}
},
None => {
writer.put_error(404, "Missing Sec-Websocket-Key in header");
return;
}
};
writer.put_ok_switch_protocol(
"websocket",
vec![("Sec-WebSocket-Accept".to_string(), websocket_accept)],
)
}
struct WebSocketTransport {
websocket_writer: Arc<Mutex<WebSocket<TcpStream>>>,
}
impl Response for WebSocketTransport {
fn response(&mut self, packet: Vec<u8>, packet_type: u8) {
let mut buffer = Vec::new();
buffer.push(packet_type);
buffer.extend(packet);
if let Err(err) = self
.websocket_writer
.lock()
.expect("Failed to acquire lock on WebSocket")
.write_message(Message::Binary(buffer))
{
error!("{err}");
};
}
}
/// Run websocket transport for packet flow in netsim
pub fn run_websocket_transport(stream: TcpStream, queries: HashMap<&str, &str>) {
let chip_create_params = chip::CreateParams {
kind: ChipKind::BLUETOOTH,
address: queries.get("address").unwrap_or(&"").to_string(),
name: Some(format!("websocket-{}", stream.peer_addr().unwrap())),
manufacturer: "Google".to_string(),
product_name: "Google".to_string(),
bt_properties: None,
};
#[cfg(not(test))]
let echip_create_params = echip::CreateParam::Bluetooth(echip::bluetooth::CreateParams {
address: chip_create_params.address.clone(),
bt_properties: None,
});
#[cfg(test)]
let echip_create_params =
echip::CreateParam::Mock(echip::mocked::CreateParams { chip_kind: ChipKind::BLUETOOTH });
// Add Chip
let result = match add_chip(
&stream.peer_addr().unwrap().port().to_string(),
queries
.get("name")
.unwrap_or(&format!("websocket-device-{}", stream.peer_addr().unwrap()).as_str()),
&chip_create_params,
&echip_create_params,
) {
Ok(chip_result) => chip_result,
Err(err) => {
warn!("{err}");
return;
}
};
// Create websocket_writer to handle packet responses, write pong or close messages
let websocket_writer = Arc::new(Mutex::new(WebSocket::from_raw_socket(
stream.try_clone().unwrap(),
Role::Server,
None,
)));
// Websocket reader
let mut websocket_reader = WebSocket::from_raw_socket(stream, Role::Server, None);
// Sending cloned websocket into packet dispatcher
register_transport(
result.chip_id,
Box::new(WebSocketTransport { websocket_writer: websocket_writer.clone() }),
);
// Running Websocket server
loop {
let packet_msg =
match websocket_reader.read_message().map_err(|_| "Failed to read Websocket message") {
Ok(message) => message,
Err(err) => {
error!("{err}");
break;
}
};
if packet_msg.is_binary() {
let mut cursor = Cursor::new(packet_msg.into_data());
match h4::read_h4_packet(&mut cursor) {
Ok(mut packet) => {
echip::handle_request(result.chip_id, &mut packet.payload, packet.h4_type);
}
Err(error) => {
error!(
"netsimd: end websocket reader {}: {:?}",
websocket_reader.get_ref().peer_addr().unwrap(),
error
);
break;
}
}
} else if packet_msg.is_ping() {
if let Err(err) = websocket_writer
.lock()
.expect("Failed to acquire lock on WebSocket")
.write_message(Message::Pong(packet_msg.into_data()))
{
error!("{err}");
}
} else if packet_msg.is_close() {
if let Message::Close(close_frame) = packet_msg {
if let Err(err) = websocket_writer
.lock()
.expect("Failed to acquire lock on WebSocket")
.close(close_frame)
.map_err(|_| "Failed to close Websocket")
{
error!("{err}");
}
}
break;
}
}
// unregister before remove_chip because facade may re-use facade_id
// on an intertwining create_chip and the unregister here might remove
// the recently added chip creating a disconnected transport.
unregister_transport(result.chip_id);
if let Err(err) = remove_chip(result.device_id, result.chip_id) {
warn!("{err}");
};
info!("Removed chip: device_id: {}, chip_id: {}", result.device_id, result.chip_id);
}