TR-069 example ============== Overview -------- [TR-069](https://www.broadband-forum.org/standards-and-software/major-projects/tr-069-and-its-evolution) (Technical Report 069) is a technical specification that defines an application layer protocol for remote management of end-user devices. It was published by the [Broadband Forum](http://www.broadband-forum.org) and entitled CPE WAN Management Protocol (CWMP). This example demonstrates how to use gSOAP to create clients and services for TR-069 common operations starting from the TR-069 schemas. The schemas are converted to code to implement a client and a service. The full presentation of TR-069 is beyond the scope of this document. [![To top](../../images/go-up.png) To top](#) Converting TR-069 schemas to code --------------------------------- From the [TR-069 schema files](https://www.broadband-forum.org/standards-and-software/technical-specifications/tr-069-files-tools#Schema%20Files) we select the CWMP 1.4 schema [cwmp-1-4.xsd](https://www.broadband-forum.org/cwmp/cwmp-1-4.xsd) to convert to C code. Other schemas such as TR-106 cwmp-datamodel.xsd can be included as necessary. We will focus on C in this example. The schema to C++ conversion is similar. First we must make wsdl2h accept HTTPS connections by building a secure version of wsdl2h: [command] cd gsoap/wsdl make secure cp wsdl2h some-path-to-bin/ This requires the installation of OpenSSL. Copy the wsdl2h executable to one of your top-level bin directories. If you cannot install OpenSSL, then the other option is to download the XSD files, and also all XSD files that are imported by the schemas, to a local folder. Because the schema import schemaLocation are absolute URLs instead of relative paths (relative paths are generally preferred), you will have to change the schemaLocation values in the XSD files to point to the files in your local folder. Try running wsdl2h as shown in the next step below and then download files that wsdl2h cannot access while editing the schemaLocation values. Repeat until all files are downloaded and modified. The next step is to run wsdl2h to generate the [`tr-069.h`](tr-069.h) XML data binding interface header file in C for soapcpp2: [command] wsdl2h -t typemap.dat -c -x -o tr-069.h https://www.broadband-forum.org/cwmp/cwmp-1-4.xsd where the [`typemap.dat`](typemap.dat) file contains the following type definitions and XML namespace prefix bindings: [command] # XSD type redefinitions that are type safe for TR-069 xsd__integer = typedef int64_t xsd__integer; xsd__nonNegativeInteger = typedef uint64_t xsd__nonNegativeInteger; xsd__positiveInteger = typedef uint64_t xsd__positiveInteger 1:; xsd__boolean = typedef enum xsd__boolean { false_, true_ } xsd__boolean; # TR-069 recommended prefixes # old 1.1 namespace: # cwmp = "urn:dslforum-org:cwmp-1-1" # new 1.2 (and upto 1.4) namespace: cwmp = "urn:dslforum-org:cwmp-1-2" lwn = "urn:broadband-forum-org:cwmp:lwnotif-1-0" dm = "urn:broadband-forum-org:cwmp:datamodel-1-5" dmr = "urn:broadband-forum-org:cwmp:datamodel-report-0-1" dt = "urn:broadband-forum-org:cwmp:devicetype-1-3" dtf = "urn:broadband-forum-org:cwmp:devicetype-features" xmpp = "urn:broadband-forum-org:cwmp:xmppConnReq-1-0" bdc = "urn:broadband-forum-org:ipdr:tr-232-1-0" ipdr = "http://www.ipdr.org/namespaces/ipdr" The wsdl2h command saves the [`tr-069.h`](tr-069.h) interface file that uses these [`typemap.dat`](typemap.dat) types and XML namespace prefix bindings. To generate C++ code instead of C: [command] wsdl2h -t typemap.dat -x -o tr-069.h https://www.broadband-forum.org/cwmp/cwmp-1-4.xsd To generate C++11 code, run wsdl2h with option `-c++11` as follows: [command] wsdl2h -t typemap.dat -c++11 -x -o tr-069.h https://www.broadband-forum.org/cwmp/cwmp-1-4.xsd Other TR-069 schemas can be added to the command line when executing the wsdl2h command if necessary to expand the TR-069 protocol coverage in your application, for example TR-106 cwmp-datamodel.xsd: [command] wsdl2h -t typemap.dat -c -x -o tr-069.h https://www.broadband-forum.org/cwmp/cwmp-1-4.xsd \ https://www.broadband-forum.org/cwmp/cwmp-datamodel-1-1.xsd Because the set of available TR-069 documents does not include a WSDL document with Web service definitions, you will have to declare the TR-069 CWMP common operations yourself. Fortunately, this is very easy in gSOAP as we will show next. It does not require you to write a WSDL file. [![To top](../../images/go-up.png) To top](#) Creating wrapper functions for TR-069 CWMP messages --------------------------------------------------- As described in the [tutorials](../../tutorials.html), we can simply create wrapper functions for the request and response messages that are defined in the [`tr-069.h`](tr-069.h) file. We also need to declare the `SOAP_ENV__Header` structure for SOAP headers and the `SOAP_ENV__Detail` structure for SOAP fault details. We specify these declarations in a new file [`trapi.h`](trapi.h) with the services API: /* import tr-069.h generated by wsdl2h */ #import "tr-069.h" /** TR-069 SOAP headers */ struct SOAP_ENV__Header { struct _cwmp__SupportedCWMPVersions *cwmp__SupportedCWMPVersions; struct _cwmp__UseCWMPVersion *cwmp__UseCWMPVersion; struct _cwmp__SessionTimeout *cwmp__SessionTimeout; struct _cwmp__ID *cwmp__ID; struct _cwmp__HoldRequests *cwmp__HoldRequests; }; /** TR-069 SOAP fault details */ struct SOAP_ENV__Detail { struct _cwmp__Fault *cwmp__Fault; _XML __any; /* catch all else in an XML string */ }; /* TR-069 common operations */ //gsoap cwmp service name: cwmp //gsoap cwmp service style: rpc //gsoap cwmp service encoding: encoded //gsoap cwmp service method-fault: GetParameterNames cwmp__Fault /** GetParameterNames: Retrieve list of supported parameters from the device. */ int __cwmp__GetParameterNames( struct _cwmp__GetParameterNames *cwmp__GetParameterNames, struct _cwmp__GetParameterNamesResponse *cwmp__GetParameterNamesResponse); //gsoap cwmp service method-fault: GetParameterValues cwmp__Fault /** GetParameterValues: Retrieve current value of the parameter(s) identified by keys. A variation of this call takes an object as its key. It retrieves all of the object's parameters */ int __cwmp__GetParameterValues( struct _cwmp__GetParameterValues *cwmp__GetParameterValues, struct _cwmp__GetParameterValuesResponse *cwmp__GetParameterValuesResponse); //gsoap cwmp service method-fault: GetParameterValuesResponse cwmp__Fault /** GetParameterValuesResponse: Send current value of the parameter(s) identified by keys. This is a SOAP one-way message invoked as soap_send___cwmp__GetParameterValuesResponse() followed by soap_recv_empty_response() to check the server's response that should be a HTTP 204 status code */ int __cwmp__GetParameterValuesResponse( struct _cwmp__GetParameterValuesResponse *cwmp__GetParameterValuesResponse, void); //gsoap cwmp service method-input-header-part: SetParameterValues cwmp__ID //gsoap cwmp service method-fault: SetParameterValues cwmp__Fault /** SetParameterValues: Set the value of one or more parameters */ int __cwmp__SetParameterValues( struct _cwmp__SetParameterValues *cwmp__SetParameterValues, struct _cwmp__SetParameterValuesResponse *cwmp__SetParameterValuesResponse); //gsoap cwmp service method-fault: GetParameterAttributes cwmp__Fault /** GetParameterAttributes: Retrieve attributes of one or more parameters */ int __cwmp__GetParameterAttributes( struct _cwmp__GetParameterAttributes *cwmp__GetParameterAttributes, struct _cwmp__GetParameterAttributesResponse *cwmp__GetParameterAttributesResponse); //gsoap cwmp service method-fault: SetParameterAttributes cwmp__Fault /** SetParameterAttributes: Set attributes of one or more parameters */ int __cwmp__SetParameterAttributes( struct _cwmp__SetParameterAttributes *cwmp__SetParameterAttributes, struct _cwmp__SetParameterAttributesResponse *cwmp__SetParameterAttributesResponse); //gsoap cwmp service method-fault: Download cwmp__Fault /** Download: Order CPE to download and use a file, specified by URL. File types include Firmware Image, Configuration File, Ringer file, etc. */ int __cwmp__Download( struct _cwmp__Download *cwmp__Download, struct _cwmp__DownloadResponse *cwmp__DownloadResponse); //gsoap cwmp service method-fault: Upload cwmp__Fault /** Upload: Order CPE to upload a file to a specified destination. File types include the current configuration file, log files, etc. */ int __cwmp__Upload( struct _cwmp__Upload *cwmp__Upload, struct _cwmp__Uploa # old 1.1 namespace: # cwmp = "urn:dslforum-org:cwmp-1-1" # new 1.2 (and upto 1.4) namespace: cwmp = "urn:dslforum-org:cwmp-1-2" dResponse *cwmp__UploadResponse); //gsoap cwmp service method-fault: AddObject cwmp__Fault /** AddObject: Add new instance to an object */ int __cwmp__AddObject( struct _cwmp__AddObject *cwmp__AddObject, struct _cwmp__AddObjectResponse *cwmp__AddObjectResponse); //gsoap cwmp service method-fault: DeleteObject cwmp__Fault /** DeleteObject: Remove instance from an object */ int __cwmp__DeleteObject( struct _cwmp__DeleteObject *cwmp__DeleteObject, struct _cwmp__DeleteObjectResponse *cwmp__DeleteObjectResponse); //gsoap cwmp service method-fault: GetRPCMethods cwmp__Fault /** _cwmp__GetRPCMethods: Annex A.3.1.1 */ int __cwmp__GetRPCMethods( struct _cwmp__GetRPCMethods *cwmp__GetRPCMethods, struct _cwmp__GetRPCMethodsResponse *cwmp__GetRPCMethodsResponse); When generating C++ code with wsdl2h option `-c++` or `-c++11`, these declarations should use the class names instead of structs, except for the declarations of `SOAP_ENV__Header` and `SOAP_ENV__Detail` which may be either a class or a struct. Let's break this down: - The `#import "tr-069.h"` imports the wsdl2h-generated [`tr-069.h`](tr-069.h) file into our [`trapi.h`](trapi.h) specification. - The `SOAP_ENV__Header` structure contains pointer-based members since these are optional. Each member is declared in [`tr-069.h`](tr-069.h) as a SOAP header structure. These structures contain a *`SOAP-ENV:mustUnderstand`* attribute that when set to `"1"` requires the receiver to process the corresponding SOAP header element as per protocol logic. - The `SOAP_ENV__Detail` structure contains a pointer-based (since it is optional) member `cwmp__Fault` for CWMP faults. - The `//gsoap cwmp service name: cwmp` directive declares a short name `cwmp` for the CWMP service API. - The `//gsoap cwmp service style: rpc` and `//gsoap cwmp service encoding: encoded` directives declare that SOAP RPC encoding must be used for the CWMP service API. - All wrapper function names have leading double underscores. Each function's parameters consist of a request message and a response message. - The `//gsoap cwmp service method-fault` directives specify that SOAP faults with details can be raised by the service operations. [![To top](../../images/go-up.png) To top](#) Implementing the TR-069 CWMP client ----------------------------------- Because the service operations are similar with respect to the C source code required, only one service operation call is implemented in the example client code [`trapi.c`](trapi.c) for demonstration purposes as shown below: #include "soapH.h" #include "cwmp.nsmap" void tr069_print_fault(struct soap *soap); /* defined further below */ void SetParameterValues(struct soap *soap, const char *URL) { struct _cwmp__SetParameterValues req; struct _cwmp__SetParameterValuesResponse res; /* construct request SetParameterValues */ req.ParameterList = soap_new_ParameterValueList(soap, -1); /* -1 means one (non-array) copy */ if (!req.ParameterList) return; /* construct SOAP array ParameterList with one element */ req.ParameterList->__size = 1; req.ParameterList->__ptrParameterValueStruct = (struct cwmp__ParameterValueStruct**)soap_malloc(soap, req.ParameterList->__size * sizeof(struct cwmp__ParameterValueStruct*)); if (!req.ParameterList->__ptrParameterValueStruct) return; req.ParameterList->__ptrParameterValueStruct[0] = soap_new_cwmp__ParameterValueStruct(soap, -1); if (!req.ParameterList->__ptrParameterValueStruct[0]) return; req.ParameterList->__ptrParameterValueStruct[0]->Name = soap_strdup(soap, "Device.WiFi.AccessPoint.10001.Enable"); req.ParameterList->__ptrParameterValueStruct[0]->Value = soap_strdup(soap, "1"); /* construct ParameterKey */ req.ParameterKey = soap_strdup(soap, "bulk_set_1"); /* construct the SOAP header */ soap->header = NULL; soap_header(soap); /* allocate a new SOAP header */ soap->header->cwmp__ID = soap_new__cwmp__ID(soap, -1); if (!soap->header->cwmp__ID) return; soap->header->cwmp__ID->__item = soap_strdup(soap, "112"); /* make the call */ if (soap_call___cwmp__SetParameterValues(soap, URL, NULL, &req, &res)) tr069_print_fault(soap); else printf("Response Status = %d\n", res.Status); } int main(int argc, char **argv) { /* set up context and initializations */ struct soap *soap = soap_new1(SOAP_IO_KEEPALIVE | SOAP_XML_INDENT | SOAP_XML_STRICT); soap->connect_timeout = 60; /* 60 sec */ soap->send_timeout = 10; /* 10 sec */ soap->recv_timeout = 10; /* 10 sec */ soap->transfer_timeout = 30; /* 30 sec */ /* make the call */ SetParameterValues(soap, argc < 2 ? "" : argv[1]); /* close HTTP keep-alive connection and clean up */ soap_force_closesock(soap); soap_destroy(soap); soap_end(soap); soap_free(soap); return 0; } void tr069_print_fault(struct soap *soap) { /* print the fault */ soap_print_fault(soap, stderr); /* then print TR069-specific fault, if present */ if (soap->fault && soap->fault->detail) { struct _cwmp__Fault *fault = soap->fault->detail->cwmp__Fault; if (fault) { int i; if (fault->FaultCode) printf("FaultCode = %s\n", fault->FaultCode); if (fault->FaultString) printf("FaultString = %s\n", fault->FaultString); for (i = 0; i < fault->__sizeSetParameterValuesFault; i++) { if (fault->SetParameterValuesFault[i].ParameterName) printf("ParameterName[%d] = %s\n", i, fault->SetParameterValuesFault[i].ParameterName); if (fault->SetParameterValuesFault[i].FaultCode) printf("FaultCode[%d] = %s\n", i, fault->SetParameterValuesFault[i].FaultCode); if (fault->SetParameterValuesFault[i].FaultString) printf("FaultString[%d] = %s\n", i, fault->SetParameterValuesFault[i].FaultString); } } } } Note that the socket connection is kept alive with `SOAP_IO_KEEPALIVE` to permit other calls to flow through uninterrupted. The connection is closed with `soap_force_closesock` and by `soap_free`, but using `soap_force_closesock` is more flexible. Actually, to make this easier on the server we should indicate to the server that when we make the last call the client closes the socket as follows: /* disable keep-alive */ soap_clr_mode(soap, SOAP_IO_KEEPALIVE); if (soap_call___cwmp__SetParameterValues(soap, URL, NULL, &req, &res)) ... /* socket connection is closed after the call */ ... /* re-enable keep-alive, in case we make another sequence of keep-alive connection calls */ soap_set_mode(soap, SOAP_IO_KEEPALIVE); It is not difficult to expand the code to invoke other CWMP common operations. A slightly more difficult CWMP common operation to invoke is `GetParameterValues` after an `Inform` operation message exchange, because we need to send an empty HTTP POST message to receive a `GetParameterValues`. To do so, we make some lower-level gSOAP API function calls that are similar to the soapcpp2-generated `soap_call___cwmp__GetParameterValues` code in [`soapClient.c`](soapClient.c) but stripping off the sending part and basically replacing it with `soap_POST` and `soap_end_send`: int GetParameterValues(struct soap *soap, const char *URL, struct _cwmp__GetParameterValues *res) { /* send empty HTTP POST */ if (soap_POST(soap, URL, NULL, NULL) || soap_end_send(soap)) return soap_closesock(soap); /* receive SOAP message containing GetParameterValues */ if (soap_begin_recv(soap) || soap_envelope_begin_in(soap) || soap_recv_header(soap) || soap_body_begin_in(soap)) return soap_closesock(soap); if (soap_recv_fault(soap, 1)) return soap->error; soap_get__cwmp__GetParameterValues(soap, res, "", NULL); if (soap->error) return = soap_recv_fault(soap, 0); if (soap_body_end_in(soap) || soap_envelope_end_in(soap) || soap_end_recv(soap)) return soap_closesock(soap); return soap_closesock(soap); } Note that `soap_closesock` only closes the socket connection if keep-alive is disabled. If keep-alive is still enabled, which depends on the server's response to keep it open, the connection will remain open. See the [HTTP and IO functions](../../doc/guide/html/group__group__io.html) API documentation module. To send the `GetParameterValuesResponse` message to the server, use the SOAP one-way operation `__cwmp__GetParameterValuesResponse` as follows: struct _cwmp__GetParameterValuesResponse res; ... // populate res ... // set the soap->header->cwmp__ID; if (soap_send___cwmp__GetParameterValuesResponse(soap, endpoint, NULL, &res)) ... // error, could not send int err = soap_recv_empty_response(soap); if (err && err != 204) ... // error, we expect HTTP 204 For more details on how to set and get values from XML data bindings in C and in C++, see the [XML data binding](../../doc/databinding/html/index.html) documentation. This example does not perform authentication, which is unrealistic in TR-069. See the [tutorials](../../tutorials.html) for more information about authentication. [![To top](../../images/go-up.png) To top](#) Build steps for the TR-069 CWMP client -------------------------------------- Use the soapcpp2 tool on the [`trapi.h`](tr-069.h) file to generate the XML data binding implementation code in C: [command] soapcpp2 -c -C r trapi.h This generates client-side code (`-C` option) in C (`-c` option) and a report (`-r` option): * [`soapStub.h`](soapStub.h) a copy of the specification in plain C/C++ header file syntax without annotations. * [`soapH.h`](soapH.h) declares XML serializers. * [`soapC.c`](soapC.c) implements XML serializers. * [`soapClient.c`](soapClient.c) defines the client-side XML services API * [`cwmp.nsmap`](cwmp.nsmap) XML namespace binding table, you should #include this file. * [`soapReadme.md`](soapReadme.html) service and data binding interface details. Compile the source code files together with `stdsoap2.h` and `stdsoap2.c`. For example: [command] cc -DWITH_OPENSSL -o trapi trapi.c soapC.c soapClient.c stdsoap2.c -lssl -lcrypto We used option `-DWITH_OPENSSL` and linkage with `-lssl` and `-lcrypto` to enable HTTPS. For C++ applications, use the same soapcpp2 command to generate C++ source code. Compile the generated C++ files and `stdsoap2.cpp`. You could use soapcpp2 option `-j` to generate a C++ proxy class to implement the client. The generated report will help you out. [![To top](../../images/go-up.png) To top](#) Running the TR-069 CWMP client ------------------------------ To run the `trapi` client, pass a URL to the command line. If no command line argument is given the client will print the SOAP message to stdout and read from stdin, which we can use to test the client with the auto-generated sample SOAP response message: [command] ./trapi < cwmp.SetParameterValues.res.xml which displays the SOAP request message: [xml] <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:cwmp="urn:dslforum-org:cwmp-1-2"> <SOAP-ENV:Header> <cwmp:ID SOAP-ENV:mustUnderstand="1">112</cwmp:ID> </SOAP-ENV:Header> <SOAP-ENV:Body SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> <cwmp:SetParameterValues> <ParameterList SOAP-ENC:arrayType="cwmp:ParameterValueStruct[1]"> <ParameterValueStruct> <Name>Device.WiFi.AccessPoint.10001.Enable</Name> <Value>1</Value> </ParameterValueStruct> </ParameterList> <ParameterKey>bulk_set_1</ParameterKey> </cwmp:SetParameterValues> </SOAP-ENV:Body> </SOAP-ENV:Envelope> and the response: [command] Response Status = 0 The `cwmp.SetParameterValues.res.xml` file was generated in an earlier step by soapcpp2 as an example template SOAP response. [![To top](../../images/go-up.png) To top](#) Implementing a TR-069 CWMP server application --------------------------------------------- We will show a combined CGI and stand-alone server application. The server applications runs as a simple iterative stand-alone service on the port specified on the command line. The application runs as a CGI application when no command-line port argument is given. To implement multi-threaded services, see our [tutorials](../../tutorials.html). See the [documentation](https://www.genivia.com/docs.html) on how to use ISAPI and Apache modules to develop and deploy services. The server application `trapiserver.c` implements one service operation `SetParameterValues` as a demo and accepts requests on stdin using `soap_serve(soap)` that dispatches the request to one of the service operations: #include "soapH.h" #include "cwmp.nsmap" int main(int argc, char **argv) { struct soap *soap = soap_new1(SOAP_XML_INDENT); /* new context */ if (argc < 2) { soap_serve(soap); /* serve CGI request */ } else { int port = atoi(argv[1]); /* get port number given on the command line */ soap_set_mode(soap, SOAP_IO_KEEPALIVE); /* enable HTTP keep-alive connection */ soap->send_timeout = soap->recv_timeout = 5; /* max send and receive socket inactivity time (sec) */ soap->transfer_timeout = 10; /* max time for send or receive of messages (sec) */ if (soap_valid_socket(soap_bind(soap, NULL, port, 1))) /* backlog=1 for iterative servers, multi-threaded services should use 100 */ { while (soap_valid_socket(soap_accept(soap))) { soap_serve(soap); /* serve request, ignoring failures */ soap_destroy(soap); /* delete deserialized objects */ soap_end(soap); /* delete heap and temp data */ } } } soap_destroy(soap); /* delete deserialized objects */ soap_end(soap); /* delete heap and temp data */ soap_free(soap); /* we're done with the context */ return 0; } /** GetParameterNames: Retrieve list of supported parameters from the device. */ int __cwmp__GetParameterNames( struct soap *soap, struct _cwmp__GetParameterNames *cwmp__GetParameterNames, struct _cwmp__GetParameterNamesResponse *cwmp__GetParameterNamesResponse) { return SOAP_NO_METHOD; /* not implemented in this example */ } /** GetParameterValues: Retrieve current value of the parameter(s) identified by keys. A variation of this call takes an object as its key. It retrieves all of the object's parameters */ int __cwmp__GetParameterValues( struct soap *soap, struct _cwmp__GetParameterValues *cwmp__GetParameterValues, struct _cwmp__GetParameterValuesResponse *cwmp__GetParameterValuesResponse) { return SOAP_NO_METHOD; /* not implemented in this example */ } /** SetParameterValues: Set the value of one or more parameters */ int __cwmp__SetParameterValues( struct soap *soap, struct _cwmp__SetParameterValues *cwmp__SetParameterValues, struct _cwmp__SetParameterValuesResponse *cwmp__SetParameterValuesResponse) { if (!soap->header || !soap->header->cwmp__ID) return soap_sender_fault(soap, "SOAP header cwmp:ID required", NULL); soap->header = NULL; cwmp__SetParameterValuesResponse->Status = 0; if (cwmp__SetParameterValues->ParameterList && cwmp__SetParameterValues->ParameterList->__size > 0) { if (cwmp__SetParameterValues->ParameterList->__ptrParameterValueStruct[0]) { char *name = cwmp__SetParameterValues->ParameterList->__ptrParameterValueStruct[0]->Name; char *value = cwmp__SetParameterValues->ParameterList->__ptrParameterValueStruct[0]->Value; /* use name and value here (not shown) */ } cwmp__SetParameterValuesResponse->Status = 1; } return SOAP_OK; } /** GetParameterAttributes: Retrieve attributes of one or more parameters */ int __cwmp__GetParameterAttributes( struct soap *soap, struct _cwmp__GetParameterAttributes *cwmp__GetParameterAttributes, struct _cwmp__GetParameterAttributesResponse *cwmp__GetParameterAttributesResponse) { return SOAP_NO_METHOD; /* not implemented in this example */ } /** SetParameterAttributes: Set attributes of one or more parameters */ int __cwmp__SetParameterAttributes( struct soap *soap, struct _cwmp__SetParameterAttributes *cwmp__SetParameterAttributes, struct _cwmp__SetParameterAttributesResponse *cwmp__SetParameterAttributesResponse) { return SOAP_NO_METHOD; /* not implemented in this example */ } /** Download: Order CPE to download and use a file, specified by URL. File types include Firmware Image, Configuration File, Ringer file, etc. */ int __cwmp__Download( struct soap *soap, struct _cwmp__Download *cwmp__Download, struct _cwmp__DownloadResponse *cwmp__DownloadResponse) { return SOAP_NO_METHOD; /* not implemented in this example */ } /** Upload: Order CPE to upload a file to a specified destination. File types include the current configuration file, log files, etc. */ int __cwmp__Upload( struct soap *soap, struct _cwmp__Upload *cwmp__Upload, struct _cwmp__UploadResponse *cwmp__UploadResponse) { return SOAP_NO_METHOD; /* not implemented in this example */ } /** AddObject: Add new instance to an object */ int __cwmp__AddObject( struct soap *soap, struct _cwmp__AddObject *cwmp__AddObject, struct _cwmp__AddObjectResponse *cwmp__AddObjectResponse) { return SOAP_NO_METHOD; /* not implemented in this example */ } /** DeleteObject: Remove instance from an object */ int __cwmp__DeleteObject( struct soap *soap, struct _cwmp__DeleteObject *cwmp__DeleteObject, struct _cwmp__DeleteObjectResponse *cwmp__DeleteObjectResponse) { return SOAP_NO_METHOD; /* not implemented in this example */ } /** _cwmp__GetRPCMethods: Annex A.3.1.1 */ int __cwmp__GetRPCMethods( struct soap *soap, struct _cwmp__GetRPCMethods *cwmp__GetRPCMethods, struct _cwmp__GetRPCMethodsResponse *cwmp__GetRPCMethodsResponse) { return SOAP_NO_METHOD; /* not implemented in this example */ } This example does not require authentication, which is unrealistic in TR-069. See the [tutorials](../../tutorials.html) for more information about authentication. To implement a server that responds to an empty HTTP POST request, we will use the [HTTP POST plugin](../../doc/guide/html/group__group__plugin.html), which requires gSOAP 2.8.72 or greater to handle empty HTTP POST requests: #include "plugin/httppost.h" #include "soapH.h" #include "cwmp.nsmap" int POST_handler(struct soap *soap); struct http_post_handlers http_post_handlers[] = { { "POST", POST_handler }, { NULL } }; int main(int argc, char **argv) { struct soap *soap = soap_new1(SOAP_XML_INDENT); soap_register_plugin_arg(soap, http_post, (void*)http_post_handlers); ... /* pass a pointer to some user data (e.g. server state information) to the handler */ soap->user = (void*)...; } int POST_handler(struct soap *soap) { struct _cwmp__GetParameterValues *res = soap_new__cwmp__GetParameterValues(soap, -1); struct ParameterNames *list = soap_new_ParameterNames(soap, -1); /* if HTTP has a content type header or a HTTP body is present then return 404 */ if (soap->http_content || soap_has_http_body(soap)) return 404; (void)soap_end_recv(soap); /* populate the response with one array item */ if (res && list) { list->__size = 1; if ((list->__ptrstring = (char**)soap_malloc(soap, list->__size * sizeof(char*))) != NULL) { list->__ptrstring[0] = soap_strdup(soap, "..."); res->ParameterNames = list; } } /* send response */ soap->encodingStyle = ""; soap_serializeheader(soap); soap_serialize__cwmp__GetParameterValues(soap, res); if (soap_begin_count(soap)) return soap->error; if (soap->mode & SOAP_IO_LENGTH) { if (soap_envelope_begin_out(soap) || soap_putheader(soap) || soap_body_begin_out(soap) || soap_put__cwmp__GetParameterValues(soap, res, "cwmp:GetParameterValues", "") || soap_body_end_out(soap) || soap_envelope_end_out(soap)) return soap->error; }; if (soap_end_count(soap) || soap_response(soap, SOAP_OK) || soap_envelope_begin_out(soap) || soap_putheader(soap) || soap_body_begin_out(soap) || soap_put__cwmp__GetParameterValues(soap, res, "cwmp:GetParameterValues", "") || soap_body_end_out(soap) || soap_envelope_end_out(soap) || soap_end_send(soap)) return soap->error; return soap_closesock(soap); } A `GetParameterValuesResponse` is returned by the service. This code makes some lower-level gSOAP API function calls that are similar to the soapcpp2-generated `soap_serve___cwmp__GetParameterValues` code in [`soapServer.c`](soapServer.c) but stripping off the first part. The `soap_has_http_body` function is new in gSOAP 2.8.72. In case you don't have it: soap_has_http_body(struct soap *soap) { return soap->length || (soap->mode & SOAP_ENC_ZLIB) || (soap->mode & SOAP_IO) == SOAP_IO_CHUNK; } Note that we use `void* soap::user` to pass a pointer to user data, for example with server state information, to the `POST_handler`, which is up to the server implementation. [![To top](../../images/go-up.png) To top](#) Build steps for the TR-069 CWMP server -------------------------------------- Use the soapcpp2 tool on the [`trapi.h`](tr-069.h) file: [command] soapcpp2 -c -S -r trapi.h This generates server-side code (`-S` option) in C (`-c` option) and a report (`-r` option): * [`soapStub.h`](soapStub.h) a copy of the specification in plain C/C++ header file syntax without annotations. * [`soapH.h`](soapH.h) declares XML serializers. * [`soapC.c`](soapC.c) implements XML serializers. * [`soapServer.c`](soapServer.c) defines the server-side XML services API * [`cwmp.nsmap`](cwmp.nsmap) XML namespace binding table, you should #include this file. * [`soapReadme.md`](soapReadme.html) service and data binding interface details. Compile the source code files together with `stdsoap2.h` and `stdsoap2.c`. For example: [command] cc -DWITH_OPENSSL -o trapiserver trapiserver.c soapC.c soapServer.c stdsoap2.c -lssl -lcrypto We used option `-DWITH_OPENSSL` and linkage with `-lssl` and `-lcrypto` to enable HTTPS. For C++ applications, use the same soapcpp2 command to generate C++ source code. Compile the generated C++ files and `stdsoap2.cpp`. You could use soapcpp2 option `-j` to generate a C++ service class to implement the server. The generated report will help you out. [![To top](../../images/go-up.png) To top](#) Testing the TR-069 CWMP server ------------------------------ Because CGI applications use stdin and stdout to communicate, you can test the CGI server application at the command line: [command] ./trapiserver < cwmp.SetParameterValues.req.xml Status: 200 OK Server: gSOAP/2.8 X-Frame-Options: SAMEORIGIN Content-Type: text/xml; charset=utf-8 Content-Length: 534 Connection: close <?xml version="1.0" encoding="UTF-8"?> <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:cwmp="urn:dslforum-org:cwmp-1-2"> <SOAP-ENV:Body SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> <cwmp:SetParameterValuesResponse> <Status>1</Status> </cwmp:SetParameterValuesResponse> </SOAP-ENV:Body> </SOAP-ENV:Envelope> The `cwmp.SetParameterValues.req.xml` file was generated in an earlier step by soapcpp2 as an example template SOAP request. Because the template SOAP request message has no valid SOAP header *`cwmp:ID`* the service produces a fault. You can edit the files as needed for more testing before deploying services. For more extensive testing use our [Test Messenger](../../doc/testmsgr/html/index.html) application that comes with gSOAP. [![To top](../../images/go-up.png) To top](#) Improvements: serializing xsd:anySimpleType in C ------------------------------------------------ The CWMP 1.4 schema *`cwmp:ParameterValueStruct`* type has a *`Value`* element of type *`xsd:anySimpleType`*. This value should be populated by a derived type from *`xsd:anySimpleType`*. The simple XML string type generated by default by wsdl2h for `xsd:anySimpleType`* won't suffice in general. The *`xsd:anySimpleType`* type may also contain *`SOAP-ENC:base64`* data, so we should make some changes to accommodate this requirement. The gSOAP 2.8.83 wsdl2h tool adds a new option `-Q` that can be used together with option `-F` to achieve similar results as shown here but without requiring `typemap.dat` edits. To implement our application in C with support for *`xsd:anySimpleType`* we change the [`typemap.dat`](typemap.dat) file as follows: [command] # XSD type redefinitions that are type safe for TR-069 xsd__anySimpleType = struct xsd__anySimpleType { \ [ struct xsd__anyType *xsd__anyType; ] \ [ struct SOAP_ENC__base64 *SOAP_ENC__base64; ] \ }; | struct xsd__anySimpleType xsd__integer = typedef int64_t xsd__integer; xsd__nonNegativeInteger = typedef uint64_t xsd__nonNegativeInteger; xsd__positiveInteger = typedef uint64_t xsd__positiveInteger 1:; xsd__boolean = typedef enum xsd__boolean { false_, true_ } xsd__boolean; # TR-069 recommended prefixes # old 1.1 namespace: # cwmp = "urn:dslforum-org:cwmp-1-1" # new 1.2 (and upto 1.4) namespace: cwmp = "urn:dslforum-org:cwmp-1-2" lwn = "urn:broadband-forum-org:cwmp:lwnotif-1-0" dm = "urn:broadband-forum-org:cwmp:datamodel-1-5" dmr = "urn:broadband-forum-org:cwmp:datamodel-report-0-1" dt = "urn:broadband-forum-org:cwmp:devicetype-1-3" dtf = "urn:broadband-forum-org:cwmp:devicetype-features" xmpp = "urn:broadband-forum-org:cwmp:xmppConnReq-1-0" bdc = "urn:broadband-forum-org:ipdr:tr-232-1-0" ipdr = "http://www.ipdr.org/namespaces/ipdr" Then run: [command] wsdl2h -t typemap.dat -c -F -x -o tr-069.h https://www.broadband-forum.org/cwmp/cwmp-1-4.xsd Option `-F` generates additional members for structs to support a form of inheritance by simulating derived types. See the manual for more details on wsdl2h option `-F`. The `xsd__anySimpleType` structure we created contains two pointer members that are used to simulate inheritance. This means that we should set the `xsd__anyType` member of `xsd__anySimpleType` to a pointer to a `xsd__anyType` structure with the actual value to serialize. The `cwmp__ParameterValueStruct::Value` is now a `xsd__anySimpleType` that can be assigned any other typed value derived from `xsd__anyType`. For example, to assign `cwmp__ParameterValueStruct::Value` a `xsd__int` we use the `xsd__int_` wrapper by the last three changed lines shown below in our TR-069 CWMP C client: void SetParameterValues(struct soap *soap, const char *URL) { struct _cwmp__SetParameterValues req; struct _cwmp__SetParameterValuesResponse res; /* construct request SetParameterValues */ req.ParameterList = soap_new_ParameterValueList(soap, -1); /* -1 means one (non-array) copy */ if (!req.ParameterList) return; /* construct SOAP array ParameterList with one element */ req.ParameterList->__size = 1; req.ParameterList->__ptrParameterValueStruct = (struct cwmp__ParameterValueStruct**)soap_malloc(soap, req.ParameterList->__size * sizeof(struct cwmp__ParameterValueStruct*)); if (!req.ParameterList->__ptrParameterValueStruct) return; req.ParameterList->__ptrParameterValueStruct[0] = soap_new_cwmp__ParameterValueStruct(soap, -1); if (!req.ParameterList->__ptrParameterValueStruct[0]) return; req.ParameterList->__ptrParameterValueStruct[0]->Name = soap_strdup(soap, "Device.WiFi.AccessPoint.10001.Enable"); req.ParameterList->__ptrParameterValueStruct[0]->Value.xsd__anyType = soap_new_xsd__anyType(soap, -1); req.ParameterList->__ptrParameterValueStruct[0]->Value.xsd__anyType->xsd__int = soap_new_xsd__int_(soap, -1); req.ParameterList->__ptrParameterValueStruct[0]->Value.xsd__anyType->xsd__int->__item = 1; [![To top](../../images/go-up.png) To top](#) Improvements: serializing xsd:anySimpleType in C++ -------------------------------------------------- The CWMP 1.4 schema *`cwmp:ParameterValueStruct`* type has a *`Value`* element of type *`xsd:anySimpleType`*. This value should be populated by a derived type from *`xsd:anySimpleType`*. The simple XML string type generated by default by wsdl2h for `xsd:anySimpleType`* won't suffice in general. The *`xsd:anySimpleType`* type may also contain *`SOAP-ENC:base64`* data, so we should make some changes to accommodate this requirement. The gSOAP 2.8.83 wsdl2h tool adds a new option `-Q` that can be used together with option `-p` to achieve similar results but without requiring `typemap.dat` edits. To implement our application in C++ with support for *`xsd:anySimpleType`* we change the [`typemap.dat`](typemap.dat) file as follows: [command] # XSD type redefinitions that are type safe for TR-069 xsd__anySimpleType = | xsd__anyType* | xsd__anyType* xsd__integer = typedef int64_t xsd__integer; xsd__nonNegativeInteger = typedef uint64_t xsd__nonNegativeInteger; xsd__positiveInteger = typedef uint64_t xsd__positiveInteger 1:; xsd__boolean = typedef enum xsd__boolean { false_, true_ } xsd__boolean; SOAP_ENC__base64 = class SOAP_ENC__base64 : public xsd__anyType { unsigned char *__ptr; int __size; }; # TR-069 recommended prefixes # old 1.1 namespace: # cwmp = "urn:dslforum-org:cwmp-1-1" # new 1.2 (and upto 1.4) namespace: cwmp = "urn:dslforum-org:cwmp-1-2" lwn = "urn:broadband-forum-org:cwmp:lwnotif-1-0" dm = "urn:broadband-forum-org:cwmp:datamodel-1-5" dmr = "urn:broadband-forum-org:cwmp:datamodel-report-0-1" dt = "urn:broadband-forum-org:cwmp:devicetype-1-3" dtf = "urn:broadband-forum-org:cwmp:devicetype-features" xmpp = "urn:broadband-forum-org:cwmp:xmppConnReq-1-0" bdc = "urn:broadband-forum-org:ipdr:tr-232-1-0" ipdr = "http://www.ipdr.org/namespaces/ipdr" Then run: [command] wsdl2h -t typemap.dat -p -x -o tr-069.h https://www.broadband-forum.org/cwmp/cwmp-1-4.xsd Option `-p` generates classes inherited from `xsd__anyType` to support inheritance. See the manual for details. With these changes, the `cwmp__ParameterValueStruct::Value` is now a `xsd__anyType*` that can be assigned any other typed value derived from class `xsd__anyType`. The wsdl2h tool also generates class wrappers for primitive types, such as the `xsd__int` enumeration which has a wrapper class `xsd__int_` derived from `xsd__anyType`. For example, to assign `cwmp__ParameterValueStruct::Value` a `xsd__int` we use the `xsd__int_` wrapper by the last three changed lines shown below in our TR-069 CWMP C++ client: void SetParameterValues(struct soap *soap, const char *URL) { struct _cwmp__SetParameterValues req; struct _cwmp__SetParameterValuesResponse res; /* construct request SetParameterValues */ req.ParameterList = soap_new_ParameterValueList(soap, -1); /* -1 means one (non-array) copy */ if (!req.ParameterList) return; /* construct SOAP array ParameterList with one element */ req.ParameterList->__size = 1; req.ParameterList->__ptrParameterValueStruct = (struct cwmp__ParameterValueStruct**)soap_malloc(soap, req.ParameterList->__size * sizeof(struct cwmp__ParameterValueStruct*)); if (!req.ParameterList->__ptrParameterValueStruct) return; req.ParameterList->__ptrParameterValueStruct[0] = soap_new_cwmp__ParameterValueStruct(soap, -1); if (!req.ParameterList->__ptrParameterValueStruct[0]) return; req.ParameterList->__ptrParameterValueStruct[0]->Name = "Device.WiFi.AccessPoint.10001.Enable"; xsd__int_ *value = soap_new_xsd__int_(soap, -1); value->__item = 1; req.ParameterList->__ptrParameterValueStruct[0]->Value = value; [![To top](../../images/go-up.png) To top](#) Readme report ------------- See the auto-generated [soapReadme](soapReadme.html) for this example. [![To top](../../images/go-up.png) To top](#)