plumber_rs/protocol.rs
1// Copyright (C) 2018, Hao Hou
2
3//! The inter-component procotol utilities
4//!
5//! Plumber has a language-neutral, centralized protocol management machenism. And this module
6//! provides the binding to the centralized protocol database and performe language neutral
7//! typeing.
8//!
9
10use ::pipe::PipeDescriptor;
11use ::pstd::{
12 pstd_type_accessor_t,
13 pstd_type_field_t,
14 pstd_type_model_t,
15 pstd_type_instance_t,
16 pstd_type_model_get_accessor,
17 pstd_type_model_get_field_info,
18 pstd_type_model_on_pipe_type_checked,
19 pstd_type_instance_read,
20 pstd_type_instance_write
21};
22
23use ::plumber_api_call::get_cstr;
24
25use std::marker::PhantomData;
26use std::collections::HashMap;
27use std::rc::Rc;
28
29/**
30 * Type type instance object. For each time the Plumber framework activate the execution of the
31 * servlet, it will automatically create a data buffer called type instance, which is used to
32 * tracking the protocol data.
33 *
34 * This is the Rust wrapper for the type instance object. It doesn't represent the ownership of the
35 * lower-level type instance data type. This is just a wrapper in Rust.
36 **/
37pub struct TypeInstanceObject {
38 /// The pointer to the actual instance object
39 object: *mut pstd_type_instance_t
40}
41
42impl TypeInstanceObject {
43 /**
44 * Createe a new type instance object wrapper from the raw pointer
45 *
46 * * `raw`: The raw pointer to create
47 *
48 * Return either the newly created wrapper object or None
49 **/
50 pub fn from_raw(raw: *mut ::std::os::raw::c_void) -> Option<TypeInstanceObject>
51 {
52 if !raw.is_null()
53 {
54 return Some(TypeInstanceObject{
55 object : raw as *mut pstd_type_instance_t
56 });
57 }
58 return None;
59 }
60
61 /**
62 * Read an accessor from the given type instance object.
63 *
64 * This is the low level read function of the Plumber's protocol typing system.
65 *
66 * * `acc`: The accessor to read
67 * * `buf`: The buffer used to return the read result
68 * * `size`: The number of bytes that needs to be read
69 *
70 * Returns if the read operation has successfully done which means we read all the expected
71 * data.
72 **/
73 fn read(&mut self,
74 acc:pstd_type_accessor_t,
75 buf:*mut ::std::os::raw::c_void,
76 size: usize) -> bool
77 {
78 let result = unsafe{ pstd_type_instance_read(self.object, acc, buf, size) };
79
80 return result == size;
81 }
82
83 /**
84 * Write an accessor data from given type instance object.
85 *
86 * This is the low level write function of Plumber's pstd lib
87 *
88 * * `acc`: The accessor to write
89 * * `buf`: The data buffer
90 * * `size`: The number of bytes that needs to be written
91 *
92 * Returns if the operation has successfully done
93 **/
94 fn write(&mut self,
95 acc:pstd_type_accessor_t,
96 buf:*const ::std::os::raw::c_void,
97 size:usize) -> bool
98 {
99 let result = unsafe{ pstd_type_instance_write(self.object, acc, buf, size) };
100
101 return result != -1;
102 }
103}
104
105/**
106 * The shape of a primitive. This is used to check if the Rust type is a supported protocol
107 * primitive type. Also, it provides the data so that we can check the protocol primitive is
108 * expected type shape.
109 **/
110pub type PrimitiveTypeShape = pstd_type_field_t;
111
112impl Default for PrimitiveTypeShape {
113 fn default() -> PrimitiveTypeShape
114 {
115 return PrimitiveTypeShape {
116 offset:0,
117 size :0,
118 _bitfield_1: PrimitiveTypeShape::new_bitfield_1(0,0,0,0,0,0),
119 __bindgen_padding_0: [0;3usize]
120 };
121 }
122}
123
124/**
125 * The object wrapper for a type model.
126 *
127 * A type model is the container of the protocol data request used by the servlet.
128 * For Rust servlet, the type model is created automatically by the servet loader and will be
129 * disposed after the servlet is dead.
130 *
131 * Owning the data object doesn't own the type model itself. There's no way for Rust code to get
132 * the owership of the internal type model.
133 **/
134pub struct TypeModelObject {
135 /// The pointer to the actual data model
136 object : *mut pstd_type_model_t
137}
138
139/**
140 * The additonal data used when we want to check the type shape of the primitive
141 **/
142struct TypeShapeChecker<'a , T : PrimitiveTypeTag<T> + Default> {
143 /// The shape buffer that will be written when the type inference is done
144 shape : &'a PrimitiveTypeShape,
145 /// Keep the type information
146 phantom : PhantomData<T>
147}
148
149impl <'a, T:PrimitiveTypeTag<T> + Default> TypeShapeChecker<'a,T> {
150 fn do_check(&self) -> bool { return T::validate_type_shape(self.shape); }
151}
152
153impl TypeModelObject {
154 /**
155 * Create a new type model wrapper object form the raw pointer
156 *
157 * This function is desgined to be called inside the `export_bootstrap!` macro, be awrared if
158 * you feel you have to use this.
159 *
160 * * `raw`: The raw pointer to wrap
161 *
162 * Returns the newly created wrapper object or None
163 **/
164 pub fn from_raw(raw : *mut ::std::os::raw::c_void) -> Option<TypeModelObject>
165 {
166 let inner_obj = raw;
167 if !inner_obj.is_null()
168 {
169 return Some(TypeModelObject {
170 object : inner_obj as *mut pstd_type_model_t
171 });
172 }
173 return None;
174 }
175
176
177 /**
178 * Add a check of type shape for the accessor
179 **/
180 fn _add_type_shape_check<T>(&self,
181 pipe:PipeDescriptor,
182 path:*const ::std::os::raw::c_char,
183 primitive:&mut Primitive<T>) -> bool
184 where T : PrimitiveTypeTag<T> + Default
185 {
186 if -1 == unsafe {
187 pstd_type_model_get_field_info(self.object,
188 pipe,
189 path,
190 (&mut primitive.shape) as *mut PrimitiveTypeShape)
191 }
192 {
193 return false;
194 }
195
196 let check_shape = Box::new(TypeShapeChecker::<T>{
197 shape : &primitive.shape,
198 phantom: PhantomData
199 });
200
201 extern "C" fn _validate_primitive_type_shape<T>(_pipe: ::plumber_api::runtime_api_pipe_t,
202 data : *mut ::std::os::raw::c_void) -> i32
203 where T : PrimitiveTypeTag<T>+Default
204 {
205 let check_shape = unsafe{ Box::<TypeShapeChecker<T>>::from_raw(data as *mut TypeShapeChecker<T>) };
206 if check_shape.do_check()
207 {
208 return 0;
209 }
210 return -1;
211 }
212
213 let check_shape_ref = Box::leak(check_shape) as *mut TypeShapeChecker<T>;
214
215 unsafe{ pstd_type_model_on_pipe_type_checked(self.object,
216 pipe,
217 Some(_validate_primitive_type_shape::<T>),
218 check_shape_ref as *mut ::std::os::raw::c_void) };
219 return true;
220 }
221
222 /**
223 * Assign a primitive data object to the type model. This will cause the Plumber framework
224 * check the protocol database and keep the type information in the primitive object for
225 * further protocol parsing
226 *
227 * * `pipe`: The pipe we want to access
228 * * `path`: The path to the pipe
229 * * `primitive`: The primitive object
230 * * `validate_type` If we want to validate the type shape
231 *
232 * Returns if the operation has sucessfully completed
233 **/
234 pub fn assign_primitive<'a, 'b, T>(&self,
235 pipe:PipeDescriptor,
236 path:&'a str,
237 primitive:&'b mut Primitive<T>,
238 validate_type:bool) -> bool
239 where T : PrimitiveTypeTag<T> + Default
240 {
241 if let None = primitive.accessor
242 {
243 let (c_path, _path) = get_cstr(Some(path));
244
245 if validate_type && !self._add_type_shape_check(pipe, c_path, primitive)
246 {
247 return false;
248 }
249
250 let accessor = unsafe { pstd_type_model_get_accessor(self.object, pipe, c_path) };
251
252 if accessor as i32 == -1
253 {
254 return false;
255 }
256
257 let mut new_val = Some(accessor);
258
259 ::std::mem::swap(&mut primitive.accessor, &mut new_val);
260
261 return true;
262 }
263
264 return false;
265 }
266}
267
268/**
269 * The object used to represent a pritmive type in the language-neutral protocol database
270 **/
271pub struct Primitive<ActualType : PrimitiveTypeTag<ActualType> + Default> {
272 /// The type accessor object
273 accessor : Option<pstd_type_accessor_t>,
274 /// The shape of this primmitive
275 shape : PrimitiveTypeShape,
276 /// The type holder
277 _phantom : PhantomData<ActualType>
278}
279
280impl <T : PrimitiveTypeTag<T> + Default> Primitive<T> {
281 /**
282 * Create a new type primitive
283 **/
284 pub fn new() -> Primitive<T>
285 {
286 return Primitive {
287 accessor : None,
288 shape : Default::default(),
289 _phantom : PhantomData
290 };
291 }
292
293 /**
294 * Get a primitive value from the primitive descriptor.
295 *
296 * This function will be valid only when it's called from execution function and there's
297 * type instance object has been created. Otherwise it will returns a failure
298 *
299 * * `type_inst`: Type instance object where we read the primitive from
300 *
301 * Return the read result, None indicates we are unable to read the data
302 **/
303 pub fn get(&self, type_inst:&mut TypeInstanceObject) -> Option<T>
304 {
305 if let Some(ref acc_ref) = self.accessor
306 {
307 let mut buf:T = Default::default();
308 let mut buf_ptr = &mut buf as *mut T;
309 let acc = acc_ref.clone();
310
311 if type_inst.read(acc, buf_ptr as *mut ::std::os::raw::c_void, ::std::mem::size_of::<T>())
312 {
313 return Some(buf);
314 }
315 }
316
317 return None;
318 }
319
320 /**
321 * Write a primitive to the primitive descriptor within current task context
322 *
323 * This function will be valid only when it's called from the execution function, because it
324 * requires the task context.
325 *
326 * * `type_inst`: The type instance object where we want to write to
327 *
328 * Return the operation result, if the operation has successuflly done.
329 **/
330 pub fn set(&self, type_inst:&mut TypeInstanceObject, val:T) -> bool
331 {
332 if let Some(ref acc_ref) = self.accessor
333 {
334 let acc = acc_ref.clone();
335 let val_ref = &val;
336 let val_ptr = val_ref as *const T;
337
338 if type_inst.write(acc, val_ptr as *mut ::std::os::raw::c_void, ::std::mem::size_of::<T>())
339 {
340 return true;
341 }
342 }
343 return false;
344 }
345
346}
347
348/**
349 * The tag trait indicates that this is a rust type which can be mapped into a Plumber
350 * language-neutral primitive type
351 **/
352pub trait PrimitiveTypeTag<T:Sized + Default>
353{
354 /**
355 * Validate the type shape
356 *
357 * * `shape` The type shape to validate
358 *
359 * Return the validation result
360 */
361 fn validate_type_shape(shape : &PrimitiveTypeShape) -> bool;
362}
363
364
365impl pstd_type_field_t {
366 fn type_size(&self) -> u32 { self.size }
367}
368
369macro_rules! primitive_type {
370 ($($type:ty => [$($var:ident : $val:expr);*]);*;) => {
371 $(impl PrimitiveTypeTag<$type> for $type {
372 fn validate_type_shape(ts : &PrimitiveTypeShape) -> bool
373 {
374 return $((ts.$var() == $val)&&)* true;
375 }
376 })*
377 }
378}
379
380primitive_type!{
381 i8 => [type_size:1; is_numeric:1; is_signed:1; is_float:0; is_primitive_token:0; is_compound:0];
382 i16 => [type_size:2; is_numeric:1; is_signed:1; is_float:0; is_primitive_token:0; is_compound:0];
383 i32 => [type_size:4; is_numeric:1; is_signed:1; is_float:0; is_primitive_token:0; is_compound:0];
384 i64 => [type_size:8; is_numeric:1; is_signed:1; is_float:0; is_primitive_token:0; is_compound:0];
385 u8 => [type_size:1; is_numeric:1; is_signed:0; is_float:0; is_primitive_token:0; is_compound:0];
386 u16 => [type_size:2; is_numeric:1; is_signed:0; is_float:0; is_primitive_token:0; is_compound:0];
387 u32 => [type_size:4; is_numeric:1; is_signed:0; is_float:0; is_primitive_token:0; is_compound:0];
388 u64 => [type_size:8; is_numeric:1; is_signed:0; is_float:0; is_primitive_token:0; is_compound:0];
389 f32 => [type_size:4; is_numeric:1; is_signed:1; is_float:1; is_primitive_token:0; is_compound:0];
390 f64 => [type_size:8; is_numeric:1; is_signed:1; is_float:1; is_primitive_token:0; is_compound:0];
391}
392
393/**
394 * The trait of the data models, which is used to read/write the typed data from/input Plumber
395 * pipe port.
396 *
397 * A data model is created when the exec function have created the type instance object and
398 * bufferred the typed data into the type instance object. This is the Rust wrapper for the type
399 * instance object from libpstd.
400 *
401 * This trait is usually implemented by the macro `protodef!`.
402 * It's rare that you have to manually implement the data model class.
403 * See the documentaiton for `protodef!` macro for details.
404 **/
405pub trait DataModel<T:ProtocolModel> where Self:Sized {
406 /**
407 * Create the new data type model
408 *
409 * * `model`: The smart pointer for the data model
410 * * `type_inst`: The data instance object created for current task
411 *
412 * Returns the newly created data model object
413 **/
414 fn new_data_model(model : Rc<T>, type_inst:TypeInstanceObject) -> Self;
415}
416
417/**
418 * The trait for the protocol model, which defines what field we want to read/write to the typed
419 * Plumber pipe port.
420 *
421 * This is the high-level Rust wrapper for the PSTD's type model object, which keep tracking the
422 * memory layout of the typed port and memorize the method that we can use to interept the typed
423 * data. See the Plumber documentaiton of pstd_type_model_t for the details.
424 *
425 * This trait is usually implemented by the macro `protodef!`.
426 * It's rare that you have to manually implement the data model class.
427 * See the documentaiton for `protodef!` macro for details.
428 **/
429pub trait ProtocolModel {
430 /**
431 * Initialize the model, which assign the actual pipe to the model's pipe.
432 *
433 * This function is desgined to be called in the servlet's init function and it actually does
434 * some initialization, such as requires a accessor from the lower level pstd_type_model_t
435 * object, etc.
436 *
437 * * `pipes`: A map that contains the map from the pipe name to pipe descriptor
438 *
439 * Returns if or not this model has been successfully initialized
440 **/
441 fn init_model(&mut self, pipes: HashMap<String, PipeDescriptor>) -> bool;
442
443 /**
444 * Create a new protocol model, which is the high-level wrapper of the Type Model object
445 *
446 * * `type_model`: The low-level type model object
447 *
448 * Return the newly created type model object
449 **/
450 fn new_protocol_model(type_model:TypeModelObject) -> Self;
451}
452
453/**
454 * The placeholder for the data model and protocol model of a totally untyped servlet.
455 *
456 * If all the pipes ports of your servlet are untyped, this is the type you should put into the
457 * `ProtocolType` and `DataModelType`.
458 **/
459pub type Untyped = ();
460
461impl ProtocolModel for () {
462 fn init_model(&mut self, _p:HashMap<String, PipeDescriptor>) -> bool { true }
463 fn new_protocol_model(_tm:TypeModelObject) -> Untyped {}
464}
465
466impl DataModel<Untyped> for Untyped {
467 fn new_data_model(_m : Rc<Untyped>, _ti: TypeInstanceObject) -> Untyped {}
468}
469
470// TODO: how to handle the writer ?
471//
472// Also we need to handle the token type
473//
474// Another thing is constant support
475/**
476 * Defines a language-neutural protocol binding for the Rust servlet.
477 *
478 * This is the major way a `ProtocolModel` and `DataModel` is created. The input of the macro is
479 * which field of the language-neutural type you want to map into the Rust servlet.
480 *
481 * For example, a servlet may want to read a `Point2D` type from the input port. And the servlet
482 * uses the `Point2D.x` and `Point2D.y`, we can actually map it with the following syntax:
483 *
484 * ```
485 * protodef!{
486 * protodef MyProtocol {
487 * [input.x]:f32 => input_x;
488 * [input.y]:f32 => input_y;
489 * }
490 * }
491 * ```
492 * Which means we want to map the the `x` field of the input with `f32` type to identifer `input_x`
493 * and `y` field of the input with `f32` type to identifer `input_y`.
494 *
495 * In the init function of the servlet, we should assign the actual pipe object to the protocol
496 * pipes with the macro `init_protocol`. For example:
497 *
498 * ```
499 * fn init(&mut self, args:&[&str], model:Self::ProtocolType)
500 * {
501 * ....
502 * init_protocol!{
503 * model {
504 * self.input => input,
505 * self.output => output
506 * }
507 * }
508 * ....
509 * }
510 * ```
511 * This will assign `self.input` as the `input` mentioned in protocol, and `self.out` as the
512 * `output` mentioned in the protocol.
513 *
514 * By doing that we are abe to read the data in the servlet execution function with the data model:
515 * ```
516 * let x = data_model.input_x().get(); // read x
517 * let y = data_model.input_y().get(); // read y
518 * ```
519 *
520 * In order to make the compiler knows our servlet actually use a specified protcol. The
521 * `use_protocol!` macro should be used inside the servlet implementation. For example
522 *
523 * ```
524 * impl SyncServlet for MyServlet {
525 * use_protocol(MyProtocol); // This makes the servlet uses the protocol we just defined
526 * ......
527 * }
528 * ```
529 * The mapping syntax is as following:
530 * ```
531 * [field.path.to.plumber]:rust_type => rust_identifer
532 * ```
533 *
534 * Limit:
535 * * Currently we do not support compound object access, for example, we can not read the entire
536 * `Point2D` object
537 * * We also leak of the RLS object support, which should be done in the future
538 **/
539#[macro_export]
540macro_rules! protodef {
541 ($(protodef $proto_name:ident { $([$pipe:ident.$($field:tt)*]:$type:ty => $model_name:ident;)* })*) => {
542 mod plumber_protocol {
543 use ::plumber_rs::protocol::{Primitive, TypeModelObject, ProtocolModel};
544 use ::plumber_rs::pipe::PipeDescriptor;
545 use ::std::collections::HashMap;
546 $(
547 pub struct $proto_name {
548 type_model : TypeModelObject,
549 $(pub $model_name : Primitive<$type>,)*
550 }
551 impl ProtocolModel for $proto_name {
552 fn init_model(&mut self,
553 pipes: HashMap<String, PipeDescriptor>) -> bool
554 {
555 $(
556 if let Some(pipe) = pipes.get(stringify!($pipe))
557 {
558 if !self.type_model.assign_primitive(*pipe, stringify!($($field)*), &mut self.$model_name, true)
559 {
560 return false;
561 }
562 }
563 else
564 {
565 return false;
566 }
567 )*
568 return true;
569 }
570 fn new_protocol_model(type_model : TypeModelObject) -> Self
571 {
572 return $proto_name {
573 type_model : type_model,
574 $(
575 $model_name : Primitive::new()
576 ),*
577 };
578 }
579 }
580 )*
581 }
582 mod plumber_protocol_accessor {
583 use ::plumber_rs::protocol::{DataModel, TypeInstanceObject, PrimitiveTypeTag, Primitive};
584 use std::rc::Rc;
585 pub struct FieldAccessor<'a, T: PrimitiveTypeTag<T> + Default + 'a> {
586 target : &'a Primitive<T>,
587 inst : &'a mut TypeInstanceObject
588 }
589
590 impl <'a, T: PrimitiveTypeTag<T> + Default> FieldAccessor<'a, T> {
591 pub fn get(&mut self) -> Option<T>
592 {
593 return self.target.get(self.inst);
594 }
595
596 pub fn set(&mut self, val:T) -> bool
597 {
598 return self.target.set(self.inst, val);
599 }
600 }
601
602 $(
603 pub struct $proto_name {
604 model : Rc<::plumber_protocol::$proto_name>,
605 inst : TypeInstanceObject
606 }
607
608 impl $proto_name {
609 $(
610 #[allow(dead_code)]
611 pub fn $model_name(&mut self) -> FieldAccessor<$type>
612 {
613 return FieldAccessor::<$type>{
614 target: &self.model.$model_name,
615 inst : &mut self.inst
616 };
617 }
618 )*
619 }
620
621 impl DataModel<::plumber_protocol::$proto_name> for $proto_name {
622 fn new_data_model(model : Rc<::plumber_protocol::$proto_name>, type_inst:TypeInstanceObject) -> $proto_name
623 {
624 return $proto_name{
625 model : model,
626 inst : type_inst
627 };
628 }
629 }
630 )*
631 }
632 }
633}
634
635/**
636 * Make the servlet implementation uses the given protocol defined by `protodef!`
637 *
638 * This should be use inside the servlet implementation block.
639 **/
640#[macro_export]
641macro_rules! use_protocol {
642 ($name:ident) => {
643 type ProtocolType = ::plumber_protocol::$name;
644 type DataModelType = ::plumber_protocol_accessor::$name;
645 }
646}
647
648/**
649 * Make the servlet implementation uses untyped mode, which we just do the pipe IO instead.
650 *
651 * This should be use inside the servlet implementation block
652 **/
653#[macro_export]
654macro_rules! no_protocol {
655 () => {
656 type ProtocolType = ::plumber_rs::protocol::Untyped;
657 type DataModelType = ::plumber_rs::protocol::Untyped;
658 }
659}
660
661/**
662 * Initialize the protocol in the init function block.
663 *
664 * The syntax is folloowing
665 *
666 * ```
667 * init_protocol! {
668 * model_object {
669 * self.pipe_obj => pipe_in_protocol
670 * }
671 * }
672 * ```
673 *
674 * For details please read the `protodef!` doc
675 **/
676#[macro_export]
677macro_rules! init_protocol {
678 ($what:ident {$($actual:expr => $model:ident),*}) => {
679 {
680 let mut pipe_map = ::std::collections::HashMap::<String, ::plumber_rs::pipe::PipeDescriptor>::new();
681 $(pipe_map.insert(stringify!($model).to_string(), $actual.as_descriptor());)*
682 if !$what.init_model(pipe_map)
683 {
684 return ::plumber_rs::servlet::fail();
685 }
686 }
687 }
688}