diff --git a/ipc/codegen/src/codegen.rs b/ipc/codegen/src/codegen.rs index 69160bed5..166ae87bd 100644 --- a/ipc/codegen/src/codegen.rs +++ b/ipc/codegen/src/codegen.rs @@ -25,6 +25,7 @@ use syntax::ast::{ Arg, PatKind, FunctionRetTy, + Ty, }; use syntax::ast; @@ -52,11 +53,13 @@ pub fn expand_ipc_implementation( let builder = aster::AstBuilder::new().span(span); - let impl_item = match implement_interface(cx, &builder, &item, push) { - Ok(item) => item, + let (impl_item, dispatches) = match implement_interface(cx, &builder, &item, push) { + Ok((item, dispatches)) => (item, dispatches), Err(Error) => { return; } }; + push_client(cx, &builder, &item, &dispatches, push); + push(Annotatable::Item(impl_item)) } @@ -75,36 +78,41 @@ fn push_invoke_signature_aster( ) -> Dispatch { let inputs = &signature.decl.inputs; - let (input_type_name, input_arg_names) = if inputs.len() > 0 { + let (input_type_name, input_arg_names, input_arg_tys) = if inputs.len() > 0 { let first_field_name = field_name(builder, &inputs[0]).name.as_str(); - if first_field_name == "self" && inputs.len() == 1 { (None, vec![]) } + if first_field_name == "self" && inputs.len() == 1 { (None, vec![], vec![]) } else { let skip = if first_field_name == "self" { 2 } else { 1 }; let name_str = format!("{}_input", implement.ident.name.as_str()); let mut arg_names = Vec::new(); + let mut arg_tys = Vec::new(); let arg_name = format!("{}", field_name(builder, &inputs[skip-1]).name); + let arg_ty = inputs[skip-1].ty.clone(); let mut tree = builder.item() .attr().word("derive(Serialize, Deserialize)") .attr().word("allow(non_camel_case_types)") .struct_(name_str.as_str()) - .field(arg_name.as_str()).ty().build(inputs[skip-1].ty.clone()); + .field(arg_name.as_str()).ty().build(arg_ty.clone()); arg_names.push(arg_name); + arg_tys.push(arg_ty.clone()); for arg in inputs.iter().skip(skip) { let arg_name = format!("{}", field_name(builder, &arg)); - tree = tree.field(arg_name.as_str()).ty().build(arg.ty.clone()); + let arg_ty = arg.ty.clone(); + tree = tree.field(arg_name.as_str()).ty().build(arg_ty.clone()); arg_names.push(arg_name); + arg_tys.push(arg_ty); } push(Annotatable::Item(tree.build())); - (Some(name_str.to_owned()), arg_names) + (Some(name_str.to_owned()), arg_names, arg_tys) } } else { - (None, vec![]) + (None, vec![], vec![]) }; - let return_type_name = match signature.decl.output { + let (return_type_name, return_type_ty) = match signature.decl.output { FunctionRetTy::Ty(ref ty) => { let name_str = format!("{}_output", implement.ident.name.as_str()); let tree = builder.item() @@ -113,16 +121,18 @@ fn push_invoke_signature_aster( .struct_(name_str.as_str()) .field(format!("payload")).ty().build(ty.clone()); push(Annotatable::Item(tree.build())); - Some(name_str.to_owned()) + (Some(name_str.to_owned()), Some(ty.clone())) } - _ => None + _ => (None, None) }; Dispatch { function_name: format!("{}", implement.ident.name.as_str()), input_type_name: input_type_name, input_arg_names: input_arg_names, + input_arg_tys: input_arg_tys, return_type_name: return_type_name, + return_type_ty: return_type_ty, } } @@ -130,7 +140,9 @@ struct Dispatch { function_name: String, input_type_name: Option, input_arg_names: Vec, + input_arg_tys: Vec>, return_type_name: Option, + return_type_ty: Option>, } fn implement_dispatch_arm_invoke( @@ -212,6 +224,7 @@ fn implement_dispatch_arm_invoke( }) } +/// generates dispatch match for method id fn implement_dispatch_arm(cx: &ExtCtxt, builder: &aster::AstBuilder, index: u32, dispatch: &Dispatch) -> ast::Arm { @@ -220,12 +233,256 @@ fn implement_dispatch_arm(cx: &ExtCtxt, builder: &aster::AstBuilder, index: u32, quote_arm!(cx, $index_ident => { $invoke_expr } ) } +/// generates client type for specified server type +/// for say `Service` it generates `ServiceClient` +fn push_client_struct(cx: &ExtCtxt, builder: &aster::AstBuilder, item: &Item, push: &mut FnMut(Annotatable)) { + let proxy_ident = builder.id(format!("{}Client", item.ident.name.as_str())); + + let proxy_struct_item = quote_item!(cx, + pub struct $proxy_ident { + socket: ::std::cell::RefCell, + phantom: ::std::marker::PhantomData, + }); + + push(Annotatable::Item(proxy_struct_item.expect(&format!("could not generate proxy struct for {:?}", proxy_ident.name)))); +} + +/// pushes generated code for the client class (type declaration and method invocation implementations) +fn push_client( + cx: &ExtCtxt, + builder: &aster::AstBuilder, + item: &Item, + dispatches: &[Dispatch], + push: &mut FnMut(Annotatable)) +{ + push_client_struct(cx, builder, item, push); + push_client_implementation(cx, builder, dispatches, item, push); +} + +/// returns an expression with the body for single operation that is being sent to server +/// operation itself serializes input, writes to socket and waits for socket to respond +/// (the latter only if original method signature returns anyting) +/// +/// assuming expanded class contains method +/// fn commit(&self, f: u32) -> u32 +/// +/// the expanded implementation will generate method for the client like that +/// #[derive(Serialize)] +/// struct Request<'a> { +/// f: &'a u32, +/// } +/// let payload = Request{f: &f,}; +/// let mut socket_ref = self.socket.borrow_mut(); +/// let mut socket = socket_ref.deref_mut(); +/// let serialized_payload = ::bincode::serde::serialize(&payload, ::bincode::SizeLimit::Infinite).unwrap(); +/// ::ipc::invoke(0, &Some(serialized_payload), &mut socket); +/// while !socket.ready().load(::std::sync::atomic::Ordering::Relaxed) { } +/// ::bincode::serde::deserialize_from::<_, u32>(&mut socket, ::bincode::SizeLimit::Infinite).unwrap() +fn implement_client_method_body( + cx: &ExtCtxt, + builder: &aster::AstBuilder, + index: u16, + dispatch: &Dispatch, + ) + -> P +{ + let request = if dispatch.input_arg_names.len() > 0 { + let arg_name = dispatch.input_arg_names[0].as_str(); + let arg_ty = builder + .ty().ref_() + .lifetime("'a") + .ty().build(dispatch.input_arg_tys[0].clone()); + + let mut tree = builder.item() + .attr().word("derive(Serialize)") + .struct_("Request") + .generics() + .lifetime_name("'a") + .build() + .field(arg_name).ty().build(arg_ty); + + for arg_idx in 1..dispatch.input_arg_names.len() { + let arg_name = dispatch.input_arg_names[arg_idx].as_str(); + let arg_ty = builder + .ty().ref_() + .lifetime("'a") + .ty().build(dispatch.input_arg_tys[arg_idx].clone()); + tree = tree.field(arg_name).ty().build(arg_ty); + } + let mut request_serialization_statements = Vec::new(); + + let struct_tree = tree.build(); + let struct_stmt = quote_stmt!(cx, $struct_tree); + request_serialization_statements.push(struct_stmt); + + // actually this is just expanded version of this: + // request_serialization_statements.push(quote_stmt!(cx, let payload = Request { p1: &p1, p2: &p2, ... pn: &pn, })); + // again, cannot dynamically create expression with arbitrary number of comma-separated members + request_serialization_statements.push({ + let ext_cx = &*cx; + ::quasi::parse_stmt_panic(&mut ::syntax::parse::new_parser_from_tts( + ext_cx.parse_sess(), + ext_cx.cfg(), + { + let _sp = ext_cx.call_site(); + let mut tt = ::std::vec::Vec::new(); + tt.push(::syntax::ast::TokenTree::Token(_sp, ::syntax::parse::token::Ident(ext_cx.ident_of("let"), ::syntax::parse::token::Plain))); + tt.push(::syntax::ast::TokenTree::Token(_sp, ::syntax::parse::token::Ident(ext_cx.ident_of("payload"), ::syntax::parse::token::Plain))); + tt.push(::syntax::ast::TokenTree::Token(_sp, ::syntax::parse::token::Eq)); + tt.push(::syntax::ast::TokenTree::Token(_sp, ::syntax::parse::token::Ident(ext_cx.ident_of("Request"), ::syntax::parse::token::Plain))); + tt.push(::syntax::ast::TokenTree::Token(_sp, ::syntax::parse::token::OpenDelim(::syntax::parse::token::Brace))); + + for arg in dispatch.input_arg_names.iter() { + tt.push(::syntax::ast::TokenTree::Token(_sp, ::syntax::parse::token::Ident(ext_cx.ident_of(arg.as_str()), ::syntax::parse::token::Plain))); + tt.push(::syntax::ast::TokenTree::Token(_sp, ::syntax::parse::token::Colon)); + tt.push(::syntax::ast::TokenTree::Token(_sp, ::syntax::parse::token::BinOp(::syntax::parse::token::And))); + tt.push(::syntax::ast::TokenTree::Token(_sp, ::syntax::parse::token::Ident(ext_cx.ident_of(arg.as_str()), ::syntax::parse::token::Plain))); + tt.push(::syntax::ast::TokenTree::Token(_sp, ::syntax::parse::token::Comma)); + } + + tt.push(::syntax::ast::TokenTree::Token(_sp, ::syntax::parse::token::CloseDelim(::syntax::parse::token::Brace))); + tt + })) + }); + + request_serialization_statements.push( + quote_stmt!(cx, let mut socket_ref = self.socket.borrow_mut())); + + request_serialization_statements.push( + quote_stmt!(cx, let mut socket = socket_ref.deref_mut())); + + request_serialization_statements.push( + quote_stmt!(cx, let serialized_payload = ::bincode::serde::serialize(&payload, ::bincode::SizeLimit::Infinite).unwrap())); + + let index_ident = builder.id(format!("{}", index).as_str()); + + request_serialization_statements.push( + quote_stmt!(cx, ::ipc::invoke($index_ident, &Some(serialized_payload), &mut socket))); + + + request_serialization_statements + } + else { + vec![] + }; + + let wait_result_stmt = quote_stmt!(cx, while !socket.ready().load(::std::sync::atomic::Ordering::Relaxed) { }); + if let Some(ref return_ty) = dispatch.return_type_ty { + let return_expr = quote_expr!(cx, + ::bincode::serde::deserialize_from::<_, $return_ty>(&mut socket, ::bincode::SizeLimit::Infinite).unwrap() + ); + quote_expr!(cx, { + $request + $wait_result_stmt + $return_expr + }) + } + else { + quote_expr!(cx, { + $request + $wait_result_stmt + }) + } +} + +/// Generates signature and body (see `implement_client_method_body`) +/// for the client (signature is identical to the original method) +fn implement_client_method( + cx: &ExtCtxt, + builder: &aster::AstBuilder, + index: u16, + dispatch: &Dispatch) + -> ast::ImplItem +{ + let method_name = builder.id(dispatch.function_name.as_str()); + let body = implement_client_method_body(cx, builder, index, dispatch); + + let ext_cx = &*cx; + // expanded version of this + // pub fn $method_name(&self, p1: p1_ty, p2: p2_ty ... pn: pn_ty, ) [-> return_ty] { $body } + // looks like it's tricky to build function declaration with aster if body already generated + let signature = ::syntax::parse::parser::Parser::parse_impl_item( + &mut ::syntax::parse::new_parser_from_tts( + ext_cx.parse_sess(), + ext_cx.cfg(), + { + let _sp = ext_cx.call_site(); + let mut tt = ::std::vec::Vec::new(); + tt.push(::syntax::ast::TokenTree::Token(_sp, ::syntax::parse::token::Ident(ext_cx.ident_of("pub"), ::syntax::parse::token::Plain))); + tt.push(::syntax::ast::TokenTree::Token(_sp, ::syntax::parse::token::Ident(ext_cx.ident_of("fn"), ::syntax::parse::token::Plain))); + tt.extend(::quasi::ToTokens::to_tokens(&method_name, ext_cx).into_iter()); + tt.push(::syntax::ast::TokenTree::Token(_sp, ::syntax::parse::token::OpenDelim(::syntax::parse::token::Paren))); + tt.push(::syntax::ast::TokenTree::Token(_sp, ::syntax::parse::token::BinOp(::syntax::parse::token::And))); + tt.push(::syntax::ast::TokenTree::Token(_sp, ::syntax::parse::token::Ident(ext_cx.ident_of("self"), ::syntax::parse::token::Plain))); + tt.push(::syntax::ast::TokenTree::Token(_sp, ::syntax::parse::token::Comma)); + + for arg_idx in 0..dispatch.input_arg_names.len() { + let arg_name = dispatch.input_arg_names[arg_idx].as_str(); + let arg_ty = dispatch.input_arg_tys[arg_idx].clone(); + + tt.push(::syntax::ast::TokenTree::Token(_sp, ::syntax::parse::token::Ident(ext_cx.ident_of(arg_name), ::syntax::parse::token::Plain))); + tt.push(::syntax::ast::TokenTree::Token(_sp, ::syntax::parse::token::Colon)); + tt.extend(::quasi::ToTokens::to_tokens(&arg_ty, ext_cx).into_iter()); + tt.push(::syntax::ast::TokenTree::Token(_sp, ::syntax::parse::token::Comma)); + } + tt.push(::syntax::ast::TokenTree::Token(_sp, ::syntax::parse::token::CloseDelim(::syntax::parse::token::Paren))); + + if let Some(ref return_ty) = dispatch.return_type_ty { + tt.push(::syntax::ast::TokenTree::Token(_sp, ::syntax::parse::token::RArrow)); + tt.extend(::quasi::ToTokens::to_tokens(return_ty, ext_cx).into_iter()); + } + + tt.push(::syntax::ast::TokenTree::Token(_sp, ::syntax::parse::token::OpenDelim(::syntax::parse::token::Brace))); + tt.extend(::quasi::ToTokens::to_tokens(&body, ext_cx).into_iter()); + tt.push(::syntax::ast::TokenTree::Token(_sp, ::syntax::parse::token::CloseDelim(::syntax::parse::token::Brace))); + + tt + })); + + signature.unwrap() +} + +/// pushes full client side code for the original class exposed via ipc +fn push_client_implementation( + cx: &ExtCtxt, + builder: &aster::AstBuilder, + dispatches: &[Dispatch], + item: &Item, + push: &mut FnMut(Annotatable)) +{ + let mut index = -1i32; + let items = dispatches.iter() + .map(|dispatch| { index = index + 1; P(implement_client_method(cx, builder, index as u16, dispatch)) }) + .collect::>>(); + + let client_ident = builder.id(format!("{}Client", item.ident.name.as_str())); + let implement = quote_item!(cx, + impl $client_ident where S: ::ipc::IpcSocket { + pub fn new(socket: S) -> $client_ident { + $client_ident { + socket: ::std::cell::RefCell::new(socket), + phantom: ::std::marker::PhantomData, + } + } + + #[cfg(test)] + pub fn socket(&self) -> &::std::cell::RefCell { + &self.socket + } + + $items + }).unwrap(); + + push(Annotatable::Item(implement)); +} + +/// implements `IpcInterface` for the given class `C` fn implement_interface( cx: &ExtCtxt, builder: &aster::AstBuilder, item: &Item, push: &mut FnMut(Annotatable), -) -> Result, Error> { +) -> Result<(P, Vec), Error> { let (generics, impl_items) = match item.node { ast::ItemKind::Impl(_, _, ref generics, _, _, ref impl_items) => (generics, impl_items), _ => { @@ -258,7 +515,7 @@ fn implement_interface( let dispatch_arms: Vec<_> = dispatch_table.iter() .map(|dispatch| { index = index + 1; implement_dispatch_arm(cx, builder, index as u32, dispatch) }).collect(); - Ok(quote_item!(cx, + Ok((quote_item!(cx, impl $impl_generics ::ipc::IpcInterface<$ty> for $ty $where_clause { fn dispatch(&self, r: &mut R) -> Vec where R: ::std::io::Read @@ -274,11 +531,6 @@ fn implement_interface( _ => vec![] } } - fn invoke(&self, _method_num: u16, _payload: &Option>, _w: &mut W) - where W: ::std::io::Write - { - } - } - ).unwrap()) + ).unwrap(), dispatch_table)) } diff --git a/ipc/rpc/Cargo.toml b/ipc/rpc/Cargo.toml index b8a05f543..99fcd3233 100644 --- a/ipc/rpc/Cargo.toml +++ b/ipc/rpc/Cargo.toml @@ -7,3 +7,4 @@ license = "GPL-3.0" [features] [dependencies] +ethcore-devtools = { path = "../../devtools" } diff --git a/ipc/rpc/src/interface.rs b/ipc/rpc/src/interface.rs index 111176b1f..8d67deca3 100644 --- a/ipc/rpc/src/interface.rs +++ b/ipc/rpc/src/interface.rs @@ -16,9 +16,43 @@ //! IPC RPC interface +use std::io::{Read, Write}; +use std::marker::Sync; +use std::sync::atomic::*; + pub trait IpcInterface { /// reads the message from io, dispatches the call and returns result - fn dispatch(&self, r: &mut R) -> Vec where R: ::std::io::Read; - /// encodes the invocation, writes payload and waits for result - fn invoke(&self, method_num: u16, params: &Option>, w: &mut W) where W: ::std::io::Write; + fn dispatch(&self, r: &mut R) -> Vec where R: Read; +} + +/// serializes method invocation (method_num and parameters) to the stream specified by `w` +pub fn invoke(method_num: u16, params: &Option>, w: &mut W) where W: Write { + // creating buffer to contain all message + let buf_len = match *params { None => 2, Some(ref val) => val.len() + 2 }; + let mut buf = vec![0u8; buf_len]; + + // writing method_num as u16 + buf[1] = (method_num & 255) as u8; + buf[0] = (method_num >> 8) as u8; + + // serializing parameters only if provided with any + if params.is_some() { + buf[2..buf_len].clone_from_slice(params.as_ref().unwrap()); + } + if w.write(&buf).unwrap() != buf_len + { + // if write was inconsistent + panic!("failed to write to socket"); + } +} + +/// IpcSocket +pub trait IpcSocket: Read + Write + Sync { + fn ready(&self) -> AtomicBool; +} + +impl IpcSocket for ::devtools::TestSocket { + fn ready(&self) -> AtomicBool { + AtomicBool::new(true) + } } diff --git a/ipc/rpc/src/lib.rs b/ipc/rpc/src/lib.rs index 5018a5cd2..f8c68cdbd 100644 --- a/ipc/rpc/src/lib.rs +++ b/ipc/rpc/src/lib.rs @@ -16,5 +16,7 @@ //! IPC RPC interface +extern crate ethcore_devtools as devtools; + pub mod interface; -pub use interface::IpcInterface; +pub use interface::{IpcInterface, IpcSocket, invoke}; diff --git a/ipc/tests/examples.rs b/ipc/tests/examples.rs index 280e25d08..52ac0cf22 100644 --- a/ipc/tests/examples.rs +++ b/ipc/tests/examples.rs @@ -33,4 +33,28 @@ mod tests { assert_eq!(10, *service.commits.read().unwrap()); } + + #[test] + fn call_service_proxy() { + let mut socket = TestSocket::new(); + socket.read_buffer = vec![0, 0, 0, 10]; + let service_client = ServiceClient::new(socket); + + let result = service_client.commit(5); + + assert_eq!(vec![0, 0, 0, 0, 0, 5], service_client.socket().borrow().write_buffer.clone()); + assert_eq!(10, result); + } + + #[test] + fn call_service_proxy_optional() { + let mut socket = TestSocket::new(); + socket.read_buffer = vec![0, 0, 0, 10]; + let service_client = ServiceClient::new(socket); + + let result = service_client.rollback(Some(5), 10); + + assert_eq!(vec![0, 1, 1, 0, 0, 0, 5, 0, 0, 0, 10], service_client.socket().borrow().write_buffer.clone()); + assert_eq!(10, result); + } } diff --git a/ipc/tests/service.rs.in b/ipc/tests/service.rs.in index 64b43c2ef..2b529534a 100644 --- a/ipc/tests/service.rs.in +++ b/ipc/tests/service.rs.in @@ -15,6 +15,7 @@ // along with Parity. If not, see . use std::sync::RwLock; +use std::ops::*; pub struct Service { pub commits: RwLock, @@ -28,10 +29,11 @@ impl Service { *lock = *lock + f as usize; f } - pub fn rollback(&self, a: u32, b: u32) -> i32 { + pub fn rollback(&self, a: Option, b: u32) -> i32 { + let a_0 = a.unwrap_or_else(|| 0); let mut lock = self.rollbacks.write().unwrap(); - *lock = *lock + a as usize - b as usize; - (a - b) as i32 + *lock = *lock + a_0 as usize - b as usize; + (a_0 - b) as i32 } }