Last active
March 21, 2017 03:00
-
-
Save vmx/2490f3be3eba3bf72fa696f7b0938559 to your computer and use it in GitHub Desktop.
Current hack to get the B2G's telephony stack back to life
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[package] | |
name = "b2g-telephony" | |
version = "0.1.0" | |
authors = ["Volker Mische <volker.mische@gmail.com>"] | |
[dependencies] | |
serde = "0.9" | |
serde_derive = "0.9" | |
serde_json = "0.9" | |
ws = "*" | |
[dependencies.js] | |
git = "https://github.com/servo/rust-mozjs.git" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#[macro_use] extern crate serde_derive; | |
extern crate serde_json; | |
extern crate ws; | |
use std::cell::RefCell; | |
use std::thread; | |
use std::sync::mpsc; | |
use ws::listen; | |
#[macro_use] extern crate js; | |
use js::conversions::{FromJSValConvertible, ToJSValConvertible}; | |
use js::jsapi::AutoCheckCannotGC; | |
use js::jsapi::AutoAssertOnGC; | |
use js::jsapi::JS_AtomizeString; | |
use js::jsapi::JS_AtomizeAndPinString; | |
use js::jsapi::CallArgs; | |
use js::jsapi::CompartmentOptions; | |
use js::jsapi::HandleValueArray; | |
use js::jsapi::HandleObject; | |
use js::jsapi::NullHandleValue; | |
use js::jsapi::JSAutoCompartment; | |
use js::jsapi::JSContext; | |
use js::jsapi::JS_CallFunctionName; | |
use js::jsapi::JS_DefineFunction; | |
use js::jsapi::JS_EncodeStringToUTF8; | |
use js::jsapi::JS_GetArrayBufferViewData; | |
use js::jsapi::JS_GetArrayBufferViewType; | |
use js::jsapi::JS_GetTypedArrayByteLength; | |
use js::jsapi::JS_IsTypedArrayObject; | |
use js::jsapi::JS_NewGlobalObject; | |
use js::jsapi::JS_ParseJSON; | |
use js::jsapi::JS_ParseJSON1; | |
use js::jsapi::JS_ReportError; | |
use js::jsapi::JS_Stringify; | |
use js::jsapi::OnNewGlobalHookOption; | |
use js::jsapi::ToJSONMaybeSafely; | |
use js::jsapi::Type; | |
use js::jsapi::Value; | |
use js::jsval::ObjectOrNullValue; | |
use js::jsval::StringValue; | |
use js::jsval::UndefinedValue; | |
use js::rust::{Runtime, SIMPLE_GLOBAL_CLASS}; | |
use js::typedarray::{CreateWith, Int8Array, Uint8Array, Uint8ClampedArray}; | |
use std::ffi::{CStr, CString}; | |
use std::io::Read; | |
use std::io::Write; | |
use std::os::raw::c_void; | |
use std::os::unix::net::UnixStream; | |
use std::ptr; | |
use std::slice; | |
use std::str; | |
// This might be what i need: | |
// https://github.com/modrzew/hnfd/blob/7826be05cfe0c83aec43ceeb50d3362655b9d800/src/server.rs | |
// Each client has an ID that come from `RadioInterfaceLayer()` in | |
// `dom/system/gonk/RadioInterfaceLayer.js` and is an unsigned integer. There's a client | |
// for every radio interface. That client ID corresponds to the index position within | |
// this vector | |
thread_local!(static ril_clients: RefCell<Vec<Option<Client>>> = RefCell::new(Vec::new())); | |
/// The name to the RIL daemon. This is just the prefix if more than one modem is there. The | |
/// first one is just called `rild`, the next one is called `rild2` (and so on). | |
/// FirefoxOS used a proxy (located at /dev/socket/rilproxy) due to the priviledges needed, we | |
/// can directly connect to the RIL daemon. | |
//const RIL_SOCKET_NAME: &'static str = "/dev/socket/rild"; | |
const RIL_SOCKET_NAME: &'static str = "/home/vmx/src/rust/b2g/ril/socketserver/uds_socket"; | |
#[derive(Debug)] | |
struct RegisterMessage { | |
client_id: u8, | |
raw_message: String, | |
websocket_sender: ws::Sender, | |
} | |
// It's name "kind" instead of "type" as "type" is a reserved word | |
#[derive(Deserialize, Debug)] | |
enum MessageKind { | |
#[serde(rename = "registerClient")] | |
Register, | |
#[serde(rename = "setRadioEnabled")] | |
SetRadioEnabled, | |
} | |
/// The deserialized message received as JSON via the WebSocket | |
#[derive(Deserialize, Debug)] | |
struct WebsocketMessage { | |
// The client ID comes from the `RadioInterfaceLayer()` in | |
// `dom/system/gonk/RadioInterfaceLayer.js` and is an unsigned integer. There's a client | |
// for every radio interface. | |
// NOTE vmx 2017-02-12: I don't expect more than 256 radio interfaces on one device | |
#[serde(rename = "rilMessageClientId")] | |
client_id: Option<u8>, | |
// `clientId` is only given if it's a register message | |
#[serde(rename = "clientId")] | |
register_client_id: Option<u8>, | |
#[serde(rename = "rilMessageToken")] | |
token: u64, | |
#[serde(rename = "rilMessageType")] | |
kind: MessageKind, | |
} | |
#[derive(Debug)] | |
struct RilMessage { | |
client_id: u8, | |
data: Vec<u8>, | |
} | |
#[derive(Debug)] | |
enum Message { | |
// Registering a client is a special case. Nit only the JS RIL Worker needs the message, but | |
// we also need to store the WebSocket it is using, so that we have proper bi-directional | |
// communication with the right client | |
Register(RegisterMessage), | |
// The messages we are receiving from the WebSocket are just forwarded to the JS Worker | |
WebsocketMessage(String), | |
// Data from the RIL Socket | |
RilMessage(RilMessage), | |
} | |
#[derive(Debug)] | |
struct Client { | |
ril_socket: RilSocket, | |
websocket_sender: ws::Sender, | |
} | |
impl Client { | |
// TODO vmx 2017-03-02: Add a proper Error struct | |
fn send(&mut self, context: *mut JSContext, args: &CallArgs) -> Result<(), String> { | |
println!("Trying to send a message to the RIL Daemon"); | |
// NOTE vmx 2017-03-05: The original FirefoxOS code checks if the socket connection is | |
// still there and returns OK if it isn't as it is assumed that it is shutting down. | |
let value = args.get(1); | |
println!("value: {:?}", value); | |
let raw = if value.is_string() { | |
println!("value: is string {:?}", value); | |
// NOTE vmx 3017-03-05: This code is from a Servo example, perhaps there's an | |
// easier way. | |
unsafe { | |
let str = js::rust::ToString(context, value); | |
rooted!(in(context) let message_root = str); | |
let message = JS_EncodeStringToUTF8(context, message_root.handle()); | |
let message = CStr::from_ptr(message); | |
println!("{}", str::from_utf8(message.to_bytes()).unwrap()); | |
//let raw = str::from_utf8(message.to_bytes()).unwrap(); | |
//str::from_utf8(message.to_bytes()).unwrap(); | |
message.to_bytes() | |
} | |
} else if !value.is_primitive() { | |
println!("value: not a primitive {:?}", value); | |
let obj = value.to_object_or_null(); | |
unsafe { | |
if !JS_IsTypedArrayObject(obj) { | |
return Err("Object passed in wasn't a typed array".to_string()); | |
} | |
let view_type = unsafe{ JS_GetArrayBufferViewType(obj) }; | |
if view_type != Type::Int8 && | |
view_type != Type::Uint8 && | |
view_type != Type::Uint8Clamped { | |
return Err("Typed array data is not octets".to_string()); | |
} | |
let size = JS_GetTypedArrayByteLength(obj); | |
let mut is_shared = false; | |
let nogc = AutoCheckCannotGC{ | |
_base: AutoAssertOnGC{} | |
}; | |
let data = JS_GetArrayBufferViewData(obj, | |
&mut is_shared as *mut bool, | |
&nogc as *const _); | |
if is_shared { | |
return Err("Incorrect argument. Shared memory not supported".to_string()); | |
} | |
// NOTE vmx 2017-03-05: I'm not sure if casting to `*const u8` will always be | |
// the right thing. | |
slice::from_raw_parts(data as *const u8, size as usize) | |
} | |
} else { | |
return Err("Incorrect argument. Expecting a string or a typed array".to_string()); | |
}; | |
println!("raw: {:?}", raw); | |
self.ril_socket.send(raw); | |
Ok(()) | |
} | |
} | |
// `RilSocket` is just a light wrapper around the Unix Socket communicating with the RIL deamons | |
#[derive(Debug)] | |
struct RilSocket { | |
client_id: u8, | |
socket: UnixStream, | |
//message_sender: mpsc::Sender<Message>, | |
} | |
impl RilSocket { | |
fn new(client_id: u8, message_sender: mpsc::Sender<Message>) -> RilSocket { | |
// The RIL daemons are named `rild`, `rild2`, `rild3`... | |
let address = match client_id { | |
0 => RIL_SOCKET_NAME.to_string(), | |
_ => format!("{}{}", RIL_SOCKET_NAME, client_id), | |
}; | |
// TODO vmx 2017-03-05: Add proper error handling | |
let socket = UnixStream::connect(address).unwrap(); | |
let mut read_socket = socket.try_clone().unwrap(); | |
thread::spawn(move || { | |
let mut buf = [0u8; 1024]; | |
loop { | |
let num_bytes = read_socket.read(&mut buf).unwrap(); | |
println!("Read {} bytes of the socket: {:?}", num_bytes, &buf[..num_bytes]); | |
//XXX vmx 2017-03-13: GO ON HERE and send the data over a channel to the JS worker. Do the same as in Ril.cpp RilConsumer::Receive() | |
message_sender.send(Message::RilMessage(RilMessage{ | |
client_id: client_id, | |
data: (&buf[..num_bytes]).to_vec(), | |
})); | |
} | |
}); | |
RilSocket{ | |
client_id: client_id, | |
socket: socket, | |
} | |
} | |
fn send(&mut self, data: &[u8]) { | |
self.socket.write_all(data).unwrap(); | |
} | |
} | |
struct JsWorker { | |
// The receiver is needed to get messages from the WebSocket | |
message_receiver: mpsc::Receiver<Message>, | |
// The sender is needed to send messages from the RIL Socket to the JS Worker | |
message_sender: mpsc::Sender<Message>, | |
// NOTE vmx 2017-02-15: Perhaps I want to refactor this into a new struct that contains | |
// all the JS related stuff | |
// For the JS stuff | |
js_context: Option<*mut JSContext>, | |
js_global: Option<HandleObject>, | |
js_runtime: Option<Runtime>, | |
} | |
impl JsWorker { | |
fn run(&mut self) { | |
println!("JS Worker got started!"); | |
let runtime = Runtime::new(); | |
let context = runtime.cx(); | |
let h_option = OnNewGlobalHookOption::FireOnNewGlobalHook; | |
let c_option = CompartmentOptions::default(); | |
unsafe { | |
let global2 = JS_NewGlobalObject(context, &SIMPLE_GLOBAL_CLASS, ptr::null_mut(), h_option, &c_option); | |
rooted!(in(context) let global_root = global2); | |
let global = global_root.handle(); | |
let _ac = JSAutoCompartment::new(context, global.get()); | |
let post_ril_message = JS_DefineFunction(context, global, b"postRILMessage\0".as_ptr() as *const _, | |
Some(post_ril_message), 2, 0); | |
assert!(!post_ril_message.is_null()); | |
let post_message = JS_DefineFunction(context, global, b"postMessage\0".as_ptr() as *const _, | |
Some(post_message), 1, 0); | |
assert!(!post_message.is_null()); | |
//let javascript = "postRILMessage(0, new Uint8Array([1, 2, 3]));"; | |
//let javascript = "postRILMessage(123, {\"foo\": [1,2]});"; | |
//let javascript = "postRILMessage(123, [1,2]);"; | |
//let javascript = "var hello = function() {postRILMessage(0, new Uint8Array([1, 2, 3]));};"; | |
//let javascript = r#"var hello = function() {postMessage({"some": "data"});};"#; | |
// let javascript = r#"postMessage({"some": "data"});"#; | |
//let javascript = r#"var onmessage = function(event) { return JSON.stringify(event.data); };"#; | |
let javascript = r#"var onRILMessage = function(clientId, data) { return data.toString(); };"#; | |
rooted!(in(context) let mut rval = UndefinedValue()); | |
let _ = runtime.evaluate_script(global, javascript, "test.js", 0, rval.handle_mut()); | |
//JS_CallFunctionName(context, global, b"hello".as_ptr() as *const _, | |
// &HandleValueArray { | |
// length_: 0 as _, | |
// elements_: ptr::null_mut() | |
// }, rval.handle_mut()); | |
//self.js_global = Some(global); | |
// }; | |
//self.js_context = Some(context); | |
//self.js_global = Some(global); | |
//self.js_runtime = Some(runtime); | |
//let javascript = r#"postMessage({"some": "data"});"#; | |
//rooted!(in(context) let mut rval = UndefinedValue()); | |
//let _ = runtime.evaluate_script( | |
// global, javascript, "test.js", 0, rval.handle_mut()); | |
//println!("Called postMessage()"); | |
println!("JS Worker is waiting for a message!"); | |
//XXX vmx 2017-02-08: GO ON HERE and send the websocket along, so that we can send | |
// messages async. It would make sense to have a loop below that is waiting for | |
// different kind of messages. One message will be sent by the websocket on | |
// the connection start (which will include the websocket itself). Others will | |
// be sent by postMessage() to actually send something back over the socket | |
while let Ok(message) = self.message_receiver.recv() { | |
println!("JS Worker received a message: {:?}", message); | |
let raw_message = match message { | |
Message::Register(RegisterMessage{client_id, raw_message, websocket_sender}) => { | |
self.handle_register(client_id, websocket_sender); | |
raw_message | |
}, | |
Message::WebsocketMessage(raw_message) => { | |
raw_message | |
////XXX vmx GO ON HERE 2017-02-13: This is the message the JavaScript context | |
//// gets via `onmessage`. | |
//println!("JS Worker received a ril message"); | |
// | |
//let javascript = r#"postMessage({"some": "data"});"#; | |
////rooted!(in(self.js_context.unwrap()) let mut rval = UndefinedValue()); | |
////let _ = self.js_runtime.as_ref().unwrap().evaluate_script( | |
//// self.js_global.unwrap(), javascript, "test.js", 0, rval.handle_mut()); | |
//rooted!(in(context) let mut rval = UndefinedValue()); | |
//let _ = runtime.evaluate_script( | |
// global, javascript, "test.js", 0, rval.handle_mut()); | |
//println!("Called postMessage()"); | |
}, | |
Message::RilMessage(RilMessage{client_id, data}) => { | |
println!("JS Worker is processing a message from the RIL socket"); | |
rooted!(in(context) let mut array = ptr::null_mut()); | |
Uint8Array::create(context, CreateWith::Slice(&data[..]), array.handle_mut()).is_ok(); | |
rooted!(in(context) let mut client_id_val = UndefinedValue()); | |
client_id.to_jsval(context, client_id_val.handle_mut()); | |
let args = vec![client_id_val.get(), ObjectOrNullValue(array.get())]; | |
let handle_value_array = HandleValueArray::from_rooted_slice(&args); | |
rooted!(in(context) let mut not_used = UndefinedValue()); | |
JS_CallFunctionName(context, global, b"onRILMessage".as_ptr() as *const _, | |
&handle_value_array, not_used.handle_mut()); | |
let return_value = String::from_jsval(context, not_used.handle(), ()); | |
println!("Return value of onRILMessage: {:?}", return_value.unwrap()); | |
"notyetimplemented".to_string() | |
}, | |
}; | |
// NOTE vmx 2017-03-05: All this javascript evaulations are just for testing purpose, | |
// they don't serve are real purpose at the moment. | |
//let javascript = r#"postMessage({"rilMessageClientId": 0, "some": "data4"});"#; | |
////rooted!(in(self.js_context.unwrap()) let mut rval = UndefinedValue()); | |
////let _ = self.js_runtime.as_ref().unwrap().evaluate_script( | |
//// self.js_global.unwrap(), javascript, "test.js", 0, rval.handle_mut()); | |
//rooted!(in(context) let mut rval = UndefinedValue()); | |
//let _ = runtime.evaluate_script( | |
// global, javascript, "test.js", 0, rval.handle_mut()); | |
//println!("Called postMessage()"); | |
//let javascript = "postRILMessage(0, new Uint8Array([1, 2, 3]));"; | |
let javascript = "postRILMessage(0, \"teststring\");"; | |
rooted!(in(context) let mut rval = UndefinedValue()); | |
let _ = runtime.evaluate_script( | |
global, javascript, "test.js", 0, rval.handle_mut()); | |
println!("Called postRillMessage()"); | |
let event = format!(r#"{{"data": {}}}"#, raw_message); | |
println!("Sending event to JS RIL Worker: {}", event); | |
let event: Vec<u16> = event.encode_utf16().collect(); | |
rooted!(in(context) let mut rooted_event_json = UndefinedValue()); | |
// NOTE vmx 2017-02-22: Error handling when JSON parsing failed is missing (when false | |
// is returned) | |
JS_ParseJSON(context, event.as_ptr(), event.len() as u32, rooted_event_json.handle_mut()); | |
let args = vec![rooted_event_json.handle().get()]; | |
rooted!(in(context) let mut rval = UndefinedValue()); | |
JS_CallFunctionName(context, global, b"onmessage".as_ptr() as *const _, | |
&HandleValueArray { | |
length_: 1 , | |
elements_: args.as_ptr(), | |
}, rval.handle_mut()); | |
let return_value = String::from_jsval(context, rval.handle(), ()); | |
println!("return value of the onmessage call: {:?}", return_value); | |
} | |
}; | |
} | |
/// Register a new client | |
fn handle_register(&mut self, client_id: u8, websocket_sender: ws::Sender) { | |
println!("JS Worker handling register"); | |
self.ensure_clients_length(client_id); | |
let ril_socket = RilSocket::new(client_id, self.message_sender.clone()); | |
ril_clients.with(|client| (*client.borrow_mut())[client_id as usize] = Some(Client{ | |
ril_socket: ril_socket, | |
websocket_sender: websocket_sender, | |
})); | |
} | |
/// Make sure the vector has enough items to address it directly with an index | |
/// | |
/// This corresponds to `EnsureLengthAtLeast()` from Mozilla's `nsTArray`. | |
fn ensure_clients_length(&mut self, client_id: u8) { | |
for _ in ril_clients.with(|client| (*client.borrow()).len())..client_id as usize + 1 { | |
ril_clients.with(|client| (*client.borrow_mut()).push(None)); | |
} | |
} | |
} | |
struct WebsocketServer { | |
websocket_sender: ws::Sender, | |
// The sender side of the Rust channel between the server and the JS Worker | |
message_sender: mpsc::Sender<Message>, | |
} | |
impl ws::Handler for WebsocketServer { | |
fn on_open(&mut self, _: ws::Handshake) -> ws::Result<()> { | |
println!("new WebSocket connection"); | |
Ok(()) | |
} | |
fn on_message(&mut self, websocket_message: ws::Message) -> ws::Result<()> { | |
println!("received a message: {:?}", websocket_message); | |
if let ws::Message::Text(json) = websocket_message { | |
let worker_message: WebsocketMessage = serde_json::from_str(&json).unwrap(); | |
println!("received a message JSON parssed: {:?}", worker_message); | |
let message = match worker_message.kind { | |
MessageKind::Register => Message::Register(RegisterMessage{ | |
client_id: worker_message.register_client_id.unwrap(), | |
raw_message: json, | |
websocket_sender: self.websocket_sender.clone(), | |
}), | |
_ => Message::WebsocketMessage(json), | |
}; | |
self.message_sender.send(message); | |
} | |
Ok(()) | |
} | |
fn on_close(&mut self, code: ws::CloseCode, reason: &str) { | |
match code { | |
ws::CloseCode::Normal => println!("The client is done with the connection."), | |
ws::CloseCode::Away => println!("The client is leaving the site."), | |
ws::CloseCode::Abnormal => println!( | |
"Closing handshake failed! Unable to obtain closing status from client."), | |
_ => println!("The client encountered an error: {}", reason), | |
} | |
} | |
fn on_error(&mut self, error: ws::Error) { | |
println!("The server encountered an error: {:?}", error); | |
} | |
} | |
// Write the stringified JSON into the string that was supplied as pointer to a Box | |
unsafe extern "C" fn stringify_json_callback(string_ptr: *const u16, len: u32, rval: *mut c_void) -> bool { | |
let mut ret = Box::from_raw(rval as *mut String); | |
let slice = slice::from_raw_parts(string_ptr, len as usize); | |
ret.push_str(String::from_utf16(slice).unwrap().as_str()); | |
// Without this line the pointer can't be converted back into a box after this function was called | |
Box::into_raw(ret); | |
true | |
} | |
// Process a message from the script that is hooked up with the RIL daemon and send it over the | |
// WebSocket to Firefox | |
unsafe extern "C" fn post_message(context: *mut JSContext, argc: u32, vp: *mut Value) -> bool { | |
let args = CallArgs::from_vp(vp, argc); | |
if args._base.argc_ != 1 { | |
JS_ReportError(context, b"Expecting one argument as message\0".as_ptr() as *const _); | |
return false; | |
} | |
let arg = args.get(0); | |
rooted!(in(context) let arg = arg.to_object()); | |
let message = Box::new(String::new()); | |
let message_ptr = Box::into_raw(message); | |
let did_the_stringify_work = ToJSONMaybeSafely(context, | |
arg.handle(), | |
Some(stringify_json_callback), | |
message_ptr as *mut c_void); | |
println!("did the stringify work?: {:?}", did_the_stringify_work); | |
let message = *Box::from_raw(message_ptr); | |
println!("The return valud of stringify is: {:?}", message); | |
let parsed_message: serde_json::Value = serde_json::from_str(&message).unwrap(); | |
let client_id = parsed_message["rilMessageClientId"].as_i64().unwrap(); | |
println!("client ID is: {}", client_id); | |
ril_clients.with(|cc| { | |
let ref client = (*cc.borrow())[client_id as usize]; | |
println!("Do I have access to the thread-local clients? {:?}", client); | |
client.as_ref().unwrap().websocket_sender.send(message); | |
}); | |
true | |
} | |
// Process a message from the JS worker script and forward it to the RIL Daemon | |
unsafe extern "C" fn post_ril_message(context: *mut JSContext, argc: u32, vp: *mut Value) -> bool { | |
println!("Rust post_rill_message got called"); | |
let args = CallArgs::from_vp(vp, argc); | |
if args._base.argc_ != 2 { | |
JS_ReportError(context, b"Expecting two arguments as message\0".as_ptr() as *const _); | |
return false; | |
} | |
//let return_value = String::from_jsval(context, rval.handle(), ()); | |
//println!("return value of the onmessage call: {:?}", return_value); | |
//let client_id = args.get(0); | |
////rooted!(in(context) let client_id = client_id.get().to_int32()); | |
//let client_id = client_id.to_int32(); | |
let client_id = args.get(0).to_int32(); | |
//rooted!(in(context) let client_id = UndefinedValue()); | |
//let client_id = i32::from_jsval(context, cleint_id.handle, ()); | |
ril_clients.with(|cc| { | |
let ref mut clients = *cc.borrow_mut(); | |
if clients.len() <= client_id as usize || clients[client_id as usize].is_none() { | |
// Probably shutting down. | |
return true; | |
} | |
let ret = clients[client_id as usize].as_mut().unwrap().send(context, &args); | |
ret.is_ok() | |
}) | |
} | |
fn main() { | |
let (sender, receiver): (mpsc::Sender<Message>, mpsc::Receiver<Message>) = mpsc::channel(); | |
let sender_for_jsworker = sender.clone(); | |
thread::spawn(move || { | |
let mut js_worker = JsWorker { | |
message_receiver: receiver, | |
message_sender: sender_for_jsworker, | |
js_context: None, | |
js_global: None, | |
js_runtime: None, | |
}; | |
js_worker.run(); | |
}); | |
listen("127.0.0.1:3012", |websocket_sender| { | |
WebsocketServer { | |
websocket_sender: websocket_sender, | |
message_sender: sender.clone(), | |
} | |
}).unwrap() | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment