RPC Operation Interface

All RPC operations are data-driven within the server, using the YANG rpc statement for the operation and SIL callback functions.

Any new protocol operation can be added by defining a new YANG rpc statement in a module, and providing the proper SIL code.

RPC Callbacks

RPC Callback Template

The 'agt_rpc_method_t' typedef in agt/agt_rpc.h is used as the callback template for all RPC callback phases.

typedef status_t (*agt_rpc_method_t)(ses_cb_t *scb, rpc_msg_t *msg, xml_node_t *methnode)

Template for RPC server callbacks.

The same template is used for all RPC callback phases The callback is expected to validate if needed and then invoke if needed.

Param scb

session invoking the RPC

Param msg

message in progress for this <rpc> request

the msg->rpc_input value node contains the input (if any). It is a container matching the rpc/input node for the YANG rpc

Param methnode

XML node for the operation, which can be used in error reporting (or ignored)

Return

return status for the phase

  • An error in validate phase will cancel invoke phase

  • An rpc-error will be added if an error is returned and the msg error Q is empty

RPC Callback Initialization

The 'agt_rpc_register_method' function in agt/agt_rpc.h is used to provide a callback function for a specific callback phase. The same function can be used for multiple phases if desired.

This function call is usually generated automatically in the SIL or SIL-SA code stubs.

status_t agt_rpc_register_method(const xmlChar *module, const xmlChar *method_name, agt_rpc_phase_t phase, agt_rpc_method_t method)

add callback for 1 phase of RPC processing

Parameters
  • module -- name of the module that contains the RPC statement

  • method_name -- Identifier for the rpc statement

  • phase -- RPC server callback phase for this callback

    • AGT_PH_VALIDATE(0): validate phase

    • AGT_PH_INVOKE(1): invoke phase

    • AGT_PH_POST_REPLY(2): post-reply phase

  • method -- pointer to callback function to register

Returns

status of the operation

Example Registration Code:

res = agt_rpc_register_method(
    y_toaster_M_toaster,
    y_toaster_N_make_toast,
    AGT_RPC_PH_VALIDATE,
    y_toaster_make_toast_validate);
if (res != NO_ERR) {
    return res;
}

RPC Callback Cleanup

The 'agt_rpc_unregister_method' function in agt/agt_rpc.h is used to remove a callback function for all callback phases.

Warning

This function must be called once if any callback phase was registered. Memory may not be properly freed if an RPC callback is not properly unregistered.

This function call is usually generated automatically in the SIL or SIL-SA code stubs.

void agt_rpc_unregister_method(const xmlChar *module, const xmlChar *method_name)

remove the callback functions for all phases of RPC or Action processing for the specified RPC method or action

Parameters
  • module -- module name of RPC method or action name

  • method_name -- RPC method or action name

Example Unregistration Code:

agt_rpc_unregister_method(
    y_toaster_M_toaster,
    y_toaster_N_make_toast);

Force an RPC Operation to be Unsupported

It is possible for the SIL instrumentation to turn support for an RPC operation off in the server.

If this API is used then the RPC operation will be removed from the the list of operations that can be used in the server.

void agt_rpc_unsupport_method(const xmlChar *module, const xmlChar *method_name)

mark an RPC method or action as unsupported within the server

this is needed for operations dependent on capabilities

Parameters
  • module -- module name of RPC method (really module name)

  • method_name -- RPC method name

Use this function with care. It may break the server if this API is used on a server-provided RPC operation.

Example Usage:

agt_rpc_unsupport_method(
    y_toaster_M_toaster,
    y_toaster_N_make_toast);

RPC Message Header

The NETCONF server will parse the incoming XML message and construct an RPC message header, which is used to maintain state and any other message-specific data during the processing of an incoming <rpc> request.

rpc_msg_t

The 'rpc_msg_t' data structure defined in ncx/rpc.h is used to represent an RPC operation. It is a high-level control block which contains a low-level 'xml_msg_hdr_t' struct within it.

struct rpc_msg_t

NETCONF Server and Client RPC Request/Reply Message Header.

Public Members

dlq_hdr_t qhdr

Queue header to store RPC messages in a queue (within the session header)

xml_msg_hdr_t mhdr

generic XML message header Most in-message state is kept in the mhdr There are several places in the code where the mhdr is used alone so there is no coupling to the RPC layer.

Contains XML message prefix map and other data used to parse the request and generate the reply.

xml_attrs_t *rpc_in_attrs

incoming: top-level rpc element data Queue of xml_attr_t representing any XML attributes that were present in the <rpc> element.

A callback function may add xml_attr_t structs to this queue to send in the reply.

struct obj_template_t_ *rpc_method

incoming: Back-pointer to the object template for this RPC operation.

2nd-level method name element data, used in agt_output_filter to check get or get-config; cannot import obj.h here! The object template type will be OBJ_TYP_RPC for an RPC method For an action this will be the top-level <action> RPC wrapper in the YANG namespace

int rpc_agt_state

incoming: SERVER RPC processing state Enum value (0, 1, 2) for the current RPC callback phase.

op_errop_t rpc_err_option

Enum value for the <error-option> parameter.

This is only set if an edit operation is in progress.

op_editop_t rpc_top_editop

Enum value for the <default-operation> parameter.

This is only set if an edit operation is in progress.

val_value_t *rpc_input

Value tree representing the container of 'input' parameters for this RPC operation.

For an action, the rpc_input node is not used. Instead the rpc_actionval backptr is used instead. This is malloced in rpc_new_msg but not filled in.

struct sil_sa_cb_t_ *rpc_sil_sa_cb

backptr to SIL-SA edit control block if WITH_YCONTROL=1

dlq_hdr_t rpc_inputQ

the rpc_inputQ is used with JSON encoded input since an array is allowed at the top-level; it is used instead of rpc_input if encoding == JSON, even if only 1 array node is parsed;

Q of val_value_t *

void *rpc_user1

Void pointer that can be used by method routines to save context or whatever to store SIL-specific data.

This pointer is preserved, transferred from validate to invoke so data does not need to be regenerated or retrieved

void *rpc_user2

Same use as rpc_user1.

uint32 rpc_returncode

Internal return code used to control nested callbacks.

rpc_data_t rpc_data_type

incoming: get method reply handling builtin For RPC operations that return data, this enumeration MUST be set to indicate which type of data is returned.

  • RPC_DATA_STD: A <data> container will be used to encapsulate any returned data, within the <rpc-reply> element.

  • RPC_DATA_YANG: The <rpc-reply> element will be the only container encapsulated any returned data. If the rpc_datacb is non-NULL then it will be used as a callback to generate the rpc-reply inline, instead of buffering the output. The rpc_data and rpc_filter parameters are optionally used by the rpc_datacb function to generate a reply.

void *rpc_datacb

For operations that return streamed data, this pointer is set to the desired callback function to use for generated the data portion of the <rpc-reply> XML response.

The template for this callback is agt_rpc_data_cb_t, found in agt_rpc.h

dlq_hdr_t rpc_dataQ

For operations that return stored data, this queue of val_value_t structures can be used to provide the response data.

Each val_value_t structure will be encoded as one of the corresponding RPC output parameters. The data will be returned in order. The val_value_t nodes will be freed when the rpc_msg_t is freed

op_filter_t rpc_filter

Internal structure for optimizing subtree and XPath retrieval operations.

backptrs for get* methods.

struct agt_cfg_transaction_t_ *rpc_txcb

incoming: agent database edit transaction control block must be freed by an upper layer if set to malloced data

boolean rpc_parse_errors

load-config parse-error and &#8212;startup-error=continue flag if the val_purge_errors_from_root function is needed

xmlChar *rpc_message_id

debugging and audit message string.

Contains the message-id attribute found in the <rpc> header. backptr into rpc_in_attrs.

xmlChar *rpc_trace_id

debugging and audit message string.

Contains the trace-id attribute found in the <rpc> header. backptr into rpc_in_attrs.

boolean rpc_replay_config

TRUE if this RPC is being called in replay config mode.

boolean rpc_with_template

&#8212;with-template parameter was seen in the validate phase

dlq_hdr_t hook_inputQ

points to add_edit_value node comming from the users freed in the end of transaction.

Used only with set-hook. Contains Q of val_value_t.

boolean rpc_defer_reply

YPSERVER mode is skipping the regular rpc-reply phase and will send the reply after doing the remote task.

xml_attrs_t rpc_defer_in_attrs

the top->attrs gets deleted so a deferred rpc-reply needs to save the rpc_in_attrs.

This is malloced and cleaned when rpc-msg_t is freed

rpc_rpytyp_t rpc_reply_type

saved reply type needed for audit record

status_t rpc_status

saved processing status for audit record

time_t rpc_start_time

saved timestamp when started for audit record

const xmlChar *subrpc_filespec

saved by agt_db_api.c so the ycontrol callback function can generate an external value with the DB-API subrpc response message

RPC Validate Callback Function

../_images/rpc_validate_phase.png

The RPC validate callback function is optional to use. Its purpose is to validate any aspects of an RPC operation, beyond the constraints checked by the server engine. Only 1 validate function can register for each YANG rpc statement. The standard NETCONF operations are reserved by the server engine. There is usually zero or one of these callback functions for every 'rpc' statement in the YANG module associated with the SIL code.

It is enabled with the agt_rpc_register_method function, within the phase 1 initialization callback function.

The yangdump-sdk code generator will create this SIL callback function by default. There will be C comments in the code to indicate where your additional C code should be added.

The val_find_child (or val_child_find) function is commonly used to find particular parameters within the RPC input section, which is encoded as a val_value_t tree.

The agt_record_error function is commonly used to record any parameter or other errors. In the libtoaster example, there are internal state variables ("toaster_enabled" and "toaster_toasting"), maintained by the SIL code, which are checked in addition to any provided parameters.

Example RPC YANG definition:

    rpc make-toast {
        description
          "Make some toast.
           The toastDone notification will be sent when
           the toast is finished.
           An 'in-use' error will be returned if toast
           is already being made.
           A 'resource-denied' error will be returned
           if the toaster service is disabled.";
        input {
            leaf toasterDoneness {
                type uint32 {
                    range "1 .. 10";
                }
                default 5;
                description
                  "This variable controls how well-done is the
                   ensuing toast. It should be on a scale of 1 to 10.
                   Toast made at 10 generally is considered unfit
                   for human consumption; toast made at 1 is warmed
                   lightly.";
            }
            leaf toasterToastType {
                type identityref {
                    base toast:toast-type;
                }
                default toast:wheat-bread;
                description
                  "This variable informs the toaster of the type of
                   material that is being toasted. The toaster
                   uses this information, combined with
                   toasterDoneness, to compute for how
                   long the material must be toasted to achieve
                   the required doneness.";
            }
        }
    }

Example SIL Function:


/********************************************************************
* FUNCTION y_toaster_make_toast_validate
*
* RPC validation phase
* All YANG constraints have passed at this point.
* Add description-stmt checks in this function.
*
* INPUTS:
*     see agt/agt_rpc.h for details
*
* RETURNS:
*     error status
********************************************************************/
static status_t
    y_toaster_make_toast_validate (
        ses_cb_t *scb,
        rpc_msg_t *msg,
        xml_node_t *methnode)
{
    status_t res;
    val_value_t *errorval;
    const xmlChar *errorstr;
    val_value_t *toasterDoneness_val;
    val_value_t *toasterToastType_val;
    uint32 toasterDoneness;
    val_idref_t *toasterToastType;

    res = NO_ERR;
    errorval = NULL;
    errorstr = NULL;

    toasterDoneness_val = val_find_child(
        msg->rpc_input,
        y_toaster_M_toaster,
        y_toaster_N_toasterDoneness);
    if (toasterDoneness_val != NULL && toasterDoneness_val->res == NO_ERR) {
        toasterDoneness = VAL_UINT(toasterDoneness_val);
    }

    toasterToastType_val = val_find_child(
        msg->rpc_input,
        y_toaster_M_toaster,
        y_toaster_N_toasterToastType);
    if (toasterToastType_val != NULL && toasterToastType_val->res == NO_ERR) {
        toasterToastType = VAL_IDREF(toasterToastType_val);
    }



    /* added code starts here */
    if (toaster_enabled) {
        /* toaster service enabled, check if in use */


        if (toaster_toasting) {
            res = ERR_NCX_IN_USE;
        } else {
            /* this is where a check on bread inventory would go */

            /* this is where a check on toaster HW ready would go */
        }
    } else {
        /* toaster service disabled */
        res = ERR_NCX_RESOURCE_DENIED;
    }
    /* added code ends here */

    /* if error: set the res, errorstr, and errorval parms */
    if (res != NO_ERR) {
        agt_record_error(
            scb,
            &msg->mhdr,
            NCX_LAYER_OPERATION,
            res,
            methnode,
            NCX_NT_STRING,
            errorstr,
            NCX_NT_VAL,
            errorval);
    }

    return res;

} /* y_toaster_make_toast_validate */

RPC Invoke Callback Function

../_images/rpc_invoke_phase.png

The RPC invoke callback function is used to perform the operation requested by the client session. Only 1 invoke function can register for each YANG rpc statement. The standard NETCONF operations are reserved by the server engine. There is usually one of these callback functions for every 'rpc' statement in the YANG module associated with the SIL code.

The RPC invoke callback function is optional to use, although if no invoke callback is provided, then the operation will have no affect. Normally, this is only the case if the module is be tested by an application developer, using netconfd-pro as a server simulator.

It is enabled with the agt_rpc_register_method function, within the phase 1 initialization callback function.

The yangdump-sdk code generator will create this SIL callback function by default. There will be C comments in the code to indicate where your additional C code should be added.

For RPC operations that return either an <ok> or <rpc-error> response, there is nothing more required of the RPC invoke callback function.

For operations which return some data or <rpc-error>, the SIL code must do 1 of 2 additional tasks:

  • add a val_value_t structure to the 'rpc_dataQ' queue in the rpc_msg_t for each parameter listed in the YANG rpc 'output' section.

  • set the 'rpc_datacb' pointer in the rpc_msg_t structure to the address of your data reply callback function.

Example RPC Invoke SIL Function Registration

res = agt_rpc_register_method(
    y_toaster_M_toaster,
    y_toaster_N_make_toast,
    AGT_RPC_PH_INVOKE,
    y_toaster_make_toast_invoke);
if (res != NO_ERR) {
    return res;
}

Example RPC Invoke SIL Function


/********************************************************************
* FUNCTION y_toaster_make_toast_invoke
*
* RPC invocation phase
* All constraints have passed at this point.
* Call device instrumentation code in this function.
*
* INPUTS:
*     see agt/agt_rpc.h for details
*
* RETURNS:
*     error status
********************************************************************/
static status_t
    y_toaster_make_toast_invoke (
        ses_cb_t *scb,
        rpc_msg_t *msg,
        xml_node_t *methnode)
{
    status_t res;
    val_value_t *toasterDoneness_val;
    val_value_t *toasterToastType_val;
    uint32 toasterDoneness;
    val_idref_t *toasterToastType;

    res = NO_ERR;
    toasterDoneness = 0;

    toasterDoneness_val = val_find_child(
        msg->rpc_input,
        y_toaster_M_toaster,
        y_toaster_N_toasterDoneness);
    if (toasterDoneness_val != NULL && toasterDoneness_val->res == NO_ERR) {
        toasterDoneness = VAL_UINT(toasterDoneness_val);
    }

    toasterToastType_val = val_find_child(
        msg->rpc_input,
        y_toaster_M_toaster,
        y_toaster_N_toasterToastType);
    if (toasterToastType_val != NULL && toasterToastType_val->res == NO_ERR) {
        toasterToastType = VAL_IDREF(toasterToastType_val);
    }

    /* invoke your device instrumentation code here */

    /* make sure the toasterDoneness value is set */
    if (toasterDoneness_val == NULL) {
        toasterDoneness = 5;   /* set the default */
    }


    /* arbitrary formula to convert toaster doneness to the
     * number of seconds the toaster should be on
     */
    toaster_duration = toasterDoneness * 12;

    /* this is where the code would go to adjust the duration
     * based on the bread type
     */

    if (LOGDEBUG) {
        log_debug("\ntoaster: starting toaster for %u seconds",
                  toaster_duration);
    }

    /* this is where the code would go to start the toaster
     * heater element
     */

    /* start a timer to toast for the specified time interval */
    res = agt_timer_create(toaster_duration,
                           FALSE,
                           toaster_timer_fn,
                           NULL,
                           &toaster_timer_id);
    if (res == NO_ERR) {
        toaster_toasting = TRUE;
    } else {
        agt_record_error(
            scb,
            &msg->mhdr,
            NCX_LAYER_OPERATION,
            res,
            methnode,
            NCX_NT_NONE,
            NULL,
            NCX_NT_NONE,
            NULL);
    }
    /* added code ends here */

    return res;

} /* y_toaster_make_toast_invoke */

RPC Data Output Handling

RPC operations can return data to the client if the operation succeeds. The YANG “output” statement defines the return data for each RPC operation. Constructing YANG data is covered in detail elsewhere. This section shows a simple example SIL invoke function that returns data.

The output data is usually constructed with APIs like agt_make_leaf from agt/agt_util.h.

To use these APIs, the output object is needed.

The following code can be used to get this object:

obj_template_t *obj = RPC_MSG_METHOD(msg);
if (obj) {
    obj = obj_find_child(obj, NULL, NCX_EL_OUTPUT);
}
if (obj == NULL) {
    return ERR_NCX_DEF_NOT_FOUND;  // should not happen
}

Once this object template is retrieved from the 'msg' parameter data can be added to the 'msg' using val_value_t structures.

The following snippet shows a leaf being created using an int32 value:

int32 result = 42;
val_value_t *val =
     agt_make_int_leaf(obj,
                       y_addrpc_N_sum,
                       result,
                       &res);

After the value is created it must be added to the message using the 'agt_rpc_add_return_val' API:

void agt_rpc_add_return_val(val_value_t *return_val, rpc_msg_t *msg)

Add a return value to the msg.

Parameters
  • return_val -- value to add

  • msg -- message to add value into

if (val) {
   agt_rpc_add_return_val(val, msg);
}

RPC Data Output Example

Example YANG Module:

module addrpc {
  namespace "http://www.yumaworks.com/ns/addrpc";
  prefix add;
  revision "2020-02-25";

  rpc add {
    description "Get the sum of two numbers";
    input {
      leaf num1 {
        type int32;
        mandatory true;
        description "First number to add";
      }
      leaf num2 {
        type int32;
        mandatory true;
        description "Second number to add";
      }
    }
    output {
      leaf sum {
        type int32;
        mandatory true;
        description "The sum of the 2 numbers";
      }
    }
   }
}

Example SIL Invoke Function Returning Output Data:

/********************************************************************
* FUNCTION y_addrpc_add_invoke
*
* RPC invocation phase
* All constraints have passed at this point.
* Call device instrumentation code in this function.
*
* INPUTS:
*     see agt/agt_rpc.h for details
*
* RETURNS:
*     error status
********************************************************************/
static status_t y_addrpc_add_invoke (
    ses_cb_t *scb,
    rpc_msg_t *msg,
    xml_node_t *methnode)
{
    status_t res = NO_ERR;

    if (LOGDEBUG) {
        log_debug("\nStart SIL invoke rpc <add> from module addrpc");
    }

    val_value_t *inputval = agt_get_rpc_input(msg);
    if (inputval == NULL) return ERR_NCX_OPERATION_FAILED;

    val_value_t *v_num1_val = NULL;
    int32 v_num1 = 0;

    val_value_t *v_num2_val = NULL;
    int32 v_num2 = 0;

    v_num1_val = val_find_child(
        inputval,
        y_addrpc_M_addrpc,
        y_addrpc_N_num1);
    if (v_num1_val) {
        v_num1 = VAL_INT(v_num1_val);
    }

    v_num2_val = val_find_child(
        inputval,
        y_addrpc_M_addrpc,
        y_addrpc_N_num2);
    if (v_num2_val) {
        v_num2 = VAL_INT(v_num2_val);
    }

    /* remove the next line if scb is used */
    (void)scb;

    /* remove the next line if methnode is used */
    (void)methnode;

    /* invoke your device instrumentation code here */

    /* Following output nodes expected:
     * leaf sum
     */
    int32 result = v_num1 + v_num2;
    obj_template_t *obj = RPC_MSG_METHOD(msg);
    if (obj) {
        obj = obj_find_child(obj, NULL, NCX_EL_OUTPUT);
    }
    if (obj == NULL) {
        return ERR_NCX_DEF_NOT_FOUND;  // should not happen
    }

    val_value_t *val =
        agt_make_int_leaf(obj,
                          y_addrpc_N_sum,
                          result,
                          &res);
    if (val) {
        agt_rpc_add_return_val(val, msg);
    }

    return res;

} /* y_addrpc_add_invoke */

RPC Post Reply Callback Function

../_images/rpc_post_reply_phase.png

The RPC post-reply callback function is used to clean up after a message has been processed. Only 1 function can register for each YANG rpc statement. The standard NETCONF operations are reserved by the server engine. This callback is not needed unless the SIL validate or invoke callback allocated some memory that needs to deleted after the <rpc-reply> is sent.

The RPC post reply callback function is optional to use. It is enabled with the 'agt_rpc_register_method' function, within the phase 1 initialization callback function.

The yangdump-pro code generator will not create this SIL callback function by default.

Example SIL Function Registration

Example SIL Function:

/********************************************************************
* FUNCTION y_foo_command_post
*
* RPC post reply phase
*
* INPUTS:
*     see agt/agt_rpc.h for details
*
* RETURNS:
*     error status
********************************************************************/
static status_t
    y_foo_command_post (
        ses_cb_t *scb,
        rpc_msg_t *msg,
        xml_node_t *methnode)
{
    (void)scb;
    (void)methnode;
    if (msg->rpc_user1 != NULL) {
        m__free(msg->rpc_user1);
        msg->rpc_user1 = NULL;
    }
    return NO_ERR;
}  /* y_foo_command_post */