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}