diff --git a/Cargo.lock b/Cargo.lock index fe3fa5f..4a92665 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1502,7 +1502,7 @@ dependencies = [ "log", "presser", "thiserror 1.0.69", - "windows 0.58.0", + "windows 0.54.0", ] [[package]] @@ -3133,7 +3133,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.9.4", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -3731,7 +3731,7 @@ dependencies = [ "getrandom 0.3.3", "once_cell", "rustix 1.0.7", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] diff --git a/client/Cargo.toml b/client/Cargo.toml index 229fb8e..51fad27 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -10,5 +10,5 @@ serde_json = { workspace = true } chrono = { workspace = true } tokio = { workspace = true } s2n-quic = { workspace = true } -iced = { features = ["tokio"], git = "https://github.com/iced-rs/iced", rev = "d39022432c778a8cda455f40b9c12245db86ce45" } cpal = "0.15.3" +iced = { features = ["tokio"], git = "https://github.com/iced-rs/iced", rev = "d39022432c778a8cda455f40b9c12245db86ce45" } diff --git a/client/certificates/cert.pem b/client/certificates/cert.pem new file mode 100644 index 0000000..97a0380 --- /dev/null +++ b/client/certificates/cert.pem @@ -0,0 +1,34 @@ +-----BEGIN CERTIFICATE----- +MIIF1zCCA7+gAwIBAgIUebuL+Py/iYfh5YJh8MeAq01P/7MwDQYJKoZIhvcNAQEL +BQAwezELMAkGA1UEBhMCWFgxEjAQBgNVBAgMCVN0YXRlTmFtZTERMA8GA1UEBwwI +Q2l0eU5hbWUxFDASBgNVBAoMC0NvbXBhbnlOYW1lMRswGQYDVQQLDBJDb21wYW55 +U2VjdGlvbk5hbWUxEjAQBgNVBAMMCWxvY2FsaG9zdDAeFw0yNTAzMDEwMDAyMDha +Fw0zNTAyMjcwMDAyMDhaMHsxCzAJBgNVBAYTAlhYMRIwEAYDVQQIDAlTdGF0ZU5h +bWUxETAPBgNVBAcMCENpdHlOYW1lMRQwEgYDVQQKDAtDb21wYW55TmFtZTEbMBkG +A1UECwwSQ29tcGFueVNlY3Rpb25OYW1lMRIwEAYDVQQDDAlsb2NhbGhvc3QwggIi +MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDuCGUWsnP5KNBLNcPN3npHpt6y +VKX37w3Qd190oyxOBLzMLDBb3QZ9JqmFAghhhupYV/e9xRWCHUbNifHZxfkf2uSR +1bdfi6KcPU7uHqF6+zmPp6SQ1tYN+iCqEeprlzw3dnnSvvWbe3DSWmT8oXJwXZtG +ExY0QNRky23QBpcAUcTAO/JMGRvimxqNPHRUHxQibLUefZjoXQg5CGoREhubPkGy +Y1qPSTPR6khcSitQ006TVnpEo4YjDDwhP+oGAMBP0eAn6tfLWDGHlOfRxqW8a3EX +FL2J0ZwthJlP8lJnrYur9f2GELruhgD6vebIZQDF6/0yoZ07hc3wA5V+FxwgzcyK +MOvf7zAQQwL5Cj1VnN6t4mbhjoJOat3mgLFE87PGr6zGlqeovFOE2k5IuDCzd6VN +7UK/F36zxBVfrGVbE9FqdjdVU2OR2tBY3+De2DvBnHI6PXH1rC51pJytMjhACGNu +txsOaQiU3G2RzUqaxI7NqLyoL6mAfuzDvtDz9WKbf6rXFncRla8fbTmZ3C6hZh0l +IIb9wk6DWALL4n1l2wF1Lpe+gWgY1QVq8alZX9qvCVwHQr24URXv23HQgAPaTpv4 +c/b7eCymHLYp9CHGtgXKwlFj1Msju26OMVZ13qugOR2eF/OmsaonLVt10jX5y+VW +FUS9QjynjRqKw0yPBwIDAQABo1MwUTAdBgNVHQ4EFgQUcPQsBRhlo9zFSSFw1Mgz +COo8Ll4wHwYDVR0jBBgwFoAUcPQsBRhlo9zFSSFw1MgzCOo8Ll4wDwYDVR0TAQH/ +BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAY3724fnTOLR8eMIP06+lF9p18DD+ +Or10CgaGyamCofkrA84bbN+gGdVBWlk3V2c1E9C4yna2iqQUwf8JJIzTdEZbmOU1 +vwLu7YCGMgISOwUEEwXzdLOsQg2AEmfpI2nru5pUZFrvrBbDwfkyWckv5ZjNiHlB +nTmgatcGHTY2BKzQJzLHuo8HimFrfMXDMatb8K0OdujSYFuQ6Hk4L9NK0nx0art4 +3t5IIXg0R34mpghz+lm/FEJdKUJYsW/Czb48ro2pb1DL673PmfJ4qQC31lINiaRO +AWrfjWOsELGEFzHbFnMukUn5C7HMNnu3XZ8X/d0zxELUkKDKGXC42mWRDjWkklB/ +UhLHgy9xBmv8cQwZ+6IHppAT0wb+yYPP+xfkXeRQTmz7SDVIInm8mZxHDQbGcFsw +caXgF1U4KiMKXglq/gU7Y7zMJzLxxCiSgdngY2baP7ivO7Oh3kjvO9NmLIlk2fHE +qztnQS3CX3hIE5MKN2JKeY20HMALz73XpfzPkACt6/9iIUeHKoHJrL9pP+2mI18l +KAJUDgBgyxtMg8UiFihpeBrzqiv+JxP9v3LPKaJhITNOS7fQUWBH0YM2Y2uWm+qh +GQfCL1GS/QZINf5bkDgcM6R2sACXL+ysCY9b+xl1oWnIY21TbmbughxLNsb0SVEJ +YMPR5Ge743qMQLg= +-----END CERTIFICATE----- diff --git a/client/src/gui.rs b/client/src/gui.rs index aa5e987..2d90eaf 100644 --- a/client/src/gui.rs +++ b/client/src/gui.rs @@ -1,42 +1,172 @@ +use std::sync::{Arc, RwLock}; + use iced::{ Alignment::Center, Element, Task, Theme, - widget::{button, column, text}, + widget::{button, column, row}, +}; +use protocol::BUFFER_LENGTH; +use tokio::sync::{ + broadcast::{self}, + oneshot, }; -use crate::{ClientConfig, stream::connect}; - -#[derive(Debug, Clone)] -pub enum Message { - JoinRoom, - LeaveRoom, - MuteMicrophone, - UnmuteMicrophone, -} +use crate::{ClientConfig, stream::connect, voice::record}; #[derive(Debug, Default)] -pub struct Data { - client_config: Option, +struct Signal { + microphone: Option>, + // speaker: Option>, + connection: Option>, } -impl Data { - pub fn update(&mut self, message: Message) -> Task { - match message { - Message::JoinRoom => Task::none(), - Message::LeaveRoom => Task::none(), - Message::MuteMicrophone => Task::none(), - Message::UnmuteMicrophone => Task::none(), +#[derive(Debug)] +struct Channel { + microphone: broadcast::Sender, + // speaker: (broadcast::Sender, broadcast::Receiver), +} + +impl Channel { + fn new() -> Self { + Self { + microphone: broadcast::channel(BUFFER_LENGTH).0, + // speaker: broadcast::channel(BUFFER_LENGTH), } } +} - pub fn view(&self) -> Element<'_, Message> { - column![button("Join Room"), text("Hello").size(50)] - .padding(10) - .align_x(Center) - .into() +#[derive(Debug, Default, Clone, Copy)] +struct GUIStatus { + room: State, + microphone: State, +} + +#[derive(Debug, Clone, Copy)] +pub enum State { + Active, + Passive, + Loading, +} +impl Default for State { + fn default() -> Self { + State::Passive } +} +#[derive(Debug, Clone, Copy)] +pub enum Message { + State, + JoinRoom, + LeaveRoom, + UnmuteMicrophone, + MuteMicrophone, +} + +#[derive(Debug)] +pub struct App { + client_config: Arc, + gui_status: Arc>, + channel: Channel, + signal: Signal, +} + +impl App { pub fn theme(&self) -> Theme { Theme::Dark } + + pub fn new() -> Self { + App { + client_config: ClientConfig::new().into(), + gui_status: RwLock::new(GUIStatus::default()).into(), + channel: Channel::new(), + signal: Signal::default(), + } + } + pub fn view(&self) -> Element<'_, Message> { + let join_room_button = match self.gui_status.read().unwrap().room { + State::Active => button("Leave Room").on_press(Message::LeaveRoom), + State::Passive => button("Join Room").on_press(Message::JoinRoom), + State::Loading => button("Loading"), + }; + + let microphone_button = match self.gui_status.read().unwrap().microphone { + State::Active => button("Mute").on_press(Message::MuteMicrophone), + State::Passive => button("Unmute").on_press(Message::UnmuteMicrophone), + State::Loading => button("Loading"), + }; + column![ + row![join_room_button, microphone_button] + .spacing(20) + .align_y(Center) + ] + .align_x(Center) + .into() + } + pub fn update(&mut self, message: Message) -> Task { + match message { + Message::State => Task::none(), + Message::JoinRoom => { + self.gui_status.write().unwrap().room = State::Loading; + let client_config = self.client_config.clone(); + let gui_status = self.gui_status.clone(); + let microphone_receiver = self.channel.microphone.subscribe(); + + let connection_signal = oneshot::channel(); + self.signal.connection = Some(connection_signal.0); + Task::perform( + connect(connection_signal.1, microphone_receiver, client_config), + move |result| match result { + Ok(_) => gui_status.write().unwrap().room = State::Active, + Err(err_val) => { + eprintln!("Error: Join Room | {}", err_val); + gui_status.write().unwrap().room = State::Passive; + } + }, + ) + .map(|_| Message::State) + } + Message::LeaveRoom => { + self.gui_status.write().unwrap().room = State::Loading; + if let Some(connection_signal) = &self.signal.connection { + if !connection_signal.is_closed() { + self.signal + .connection + .take() + .expect("Never") + .send(true) + .unwrap(); + self.signal.connection = None; + self.gui_status.write().unwrap().room = State::Passive; + } + } + Task::none() + } + Message::UnmuteMicrophone => { + self.gui_status.write().unwrap().microphone = State::Active; + let microphone_sender = self.channel.microphone.clone(); + let microphone_stop_signal = oneshot::channel(); + self.signal.microphone = Some(microphone_stop_signal.0); + Task::perform(record(microphone_sender, microphone_stop_signal.1), |_| { + Message::State + }) + } + Message::MuteMicrophone => { + self.gui_status.write().unwrap().microphone = State::Loading; + if let Some(microphone_signal) = &self.signal.microphone { + if !microphone_signal.is_closed() { + self.signal + .microphone + .take() + .expect("Never") + .send(true) + .unwrap(); + self.signal.microphone = None; + self.gui_status.write().unwrap().microphone = State::Passive; + } + } + Task::none() + } + } + } } diff --git a/client/src/lib.rs b/client/src/lib.rs index 9fd1d5a..fd0e0e7 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -3,6 +3,16 @@ pub mod stream; pub mod voice; #[derive(Debug)] -struct ClientConfig { +pub struct ClientConfig { + certificate_path: String, server_address: String, } + +impl ClientConfig { + fn new() -> Self { + Self { + certificate_path: "./client/certificates/cert.pem".to_string(), + server_address: "localhost:4546".to_string(), + } + } +} diff --git a/client/src/main.rs b/client/src/main.rs index 705d362..6b30738 100644 --- a/client/src/main.rs +++ b/client/src/main.rs @@ -1,10 +1,11 @@ -use client::gui::Data; +use client::gui::App; fn main() { println!("Hello, world!"); - - iced::application(Data::default, Data::update, Data::view) - .theme(Data::theme) + iced::application(App::new, App::update, App::view) + .title("Tahinli Communication") + .theme(App::theme) + .centered() .run() .unwrap() } diff --git a/client/src/stream.rs b/client/src/stream.rs index 4f3e5c4..877ca9b 100644 --- a/client/src/stream.rs +++ b/client/src/stream.rs @@ -1,65 +1,84 @@ -use std::{io, net::SocketAddr, path::Path}; +use std::{net::SocketAddr, path::Path, sync::Arc}; -use protocol::BUFFER_LENGTH; +use protocol::{BUFFER_LENGTH, Error}; use s2n_quic::{Client, client::Connect}; use tokio::{ io::AsyncReadExt, sync::{broadcast, oneshot}, }; -use crate::{ - ClientConfig, - voice::{play, record}, -}; +use crate::{ClientConfig, voice::play}; -pub async fn connect(client_config: &ClientConfig) { +pub async fn connect( + connection_signal: oneshot::Receiver, + mut microphone_receiver: broadcast::Receiver, + client_config: Arc, +) -> Result<(), Error> { let client = Client::builder() .with_io("0:0") - .unwrap() - .with_tls(Path::new("certificates/cert.pem")) - .unwrap() + .map_err(|err_val| Error::ConnectionSetup(err_val.to_string()))? + .with_tls(Path::new(&client_config.certificate_path)) + .map_err(|err_val| Error::ConnectionSetup(err_val.to_string()))? .start() - .unwrap(); + .map_err(|err_val| Error::ConnectionSetup(err_val.to_string()))?; println!("Client Address = {}", client.local_addr().unwrap()); - let connect = Connect::new(client_config.server_address.parse::().unwrap()) - .with_server_name("localhost"); + let connect = Connect::new( + client_config + .server_address + .parse::() + .map_err(|inner| Error::Connection(inner.to_string()))?, + ) + .with_server_name("localhost"); let mut connection = match client.connect(connect).await { Ok(connection) => connection, Err(err_val) => { eprintln!("Error: Client Connection | {}", err_val); - return; + return Err(Error::Connection(err_val.to_string())); } }; - connection.keep_alive(true).unwrap(); + connection + .keep_alive(true) + .map_err(|err_val| Error::ConnectionSetup(err_val.to_string()))?; - let stream = connection.open_bidirectional_stream().await.unwrap(); + let stream = connection + .open_bidirectional_stream() + .await + .map_err(|err_val| Error::ConnectionSetup(err_val.to_string()))?; let (mut receive_stream, mut send_stream) = stream.split(); - let (microphone_sender, mut microphone_receiver) = broadcast::channel::(BUFFER_LENGTH); let (speaker_sender, speaker_receiver) = broadcast::channel::(BUFFER_LENGTH); - let (microphone_stop_signal_sender, microphone_stop_signal_receiver) = - oneshot::channel::(); let (spearker_stop_signal_sender, speaker_stop_signal_receiver) = oneshot::channel::(); - tokio::spawn(play(speaker_receiver, speaker_stop_signal_receiver)); - tokio::spawn(record(microphone_sender, microphone_stop_signal_receiver)); - tokio::spawn(async move { + let play_task = tokio::spawn(play(speaker_receiver, speaker_stop_signal_receiver)); + let receive_task = tokio::spawn(async move { while let Ok(data) = receive_stream.read_f32_le().await { - speaker_sender.send(data).unwrap(); + speaker_sender + .send(data) + .map_err(|err_val| Error::Signal(err_val.to_string())) + .unwrap(); } }); - tokio::spawn(async move { + let send_task = tokio::spawn(async move { while let Ok(data) = microphone_receiver.recv().await { send_stream .send(data.to_le_bytes().to_vec().into()) .await + .map_err(|err_val| Error::Send(err_val.to_string())) .unwrap(); } }); - let mut read_buffer = String::default(); - io::stdin().read_line(&mut read_buffer).unwrap(); - microphone_stop_signal_sender.send(true).unwrap(); - spearker_stop_signal_sender.send(true).unwrap(); + if let Ok(_) = connection_signal.await { + println!("Connection Closing"); + } + spearker_stop_signal_sender + .send(true) + .map_err(|err_val| Error::Signal(err_val.to_string()))?; + + send_task.abort(); + receive_task.abort(); + play_task.abort(); + + Ok(()) } diff --git a/client/src/voice.rs b/client/src/voice.rs index 592585a..be4d7bc 100644 --- a/client/src/voice.rs +++ b/client/src/voice.rs @@ -3,7 +3,7 @@ use tokio::sync::{broadcast, oneshot}; pub async fn record( microphone_sender: broadcast::Sender, - stop_signal_receiver: oneshot::Receiver, + microphone_stop_signal: oneshot::Receiver, ) { let host = cpal::default_host(); let input_device = host.default_input_device().unwrap(); @@ -26,7 +26,7 @@ pub async fn record( println!("Recording Started"); tokio::task::block_in_place(|| { - let _ = stop_signal_receiver.blocking_recv(); + let _ = microphone_stop_signal.blocking_recv(); }); input_stream.pause().unwrap(); @@ -34,7 +34,7 @@ pub async fn record( pub async fn play( mut speaker_receiver: broadcast::Receiver, - stop_signal_receiver: oneshot::Receiver, + speaker_stop_signal: oneshot::Receiver, ) { let host = cpal::default_host(); let output_device = host.default_output_device().unwrap(); @@ -61,7 +61,7 @@ pub async fn play( println!("Playing Started"); tokio::task::block_in_place(|| { - let _ = stop_signal_receiver.blocking_recv(); + let _ = speaker_stop_signal.blocking_recv(); }); output_stream.pause().unwrap(); diff --git a/protocol/src/lib.rs b/protocol/src/lib.rs index 4b00c8f..4a6f03f 100644 --- a/protocol/src/lib.rs +++ b/protocol/src/lib.rs @@ -1,11 +1,45 @@ -use std::{collections::VecDeque, fs::File, io::Read}; +use std::{ + collections::{HashMap, VecDeque}, + fmt::Display, + fs::File, + io::Read, +}; + +use serde::{Deserialize, Serialize}; pub const BUFFER_LENGTH: usize = 1024; +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum Error { + ConnectionSetup(String), + Connection(String), + Send(String), + Receive(String), + Signal(String), +} + +impl std::error::Error for Error { + fn cause(&self) -> Option<&dyn std::error::Error> { + self.source() + } +} + +impl Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Error::ConnectionSetup(inner) => write!(f, "Connection Setup | {}", inner), + Error::Connection(inner) => write!(f, "Connection | {}", inner), + Error::Send(inner) => write!(f, "Send | {}", inner), + Error::Receive(inner) => write!(f, "Receive | {}", inner), + Error::Signal(inner) => write!(f, "Signal | {}", inner), + } + } +} + #[derive(Debug, Clone)] struct TOML { header: String, - fields: VecDeque, + fields: HashMap, } fn naive_toml_parser(file_location: &str) -> TOML { @@ -21,19 +55,14 @@ fn naive_toml_parser(file_location: &str) -> TOML { .replace(']', "") .trim_end() .to_string(); - - let fields = toml_ingredients - .iter() - .map(|ingredient| { - ingredient - .split_once('=') - .unwrap() - .1 - .replace('"', "") - .trim() - .to_string() + let mut fields = HashMap::new(); + let _ = toml_ingredients.iter().map(|ingredient| { + ingredient.split_once('=').map(|(key, value)| { + let key = key.replace('"', "").trim().to_string(); + let value = value.replace('"', "").trim().to_string(); + fields.insert(key, value); }) - .collect(); + }); TOML { header, fields } } diff --git a/server/certificates/cert.pem b/server/certificates/cert.pem new file mode 100644 index 0000000..97a0380 --- /dev/null +++ b/server/certificates/cert.pem @@ -0,0 +1,34 @@ +-----BEGIN CERTIFICATE----- +MIIF1zCCA7+gAwIBAgIUebuL+Py/iYfh5YJh8MeAq01P/7MwDQYJKoZIhvcNAQEL +BQAwezELMAkGA1UEBhMCWFgxEjAQBgNVBAgMCVN0YXRlTmFtZTERMA8GA1UEBwwI +Q2l0eU5hbWUxFDASBgNVBAoMC0NvbXBhbnlOYW1lMRswGQYDVQQLDBJDb21wYW55 +U2VjdGlvbk5hbWUxEjAQBgNVBAMMCWxvY2FsaG9zdDAeFw0yNTAzMDEwMDAyMDha +Fw0zNTAyMjcwMDAyMDhaMHsxCzAJBgNVBAYTAlhYMRIwEAYDVQQIDAlTdGF0ZU5h +bWUxETAPBgNVBAcMCENpdHlOYW1lMRQwEgYDVQQKDAtDb21wYW55TmFtZTEbMBkG +A1UECwwSQ29tcGFueVNlY3Rpb25OYW1lMRIwEAYDVQQDDAlsb2NhbGhvc3QwggIi +MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDuCGUWsnP5KNBLNcPN3npHpt6y +VKX37w3Qd190oyxOBLzMLDBb3QZ9JqmFAghhhupYV/e9xRWCHUbNifHZxfkf2uSR +1bdfi6KcPU7uHqF6+zmPp6SQ1tYN+iCqEeprlzw3dnnSvvWbe3DSWmT8oXJwXZtG +ExY0QNRky23QBpcAUcTAO/JMGRvimxqNPHRUHxQibLUefZjoXQg5CGoREhubPkGy +Y1qPSTPR6khcSitQ006TVnpEo4YjDDwhP+oGAMBP0eAn6tfLWDGHlOfRxqW8a3EX +FL2J0ZwthJlP8lJnrYur9f2GELruhgD6vebIZQDF6/0yoZ07hc3wA5V+FxwgzcyK +MOvf7zAQQwL5Cj1VnN6t4mbhjoJOat3mgLFE87PGr6zGlqeovFOE2k5IuDCzd6VN +7UK/F36zxBVfrGVbE9FqdjdVU2OR2tBY3+De2DvBnHI6PXH1rC51pJytMjhACGNu +txsOaQiU3G2RzUqaxI7NqLyoL6mAfuzDvtDz9WKbf6rXFncRla8fbTmZ3C6hZh0l +IIb9wk6DWALL4n1l2wF1Lpe+gWgY1QVq8alZX9qvCVwHQr24URXv23HQgAPaTpv4 +c/b7eCymHLYp9CHGtgXKwlFj1Msju26OMVZ13qugOR2eF/OmsaonLVt10jX5y+VW +FUS9QjynjRqKw0yPBwIDAQABo1MwUTAdBgNVHQ4EFgQUcPQsBRhlo9zFSSFw1Mgz +COo8Ll4wHwYDVR0jBBgwFoAUcPQsBRhlo9zFSSFw1MgzCOo8Ll4wDwYDVR0TAQH/ +BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAY3724fnTOLR8eMIP06+lF9p18DD+ +Or10CgaGyamCofkrA84bbN+gGdVBWlk3V2c1E9C4yna2iqQUwf8JJIzTdEZbmOU1 +vwLu7YCGMgISOwUEEwXzdLOsQg2AEmfpI2nru5pUZFrvrBbDwfkyWckv5ZjNiHlB +nTmgatcGHTY2BKzQJzLHuo8HimFrfMXDMatb8K0OdujSYFuQ6Hk4L9NK0nx0art4 +3t5IIXg0R34mpghz+lm/FEJdKUJYsW/Czb48ro2pb1DL673PmfJ4qQC31lINiaRO +AWrfjWOsELGEFzHbFnMukUn5C7HMNnu3XZ8X/d0zxELUkKDKGXC42mWRDjWkklB/ +UhLHgy9xBmv8cQwZ+6IHppAT0wb+yYPP+xfkXeRQTmz7SDVIInm8mZxHDQbGcFsw +caXgF1U4KiMKXglq/gU7Y7zMJzLxxCiSgdngY2baP7ivO7Oh3kjvO9NmLIlk2fHE +qztnQS3CX3hIE5MKN2JKeY20HMALz73XpfzPkACt6/9iIUeHKoHJrL9pP+2mI18l +KAJUDgBgyxtMg8UiFihpeBrzqiv+JxP9v3LPKaJhITNOS7fQUWBH0YM2Y2uWm+qh +GQfCL1GS/QZINf5bkDgcM6R2sACXL+ysCY9b+xl1oWnIY21TbmbughxLNsb0SVEJ +YMPR5Ge743qMQLg= +-----END CERTIFICATE----- diff --git a/server/certificates/key.pem b/server/certificates/key.pem new file mode 100644 index 0000000..7fe6389 --- /dev/null +++ b/server/certificates/key.pem @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +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 +OMQMuXIQAe6ADrQYo1TtBAwZwM7Jtf4= +-----END PRIVATE KEY-----