Integrate actix websocket with rabbitmq (lapin) in Rust - websocket

I've written a websocket server in Rust using actix. If anyone wants to check the full repo here https://github.com/fabracht/actix-websocket.
Now, I want to integrate rabbitmq into the project. For that, I found the lapin crate https://docs.rs/lapin/1.8.0/lapin/. But I'm having problems integrating it with the actix framework.
I would like to use my current implementation of the websocket to proxy the messages from rabbitmq back to the client.
This is the beginning of my attempt. Very early stages, so let me know if I'm going the wrong way, because now would be the time to change the approach.
First some context:
The websocket actor communicates with another actor that holds information about the sockets and the rooms.
pub struct WSConn {
pub id: Uuid,
room_id: Uuid,
hb: Instant,
lobby_address: Addr<Lobby>,
}
impl WSConn {
pub fn new(lobby: Addr<Lobby>, rabbit: Addr<MyRabbit>) -> Self {
Self {
id: Uuid::new_v4(),
room_id: Uuid::nil(),
hb: Instant::now(),
lobby_address: lobby,
}
}
fn hb(&self, context: &mut WebsocketContext<Self>) {
/// HEARTBEAT CODE GOES HERE ///
}
}
So, when the actor starts, I send a connect message to the Lobby, that handles all the logic for adding the connection to the Lobby struct.
impl Actor for WSConn {
type Context = WebsocketContext<Self>;
fn started(&mut self, ctx: &mut Self::Context) {
info!("Starting hearbeat");
self.hb(ctx);
let wsserver_address = ctx.address();
info!("A new client has connected with id {}", self.id);
self.lobby_address.send(Connect {
address: wsserver_address.recipient(),
room_id: self.room_id,
id: self.id
}).into_actor(self).then(|res, _, ctx| {
match res {
Ok(_) => (),
_ => ctx.stop()
}
fut::ready(())
}).wait(ctx);
}
fn stopping(&mut self, _ctx: &mut Self::Context) -> Running {
info!("Stopping actor");
self.lobby_address.do_send(Disconnect {
room_id: self.room_id,
id: self.id
});
Running::Stop
}
}
Here's what the lobby looks like:
type Socket = Recipient<WSServerMessage>;
pub struct Lobby {
pub sessions: DashMap<Uuid, Socket>,//self id to self
pub rooms: DashMap<Uuid, DashSet<Uuid>>,//room id to list of users id
}
The entire Lobby code is quite big, so I won't put it here. Let me know if you need to see that and I'll provide the code.
So, once the client connects, it gets assigned to the default room. When a client sends a message to the server, the message gets processed by the StreamHandler.
impl StreamHandler<Result<ws::Message, ws::ProtocolError>> for WSConn {
fn handle(&mut self, item: Result<Message, ProtocolError>, ctx: &mut Self::Context) {
match item.unwrap() {
ws::Message::Binary(bin) => ctx.binary(bin),
ws::Message::Ping(bin) => {
self.hb = Instant::now();
ctx.pong(&bin);
}
ws::Message::Pong(_) => self.hb = Instant::now(),
ws::Message::Close(reason) => {
ctx.close(reason);
ctx.stop();
}
ws::Message::Text(text) => {
let command = serde_json::from_str::<Command>(&text)
.expect(&format!("Can't parse message {}", &text));
info!("{:?}", command);
if command.command.starts_with("/") {
info!("This is a {} request", command.command);
match command.command.as_ref() {
"/join" => {
info!("Join Room {}", command.payload);
let uid = Uuid::from_str(command.payload.as_str().unwrap()).expect("Can't parse message {} to uuid");
self.lobby_address.send(Join {
current_room: self.room_id,
room_id: uid,
id: self.id
}).into_actor(self).then(|res, _, ctx| {
match res {
Ok(_) => (),
_ => ctx.stop()
}
fut::ready(())
}).wait(ctx);
self.room_id = uid;
},
_ => ()
}
} else {
info!("Text is {}", text);
self.lobby_address.do_send(ClientActorMessage {
id: self.id,
msg: command,
room_id: self.room_id,
});
}
}
_ => {
info!("Something weird happened. Closing");
self.lobby_address.do_send(Disconnect {
room_id: self.room_id,
id: self.id
});
ctx.stop();}
}
}
}
So, as you can see, if you send a message with the command /join and a payload with a valid uuidv4, you join the room. I'm only allowing the client to be a part of one room at a time. So, when you join one, you're automatically removed from the last one.
Ok, so now let's talk about the rabbitmq connection.
The way I thought about this was to use a connection pool to keep the rabbitmq connection and use that connection to create the channels.
So I started by defining the struct that will hold my connection pool.
use actix::{Actor, Context, Handler, StreamHandler};
use deadpool_lapin::{Config, Pool, Runtime};
use deadpool_lapin::lapin::Error;
use lapin::message::Delivery;
use crate::lapin_server::messages::CreateChannel;
pub struct MyRabbit {
pub pool: Pool,
}
impl MyRabbit {
pub fn new() -> Self {
let mut cfg = Config::default();
cfg.url = Some("amqps://ghqcmhat:KbhPAA309QRg7TjdgFEV14pQRheoh44P#codfish.rmq.cloudamqp.com/ghqcmhat".into());
let new_pool = cfg.create_pool(Some(Runtime::Tokio1)).expect("Can't create pool");
MyRabbit {
pool: new_pool
}
}
}
impl Actor for MyRabbit {
type Context = Context<Self>;
}
Having this as an actor allows me to start the actor as I start the websocket server. This is done in main.
#[actix_web::main]
async fn main() -> std::io::Result<()>{
std::env::set_var("RUST_LOG", "actix_web=info,info");
env_logger::init();
// Start Lobby actor and get his address
let websocket_lobby = Lobby::default().start();
let rabbit = MyRabbit::new().start();
let application_data = web::Data::new(Appdata::new());
info!("Starting server on 127.0.0.1:8080");
let server = HttpServer::new(move || {
App::new()
.wrap(Logger::default())
.route("/ws/",web::get().to(websocket_handler))
.app_data(Data::new(websocket_lobby.clone()))
.app_data(Data::new(rabbit.clone()))
.app_data(application_data.clone())
});
server.bind("127.0.0.1:8080")?.run().await
}
To accommodate for the new actor, I added a new parameter to my request handler.
pub async fn websocket_handler(request: HttpRequest, stream: web::Payload, srv: Data<Addr<Lobby>>, rab: Data<Addr<MyRabbit>>, data: Data<Appdata>) -> Result<HttpResponse, Error> {
let mut counter = data.counter.lock().unwrap();
counter.add_assign(1);
info!("This is request # {}", counter);
let ws = WSConn::new(srv.get_ref().clone(), rab.get_ref().clone());
let response = ws::start(ws, &request, stream);
debug!("Response: {:?}", &response);
response
}
Now, my WSConn struct looks like this.
pub struct WSConn {
pub id: Uuid,
room_id: Uuid,
hb: Instant,
lobby_address: Addr<Lobby>,
rabbit_address: Addr<MyRabbit>
}
I know that, if I want to consume from a topic in rabbitmq, I need the exchange name, the type, the routing key and the queue name.
So, I put these in a struct as well
pub struct Channel {
pub queue_name: String,
pub exchange_name: String,
pub exchange_type: ExchangeKind,
pub routing_key: String
}
impl Default for Channel {
fn default() -> Self {
Self {
queue_name: "".to_string(),
exchange_name: "".to_string(),
exchange_type: Default::default(),
routing_key: "".to_string()
}
}
}
But that's where I'm stuck. First, I'm not sure this deadpool_lapin is the right crate to use for this. I'm also not sure how to translate the example on lapin's page, which uses
async_global_executor::block_on
And spawns new threads using async_global_executor::spawn to consume messages.
So, again, what I want is to be able to proxy messages coming from the websocket to rabbitmq and vice versa.
So, if a client connects to the websocket and sends a message like:
{
command: "SUBSCRIBE"
payload: "topic_name"
}
The result should be that messages published on that topic will get sent to him.
Sending an UNSUBSCRIBE should undo that.
Any help here would be greatly appreciated.
Please let me know if more information is needed.
Thank you
Fabricio

Related

How can I speed up the process of parsing a 40Gb txt file and inserting a row, per line, into an sqlite db

Background
I'm currently trying to parse a .txt com zone file. It is structured like so
blahblah.com xx xx examplens.com
Currently my code is structured like so
extern crate regex;
use regex::Regex;
use rusqlite::{params, Connection, Result};
#[derive(Debug)]
struct Domain {
id: i32,
domain: String,
}
use std::io::stdin;
fn main() -> std::io::Result<()> {
let mut reader = my_reader::BufReader::open("data/com_practice.txt")?;
let mut buffer = String::new();
let conn: Connection = Connection::open("/Users/alex/Projects/domain_randomizer/data/domains.db").unwrap();
while let Some(line) = reader.read_line(&mut buffer) {
let regexed_i_think = rexeginton_the_domain_only(line?.trim());
println!("{:?}", regexed_i_think);
sqliting(regexed_i_think, &conn).unwrap();
}
let mut stmt = conn.prepare("SELECT id, domain FROM domains").unwrap();
let domain_iter = stmt.query_map(params![], |row| {
Ok(Domain {
id: row.get(0)?,
domain: row.get(1)?,
})
}).unwrap();
for domain in domain_iter {
println!("Found person {:?}", domain.unwrap());
}
Ok(())
}
pub fn sqliting(domain_name: &str, conn: &Connection) -> Result<()> {
let yeah = Domain {
id: 0,
domain: domain_name.to_string()
};
conn.execute(
"INSERT INTO domains (domain) VALUES (?1)",
params![yeah.domain],
)?;
Ok(())
}
mod my_reader {
use std::{
fs::File,
io::{self, prelude::*},
};
pub struct BufReader {
reader: io::BufReader<File>,
}
impl BufReader {
pub fn open(path: impl AsRef<std::path::Path>) -> io::Result<Self> {
let file = File::open(path)?;
let reader = io::BufReader::new(file);
Ok(Self { reader })
}
pub fn read_line<'buf>(
&mut self,
buffer: &'buf mut String,
) -> Option<io::Result<&'buf mut String>> {
buffer.clear();
self.reader
.read_line(buffer)
.map(|u| if u == 0 { None } else { Some(buffer) })
.transpose()
}
}
}
pub fn rexeginton_the_domain_only(full_line: &str) -> &str {
let regex_str = Regex::new(r"(?m).*?.com").unwrap();
let final_str = regex_str.captures(full_line).unwrap().get(0).unwrap().as_str();
return final_str;
}
Issue
So I am Parsing a single domain at a time, each time making an Insert. As I've gathered, Inserting would be far more efficient if I was making thousands of Inserts in a single transaction. However, I'm not quite sure what an efficient approach is to refactoring my parsing around this.
Question
How should I reorient my parsing process around my Insertion process? Also how can I actively gauge the speed and efficiency of the process in the first place so I can compare and contrast in an articulate manner?

Rust Actix Actor send message to actor

How to send a message to another actor?
pub struct MyWs {
}
impl Actor for MyWs {
type Context = ws::WebsocketContext<Self>;
}
impl StreamHandler<Result<ws::Message, ws::ProtocolError>> for MyWs {
fn handle(&mut self, msg: Result<ws::Message, ws::ProtocolError>, ctx: &mut Self::Context) {
match msg {
Ok(ws::Message::Ping(msg)) => ctx.pong(&msg),
Ok(ws::Message::Text(message)) => {
//considering that here he sent the message to self
ctx.text(message);
//how to do something like this
//find the actor by index (or uuid) and send text
//actors[0].text(message);
//
},
Ok(ws::Message::Binary(bin)) => ctx.binary(bin),
Ok(ws::Message::Close(reason)) => ctx.close(reason),
_ => (),
}
}
}
#[get("/ws")]
pub async fn websocket(req: HttpRequest, stream: web::Payload,) -> actix_web::Result<HttpResponse> {
let resp = ws::start(
MyWs {},
&req,
stream,
);
return resp;
}
Could I make a hashMap of actors?
pub struct MyWs { sessions: HashMap<Uuid, Socket> }
and later
self.sessions.text(message)
I'm new to rust and I don't see a way to save the socket (the context or actor) to find it and send it messages.
You might want to check the official example of a chat room app using actix web.
https://github.com/actix/examples/blob/743af0ff1a9be6fb1cc13e6583108463c89ded4d/websockets/chat/src/main.rs
There are three key points:
create another server actor and get the address of it.
let server = server::ChatServer::new(app_state.clone()).start();
set the server actor's address as application data of HttpServer.
HttpServer::new(move || {
App::new()
.data(app_state.clone())
// copy server actor's address into app
.data(server.clone())
.service(web::resource("/").route(web::get().to(|| {
HttpResponse::Found()
.header("LOCATION", "/static/websocket.html")
.finish()
})))
.route("/count/", web::get().to(get_count))
// websocket
.service(web::resource("/ws/").to(chat_route))
// static resources
.service(fs::Files::new("/static/", "static/"))
})
.bind("127.0.0.1:8080")?
.run()
.await
store the address while starting websocket actor in function chat_route. And then you could use the address to send messages whenever you want
/// Entry point for our websocket route
async fn chat_route(
req: HttpRequest,
stream: web::Payload,
srv: web::Data<Addr<server::ChatServer>>,
) -> Result<HttpResponse, Error> {
ws::start(
WsChatSession {
id: 0,
hb: Instant::now(),
room: "Main".to_owned(),
name: None,
addr: srv.get_ref().clone(),
},
&req,
stream,
)
}

Send messages to clients with multiple references to websockets

My question here is in the context of using actix-web with Rust.
Unfortunately I can't explain this without a somewhat hefty code example, so let me start with that.
struct MyWs {
game: Arc<RwLock<Game>>,
}
impl Actor for MyWs {
type Context = ws::WebsocketContext<Self>;
}
impl StreamHandler<Result<ws::Message, ws::ProtocolError>> for MyWs {
fn handle(&mut self, msg: Result<ws::Message, ws::ProtocolError>, ctx: &mut Self::Context) {
match msg {
Ok(ws::Message::Text(text)) => {
debug!("Echoing text with {:?}", text);
self.game.write().unwrap().some_method();
ctx.text(text)
},
_ => (),
}
}
}
struct Game {
websockets: Vec<Arc<RwLock<MyWs>>>,
}
impl Game {
pub fn new() -> GameWrapper {
GameWrapper {
websockets: vec![],
}
}
pub fn add_websocket(&mut self, my_ws: Arc<RwLock<MyWs>>) {
self.websockets.push(my_ws);
}
pub fn some_method(&mut self) {
// Do something to influence internal state.
self.push_state();
}
pub fn push_state(&self) {
for w in self.websockets {
// I'm not sure about this part, idk how to access the
// WebsocketContext with which I can send stuff back to the client.
let game_state = get_game_state_or_something();
w.write().unwrap().ctx.text(self.game_state);
}
}
}
struct GameWrapper {
pub game: Arc<RwLock<Game>>,
}
impl GameWrapper {
pub fn new(game: Arc<RwLock<Game>>) -> GameWrapper {
GameWrapper { game }
}
}
#[actix_rt::main]
async fn main() -> std::io::Result<()> {
let game = Arc::new(RwLock::new(Game::new()));
let game_wrapper = RwLock::new(GameWrapper::new(game.clone()));
let game_wrapper_data = web::Data::new(game_wrapper);
HttpServer::new(move || {
App::new()
.app_data(game_wrapper_data.clone())
.route("/play_game", web::get().to(play_game))
})
.bind(ip_port)?
.run()
.await
}
pub async fn play_game(
req: HttpRequest,
stream: web::Payload,
game_wrapper: web::Data<GameWrapper>,
) -> impl Responder {
let my_ws = MyWs { game: game_wrapper.game.clone() };
let my_ws = Arc::new(RwLock::new(my_ws));
let mut game = game_wrapper.game.write().unwrap();
game.add_websocket(my_ws);
let resp = ws::start(my_ws, &req, stream); // This is the problem.
let resp = match resp {
Ok(resp) => resp,
Err(e) => return HttpResponse::from_error(e),
};
debug!("Successfully upgraded to websocket");
resp
}
Let me explain what I'm trying to do first. When I client connects, I establish a websocket with them. I need a list of these websockets, so when something changes in Game, I can push an update to all clients.
I bind the play_game function as the handler for the play_game route. In this function, I upgrade the HTTP get request to a websocket. IBefore that, I make a copy of an Arc+RwLock of a Game and pass it into MyWs, the websocket struct. You can see in the handle function of the MyWs impl of StreamHandler that I modify the Game (with the some_method function). This is fine so far.
Things explode when I try to get multiple references to the websocket. You can see in play_game that I call add_websocket, giving Game a reference to it, so it can push updates back to all clients when something changes. For example, after calling some_method, we would call push_updates. The problem with this, is ws::start doesn't take in an Arc, it must take in an Actor that impls StreamHandler with a WebSocketContext.
So my main two issues are:
I need a way to keep multiple references to the websocket, so I can talk to the client from multiple locations (read: threads).
I need some way to even do this. I'm not sure in actix how to actually send messages back to the client outside of the context of my MyWs actor. The framework passes in the WebSocketContext to handle, but I don't know how to get my hands on this myself.
My ideas for fixing this:
In the handle (or started) function of MyWs, pass out a reference to Context into self.game. This doesn't work because I'm moving out a mutable ref.
Make my own ws::start that can take a reference. I haven't tried this yet because it seems like I'd end up rewriting a lot.
Somehow impl Actor and StreamHandler on an Arc, or my own struct with interior mutability / something that allows me to keep multiple references to it.
This doesn't really help me send messages back because I still don't know how to send messages back via the websocket outside of the context of the handle function.
Sorry for the length of this question. The tl;dr is, how do I get multiple references to a websocket in actix-web and send messages to the client with them?
Here are the relevant docs for each of the components I'm using:
https://docs.rs/actix-web-actors/2.0.0/actix_web_actors/ws/fn.start.html
https://docs.rs/actix-web-actors/2.0.0/actix_web_actors/ws/struct.WebsocketContext.html
https://actix.rs/docs/websockets/
Okay so the solution to my dilemma here was unsurprisingly to change the way I was trying to solve this problem. Instead of holding multiple references to the websockets, what I really need is references to each of the actors that hold the websocket. I figure this is how you're meant to do it, given Actix is an actor framework.
This means the code should look like this:
impl Game {
...
pub fn register_actor(&mut self, actor: Addr<MyWs>) {
self.actors.push(actor);
}
}
pub async fn play_game(
req: HttpRequest,
stream: web::Payload,
game_wrapper: web::Data<GameWrapper>,
) -> impl Responder {
let my_ws = MyWs { game: game_wrapper.game.clone() };
let my_ws = Arc::new(RwLock::new(my_ws));
let mut game = game_wrapper.game.write().unwrap();
let res = ws::start_with_addr(my_ws, &req, stream);
let (addr, resp) = match res {
Ok(res) => res,
Err(e) => return HttpResponse::from_error(e),
};
game_manager.register_actor(handle, addr);
debug!("Successfully upgraded to websocket");
resp
}
You can then send messages to the actor instead via the Addr<MyWs>.
I'm going to leave the question for a while in case others have ideas for how to do this whole thing better.

I want to keep a reference inside an HashMap but I'm not able to specify correctly the lifetime

I'm using ws-rs to build a chat app. I need to keep associations between a Sender and a Username but I'm having issues in referencing the Sender in my HashMap.
I'm 99.99% sure that Handler keeps the ownership of Sender.
I had solved this problem cloning every time the sender passing it to another thread, together with the username, via a mspc::channel but I wanna try to use smart pointers and reference.
Here is a Minimal, Reproducible Example:
use std::collections::HashMap;
use std::sync::Arc;
use std::thread;
trait Factory {
fn connection_made(&mut self, _: Sender) -> MHandler;
}
trait Handler {
fn on_open(&mut self) -> ();
}
struct MFactory<'a> {
connections: Arc<HashMap<String, &'a Sender>>,
}
struct MHandler<'a> {
sender: Sender,
connections: Arc<HashMap<String, &'a Sender>>,
}
struct Sender{}
fn main() {
let mut connections: Arc<HashMap<String, &Sender>> = Arc::new(HashMap::new());
// Server thread
let server = thread::Builder::new()
.name(format!("server"))
.spawn(|| {
let mFactory = MFactory {
connections: connections.clone(),
};
let mHandler = mFactory.connection_made(Sender{});
mHandler.on_open();
})
.unwrap();
}
impl Factory for MFactory<'_> {
fn connection_made(&mut self, s: Sender) -> MHandler {
MHandler {
sender: s,
connections: self.connections.clone(),
}
}
}
impl Handler for MHandler<'_> {
fn on_open(&mut self) -> () {
self.connections.insert(format!("Alan"), &self.sender);
}
}
Playground.
Ps: I'm aware that Arc doesn't guarantee mutual exclusion so I have to wrap my HasMap in a Mutex. I've decided to ignore it for the moment.
What you're trying to do is unsafe. You're keeping in a map that lives for the duration of your program references to a structure that is owned by another object inside a thread. So the map outlives the the objects it stores references to, which Rust prevents.
Following on my comment, this code compiles (I've removed the factory for clarity):
use std::collections::HashMap;
use std::sync::{Arc,Mutex};
use std::thread;
use std::ptr::NonNull;
struct MHandler {
sender: Sender,
}
struct Sender{}
struct Wrapper(NonNull<Sender>);
unsafe impl std::marker::Send for Wrapper { }
fn main() {
let connections: Arc<Mutex<HashMap<String, Wrapper>>> = Arc::new(Mutex::new(HashMap::new()));
// Server thread
let server = thread::Builder::new()
.name(format!("server"))
.spawn(move || {
let mut handler = MHandler {
sender: Sender{},
};
let w = Wrapper(NonNull::new(&mut handler.sender as *mut Sender).unwrap());
Arc::clone(&connections).lock().unwrap().insert(format!("Alan"), w);
})
.unwrap();
}
This is using raw pointers (https://doc.rust-lang.org/book/ch19-01-unsafe-rust.html#dereferencing-a-raw-pointer) and NonNull to be able to implement Send (see https://github.com/rust-lang/rust/issues/21709 and https://play.rust-lang.org/?gist=1ce2532a0eefc60695663c26faddebe1&version=stable)
Not sure this helps you.

RxBluetoothKit - implement read and write protocol and automatically disconnect

I'm implementing a BLE protocol between a central (iPhone) and peripheral (custom device). The protocol works as follows:
central connects to peripheral and sets up notification
peripheral sends data on notification characteristic
central processes data and sends response on separate characteristic
peripheral sends addtnl data on notification characteristic
central process data and disconnects.
I'm attempting to implement this in a clean way using RxBluetoothKit. It currently works, but I'd like to solve the following challenges:
What is the best way to cleanly disconnect in step 5. I'm hoping to not have to dispose the overall observable, but rather just have it 'complete'. I'm currently using 'takeUntil', but not sure if that's the best way.
Allow for the notification to cleanup gracefully prior to disconnect. With my current code, I receive an 'API MISUSE can only accept commands while in the connected state' because I believe the notification is cleaning up while the disconnect is occurring.
Thanks.
enum TestPeripheralService: String, ServiceIdentifier {
case main = "CED916FA-6692-4A12-87D5-6F2764762B23"
var uuid: CBUUID { return CBUUID(string: self.rawValue) }
}
enum TestPeripheralCharacteristic: String, CharacteristicIdentifier {
case writer = "CED927B4-6692-4A12-87D5-6F2764762B2A"
case reader = "CED9D5D8-6692-4A12-87D5-6F2764762B2A"
var uuid: CBUUID { return CBUUID(string: self.rawValue) }
var service: ServiceIdentifier { return TestPeripheralService.main }
}
fileprivate lazy var centralManager: CentralManager = {
RxBluetoothKitLog.setLogLevel(.verbose)
return CentralManager(queue: .main)
}()
func executeConnectionAndHandshake() {
let disconnectSubject = PublishSubject<Bool>.init()
var peripheral: Peripheral?
var packetNum = 0
_ = centralManager
.observeState()
.startWith(centralManager.state)
.filter { $0 == .poweredOn }
.flatMap { _ in self.centralManager.scanForPeripherals(withServices: [TestPeripheralService.main.uuid]) }
.flatMap { $0.peripheral.establishConnection().takeUntil(disconnectSubject) }
.do(onNext: { peripheral = $0 })
.flatMap { $0.discoverServices([TestPeripheralService.main.uuid])}
.flatMap { $0[0].discoverCharacteristics(nil)}
.flatMap { _ in
Observable<Bool>.create { event in
let disposables = CompositeDisposable()
let readSubject = PublishSubject<Data>.init()
_ = disposables.insert(peripheral!.observeValueUpdateAndSetNotification(for: TestPeripheralCharacteristic.reader)
.subscribe(onNext: {
packetNum += 1
let packet = $0.value!
if (packetNum <= 1) {
readSubject.onNext(packet)
} else {
event.onNext(true)
event.onCompleted()
}
}, onError: { event.onError($0) })
)
_ = disposables.insert(readSubject
.flatMapLatest { data -> Single<Characteristic> in
var writeData = Data(capacity: 300)
for _ in 0..<300 {
writeData.append(0xFF)
}
return peripheral!.writeValue(writeData, for: TestPeripheralCharacteristic.writer, type: .withResponse)
}
.subscribe(onError: { event.onError($0) })
)
return Disposables.create {
disposables.dispose()
}
}
.do(onCompleted: { disconnectSubject.onNext(true) })
}
.subscribe(onError: { print($0) },
onCompleted: { print("Connection and handshake completed") })
}

Resources