/* wsrmapi.c WS-ReliableMessaging plugin. Implements the WS-RM logic for import/wsrm.h and import/wsrx.h gSOAP XML Web services tools Copyright (C) 2000-2010, Robert van Engelen, Genivia Inc., All Rights Reserved. This part of the software is released under one of the following licenses: GPL, the gSOAP public license, or Genivia's license for commercial use. -------------------------------------------------------------------------------- gSOAP public license. The contents of this file are subject to the gSOAP Public License Version 1.3 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.cs.fsu.edu/~engelen/soaplicense.html Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the specific language governing rights and limitations under the License. The Initial Developer of the Original Code is Robert A. van Engelen. Copyright (C) 2000-2010, Robert van Engelen, Genivia Inc., All Rights Reserved. -------------------------------------------------------------------------------- GPL license. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Author contact information: engelen@genivia.com / engelen@acm.org This program is released under the GPL with the additional exemption that compiling, linking, and/or using OpenSSL is allowed. This program is released under the GPL with the additional exemption that compiling, linking, and/or using OpenSSL is allowed. -------------------------------------------------------------------------------- A commercial use license is available from Genivia, Inc., contact@genivia.com -------------------------------------------------------------------------------- */ /** @mainpage - @ref wsrm_0 documents the wsrm plugin for WS-ReliableMessaging support. Although wsrm uses the wsa plugin for WS-Addressing, there is no need to read the wsa plugin documentation because this part of the documentation is self-contained and presents WS-Addressing usage when needed. - @ref wsa_0 documents the wsa plugin for WS-Addressing (2003/2004/2005 standards) support. */ /** @page wsrm_0 The wsrm plugin for client applications and stand-alone services @section wsrm_1 WS-ReliableMessaging Setup The material in this section relates to the WS-ReliableMessaging and WS-Addressing (2005) specifications. To use the wsrm plugin: -# Run wsdl2h -t typemap.dat on a WSDL of a service that requires WS-ReliableMessaging and WS-Addressing headers. The typemap.dat file included in the gSOAP package is used to recognize and translate header blocks. -# Run soapcpp2 -a on the header file produced by wsdl2h. To enable reliable-messaging and addressing-based service operation selection, you MUST use soapcpp2 option -a. This allows the service to dispatch methods based on the WS-Addressing action information header value (when the wsa plugin is registered). -# (Re-)compile and link stdsoap2.c/pp or libgsoap, (dom.c/pp when needed), plugin/wsrmapi.c, plugin/wsaapi.c, custom/duration.c, and the soapcpp2- generated source files. -# Use the wsrm plugin API functions described below. The wsrm plugin uses the wsa plugin to implement the WS-Addressing 2005 operations. Both must be registered. The wsrm plugin API is self-contained. There is no need to use the wsa plugin API, unless WS-Addressing-specific headers must be added to messages. An example wsrm client/server application can be found in samples/wsrm. A gSOAP service definitions header file with a "wsrm import" to support WS-ReliableMessaging is automatically generated by wsdl2h for a set of WSDLs that use WS-ReliableMessaging. The wsdl2h-generated header file should be further processed by soapcpp2 to generate the binding code. The wsrmapi.h and wsrmapi.c implement the WS-ReliableMessaging API described in this document. A wsdl2h-generated service definitions header file might include the following imports: @code #import "soap12.h" #import "wsrm.h" @endcode The wsrm.h header file is imported from import/wsrm.h by soapcpp2. The wsrm.h import can be manually added to enable WS-ReliableMessaging when needed. The gSOAP service definitions header file is processed with soapcpp2 to generate the client-side and/or server-side binding code. Note that the wsrm.h and the WS-ReliableMessaging-dependent wsrx.h and wsa5.h header files are located in the import directory of the gSOAP package. These files define the WS-ReliableMessaging and WS-Addressing information header elements and types. The wsrx.h header file defines the WS-ReliableMessaging CreateSequence, CloseSequence, and TerminateSequence operations, as well as an one-way SequenceAcknowledgement operation to accept acknowledgements. The soap12.h header file enables SOAP 1.2 messaging. For developers: the WS-ReliableMessaging header blocks in wsrm.h were generated from the WS-ReliableMessaging schema with the wsdl2h tool and WS/WS-typemap.dat as follows: @code > wsdl2h -cgyex -o wsrm.h -t WS/WS-typemap.dat WS/WS-ReliableMessaging.xsd @endcode Refer to wsrm.h for more details. @section wsrm_2 WS-ReliableMessaging Information Header Bindings To associate WS-ReliableMessaging and WS-Addressing information headers with service operations, the SOAP Header struct SOAP_ENV__Header must have been declared and contain the necessary header blocks to be transported with SOAP messages. The SOAP_ENV__Header for WS-ReliableMessaging and WS-Addressing is predefined in wsrm.h and imported into the gSOAP service definitions header file (this is automatically generated by wsdl2h). For each service operation in the gSOAP service definitions header file that uses WS-ReliableMessaging and/or WS-Addressing method-header-part directives are used. For example, the following gSOAP service definitions header file illustrates a typical import and service operation definition of operation 'example' in service namespace 'ns': @code #import "wsrm.h" //gsoap ns service method-header-part: example wsa5__MessageID //gsoap ns service method-header-part: example wsa5__RelatesTo //gsoap ns service method-header-part: example wsa5__From //gsoap ns service method-header-part: example wsa5__ReplyTo //gsoap ns service method-header-part: example wsa5__FaultTo //gsoap ns service method-header-part: example wsa5__To //gsoap ns service method-header-part: example wsa5__Action //gsoap ns service method-header-part: example wsrm__Sequence //gsoap ns service method-header-part: example wsrm__AckRequested //gsoap ns service method-header-part: example wsrm__SequenceAcknowledgement //gsoap ns service method-action: example urn:example/examplePort/example //gsoap ns service method-response-action: example urn:example/examplePort/exampleResponse int ns__example(char *in, struct ns__exampleResponse { char *out; } *); @endcode The wsa5 information headers are defined in wsa5.h and imported by wsrm.h (both are located in the 'import' directory). Here, all of the WS-Addressing and WS-ReliableMessaging information headers are bound to the ns__example operation request and response messages. The method action directive is important for WS-Addressing, because WS-Addressing Action information headers must be included that are unique for each operation. The soapcpp2 option -a ensures that WS-Addressing Action header blocks (and HTTP Action headers) are processed at the receiving side, which means that the service dispatcher uses the Action together with the operation name to invoke the service operation at the destination. This also means that Action headers must be properly set by the client. Note: the SOAP_ENV__Header struct can be declared in multiple files. The soapcpp2 tool gathers all members of the structs into the "final" SOAP_ENV__Header struct used by the gSOAP engine and your application. This is convenient when service-specific header blocks are combined with WS-ReliableMessaging and WS-Addressing header blocks or when WS-Security header blocks are added by the WSSE plugin. @section wsrm_3 WS-ReliableMessaging Overview In this section a brief overview of WS-ReliableMessaging is given. For more details please refer to the WS-ReliableMessaging protocol or tutorials on the subject. The following introduces the basic concepts of WS-ReliableMessaging from a practical point of view. WS-ReliableMessaging is useful to improve the reliability of one-way asynchronous messaging, for unreliable data gram messaging (SOAP-over-UDP), or to improve reliable delivery of responses relayed to other destinations, such as response messages that are relayed to destinations indicated by the WS-Addressing ReplyTo header. The protocol is also useful when multiple sources are sending messages that arrive in different order or must be flagged as an incomplete message collection when messages are missing as defined by the notion of a collection of related messages. WS-ReliableMessaging is not essential to improve the reliability of request-response message exchanges between two parties over HTTP, since a successful delivery of a request message can be inferred from the fact that a response was received for the request sent. WS-ReliableMessaging "protects" message sequences, i.e. a collection of related messages. A WS-ReliableMessaging message sequence is created after which the sequence of messages is sent. The sequence is closed and terminated by the client after the last message. Either the message sequence is complete or not, and the resulting action to discard the incomplete message sequence or not depends on the chosen behavior. Duplicate messages (e.g. resulting from resends) are always discarded. To create a new sequence, a client (WS-RM source) requests from the server (WS-RM destination) a unique (new) message sequence identification. The server responds with the identification to be used as well as other details, such as the expiration time of the sequence and the behavior implemented when a sequence was received incomplete: - NoDiscard means that the sequence of messages is not discarded by the destination server when one or more messages are missing; - DiscardFollowingFirstGap means that the initial messages of the sequence are retained by the destination, but the messages that arrived after the first missing are discarded; - DiscardEntireSequence means that the entire sequence of messages will be discarded when a single message is missing. When the client terminates the sequence, it first sends a sequence close request and then a sequence termination request to the destination server. The sequence close informs the server how many messages should have been received. The client can still resend messages after the close, but no new messages are supposed to be send. After the optional resends, the client requests termination of the sequence. The termination will be successful depending on the behavior when messages went missing, as was listed above. The ensure reliable delivery, the WS-ReliableMessaging protocol allows the client to resend messages. Message resends are desirable when messages are lost in transit. Since the client has limited information on delivery success (message delivery acknowledgments can get lost as well), the client may resend more messages than necessary. This could lead to message duplication. However, messages that were already received by the server are discarded. The client may request message delivery acknowledgements from the server. The server sends message receipt acknowledgements for all the messages it has received in the sequence back to the client, usually by piggy-backing them with the response of a subsequent message exchange. When the client is informed about successful delivery it reduces the number of resends the client will attempt. Messages in a sequence are uniquely identified by their enumeration number in the sequence. Messages may be transmitted out of order. A missing message number indicates a gap in the message sequence. Message receipt acknowledgements consist of ranges of message numbers. Acknowledgements are normally sent to the source to help identify which messages should be resend. With the WS-Addressing protocol, message responses and fault messages can be relayed to other destinations. The ReplyTo and FaultTo WS-Addressing header blocks are used for this purpose. The WS-ReliableMessaging protocol allows message acknowledgements to be relayed. The WS-ReliableMessaging AcksTo header block is used for this purpose. In all, there are four types of communicating peers that are visible to the source (the client): - The destination service. The WS-ReliableMessaging sequence is essentially controlled by this service. When a message is sent by the source to the destination service (over HTTP, TCP, or UDP), the WS-Addressing To information header may contain the endpoint address. The destination service normally returns message responses back to the client (HTTP request-response). - One of more ReplyTo destination services that accept response messages from the destination service. Rather than sending responses back to the client, the destination service relays them to another service. The WS-Addressing ReplyTo information header is used by the client to indicate the response relay target. - One or more FaultTo destination services that accept SOAP fault messages from the destination service. Rather than sending SOAP Faults back to the client, the destination service relays them to another service. The WS-Addressing FaultTo information header is used by the client to indicate the fault relay target. - One AcksTo destination service that accept WS-ReliableMessaging acknowledgements. Rather than sending acknowledgements piggy-backed with response messages back to the client, the destination service relays them to another service. The WS-ReliableMessaging AcksTo information header is used by the client when the sequence is created to indicate the acknowledgements relay target. The AcksTo cannot be changed after sequence creation to termination. Any or all of the above destination services except the first can be the source (client) itself, so message responses, faults, and acknowledgements can be relayed back to the client. To this end, it is important that the client sets the WS-Addressing From information header for each message to match the ReplyTo, FaultTo, and AcksTo address. The practical aspects of message sequence creation, the message exchanges, the message relays, and sequence close/termination are presented for the client side first and then for each of the four types of destination servers. @section wsrm_4 Client-side Usage @subsection wsrm_4_1 Creating, Closing, and Terminating Message Sequences A sequence is created, closed, terminated, and cleaned-up on the client side as follows: @code struct soap *soap = soap_new(); // Note: can use C++ proxy instead of 'soap' soap_register_plugin(soap, soap_wsa); soap_register_plugin(soap, soap_wsrm); const char *destination = "..."; // WS-RM destination server address const char *acksto = "..."; // WS-RM AcksTo server, or NULL if none soap_wsrm_sequence_handle seq; // a local handle to the sequence state // Step 1: create a sequence if (soap_wsrm_create(soap, destination, acksto, NULL, &seq)) { if (seq) soap_wsrm_seq_free(soap, seq); ... // error creating sequence } // Step 2: exchange messages with WS-RM destination, request acks, receive acks, issue resends (see later) ... // Step 3: optionally close first before terminating if (soap_wsrm_close(soap, seq, NULL)) { soap_wsrm_seq_free(soap, seq); ... // error closing sequence } // Step 4: terminate if (soap_wsrm_terminate(soap, seq, NULL)) { soap_wsrm_seq_free(soap, seq); ... // error terminating sequence } // Step 5: cleanup soap_wsrm_seq_free(soap, seq); @endcode As usual, the context can release all its temporary and deserialized data with the following pair of calls: @code soap_destroy(soap); soap_end(soap); @endcode This cleanup of memory resources may be performed at any time in the sequence of message exchange or afterwards when desired. The sequence state is maintained independent of these cleanup operations. The sequence termination may fail when the delivery of a sequence of messages is incomplete or when the lifetime of the sequence expired (see @ref SOAP_WSRM_MAX_SEC_TO_EXPIRE). The WS-RM destination determines the failure based on the final sequence state and the sequence behavior. The behavior is set to NoDiscard by default, which means that the sequence is not discared when transmission gaps appeared in the messages and the sequence is incomplete. The desired behavior can be specified with a sequence creation offer as explained in the next section. @subsection wsrm_4_2 Creating a Sequence with an Offer A sequence is created with an offer of sequence lifetime and behavior parameters as follows: @code ULONG62 expires = 60000; // 1 minute = 60,000 milliseconds if (soap_wsrm_create_offer(soap, destination, acksto, expires, DiscardEntireSequence, NULL, &seq)) { if (seq) soap_wsrm_seq_free(soap, seq); ... // error creating sequence } @endcode This offers sequence parameters to the WS-RM destination, which may accept the offer and apply the requested lifetime (expiration time in ms) and sequence behavior. The sequence behavior is either - NoDiscard (default) means that the sequence of messages is not discarded by the destination server when one or more messages are missing; - DiscardFollowingFirstGap means that the initial messages of the sequence are retained by the destination, but the messages that arrived after the first missing are discarded; - DiscardEntireSequence means that the entire sequence of messages will be discarded when a single message is missing. @subsection wsrm_4_3 Exchanging Messages in a Sequence Each message exchange with the WS-RM destination should be preceded with a soap_wsrm_request() or soap_wsrm_request_ack() call to set the required WS-RM information headers for the message send operation or request-response exchange. For example, consider the 'example' operation defined previously and suppose we invoke the 'example' operation in a sequence (after creation and before closing): @code const char *exampleRequestAction = "urn:example/examplePort/example"; const char *exampleRequestMessageID = NULL; // optional WS-Addressing ID struct ns__exampleResponse response; if (soap_wsrm_request(soap, seq, exampleRequestMessageID, exampleRequestAction)) ... // error: out of memory if (soap_call_ns__example(soap, soap_wsrm_to(seq), exampleRequestAction, &response)) soap_print_fault(soap, stderr); // an error occurred else ... // process the response @endcode The soap_wsrm_request() takes the sequence handle and optional WS-Addressing message ID and mandatory WS-Addressing action string (this string must match the method-action defined in the gSOAP service definition header file). It produces a WS-RM header block with the message number incremented by one for the invocation. Messages are enumerated from one and included in the WS-RM header to allow the destination to determine which messages were received in the sequence (for acknowledgements) and to ignore duplicate messages. The remote invocation soap_call_ns__example() uses soap_wsrm_to(seq) for the WS-RM destination address, which was set by soap_wsrm_create() or soap_wsrm_create_offer(). Because the address may change due to a redirect, we encourage the use of soap_wsrm_to() for the WS-RM destination address. A C++ proxy object (generated by soapcpp2 option -i) that invokes a service operation should reset the destination address explicitly by setting the 'soap_endpoint' member string before each operation invocation. @subsection wsrm_4_4 Exchanging Messages with Acknowledgements in a Sequence Message acknowledgements are requested as follows: @code if (soap_wsrm_request_ack(soap, seq, exampleRequestMessageID, exampleRequestAction)) ... // error: out of memory @endcode This informs the WS-RM destination to return message delivery acknowledgements back to the sender (piggy-backed in the header of the response message), unless the AcksTo is set to target an acknowledgement service endpoint. It is advisable to use soap_wsrm_request_ack() for the last messages in the sequence only to avoid excessive acknowledgement exchanges and resends. Resends can be strategically issued between message exchanges, see the next section. @subsection wsrm_4_5 Resending Non-Acknowledged Messages All non-acknowledged messages in a sequence that were previously sent can be resend (from the internal sender-side cache of sent messages in a sequence) as follows: @code soap_wsrm_resend(soap, seq, 0, 0); @endcode To resend a range of non-acknowledged messages, say between 3 and 7, use: @code soap_wsrm_resend(soap, seq, 3, 7); @endcode Or all messages after message number 3: @code soap_wsrm_resend(soap, seq, 3, 0); @endcode Resends should be used with care, since in the worst case when no acknowledgements have been received all messages up to the last will be resend (and ignored by the WS-RM destination when a message is received more than once). Note that when an AcksTo destination service address was set with soap_wsrm_create() or soap_wsrm_create_offer(), then the acknowledgements will not be returned to the sender (client). In this case message resends are performed for all messages sent, since these have not been acknowledged. It is permitted to issue resends between creation and termination of a sequence, including after a sequence close (as long as no new messages are sent after close). @subsection wsrm_4_6 Relaying Response and Fault Messages with WS-Addressing WS-ReliableMessaging is important when messages are relayed, and especially when relayed over UDP. The ReplyTo and FaultTo destination service endpoints can be specified for each message as follows: @code const char *replyto = "..."; // endpoint of response processing service const char *faultto = "..."; // endpoint of fault processing service if (soap_wsrm_request_ack(soap, seq, NULL, exampleRequestAction) || soap_wsa_add_ReplyTo(soap, replyto) || soap_wsa_add_FaultTo(soap, faultto)) ... // error: out of memory if (soap_call_ns__example(soap, soap_wsrm_to(seq), exampleRequestAction, &response)) { if (soap->error == 202) ... // request was accepted by destination (HTTP 202 Accept) else ... // other error, see below on how to detect acks } @endcode An optional source address information header can be added with soap_wsa_add_From(): @code const char *from = "..."; // endpoint of the client (could be any URI) if (soap_wsrm_request_ack(soap, seq, NULL, exampleRequestAction) || soap_wsa_add_From(soap, from)) ... // error: out of memory @endcode This can be extremely useful when responses are relayed but acknoweldgements must be sent back to the client. In this case it is recommended to use the From address header and AcksTo: @code const char *destination = "..."; // WS-RM destination server address const char *from = "..."; // endpoint of the client (could be any URI) const char *acksto = from; soap_wsrm_sequence_handle seq; // a local handle to the sequence state if (soap_wsrm_create(soap, destination, acksto, NULL, &seq)) { if (seq) soap_wsrm_seq_free(soap, seq); ... // error creating sequence } if (soap_wsrm_request_ack(soap, seq, NULL, exampleRequestAction) || soap_wsa_add_From(soap, from)) ... // error: out of memory if (soap_call_ns__example(soap, soap_wsrm_to(seq), exampleRequestAction, &response)) { if (soap->error == 202) ... // request was accepted by destination (HTTP 202 Accept) else if (soap->error == SOAP_NO_TAG) // empty ... // request was accepted by destination, acks are returned in empty Body else ... // other error } @endcode @subsection wsrm_4_7 Using Retry Loops to Improve Robustness of Message Sends A potential problem with message resends could be an unsuccessful initial send operation. For example, when a lengthy message transmission to the WS-RM destination was interrupted. When the local sequence state cache contains incomplete messages, these messages cannot be resend. Also, when a request-response message exchange fails it may not be due to the request transmission but the return of an HTTP code such as a benign 202 Accept. To distinguish fatal send errors from errors returned by the peer, the soap_wsrm_retry() function can be used as follows to only retry the message exchange (or send) when needed: @code if (soap_wsrm_request_ack(soap, seq, NULL, exampleRequestAction)) ... // error: out of memory while (soap_call_ns__example(soap, soap_wsrm_to(seq), exampleRequestAction, &response)) { if (soap->error == 202) { // request was accepted by destination (HTTP 202 Accept) break; } else if (soap->error == SOAP_NO_TAG) // empty { // request was accepted by destination, acks are returned break; } soap_print_fault(soap, stderr); if (soap_wsrm_check_retry(soap, seq)) break; // do not continue sleep(1); // wait a second to give network a chance to recover } if (soap->error == SOAP_OK) ... // response can be processed @endcode Note that the soap_wsrm_request() is only invoked once in the above to increment the message enumeration. The loop retries transmissions a maximum of @ref SOAP_WSRM_MAX_RETRIES iterations before giving up. @subsection wsrm_4_8 Example Client The following code shows an example WS-RM client fragment that combines the concepts introduced in the previous sections: @code struct soap *soap = soap_new(); // Note: can use C++ proxy instead of 'soap' soap_register_plugin(soap, soap_wsa); soap_register_plugin(soap, soap_wsrm); struct ns__exampleResponse response; const char *exampleRequestAction = "urn:example/examplePort/example"; const char *destination = "..."; // WS-RM destination server address const char *acksto = "..."; // WS-RM AcksTo server, or NULL if none const char *replyto = "..."; // WS-A ReplyTo server to relay response const char *faultto = "..."; // WS-A FaultTo server to relay fauls ULONG64 expires = 60000; // 1 minute sequence lifetime soap_wsrm_sequence_handle seq; if (soap_wsrm_create_offer(soap, destination, acksto, expires, DiscardEntireSequence, NULL, &seq)) { if (seq) soap_wsrm_seq_free(soap, seq); ... // error creating sequence } if (soap_wsrm_request_ack(soap, seq, NULL, exampleRequestAction) || soap_wsa_add_ReplyTo(soap, replyto) || soap_wsa_add_FaultTo(soap, faultto)) ... // error: out of memory while (soap_call_ns__example(soap, soap_wsrm_to(seq), exampleRequestAction, &response)) { if (soap->error == 202) { // request was accepted by destination (HTTP 202 Accept) break; } else if (soap->error == SOAP_NO_TAG) // empty { // request was accepted by destination, acks are returned break; } soap_print_fault(soap, stderr); if (soap_wsrm_check_retry(soap, seq)) break; // do not continue sleep(1); // wait a second to give network a chance to recover } if (soap->error == SOAP_OK) ... // response can be processed if (soap_wsrm_close(soap, seq, NULL)) { soap_wsrm_seq_free(soap, seq); ... // error closing sequence } soap_wsrm_resend(soap, seq, 0, 0); // resend non-acked messages if (soap_wsrm_terminate(soap, seq, NULL)) { soap_wsrm_seq_free(soap, seq); ... // error terminating sequence } soap_wsrm_seq_free(soap, seq); soap_destroy(soap); soap_end(soap); soap_free(soap); @endcode Responses can be discarded by the WS-RM destination service as requested with the soap_wsa_add_NoReply() call, which adds a ReplyTo 'noreply' WS-Addressing header block URI. @section wsrm_5 Server-side Usage To set up a WS-ReliableMessaging compliant server, register the wsa and wsrm plugins with the soap context (or with the C++ proxy object generated by soapcpp2 option -i): @code struct soap *soap = soap_new(); // Note: can use C++ proxy instead of 'soap' soap_register_plugin(soap, soap_wsa); soap_register_plugin(soap, soap_wsrm); @endcode The following subsections detail the differences between the types of WS-RM destination services. @subsection wsrm_5_1 Setting up a WS-RM Destination Service Each service operation that supports WS-ReliableMessaging and WS-Addressing should use the soap_wsrm_check(), soap_wsrm_sender_fault(), soap_wsrm_receiver_fault(), and soap_wsrm_reply() functions as follows: @code int ns__example(struct soap *soap, char *in, struct ns__exampleResponse *response) { const char *ResponseAction = "urn:example/examplePort/exampleResponse"; // fatal service operation-specific errors (before soap_wsrm_check) if (!database) // suppose we need a database, if there is none terminate return soap_wsrm_reciever_fault(soap, "No database!", NULL); // check for WS-RM/WSA and set WS-RM/WSA return headers if (soap_wsrm_check(soap)) return soap->error; // check for non-fatal service operation-specific errors if (!in || !*in) // sender did not put anything in the 'in' string: fault return soap_wsrm_sender_fault(soap, "No string content!", NULL); response->out = ... // return normally, relaying the response to the ReplyTo service when needed return soap_wsrm_reply(soap, NULL, ResponseAction); } @endcode An error produced by soap_wsrm_sender_fault() or soap_wsrm_receiever_fault() before soap_wsrm_check() is considered fatal, it will terminate the sequence and the sender (client) will not be able to continue the sequence transmissions. While the faults preduced after soap_wsrm_check() allow the sequence to continue. @subsection wsrm_5_2 Setting up a ReplyTo Service Endpoint A response-processing destination service may or may not support WS-RM. This allows response messages to be relayed to simple services. To set up a response-processing destination (accepting ReplyTo-relayed response messages), set up the server in the same way as the destination server. Service opertions should not use soap_wsrm_check() and soap_wsrm_reply(). Because response messages are sent (as if these were request messages), the service must define the appropriate one-way operations and gSOAP service definitions bindings. For example, the one-way response message of the ns__example operation is defined as follows in the gSOAP service definitions header file: @code //gsoap ns service method-header-part: exampleResponse wsa5__MessageID //gsoap ns service method-header-part: exampleResponse wsa5__RelatesTo //gsoap ns service method-header-part: exampleResponse wsa5__From //gsoap ns service method-header-part: exampleResponse wsa5__ReplyTo //gsoap ns service method-header-part: exampleResponse wsa5__FaultTo //gsoap ns service method-header-part: exampleResponse wsa5__To //gsoap ns service method-header-part: exampleResponse wsa5__Action //gsoap ns service method-header-part: exampleResponse wsrm__SequenceAcknowledgement //gsoap ns service method-action: exampleResponse urn:example/examplePort/exampleResponse int ns__exampleResponse(char *out, void); @endcode Note that when these definitions are combined with the previous definition for ns__example, there is no need to define the ns__ExampleResponse struct any longer as this is implied by the ns__exampleResponse function content. The server operation implementation is for example: @code int ns__exampleResponse(struct soap *soap, char *out) { ... // process return SOAP_OK; } @endcode Note that message acknowledgements can be piggy-backed with these response messages from the destination service when AcksTo is not set (at the destintation service). These acknowledgements are processed automatically to update the local sequence states when the wsrm plugin is registered. @subsection wsrm_5_3 Setting up a FaultTo Service Endpoint The SOAP Fault is accepted as a message by a service when defined as follows in the gSOAP service definition header file: @code //gsoap SOAP_ENV service method-action: Fault http://schemas.xmlsoap.org/ws/2004/08/addressing/fault int SOAP_ENV__Fault ( _QName faultcode, // SOAP 1.1 char *faultstring, // SOAP 1.1 char *faultactor, // SOAP 1.1 struct SOAP_ENV__Detail *detail, // SOAP 1.1 struct SOAP_ENV__Code *SOAP_ENV__Code, // SOAP 1.2 struct SOAP_ENV__Reason *SOAP_ENV__Reason, // SOAP 1.2 char *SOAP_ENV__Node, // SOAP 1.2 char *SOAP_ENV__Role, // SOAP 1.2 struct SOAP_ENV__Detail *SOAP_ENV__Detail, // SOAP 1.2 void ); @endcode Given the above service definitions, the soapcpp2 tool generates a SOAP_ENV__Fault service dispatcher that will invoke the SOAP_ENV__Fault() function that should be implemented at the server side: @code int SOAP_ENV__Fault( struct soap *soap, char *faultcode, char *faultstring, char *faultactor, struct SOAP_ENV__Detail *detail, struct SOAP_ENV__Code *SOAP_ENV__Code, struct SOAP_ENV__Reason *SOAP_ENV__Reason, char *SOAP_ENV__Node, char *SOAP_ENV__Role, struct SOAP_ENV__Detail *SOAP_ENV__Detail ) { // SOAP 1.1 ... = faultcode; ... = faultstring; ... = faultactor; ... = detail; // SOAP 1.2 ... = SOAP_ENV__Code; ... = SOAP_ENV__Reason; ... = SOAP_ENV__Node; ... = SOAP_ENV__Role; ... = SOAP_ENV__Detail; return soap_send_empty_response(soap, SOAP_OK); // HTTP 202 Accepted } @endcode Because the FaultTo service only uses WS-Addressing, there is no need to register or use the wsrm plugin. @subsection wsrm_5_4 Setting up an AcksTo Service Endpoint To set up an AcksTo acknowledgement-processing destination, the service definition header file should at least import wsrm.h: @code #import "wsrm.h" @endcode This is sufficient, since the wsrm plugin implements an acknowledgement processing capability that updates the local sequence state when acknowledgements are received. How to use the sequence state of the AcksTo server and use the state updates is up to the application developer. @section wsrm_6 WS-ReliableMessaging over HTTPS with Basic Authentication The HTTPS client and server are set up as shown in the gSOAP documentation and examples. There are no additional API calls needed to support WS-RM with HTTPS. Note that the WS-RM destination service may also relay messages to other HTTPS services, thus the WS-RM destination acts as a receiver (server) and sender (client). Therefore, the WS-RM destination server's SSL context should be set to authenticate the other servers: @code if (soap_ssl_server_context(soap, SOAP_SSL_DEFAULT, "server.pem", // keyfile (server) "password", // password to read the key file (server) "cacert.pem", // cacert file to store trusted certificates (client) NULL, // optional capath NULL, // DH file name or DH param key len bits, NULL: RSA NULL, // file with random data to seed randomness argv[1] // unique server identification for SSL session cache )) { soap_print_fault(soap, stderr); ... // handle error } @endcode Here, the cacert.pem file contains certificates to authenticate the ReplyTo, FaultTo, and AcksTo services when HTTPS is used. The client side sets up the SSL context with the soap_ssl_client_context() as instructed in the documentation and by the examples. Multi-threaded HTTPS clients and servers must register mutex locks with OpenSSL @section wsrm_7 WS-ReliableMessaging over UDP with Timeouts The use of UDP is automatic at the client side using the "soap.udp://" protocol. Therefore, endpoints should use a "soap.udp://" URL to connect. (when using an already opened socket, the SOAP_IO_UDP flag must be used, see the documentation.) Note that UDP datagram messages should not exceed 8K, which is usually a size that UDP datagrams can support. To reduce the message size, we recommend the use of ZLIB compression (-DWITH_ZLIB compile flag to enable ZLIB and use libgsoapssl.a/libgsoapssl++.a for OpenSSL and ZLIB compression combined). The code of an UDP-enabled server is identical to an HTTP/TCP server except that the soap_accept() call is disabled and unnecessary. When message responses are not returned to the client, the client may block indefinitely when it expects a response. Therefore we recommend the use of send and receive timeouts: @code struct soap *soap = soap_new(); const char *destination = "soap.udp://..."; const char *acksto = NULL; soap->send_timeout = soap->recv_timeout = 1; // 1 second to timeout if (soap_wsrm_create(soap, destination, acksto, NULL, &seq)) ... // an error occured if (soap_wsrm_request(soap, seq, NULL, exampleRequestAction)) ... // an error occured if (soap_call_ns__example(soap, ...)) { if (soap->error == SOAP_EOF && soap->errnum == 0) ... // a timeout occured else ... // an error occured } @endcode Note that the WS-Addressing ReplyTo and the use of NoReply do not return response message from the server. To return acknowledgements, set the WS-RM AcksTo to the WS-Addressing From address (any unique URI will do). Therefore, soap_wsrm_request_acks() does force a message response header to be returned with an empty SOAP Body: @code struct soap *soap = soap_new(); soap->send_timeout = soap->recv_timeout = 1; // 1 second to timeout const char *destination = "soap.udp://..."; const char *from = "..."; // some identifying URI const char *acksto = from; soap->send_timeout = soap->recv_timeout = 1; // 1 second to timeout if (soap_wsrm_create(soap, destination, acksto, NULL, &seq)) ... // an error occured if (soap_wsrm_request_ack(soap, seq, NULL, exampleRequestAction) || soap_wsa_add_NoReply(soap) || soap_wsa_add_From(soap, from)) ... // an error occured if (soap_call_ns__example(soap, ...)) { if (soap->error == SOAP_EOF && soap->errnum == 0) ... // a timeout occured else if (soap->error == SOAP_NO_TAG) ... // ack was received and recorded else ... // an error occured } @endcode In this case an acknowledgement will be returned and the timeout reflects a possible network packet loss. @section wsrm_8 WS-ReliableMessaging and WS-Security WS-Security can be combined with WS-ReliableMessaging using the WSSE plugin. Both plugins must be registered at the client and server side. These APIs are independent. @section wsrm_9 MT Limitations of the WS-RM-0.9 Plugin The limitation of this release version of WS-ReliableMessaging is the user's responsibility to use locks for multi-threading. The sequence states cannot be shared between threads without mutex locking. At the client side, this means that each WS-RM-based invocation must be under mutex to avoid the state to become inconsistent when other threads perform WS-RM operations. Also a mutex lock must be placed around the soap_wsrm_create(), soap_wsrm_close(), and soap_wsrm_terminate() calls. At the server side, each soap_serve() must be executed under mutex to ensure that only one thread executes the service operations. This essentially serializes the WS-RM server operations. The future release will use internal mutex locking to avoid performance degredation from message serialization. */ #include "wsrmapi.h" #ifdef __cplusplus extern "C" { #endif /** Plugin identification for plugin registry */ const char soap_wsrm_id[] = SOAP_WSRM_ID; /******************************************************************************\ * * Static protos * \******************************************************************************/ static int soap_wsrm_init(struct soap *soap, struct soap_wsrm_data *data, void *arg); static void soap_wsrm_delete(struct soap *soap, struct soap_plugin *p); static int soap_wsrm_send(struct soap *soap, const char *buf, size_t len); static int soap_wsrm_disconnect(struct soap *soap); static int soap_wsrm_process_ack(struct soap *soap, struct _wsrm__SequenceAcknowledgement *ack); static int soap_wsrm_add_acks(struct soap *soap); static int soap_wsrm_set_ack(struct soap *soap, struct soap_wsrm_sequence *seq, struct _wsrm__SequenceAcknowledgement *ack); static int soap_wsrm_post(struct soap *soap, const char *endpoint, const char *host, int port, const char *path, const char *action, size_t count); static int soap_wsrm_resend_seq(struct soap *soap, struct soap_wsrm_sequence *seq, ULONG64 lower, ULONG64 upper); static const char *soap_wsrm_seq_newid(struct soap *soap, struct soap_wsrm_data *data); static struct soap_wsrm_sequence *soap_wsrm_seq_lookup(const struct soap_wsrm_data *data, const char *id); static struct soap_wsrm_sequence *soap_wsrm_seq_insert(struct soap *soap, struct soap_wsrm_data *data); static int soap_wsrm_seq_append(struct soap *soap, struct soap_wsrm_data *data, const char *buf, size_t len); static int soap_wsrm_seq_terminated(struct soap *soap, struct soap_wsrm_sequence *seq); static int soap_wsrm_num_lookup(struct soap *soap, const struct soap_wsrm_sequence *seq, ULONG64 num); static int soap_wsrm_range_insert(struct soap *soap, struct soap_wsrm_sequence *seq, ULONG64 lower, ULONG64 upper); static int soap_wsrm_num_insert(struct soap *soap, struct soap_wsrm_sequence *seq, ULONG64 num); static int soap_wsrm_num_size(const struct soap_wsrm_sequence *seq); static void soap_wsrm_num_free(struct soap *soap, struct soap_wsrm_sequence *seq); static void soap_wsrm_msg_free(struct soap *soap, struct soap_wsrm_message *p); /******************************************************************************\ * * Client-side WS-RM Operations * \******************************************************************************/ /** @fn int soap_wsrm_create(struct soap *soap, const char *to, const char *acksto, const char *wsa_id, soap_wsrm_sequence_handle *seq) @brief Creates a new sequence. Sequences are usually created by the sender (client) and confirmed by the receiver (server). The 'to' server address must be used for all messages of the sequence to be sent to the server. Optionally the 'acksto' address can be given for acknowledgement messages to be sent to. A sequence ID is generated by the server upon success. @param soap context @param[in] to endpoint address of the WS-RM destination server (required) @param[in] acksto endpoint address of the WS-RM acknowledgement server (optional, use NULL when acks are piggy-backed on response messages to the source) @param[in] wsa_id WS-Addressing message ID (optional, use NULL when omitted) @param[out] seq sequence handle is set @return SOAP_OK or error code */ int soap_wsrm_create(struct soap *soap, const char *to, const char *acksto, const char *wsa_id, soap_wsrm_sequence_handle *seq) { return soap_wsrm_create_offer(soap, to, acksto, NULL, 0, NoDiscard, wsa_id, seq); } /** @fn int soap_wsrm_create_offer(struct soap *soap, const char *to, const char *acksto, const char *id, LONG64 expires, enum wsrm__IncompleteSequenceBehaviorType behavior, const char *wsa_id, soap_wsrm_sequence_handle *seq) @brief Creates a new sequence by offering suggested WS-RM parameters to the destination. Sequences are usually created by the sender (client) and confirmed by the receiver (server). The 'to' server address must be used for all messages of the sequence to be sent to the server. Optionally the 'acksto' address can be given for acknowledgement messages to be sent to. server upon success. @param soap context @param[in] to endpoint address of the WS-RM destination server (required) @param[in] acksto endpoint address of the WS-RM acknowledgement server (optional, use NULL when acks are piggy-backed on response messages to the source) @param[in] id offered WS-RM sequence identifier (optional, generate when NULL) @param[in] expires offered max sequence duration (its lifetime) in ms @param[in] behavior offered DiscardEntireSequence, DiscardFollowingFirstGap, or NoDiscard, which specifies the WS-RM destination's action when a sequence is closed/terminated when it is incomplete, and notifies the source when failed. @param[in] wsa_id WS-Addressing message ID (optional, use NULL when omitted) @param[out] seq sequence handle is set @return SOAP_OK or error code */ int soap_wsrm_create_offer(struct soap *soap, const char *to, const char *acksto, const char *id, LONG64 expires, enum wsrm__IncompleteSequenceBehaviorType behavior, const char *wsa_id, soap_wsrm_sequence_handle *seq) { char *s; struct wsrm__CreateSequenceType req; struct wsrm__CreateSequenceResponseType res; struct soap_wsrm_data *data = (struct soap_wsrm_data*)soap_lookup_plugin(soap, soap_wsrm_id); const char *action = SOAP_NAMESPACE_OF_wsrm"/CreateSequence"; DBGFUN2("soap_wsrm_CreateSequence", "to=%s", to?to:"(null)", "acksto=%s", acksto?acksto:"(null)"); if (!data) return soap->error = SOAP_PLUGIN_ERROR; soap_default_wsrm__CreateSequenceType(soap, &req); if (!to) to = soap_wsa_anonymousURI; if (!acksto) acksto = soap_wsa_anonymousURI; if (soap_wsa_request(soap, wsa_id, to, action)) return soap->error; req.AcksTo.Address = (char*)acksto; if (expires) { req.Expires = (xsd__duration*)soap_malloc(soap, sizeof(xsd__duration)); *req.Expires = expires; } if (id || behavior != NoDiscard) { req.Offer = (struct wsrm__OfferType*)soap_malloc(soap, sizeof(struct wsrm__OfferType)); if (!req.Offer) return soap->error; soap_default_wsrm__OfferType(soap, req.Offer); if (!id) { do id = soap_wsrm_seq_newid(soap, data); while (soap_wsrm_seq_lookup(data, id)); if (!id) return soap->error; } req.Offer->Identifier = (char*)id; /* required */ req.Offer->Endpoint = req.AcksTo; /* required: use acksto endpoint */ req.Offer->Expires = req.Expires; /* same expire value */ req.Offer->IncompleteSequenceBehavior = &behavior; } else req.Offer = NULL; data->enabled = 0; /* TODO should be thread local */ if (soap_call___wsrm__CreateSequence(soap, to, action, &req, &res)) return soap->error; if (!res.Identifier) return soap_wsrm_error(soap, *seq, wsrm__SequenceTerminated); if (soap_wsrm_seq_lookup(data, res.Identifier)) { /* error when id is not unique */ return soap_wsrm_error(soap, *seq, wsrm__CreateSequenceRefused); } *seq = soap_wsrm_seq_insert(soap, data); if (!*seq) return soap->error; /* seq has a handle, soap_wsrm_cleanup() should not remove it */ (*seq)->handle = 1; s = (char*)SOAP_MALLOC(soap, strlen(res.Identifier) + 1); if (!s) return soap->error = SOAP_EOM; (*seq)->id = strcpy(s, res.Identifier); s = (char*)SOAP_MALLOC(soap, strlen(to) + 1); if (!s) return soap->error = SOAP_EOM; (*seq)->to = strcpy(s, to); if (res.Expires) (*seq)->expires = time(NULL) + *res.Expires/1000; /* TODO: check */ if (res.IncompleteSequenceBehavior) (*seq)->behavior = *res.IncompleteSequenceBehavior; else (*seq)->behavior = behavior; if (res.Accept && res.Accept->AcksTo.Address) { s = (char*)SOAP_MALLOC(soap, strlen(res.Accept->AcksTo.Address) + 1); if (!s) return soap->error = SOAP_EOM; (*seq)->acksto = strcpy(s, res.Accept->AcksTo.Address); } else if (req.Offer) return soap_wsrm_error(soap, *seq, wsrm__CreateSequenceRefused); else (*seq)->acksto = NULL; (*seq)->state = SOAP_WSRM_CREATED; return SOAP_OK; } /** @fn int soap_wsrm_request(struct soap *soap, soap_wsrm_sequence_handle seq, const char *wsa_id, const char *wsa_action) @brief Adds a WS-RM sequence message number to the next message transmitted to the WS-RM destination and increments the message counter by one. No acks are requested. @param soap context @param seq sequence handle set by soap_wsrm_create() or soap_wsrm_create_offer() @param[in] wsa_id WS-Addressing message ID (optional, use NULL when omitted) @param[in] wsa_action mandatory WS-Addressing action of the next message sent @return SOAP_OK or error code */ int soap_wsrm_request(struct soap *soap, soap_wsrm_sequence_handle seq, const char *wsa_id, const char *wsa_action) { DBGFUN("soap_wsrm_request"); return soap_wsrm_request_num(soap, seq, wsa_id, wsa_action, seq->num + 1); } /** @fn int soap_wsrm_request_num(struct soap *soap, soap_wsrm_sequence_handle seq, const char *wsa_id, const char *wsa_action, ULONG64 num) @brief Adds a WS-RM sequence message number to the next message transmitted to the WS-RM destination. No acks are requested. @param soap context @param seq sequence handle set by soap_wsrm_create() or soap_wsrm_create_offer() @param[in] wsa_id WS-Addressing message ID (optional, use NULL when omitted) @param[in] wsa_action mandatory WS-Addressing action of the next message sent @param[in] num WS-RM sequence message number @return SOAP_OK or error code */ int soap_wsrm_request_num(struct soap *soap, soap_wsrm_sequence_handle seq, const char *wsa_id, const char *wsa_action, ULONG64 num) { struct soap_wsrm_message *p; struct soap_wsrm_data *data; DBGFUN1("soap_wsrm_request_num", "num="SOAP_ULONG_FORMAT, num); if (num == 0) return soap_wsrm_error(soap, seq, wsrm__MessageNumberRollover); if (seq->state != SOAP_WSRM_CREATED && seq->state != SOAP_WSRM_CLOSED) return soap_wsrm_error(soap, seq, wsrm__SequenceTerminated); data = (struct soap_wsrm_data*)soap_lookup_plugin(soap, soap_wsrm_id); if (!data) return soap->error = SOAP_PLUGIN_ERROR; if (seq->num < num) seq->num = num; data->enabled = 1; /* TODO should be thread local */ p = (struct soap_wsrm_message*)SOAP_MALLOC(soap, sizeof(struct soap_wsrm_message)); if (!p) return soap->error = SOAP_EOM; /* TODO: consider checking for duplicate message num? If dup, remove old? */ p->num = num; p->list = p->last = NULL; p->next = seq->messages; seq->messages = p; if (soap_wsa_request(soap, wsa_id, seq->to, wsa_action)) return soap->error; if (!(soap->header->wsrm__Sequence = (_wsrm__Sequence*)soap_malloc(soap, sizeof(_wsrm__Sequence)))) return soap->error; seq->retry = SOAP_WSRM_MAX_RETRIES; soap_default__wsrm__Sequence(soap, soap->header->wsrm__Sequence); soap->header->wsrm__Sequence->Identifier = soap_strdup(soap, seq->id); soap->header->wsrm__Sequence->MessageNumber = num; soap->header->wsrm__AckRequested = NULL; soap->header->wsrm__SequenceAcknowledgement = NULL; soap->header->wsrm__SequenceFault = NULL; return SOAP_OK; } /** @fn int soap_wsrm_request_acks(struct soap *soap, soap_wsrm_sequence_handle seq, const char *wsa_id, const char *wsa_action) @brief Adds a WS-RM sequence message number to the next message transmitted to the WS-RM destination and increments the message counter by one. Message acks for the current sequence are requested. @param soap context @param seq sequence handle set by soap_wsrm_create() or soap_wsrm_create_offer() @param[in] wsa_id WS-Addressing message ID (optional, use NULL when omitted) @param[in] wsa_action mandatory WS-Addressing action of the next message sent @return SOAP_OK or error code */ int soap_wsrm_request_acks(struct soap *soap, soap_wsrm_sequence_handle seq, const char *wsa_id, const char *wsa_action) { DBGFUN("soap_wsrm_request_ack"); if (soap_wsrm_request_num(soap, seq, wsa_id, wsa_action, seq->num + 1)) return soap->error; soap->header->__sizeAckRequested = 1; if (!(soap->header->wsrm__AckRequested = (_wsrm__AckRequested*)soap_malloc(soap, sizeof(_wsrm__AckRequested)))) return soap->error; soap_default__wsrm__AckRequested(soap, soap->header->wsrm__AckRequested); soap->header->wsrm__AckRequested->Identifier = soap_strdup(soap, seq->id); return SOAP_OK; } /** @fn int soap_wsrm_check_retry(struct soap *soap, soap_wsrm_sequence_handle seq) @brief Client-side check to verify if the remote call can be retried when a failure occured. Increases the robustness of messages sends, by ensuring that the message was at least transmitted (but not necessarily received). Also implements HTTP 307 Temporary Redirect. Retries are limited to SOAP_WSRM_MAX_RETRIES iterations. @param soap context @param seq sequence handle set by soap_wsrm_create or soap_wsrm_create_offer @return SOAP_OK when retry is safe, error code otherwise */ int soap_wsrm_check_retry(struct soap *soap, soap_wsrm_sequence_handle seq) { struct soap_wsrm_data *data = (struct soap_wsrm_data*)soap_lookup_plugin(soap, soap_wsrm_id); if (!data) return soap->error = SOAP_PLUGIN_ERROR; DBGFUN("soap_wsrm_check_retry"); if (soap_wsrm_seq_terminated(soap, seq) || seq->retry-- == 0) return soap_wsrm_error(soap, seq, wsrm__SequenceTerminated); /* should not call when OK */ if (soap->error == SOAP_OK) return soap_wsrm_error(soap, seq, wsrm__SequenceTerminated); /* Temporary redirect? */ if (soap->error == 307) { char *s; s = (char*)SOAP_MALLOC(soap, strlen(soap->endpoint) + 1); if (s) seq->to = strcpy(s, soap->endpoint); } /* if no WS-RM Sequence header then send was successful but recv failed */ else if (!soap->header || !soap->header->wsrm__Sequence || !soap->header->wsrm__Sequence->Identifier) return soap->error; /* otherwise, send was not successful and we should try again */ data->enabled = 1; return soap->error = SOAP_OK; } /** @fn int soap_wsrm_resend(struct soap *soap, soap_wsrm_sequence_handle seq, ULONG64 lower, ULONG64 upper) @brief Client-side call to resend all non-acknowledged messages that were automatically cached for this sequence. Messages stored in the sequence for retransmission (those that were previously sent but not acknowledged) are resent to the soap_wsrm_to() address, which was set by soap_wsrm_create() or soap_wsrm_create_offer(). It is recommended to resend messages after the last message in the sequence was transmitted before closing the sequence. To reduce unnecessary resend attempts, it is recommended to use soap_wsrm_request_ack() with the last message to request acks for messages already delivered. @param soap context @param seq sequence handle set by soap_wsrm_create or soap_wsrm_create_offer @param[in] lower resend message range lower bound (0 for lowest) @param[in] upper resend message range upper bound (or 0 for infinite) @return SOAP_OK or error code (can be ignored when resends are retried later) */ int soap_wsrm_resend(struct soap *soap, soap_wsrm_sequence_handle seq, ULONG64 lower, ULONG64 upper) { struct soap_wsrm_data *data = (struct soap_wsrm_data*)soap_lookup_plugin(soap, soap_wsrm_id); DBGFUN("soap_wsrm_resend"); if (!data) return soap->error = SOAP_PLUGIN_ERROR; if (seq) return soap_wsrm_resend_seq(soap, seq, lower, upper); for (seq = data->sequences; seq; seq = seq->next) if (soap_wsrm_resend_seq(soap, seq, lower, upper)) return soap->error; return SOAP_OK; } /** @fn int soap_wsrm_close(struct soap *soap, soap_wsrm_sequence_handle seq, const char *wsa_id) @brief Closes the sequence, but does not yet terminate it. No new messages should be send, but messages can be resend with soap_wsrm_resend() if desired. @param soap context @param seq sequence handle set by soap_wsrm_create or soap_wsrm_create_offer @param[in] wsa_id WS-Addressing message ID (optional) @return SOAP_OK or error code */ int soap_wsrm_close(struct soap *soap, soap_wsrm_sequence_handle seq, const char *wsa_id) { struct wsrm__CloseSequenceType req; struct wsrm__CloseSequenceResponseType res; struct soap_wsrm_data *data = (struct soap_wsrm_data*)soap_lookup_plugin(soap, soap_wsrm_id); DBGFUN1("soap_wsrm_CloseSequence", "wsa_id=%s", wsa_id?wsa_id:"(null)"); if (!data) return soap->error = SOAP_PLUGIN_ERROR; soap->header = NULL; if (soap_wsa_request(soap, wsa_id, seq->to, SOAP_NAMESPACE_OF_wsrm"/CloseSequence")) return soap->error; req.Identifier = soap_strdup(soap, seq->id); req.LastMsgNumber = &seq->num; data->enabled = 0; /* TODO should be thread local */ if (soap_call___wsrm__CloseSequence(soap, seq->to, soap->header->wsa5__Action, &req, &res)) return soap->error; if (!res.Identifier) return soap_wsrm_error(soap, seq, wsrm__SequenceTerminated); seq->state = SOAP_WSRM_CLOSED; return SOAP_OK; } /** @fn int soap_wsrm_terminate(struct soap *soap, soap_wsrm_sequence_handle seq, const char *wsa_id) @brief Terminates the sequence. No new messages should be send and no resends should be tried. Usually done after soap_wsrm_close() or any time to terminate the sequence prematurely. @param soap context @param seq sequence handle set by soap_wsrm_create or soap_wsrm_create_offer @param[in] wsa_id WS-Addressing message ID (optional) @return SOAP_OK or error code */ int soap_wsrm_terminate(struct soap *soap, soap_wsrm_sequence_handle seq, const char *wsa_id) { struct wsrm__TerminateSequenceType req; struct wsrm__TerminateSequenceResponseType res; struct soap_wsrm_data *data = (struct soap_wsrm_data*)soap_lookup_plugin(soap, soap_wsrm_id); DBGFUN1("soap_wsrm_TerminateSequence", "wsa_id=%s", wsa_id?wsa_id:"(null)"); if (!data) return soap->error = SOAP_PLUGIN_ERROR; soap->header = NULL; if (soap_wsa_request(soap, wsa_id, seq->to, SOAP_NAMESPACE_OF_wsrm"/TerminateSequence")) return soap->error; req.Identifier = soap_strdup(soap, seq->id); req.LastMsgNumber = &seq->num; data->enabled = 0; /* TODO should be thread local */ if (soap_call___wsrm__TerminateSequence(soap, seq->to, soap->header->wsa5__Action, &req, &res)) return soap->error; if (!res.Identifier) return soap_wsrm_error(soap, seq, wsrm__SequenceTerminated); seq->state = SOAP_WSRM_TERMINATED; return SOAP_OK; } /** @fn void soap_wsrm_seq_free(struct soap *soap, soap_wsrm_sequence_handle seq) @brief Must be called to free the sequence allocated by soap_wsrm_create() or by soap_wsrm_create_offer(). Sequence handles are not automatically reclaimed by the engine. @param soap context @param seq sequence handle set by soap_wsrm_create() or soap_wsrm_create_offer() */ void soap_wsrm_seq_free(struct soap *soap, soap_wsrm_sequence_handle seq) { struct soap_wsrm_data *data; struct soap_wsrm_sequence **q; time_t now; DBGFUN("soap_wsrm_seq_free"); data = (struct soap_wsrm_data*)soap_lookup_plugin(soap, soap_wsrm_id); if (!data) return; if (!seq) now = time(NULL); q = &data->sequences; while (*q) { if ((!seq && !(*q)->handle && (*q)->expires < now) || *q == seq) { struct soap_wsrm_message *p, *r; struct soap_wsrm_sequence *t = *q; DBGLOG(TEST, SOAP_MESSAGE(fdebug, "Deleting sequence %s\n", t->id?t->id:"(null)")); if (t->id) SOAP_FREE(soap, (void*)t->id); if (t->to) SOAP_FREE(soap, (void*)t->to); if (t->acksto) SOAP_FREE(soap, (void*)t->acksto); soap_wsrm_num_free(soap, t); for (p = t->messages; p; p = r) { r = p->next; soap_wsrm_msg_free(soap, p); SOAP_FREE(soap, p); } *q = t->next; SOAP_FREE(soap, t); if (seq) break; } else q = &(*q)->next; } } /** @fn const char *soap_wsrm_to(soap_wsrm_sequence_handle seq) @brief Returns the endpoint address of the destination service that serves the sequence. Initially set with soap_wsrm_create or soap_wsrm_create_offer. HTTP 307 Temporary Redirect can change the endpoint during the lifetime of a message sequence. Thus, this function returns the most recent endpoint binding that can be used to send message to the server endpoint. @param seq sequence handle set by soap_wsrm_create() or soap_wsrm_create_offer() @return the sequence endpoint address */ const char * soap_wsrm_to(soap_wsrm_sequence_handle seq) { return seq->to; } /** @fn const char *soap_wsrm_acksto(soap_wsrm_sequence_handle seq) @brief Returns the endpoint address of the AcksTo acknowledgement service that serves the sequence, when set with soap_wsrm_create() or soap_wsrm_create_offer() or NULL otherwise. HTTP 307 Temporary Redirect can change the endpoint during the lifetime of a message sequence. Thus, this function returns the most recent endpoint binding. @param seq sequence handle set by soap_wsrm_create() or soap_wsrm_create_offer() @return the sequence endpoint address */ const char * soap_wsrm_acksto(soap_wsrm_sequence_handle seq) { return seq->acksto; } /******************************************************************************\ * * Server * \******************************************************************************/ /** @fn int soap_wsrm_check(struct soap *soap) @brief Server-side check for the presence of WS-Addressing and WS-RM header blocks in the SOAP header. Also prepares the return WS-RM header. This function should be called in the each service operation that supports WS-RM. Do not use this function in a ReplyTo response-accepting destination service operation. @param soap context @return SOAP_OK or error code (and server operation must return this value) */ int soap_wsrm_check(struct soap *soap) { struct soap_wsrm_sequence *seq; struct soap_wsrm_data *data; DBGFUN("soap_wsrm_check"); if (soap_wsa_check(soap)) return soap->error; data = (struct soap_wsrm_data*)soap_lookup_plugin(soap, soap_wsrm_id); if (!data) return soap->error = SOAP_PLUGIN_ERROR; if (!soap->header || !soap->header->wsrm__Sequence) return soap_wsrm_error(soap, NULL, wsrm__WSRMRequired); seq = soap_wsrm_seq_lookup(data, soap->header->wsrm__Sequence->Identifier); if (!seq) return soap_wsrm_error(soap, NULL, wsrm__UnknownSequence); if (soap_wsrm_seq_terminated(soap, seq)) return soap_wsrm_error(soap, seq, wsrm__SequenceTerminated); if (soap_wsrm_num_lookup(soap, seq, soap->header->wsrm__Sequence->MessageNumber)) { DBGLOG(TEST, SOAP_MESSAGE(fdebug, "Message already received\n")); return soap_send_empty_response(soap, SOAP_OK); /* no response */ } if (soap_wsrm_num_insert(soap, seq, soap->header->wsrm__Sequence->MessageNumber)) return soap->error; if (seq->num < soap->header->wsrm__Sequence->MessageNumber) seq->num = soap->header->wsrm__Sequence->MessageNumber; return soap_wsrm_add_acks(soap); } /** @fn int soap_wsrm_reply(struct soap *soap, const char *wsa_id, const char *wsa_action) @brief Server-side server operation reply to be performed when the service operation returns. Server operations that support WS-Addressing and WS-RM must call this function to return normallly (and allow the response to be relayed). @param soap context @param[in] wsa_id WS-Addressing message ID (optional) @param[in] wsa_action mandatory WS-Addressing action of the response @return SOAP_OK or error code (and server operation must return this value) */ int soap_wsrm_reply(struct soap *soap, const char *wsa_id, const char *wsa_action) { DBGFUN("soap_wsrm_reply"); return soap_wsa_reply(soap, wsa_id, wsa_action); } /** @fn void soap_wsrm_cleanup(struct soap *soap) @brief Cleans up all expired sequences and releases resources. To be used at the server side to periodically clean up WS-RM sequences. Server-side cleanup is automatic, as long as WS-RM is in use. Does not release client-side sequences allocated by soap_wsrm_create() or soap_wsrm_create_offer(). @param soap context */ void soap_wsrm_cleanup(struct soap *soap) { DBGFUN("soap_wsrm_cleanup"); soap_wsrm_seq_free(soap, NULL); } /******************************************************************************\ * * Server-side Predefined WS-RM Operations * \******************************************************************************/ /** @fn int __wsrm__CreateSequence(struct soap *soap, struct wsrm__CreateSequenceType *req, struct wsrm__CreateSequenceResponseType *res) @brief WS-RM CreateSequence service operation. Automatically invoked by the server to process a create sequence request (with or without offer). Creates a new local sequence state to keep track of messages. @param soap context @param[in] req wsrm__CreateSequence request message @param[out] req wsrm__CreateSequenceResponse response message @return SOAP_OK or error code */ int __wsrm__CreateSequence(struct soap *soap, struct wsrm__CreateSequenceType *req, struct wsrm__CreateSequenceResponseType *res) { struct soap_wsrm_sequence *seq; struct soap_wsrm_data *data; DBGFUN("__wsrm__CreateSequence"); if (soap_wsa_check(soap)) return soap->error; data = (struct soap_wsrm_data*)soap_lookup_plugin(soap, soap_wsrm_id); if (!data) return soap->error = SOAP_PLUGIN_ERROR; if (!req) return soap_wsrm_error(soap, seq, wsrm__WSRMRequired); soap_wsrm_cleanup(soap); seq = soap_wsrm_seq_insert(soap, data); if (!seq) return soap->error; seq->num = 0; if (req->AcksTo.Address) { char *s; s = (char*)SOAP_MALLOC(soap, strlen(req->AcksTo.Address) + 1); if (!s) return soap->error = SOAP_EOM; seq->acksto = strcpy(s, req->AcksTo.Address); } else seq->acksto = NULL; if (req->Expires && *req->Expires/1000 < SOAP_WSRM_MAX_SEC_TO_EXPIRE) seq->expires = time(NULL) + *req->Expires/1000; if (req->Offer) { res->Accept = (struct wsrm__AcceptType*)soap_malloc(soap, sizeof(struct wsrm__AcceptType)); if (!res->Accept) return SOAP_EOM; soap_default_wsrm__AcceptType(soap, res->Accept); res->Accept->AcksTo = req->AcksTo; res->Expires = req->Expires; if (req->Offer->Identifier) { /* check if offered identifier is acceptable */ if (!soap_wsrm_seq_lookup(data, req->Offer->Identifier)) { char *s; if ((s = (char*)SOAP_MALLOC(soap, strlen(req->Offer->Identifier) + 1))) seq->id = strcpy(s, req->Offer->Identifier); } } if (req->Offer->Expires && *req->Offer->Expires/1000 < SOAP_WSRM_MAX_SEC_TO_EXPIRE) seq->expires = time(NULL) + *req->Offer->Expires/1000; if (req->Offer->IncompleteSequenceBehavior) { res->IncompleteSequenceBehavior = req->Offer->IncompleteSequenceBehavior; seq->behavior = *res->IncompleteSequenceBehavior; } } if (!seq->id) { /* gen a new id, check against previously accepted seq id */ const char *s; do s = soap_wsrm_seq_newid(soap, data); while (soap_wsrm_seq_lookup(data, s)); if (!s) return soap->error; seq->id = s; } seq->state = SOAP_WSRM_CREATED; res->Identifier = soap_strdup(soap, seq->id); soap->header->wsrm__Sequence = NULL; soap->header->wsrm__AckRequested = NULL; return soap_wsa_reply(soap, NULL, SOAP_NAMESPACE_OF_wsrm"CreateSequenceResponse"); } /** @fn int __wsrm__CloseSequence(struct soap *soap, struct wsrm__CloseSequenceType *req, struct wsrm__CloseSequenceResponseType *res) @brief WS-RM CloseSequence service operation. Automatically invoked by the server to process the close sequence request. Updates the server's local sequence state to closed. @param soap context @param[in] req wsrm__CloseSequence request message @param[out] req wsrm__CloseSequenceResponse response message @return SOAP_OK or error code */ int __wsrm__CloseSequence(struct soap *soap, struct wsrm__CloseSequenceType *req, struct wsrm__CloseSequenceResponseType *res) { struct soap_wsrm_sequence *seq; struct soap_wsrm_data *data; DBGFUN("__wsrm__CloseSequence"); if (soap_wsa_check(soap)) return soap->error; data = (struct soap_wsrm_data*)soap_lookup_plugin(soap, soap_wsrm_id); if (!data) return soap->error = SOAP_PLUGIN_ERROR; if (!req) return soap_wsrm_error(soap, NULL, wsrm__WSRMRequired); seq = soap_wsrm_seq_lookup(data, req->Identifier); if (!seq) return soap_wsrm_error(soap, NULL, wsrm__UnknownSequence); if (seq->state == SOAP_WSRM_TERMINATED) return soap_wsrm_error(soap, seq, wsrm__SequenceTerminated); if (req->LastMsgNumber) seq->num = *req->LastMsgNumber; seq->state = SOAP_WSRM_CLOSED; res->Identifier = req->Identifier; soap->header->wsrm__Sequence = NULL; soap->header->wsrm__AckRequested = NULL; return soap_wsa_reply(soap, NULL, SOAP_NAMESPACE_OF_wsrm"CloseSequenceResponse"); } /** @fn int __wsrm__TerminateSequence(struct soap *soap, struct wsrm__TerminateSequenceType *req, struct wsrm__TerminateSequenceResponseType *res) @brief WS-RM TerminateSequence service operation. Automatically invoked by the server to process the close sequence request. Updates the server's local sequence state to terminated. @param soap context @param[in] req wsrm__TerminateSequence request message @param[out] req wsrm__TerminateSequenceResponse response message @return SOAP_OK or error code */ int __wsrm__TerminateSequence(struct soap *soap, struct wsrm__TerminateSequenceType *req, struct wsrm__TerminateSequenceResponseType *res) { struct soap_wsrm_sequence *seq; struct soap_wsrm_data *data; DBGFUN("__wsrm__TerminateSequence"); if (soap_wsa_check(soap)) return soap->error; data = (struct soap_wsrm_data*)soap_lookup_plugin(soap, soap_wsrm_id); if (!data) return soap->error = SOAP_PLUGIN_ERROR; if (!req) return soap_wsrm_error(soap, NULL, wsrm__WSRMRequired); seq = soap_wsrm_seq_lookup(data, req->Identifier); if (!seq) return soap_wsrm_error(soap, NULL, wsrm__UnknownSequence); if (seq->state == SOAP_WSRM_TERMINATED) return soap_wsrm_error(soap, seq, wsrm__SequenceTerminated); seq->state = SOAP_WSRM_TERMINATED; if (req->LastMsgNumber) { seq->num = *req->LastMsgNumber; if (!seq->ranges || seq->ranges->lower != 1 || seq->ranges->upper != *req->LastMsgNumber) { if (seq->behavior == DiscardEntireSequence) { soap_wsrm_num_free(soap, seq); return soap_wsrm_error(soap, seq, wsrm__SequenceTerminated); } } } soap_wsrm_num_free(soap, seq); res->Identifier = req->Identifier; soap->header->wsrm__Sequence = NULL; soap->header->wsrm__AckRequested = NULL; return soap_wsa_reply(soap, NULL, SOAP_NAMESPACE_OF_wsrm"TerminateSequenceResponse"); } /** @fn int __wsrm__SequenceAcknowledgement(struct soap *soap) @brief WS-RM SequenceAcknowledgement service operation of the AcksTo destination service for relayed acknowledgements. Invoked by the server. The AcksTo server maintains the messages that are acknowledged by updating its local sequence state information by registering message acknowledgement receipts. @param soap context @return SOAP_OK or error code */ int __wsrm__SequenceAcknowledgement(struct soap *soap) { DBGFUN("__wsrm__SequenceAcknowledgement"); soap_wsrm_cleanup(soap); /* soap_wsrm_disconnect was called upon invocation, to process acks */ return soap_send_empty_response(soap, SOAP_OK); } /******************************************************************************\ * * Server-side SOAP Fault * \******************************************************************************/ /** @fn int soap_wsrm_fault_subcode(struct soap *soap, int flag, const char *faultsubcode, const char *faultstring, const char *faultdetail) @brief Sets sender/receiver SOAP Fault (sub)code for server faults (can be user defined faults). When called before soap_wsrm_check() in the server operation, terminates the current sequence. Otherwise, the sequence is not terminated. In either case the fault is returned to sender (client) or to the FaultTo server when the WS-Addressing FaultTo header was set by the sender. @param soap context @param[in] flag 0=receiver, 1=sender @param[in] faultsubcode sub code string @param[in] faultstring fault string @param[in] faultdetail detail string @return SOAP_FAULT */ int soap_wsrm_fault_subcode(struct soap *soap, int flag, const char *faultsubcode, const char *faultstring, const char *faultdetail) { if (soap->header && soap->header->wsrm__Sequence) { struct soap_wsrm_data *data; data = (struct soap_wsrm_data*)soap_lookup_plugin(soap, soap_wsrm_id); if (data) { struct soap_wsrm_sequence *seq = soap_wsrm_seq_lookup(data, soap->header->wsrm__Sequence->Identifier); seq->state = SOAP_WSRM_TERMINATED; } } return soap_wsa_fault_subcode(soap, flag, faultsubcode, faultstring, faultdetail); } /** @fn int soap_wsrm_sender_fault_subcode(struct soap *soap, const char *faultsubcode, const char *faultstring, const char *faultdetail) @brief Sets sender SOAP Fault (sub)code for server faults (can be user defined faults). When called before soap_wsrm_check() in the server operation, terminates the current sequence. Otherwise, the sequence is not terminated. In either case the fault is returned to sender (client) or to the FaultTo server when the WS-Addressing FaultTo header was set by the sender. @param soap context @param[in] faultsubcode sub code string @param[in] faultstring fault string @param[in] faultdetail detail string @return SOAP_FAULT */ int soap_wsrm_sender_fault_subcode(struct soap *soap, const char *faultsubcode, const char *faultstring, const char *faultdetail) { return soap_wsrm_fault_subcode(soap, 1, faultsubcode, faultstring, faultdetail); } /** @fn int soap_wsrm_receiver_fault_subcode(struct soap *soap, const char *faultsubcode, const char *faultstring, const char *faultdetail) @brief Sets receiver SOAP Fault (sub)code for server faults (can be user defined faults). When called before soap_wsrm_check() in the server operation, terminates the current sequence. Otherwise, the sequence is not terminated. In either case the fault is returned to sender (client) or to the FaultTo server when the WS-Addressing FaultTo header was set by the sender. @param soap context @param[in] faultsubcode sub code string @param[in] faultstring fault string @param[in] faultdetail detail string @return SOAP_FAULT */ int soap_wsrm_receiver_fault_subcode(struct soap *soap, const char *faultsubcode, const char *faultstring, const char *faultdetail) { return soap_wsrm_fault_subcode(soap, 0, faultsubcode, faultstring, faultdetail); } /** @fn int soap_wsrm_sender_fault(struct soap *soap, const char *faultstring, const char *faultdetail) @brief Sets sender SOAP Fault for server faults (can be user defined faults). When called before soap_wsrm_check() in the server operation, terminates the current sequence. Otherwise, the sequence is not terminated. In either case the fault is returned to sender (client) or to the FaultTo server when the WS-Addressing FaultTo header was set by the sender. @param soap context @param[in] faultstring fault string @param[in] faultdetail detail string @return SOAP_FAULT */ int soap_wsrm_sender_fault(struct soap *soap, const char *faultstring, const char *faultdetail) { return soap_wsrm_fault_subcode(soap, 1, NULL, faultstring, faultdetail); } /** @fn int soap_wsrm_receiver_fault(struct soap *soap, const char *faultstring, const char *faultdetail) @brief Sets receiver SOAP Fault for server faults (can be user defined faults). When called before soap_wsrm_check() in the server operation, terminates the current sequence. Otherwise, the sequence is not terminated. In either case the fault is returned to sender (client) or to the FaultTo server when the WS-Addressing FaultTo header was set by the sender. @param soap context @param[in] faultstring fault string @param[in] faultdetail detail string @return SOAP_FAULT */ int soap_wsrm_receiver_fault(struct soap *soap, const char *faultstring, const char *faultdetail) { return soap_wsrm_fault_subcode(soap, 0, NULL, faultstring, faultdetail); } /******************************************************************************\ * * WS-RM Faults * \******************************************************************************/ /** @fn int soap_wsrm_check_fault(struct soap *soap, enum wsrm__FaultCodes *fault, char **info) @brief Checks the presence of a WS-RM fault at the client side (or in the FaultTo destination service) when a response is received. @param soap context @param[out] fault code @param[out] info string pointer related to the fault (or NULL) @return SOAP_OK (no fault or not a WS-RM fault) or error when not WS-RM fault */ int soap_wsrm_check_fault(struct soap *soap, enum wsrm__FaultCodes *fault, const char **info) { if (soap->error && soap->fault && soap->fault->SOAP_ENV__Code) { const char *code = *soap_faultsubcode(soap); if (info) *info = *soap_faultdetail(soap); if (code) return soap_s2wsrm__FaultCodes(soap, code, fault); } return SOAP_OK; } /** @fn int soap_wsrm_error(struct soap *soap, struct soap_wsrm_sequence *seq, enum wsrm__FaultCodes fault) @brief Sets SOAP Fault (sub)code for server WS-RM fault response. Terminates the sequence. @param soap context @param[in] seq pointer to sequence to terminate or NULL @param[in] fault is one of enum wsrm__FaultCodes enumeration values @return SOAP_FAULT */ int soap_wsrm_error(struct soap *soap, struct soap_wsrm_sequence *seq, enum wsrm__FaultCodes fault) { const char *code = soap_wsrm__FaultCodes2s(soap, fault); const char *reason = NULL; DBGFUN1("soap_wsrm_error", "code=%s", code?code:"(null)"); if (seq) { seq->fault = fault; seq->state = SOAP_WSRM_TERMINATED; } switch (fault) { case wsrm__SequenceTerminated: reason = "The Sequence has been terminated due to an unrecoverable error."; break; case wsrm__UnknownSequence: reason = "The value of the wsrm:Identifier is not a known Sequence identifier."; break; case wsrm__InvalidAcknowledgement: reason = "The SequenceAcknowledgement violates the cumulative Acknowledgement invariant."; break; case wsrm__MessageNumberRollover: reason = "The maximum value for wsrm:MessageNumber has been exceeded."; break; case wsrm__CreateSequenceRefused: reason = "The Create Sequence request has been refused by the RM Destination."; break; case wsrm__SequenceClosed: reason = "The Sequence is closed and cannot accept new messages."; break; case wsrm__WSRMRequired: reason = "The RM Destination requires the use of WSRM."; break; default: break; } if (soap->version == 1) { soap_header(soap); if (soap->header) { soap_default_SOAP_ENV__Header(soap, soap->header); soap->header->wsrm__SequenceFault = (struct wsrm__SequenceFaultType*)soap_malloc(soap, sizeof(struct wsrm__SequenceFaultType)); if (soap->header->wsrm__SequenceFault) { soap_default_wsrm__SequenceFaultType(soap, soap->header->wsrm__SequenceFault); soap->header->wsrm__SequenceFault->FaultCode = fault; soap->header->wsrm__SequenceFault->Detail = (struct SOAP_ENV__Detail*)soap_malloc(soap, sizeof(struct SOAP_ENV__Detail)); if (soap->header->wsrm__SequenceFault->Detail) { soap_default_SOAP_ENV__Detail(soap, soap->header->wsrm__SequenceFault->Detail); soap->header->wsrm__SequenceFault->Detail->__any = (char*)reason; } } } } else soap->header = NULL; return soap_wsa_sender_fault_subcode(soap, code, reason, NULL); } /******************************************************************************\ * * WS-RM State Dump * \******************************************************************************/ /** @fn void soap_wsrm_dump(struct soap *soap, FILE *fd) @brief Dumps the current sequences and details of the wsrm plugin for diagnotics purposes. @param[in] fd file descriptor to send text to */ void soap_wsrm_dump(struct soap *soap, FILE *fd) { struct soap_wsrm_sequence *seq; struct soap_wsrm_data *data = (struct soap_wsrm_data*)soap_lookup_plugin(soap, soap_wsrm_id); if (!data) return; fprintf(fd, "\n**** %s STATE DUMP ****\n", SOAP_WSRM_ID); for (seq = data->sequences; seq; seq = seq->next) { struct soap_wsrm_range *r; struct soap_wsrm_message *p; switch (seq->state) { case SOAP_WSRM_UNKNOWN: fputs("UNKNOWN ", fd); break; case SOAP_WSRM_CREATED: fputs("CREATED ", fd); break; case SOAP_WSRM_CLOSED: fputs("CLOSED ", fd); break; case SOAP_WSRM_TERMINATED: fputs("TERMINATED ", fd); break; default: break; } fprintf(fd, "SEQUENCE %s\n", seq->id?seq->id:"(null)"); fprintf(fd, " TO = %s\n", seq->to?seq->to:"(null)"); fprintf(fd, " ACKSTO = %s\n", seq->acksto?seq->acksto:"(null)"); fprintf(fd, " EXPIRES = %s\n", soap_dateTime2s(soap, seq->expires)); fprintf(fd, " LAST # = "SOAP_ULONG_FORMAT"\n", seq->num); for (p = seq->messages; p; p = p->next) { if (p->list) fprintf(fd, " NO ACK # = "SOAP_ULONG_FORMAT"\n", p->num); } for (r = seq->ranges; r; r = r->next) fprintf(fd, " RANGE # = "SOAP_ULONG_FORMAT" : "SOAP_ULONG_FORMAT"\n", r->lower, r->upper); fprintf(fd, "END OF SEQUENCE %s\n", seq->id?seq->id:"(null)"); } } /******************************************************************************\ * * Plugin registry functions * \******************************************************************************/ /** @fn int soap_wsrm(struct soap *soap, struct soap_plugin *p, void *arg) @brief Plugin registry function, used with soap_register_plugin. @param soap context @param[in,out] p plugin created in registry @param[in] arg passed from soap_register_plugin_arg @return SOAP_OK */ int soap_wsrm(struct soap *soap, struct soap_plugin *p, void *arg) { p->id = soap_wsrm_id; /* create local plugin data */ p->data = (void*)malloc(sizeof(struct soap_wsrm_data)); /* register the destructor */ p->fdelete = soap_wsrm_delete; /* if OK then initialize */ if (p->data) { if (soap_wsrm_init(soap, (struct soap_wsrm_data*)p->data, arg)) { free(p->data); /* error: could not init */ return SOAP_EOM; /* return error */ } } return SOAP_OK; } /** @fn int soap_wsrm_init(struct soap *soap, struct soap_wsrm_data *data) @brief Initializes plugin data. @param soap context @param[in,out] data plugin data @return SOAP_OK */ static int soap_wsrm_init(struct soap *soap, struct soap_wsrm_data *data, void *arg) { char *s, t[30]; if (arg) s = (char*)arg; else { time_t r = time(NULL); sprintf(t, "%08x-%04x-%04x-%04x-%04x", (int)r, soap_random & 0xFFFF, soap_random & 0xFFFF, soap_random & 0xFFFF, soap_random & 0xFFFF); s = t; } data->idname = strcpy(SOAP_MALLOC(soap, strlen(s) + 1), s); data->enabled = 0; data->idnum = 0; data->sequences = NULL; data->fsend = soap->fsend; soap->fsend = soap_wsrm_send; data->fdisconnect = soap->fdisconnect; soap->fdisconnect = soap_wsrm_disconnect; return SOAP_OK; } /** @fn void soap_wsrm_delete(struct soap *soap, struct soap_plugin *p) @brief Deletes plugin data. @param soap context @param[in,out] p plugin @return SOAP_OK */ static void soap_wsrm_delete(struct soap *soap, struct soap_plugin *p) { /* free allocated plugin data. If fcopy() is not set, then this function is not called for all copies of the plugin created with soap_copy(). In this example, the fcopy() callback is omitted and the plugin data is shared by the soap copies created with soap_copy() */ SOAP_FREE(soap, (char*)((struct soap_wsrm_data*)p->data)->idname); free(p->data); } /******************************************************************************\ * * Callbacks registered by plugin * \******************************************************************************/ /** @fn int soap_wsrm_send(struct soap *soap, const char *buf, size_t len) @brief Internal callback function to override fsend(). When the wsrm plugin is enabled, saves the message to the current sequence that is created at the client side. Allows unacknowledged messages to be resend with soap_wsrm_resend(). @param soap context @param[in] buf message data @param[in] len message data length @return SOAP_OK or error code */ static int soap_wsrm_send(struct soap *soap, const char *buf, size_t len) { struct soap_wsrm_data *data = (struct soap_wsrm_data*)soap_lookup_plugin(soap, soap_wsrm_id); if (data->enabled) if (soap_wsrm_seq_append(soap, data, buf, len)) return soap->error; return data->fsend(soap, buf, len); /* pass data on to next send callback */ } /** @fn int soap_wsrm_disconnect(struct soap *soap) @brief Internal callback function to override fdisconnect(). Takes acks returned by response to update the states of the sequences with acknowledgements. Acknowledged messages do not have to be resend and are purged from the sequence states. @param soap context @return SOAP_OK or error code */ static int soap_wsrm_disconnect(struct soap *soap) { struct soap_wsrm_data *data = (struct soap_wsrm_data*)soap_lookup_plugin(soap, soap_wsrm_id); DBGFUN("soap_wsrm_disconnect"); data->enabled = 0; /* TODO: thread local */ if (data->fdisconnect && data->fdisconnect(soap)) return soap->error; if (soap->header && soap->header->wsrm__SequenceAcknowledgement) { int i; for (i = 0; i < soap->header->__sizeSequenceAcknowledgement; i++) { if (soap_wsrm_process_ack(soap, &soap->header->wsrm__SequenceAcknowledgement[i])) return soap->error; } } return SOAP_OK; } /******************************************************************************\ * * Process Acknowledgements * \******************************************************************************/ /** @fn int soap_wsrm_process_ack(struct soap *soap, struct _wsrm__SequenceAcknowledgement *ack) @brief Internal function to purge acknowledged messages as given by wsrm:SequenceAcknowledgement header. @param soap context @param[in] ack from the WS-RM SequenceAcknowledgement header. @return SOAP_OK or error code */ static int soap_wsrm_process_ack(struct soap *soap, struct _wsrm__SequenceAcknowledgement *ack) { struct soap_wsrm_sequence *seq; struct soap_wsrm_data *data = (struct soap_wsrm_data*)soap_lookup_plugin(soap, soap_wsrm_id); DBGFUN1("soap_wsrm_process_ack", "ack id=%s", ack->Identifier?ack->Identifier:"none"); if (!data) return soap->error = SOAP_PLUGIN_ERROR; seq = soap_wsrm_seq_lookup(data, ack->Identifier); if (!seq) { char *s; /* we fail: return soap_wsrm_error(soap, NULL, wsrm__UnknownSequence); */ if (!(seq = soap_wsrm_seq_insert(soap, data))) return soap->error; seq->state = SOAP_WSRM_UNKNOWN; s = (char*)SOAP_MALLOC(soap, strlen(ack->Identifier) + 1); if (!s) return soap->error = SOAP_EOM; seq->id = strcpy(s, ack->Identifier); } /* process Nack, there is nothing to do since resend will take place anyway */ if (ack->__sizeNack) { int i; for (i = 0; i < ack->__sizeNack; i++) { DBGLOG(TEST, SOAP_MESSAGE(fdebug, "Nack="SOAP_LONG_FORMAT"\n", ack->Nack[i])); } } else if (ack->Final) { struct soap_wsrm_message *p; /* Final, so no resends and delete all messages */ for (p = seq->messages; p; p = p->next) soap_wsrm_msg_free(soap, p); } else { /* else clear acked messages, unless None */ if (!ack->None) { int i; for (i = 0; i < ack->__sizeAcknowledgementRange; i++) { struct soap_wsrm_message *p; if (seq->state == SOAP_WSRM_UNKNOWN) { if (soap_wsrm_range_insert(soap, seq, ack->AcknowledgementRange[i].Lower, ack->AcknowledgementRange[i].Upper)) return soap->error; } else if (ack->AcknowledgementRange[i].Upper > seq->num) return soap_wsrm_error(soap, seq, wsrm__InvalidAcknowledgement); for (p = seq->messages; p; p = p->next) { if (p->list && ack->AcknowledgementRange[i].Lower <= p->num && p->num <= ack->AcknowledgementRange[i].Upper) soap_wsrm_msg_free(soap, p); } } } } return SOAP_OK; } /******************************************************************************\ * * Add Acknowledgements * \******************************************************************************/ /** @fn int soap_wsrm_add_acks(struct soap *soap) @brief Internal function to add SequenceAcknowledgement headers for AckRequested. Sends acknowledgements to the AcksTo destination service. These sends can take SOAP_WSRM_TIMEOUT seconds time, with at most 10 retries performed. @param soap context @return SOAP_OK or error code */ static int soap_wsrm_add_acks(struct soap *soap) { const char *from = NULL, *replyto = NULL; int numack = 0; struct soap_wsrm_data *data = (struct soap_wsrm_data*)soap_lookup_plugin(soap, soap_wsrm_id); DBGFUN("soap_wsrm_add_acks"); if (!data) return soap->error = SOAP_PLUGIN_ERROR; if (!soap->header) return soap_wsrm_error(soap, NULL, wsrm__WSRMRequired); if (soap->header->wsa5__From && soap->header->wsa5__From->Address && strcmp(soap->header->wsa5__From->Address, soap_wsa_anonymousURI)) from = soap->header->wsa5__From->Address; if (soap->header->wsa5__ReplyTo && soap->header->wsa5__ReplyTo->Address && strcmp(soap->header->wsa5__ReplyTo->Address, soap_wsa_anonymousURI)) replyto = soap->header->wsa5__ReplyTo->Address; soap->header->wsrm__SequenceAcknowledgement = NULL; soap->header->wsrm__SequenceFault = NULL; /* acks requested? */ if (soap->header->__sizeAckRequested) { int i; struct _wsrm__SequenceAcknowledgement *ack; ack = (struct _wsrm__SequenceAcknowledgement*)soap_malloc(soap, soap->header->__sizeAckRequested * sizeof(struct _wsrm__SequenceAcknowledgement)); if (!ack) return SOAP_EOM; /* for each ack requested, return ack response in header: */ numack = 0; for (i = 0; i < soap->header->__sizeAckRequested; i++) { struct soap_wsrm_sequence *seq = soap_wsrm_seq_lookup(data, soap->header->wsrm__AckRequested->Identifier); if (!seq) return soap_wsrm_error(soap, NULL, wsrm__UnknownSequence); /* acks with non-anonym acksto destination */ /* TODO: this might not be the optimal place to do since it takes time */ if (seq->acksto && strcmp(seq->acksto, soap_wsa_anonymousURI) && (!from || strcmp(from, seq->acksto)) && (!replyto || strcmp(replyto, seq->acksto))) { int retry; struct soap *acksto_soap = soap_copy(soap); struct _wsrm__SequenceAcknowledgement ack; DBGLOG(TEST, SOAP_MESSAGE(fdebug, "Sending ack for seq %s to %s\n", seq->id, seq->acksto)); acksto_soap->socket = SOAP_INVALID_SOCKET; acksto_soap->connect_timeout = SOAP_WSRM_TIMEOUT; acksto_soap->recv_timeout = SOAP_WSRM_TIMEOUT; acksto_soap->send_timeout = SOAP_WSRM_TIMEOUT; acksto_soap->header = NULL; soap_header(acksto_soap); soap_default_SOAP_ENV__Header(acksto_soap, acksto_soap->header); acksto_soap->header->__sizeSequenceAcknowledgement = 1; if (soap_wsrm_set_ack(soap, seq, &ack)) { soap_end(acksto_soap); soap_free(acksto_soap); return soap->error; } acksto_soap->header->wsrm__SequenceAcknowledgement = &ack; soap_wsa_request(acksto_soap, NULL, seq->acksto, SOAP_NAMESPACE_OF_wsrm"/SequenceAcknowledgement"); if (soap->header->wsa5__FaultTo && soap->header->wsa5__FaultTo->Address) soap_wsa_add_FaultTo(acksto_soap, soap->header->wsa5__FaultTo->Address); else soap_wsa_add_FaultTo(acksto_soap, soap_wsa_noneURI); /* send, retry when HTTP 307 at most 10 times */ retry = 10; while (soap_send___wsrm__SequenceAcknowledgement(acksto_soap, seq->acksto, NULL)) { char *s; if (soap->error != 307 || retry-- == 0) break; s = (char*)SOAP_MALLOC(soap, strlen(soap->endpoint) + 1); if (s) { SOAP_FREE(soap, (char*)seq->acksto); seq->acksto = strcpy(s, soap->endpoint); } else break; } if (!soap->error) soap_recv_empty_response(acksto_soap); soap_end(acksto_soap); soap_free(acksto_soap); } else { if (soap_wsrm_set_ack(soap, seq, &ack[numack])) return soap->error; numack++; } } soap->header->__sizeSequenceAcknowledgement = numack; soap->header->wsrm__SequenceAcknowledgement = ack; soap->header->wsa5__Action = SOAP_NAMESPACE_OF_wsrm"/SequenceAcknowledgement"; } soap->header->wsrm__Sequence = NULL; soap->header->wsrm__AckRequested = NULL; if (numack && from && replyto && strcmp(from, replyto)) { int (*fpost)(struct soap*, const char*, const char*, int, const char*, const char*, size_t); /* override HTTP POST by HTTP OK */ fpost = soap->fpost; soap->fpost = soap_wsrm_post; soap_send___wsrm__SequenceAcknowledgement(soap, "", NULL); soap->fpost = fpost; /* prevent wsa from sending ACCEPTED */ soap->keep_alive = 0; return soap_closesock(soap); } return SOAP_OK; } /** @fn int soap_wsrm_set_ack(struct soap *soap, struct soap_wsrm_sequence *seq, struct _wsrm__SequenceAcknowledgement *ack) @brief Internal function called by soap_wsrm_add_acks() to populate the SequenceAcknowledgement header block. @param soap context @param[in] seq pointer to sequence @param[out] ack pointer to SequenceAcknowledgement to populate @brief */ static int soap_wsrm_set_ack(struct soap *soap, struct soap_wsrm_sequence *seq, struct _wsrm__SequenceAcknowledgement *ack) { DBGFUN("soap_wsrm_set_ack"); soap_default__wsrm__SequenceAcknowledgement(soap, ack); ack->Identifier = soap->header->wsrm__AckRequested->Identifier; ack->__sizeNack = 0; ack->Nack = NULL; ack->__sizeAcknowledgementRange = soap_wsrm_num_size(seq); if (ack->__sizeAcknowledgementRange == 0) { ack->AcknowledgementRange = NULL; ack->None = (struct _wsrm__SequenceAcknowledgement_None*)soap_malloc(soap, sizeof(struct _wsrm__SequenceAcknowledgement_None)); if (!ack->None) return SOAP_EOM; soap_default__wsrm__SequenceAcknowledgement_None(soap, ack->None); } else { struct soap_wsrm_range *p; struct _wsrm__SequenceAcknowledgement_AcknowledgementRange *q; ack->AcknowledgementRange = (struct _wsrm__SequenceAcknowledgement_AcknowledgementRange*)soap_malloc(soap, ack->__sizeAcknowledgementRange * sizeof(struct _wsrm__SequenceAcknowledgement_AcknowledgementRange)); if (!ack->AcknowledgementRange) return SOAP_EOM; soap_default__wsrm__SequenceAcknowledgement_AcknowledgementRange(soap, ack->AcknowledgementRange); for (p = seq->ranges, q = ack->AcknowledgementRange; p; p = p->next, q++) { q->Lower = p->lower; q->Upper = p->upper; } ack->None = NULL; } /* if closed, Final is set */ if (seq->state == SOAP_WSRM_CREATED) ack->Final = NULL; else { ack->Final = (struct _wsrm__SequenceAcknowledgement_Final*)soap_malloc(soap, sizeof(struct _wsrm__SequenceAcknowledgement_Final)); if (!ack->Final) return SOAP_EOM; soap_default__wsrm__SequenceAcknowledgement_Final(soap, ack->Final); } return SOAP_OK; } /** @fn int soap_wsrm_post(struct soap *soap, const char *endpoint, const char *host, int port, const char *path, const char *action, size_t count) @brief Internal callback overrides the HTTP post operations to send an HTTP OK response header in place. @param soap context @param endpoint (not used) @param host (not used) @param port (not used) @param path (not used) @param action (HTTP action not used) @param count message length (if non-chunked) */ static int soap_wsrm_post(struct soap *soap, const char *endpoint, const char *host, int port, const char *path, const char *action, size_t count) { return soap->fresponse(soap, SOAP_OK, count); } /******************************************************************************\ * * WS-RM Resend * \******************************************************************************/ /** @fn int soap_wsrm_resend_seq(struct soap *soap, struct soap_wsrm_sequence_handle seq, ULONG64 lower, ULONG64 upper) @brief Internal function to resend unacknowledged messages of a sequence given a range of message numbers. Used by soap_wsrm_resend(). @param soap context @param seq sequence handle set by soap_wsrm_create() or soap_wsrm_create_offer() @param[in] lower resend message range lower bound (0 for lowest) @param[in] upper resend message range upper bound (or 0 for infinite) @return SOAP_OK or error code (can be ignored to continue sequence) */ static int soap_wsrm_resend_seq(struct soap *soap, struct soap_wsrm_sequence *seq, ULONG64 lower, ULONG64 upper) { struct soap_wsrm_message *p; struct soap_wsrm_data *data = (struct soap_wsrm_data*)soap_lookup_plugin(soap, soap_wsrm_id); DBGFUN2("soap_wsrm_resend_seq", "lower="SOAP_LONG_FORMAT, lower, "upper="SOAP_LONG_FORMAT, upper); if (!data) return soap->error = SOAP_PLUGIN_ERROR; /* disable send plugin */ data->enabled = 0; /* TODO should be thread local */ for (p = seq->messages; p; p = p->next) { if (p->num >= lower && (p->num <= upper || upper == 0)) { if (p->list) /* means not acked */ { struct soap_wsrm_content *q; DBGLOG(TEST, SOAP_MESSAGE(fdebug, "Resending message "SOAP_ULONG_FORMAT"\n", p->num)); soap->omode |= SOAP_ENC_XML; /* disables HTTP */ if (soap_connect(soap, seq->to, NULL)) return soap->error; DBGLOG(SENT, SOAP_MESSAGE(fdebug, "\n==== BEGIN RESEND ====\n")); for (q = p->list; q; q = q->next) { DBGMSG(SENT, q->buf, q->len); if (data->fsend(soap, q->buf, q->len)) return soap->error; } if (soap_end_send(soap)) return soap_closesock(soap); DBGLOG(SENT, SOAP_MESSAGE(fdebug, "\n==== END RESEND ====\n")); soap->omode &= ~SOAP_ENC_XML; /* reenables HTTP */ if (!soap_begin_recv(soap)) soap_ignore_element(soap); else if (soap->error != SOAP_NO_DATA && soap->error != 202) return soap_closesock(soap); soap_end_recv(soap); soap_closesock(soap); } } } return SOAP_OK; } /******************************************************************************\ * * Sequences * \******************************************************************************/ /** @fn const char *soap_wsrm_seq_newid(struct soap *soap, struct soap_wsrm_data *data) @brief Internal function to obtain a new sequence identifier. @param soap context @param data plugin @return sequence identifier string */ static const char * soap_wsrm_seq_newid(struct soap *soap, struct soap_wsrm_data *data) { char *s = (char*)SOAP_MALLOC(soap, strlen(data->idname) + 16); if (!s) { soap->error = SOAP_EOM; return NULL; } sprintf(s, "%s%08lx", data->idname, data->idnum++); return s; } /** @fn struct soap_wsrm_sequence *soap_wsrm_seq_lookup(const struct soap_wsrm_data *data, const char *id) @brief Internal function to look up sequence given its id. @param soap context @param data plugin @param[in] sequence identifier string @return sequence or NULL */ static struct soap_wsrm_sequence * soap_wsrm_seq_lookup(const struct soap_wsrm_data *data, const char *id) { if (id) { struct soap_wsrm_sequence *p; for (p = data->sequences; p; p = p->next) if (p->id && !strcmp(p->id, id)) return p; } return NULL; } /** @fn struct soap_wsrm_sequence *soap_wsrm_seq_insert(struct soap *soap, struct soap_wsrm_data *data) @brief Internal function to create a new local sequence state. @param soap context @param data plugin @return sequence or NULL */ static struct soap_wsrm_sequence * soap_wsrm_seq_insert(struct soap *soap, struct soap_wsrm_data *data) { struct soap_wsrm_sequence *seq; seq = (struct soap_wsrm_sequence*)SOAP_MALLOC(soap, sizeof(struct soap_wsrm_sequence)); if (!seq) { soap->error = SOAP_EOM; return NULL; } seq->handle = 0; seq->id = NULL; seq->to = NULL; seq->acksto = NULL; seq->expires = time(NULL) + SOAP_WSRM_MAX_SEC_TO_EXPIRE; seq->behavior = NoDiscard; seq->num = 0; seq->fault = (enum wsrm__FaultCodes)(-1); seq->state = SOAP_WSRM_NONE; seq->retry = 0; seq->messages = NULL; seq->ranges = NULL; seq->next = data->sequences; data->sequences = seq; return seq; } /** @fn int soap_wsrm_seq_append(struct soap *soap, struct soap_wsrm_data *data, const char *buf, size_t len) @brief Internal function used by soap_wsrm_send() to append message data to the current message being transmitted for the current sequence. @param soap context @param data plugin @param[in] buf message data @param[in] len message data length @return SOAP_OK or error code */ static int soap_wsrm_seq_append(struct soap *soap, struct soap_wsrm_data *data, const char *buf, size_t len) { struct soap_wsrm_content *p; DBGFUN1("soap_wsrm_seq_append", "len=%lu", (unsigned long)len); /* a sequence must be present and have a message head */ if (!data->sequences ||! data->sequences->messages) return soap->error = SOAP_PLUGIN_ERROR; p = (struct soap_wsrm_content*)SOAP_MALLOC(soap, sizeof(struct soap_wsrm_content)); if (!p) return soap->error = SOAP_EOM; p->buf = NULL; p->next = NULL; if (!data->sequences->messages->list) data->sequences->messages->list = p; if (data->sequences->messages->last) data->sequences->messages->last->next = p; data->sequences->messages->last = p; p->buf = (char*)SOAP_MALLOC(soap, len); if (!p->buf) return soap->error = SOAP_EOM; p->len = len; memcpy(p->buf, buf, len); return SOAP_OK; } /** @fn int soap_wsrm_seq_terminated(struct soap *soap, struct soap_wsrm_sequence *seq) @brief Internal function to check if a sequence is terminated or needs to be terminated when expired. @param soap context @param seq pointer to sequence @return 0 (not terminated) or 1 (terminated) */ static int soap_wsrm_seq_terminated(struct soap *soap, struct soap_wsrm_sequence *seq) { DBGFUN("soap_wsrm_seq_terminated"); if (seq->state == SOAP_WSRM_TERMINATED) return 1; if (seq->expires && seq->expires < time(NULL)) { DBGLOG(TEST, SOAP_MESSAGE(fdebug, "Sequence %s has expired\n", seq->id?seq->id:"(null)")); seq->state = SOAP_WSRM_TERMINATED; return 1; } return 0; } /******************************************************************************\ * * Messages and Message Number Ranges * \******************************************************************************/ /** @fn int soap_wsrm_num_lookup(struct soap *soap, const struct soap_wsrm_sequence *seq, ULONG64 num) @brief Internal function to look up a message number in the sequence state. @param soap context @param[in] seq pointer to sequence @param[in] num message num to search @return 0 (not found) 1 (found) */ static int soap_wsrm_num_lookup(struct soap *soap, const struct soap_wsrm_sequence *seq, ULONG64 num) { struct soap_wsrm_range *p; DBGFUN1("soap_wsrm_num_lookup", "num="SOAP_ULONG_FORMAT, num); if (seq) for (p = seq->ranges; p; p = p->next) if (p->lower <= num && num <= p->upper) return 1; return 0; } /** @fn int soap_wsrm_range_insert(struct soap *soap, struct soap_wsrm_sequence *seq, ULONG64 lower, ULONG64 upper) @brief Internal function to insert a message number range in the sequence state. @param soap context @param seq pointer to sequence @param[in] lower @param[in] upper @return SOAP_OK or error code (out of memory) */ static int soap_wsrm_range_insert(struct soap *soap, struct soap_wsrm_sequence *seq, ULONG64 lower, ULONG64 upper) { ULONG64 num; for (num = lower; num <= upper; num++) { if (!soap_wsrm_num_lookup(soap, seq, num)) { if (soap_wsrm_num_insert(soap, seq, num)) return soap->error; } } return SOAP_OK; } /** @fn int soap_wsrm_num_insert(struct soap *soap, struct soap_wsrm_sequence *seq, ULONG64 num) @brief Internal function to insert a message number in the sequence state. @param soap context @param seq pointer to sequence @param[in] num @return SOAP_OK or error code (out of memory) */ static int soap_wsrm_num_insert(struct soap *soap, struct soap_wsrm_sequence *seq, ULONG64 num) { struct soap_wsrm_range *p, **q; DBGFUN1("soap_wsrm_num_insert", "num="SOAP_ULONG_FORMAT, num); if (!seq) return SOAP_OK; /* assumes num is not in any range yet */ /* if match: increment upper bound and normalize by joining consecutive ranges */ for (p = seq->ranges; p; p = p->next) { if (num == p->upper + 1) { p->upper = num; if (p->next) { struct soap_wsrm_range *t = p->next; if (p->upper + 1 == t->lower) { p->upper = t->upper; p->next = t->next; SOAP_FREE(soap, t); } } return SOAP_OK; } } /* if match: decrement first range's lower bound */ if (seq->ranges && num == seq->ranges->lower - 1) { seq->ranges->lower = num; return SOAP_OK; } /* no match: insert new singleton range */ p = (struct soap_wsrm_range*)SOAP_MALLOC(soap, sizeof(struct soap_wsrm_message)); if (!p) return soap->error = SOAP_EOM; p->lower = p->upper = num; p->next = NULL; for (q = &seq->ranges; *q; q = &(*q)->next) { if (num < (*q)->lower) { p->next = *q; break; } } *q = p; return SOAP_OK; } /** @fn int soap_wsrm_num_size(const struct soap_wsrm_sequence *seq) @brief Internal function returns the number of message ranges in a sequence state. @param seq pointer to sequence @return number of message ranges */ static int soap_wsrm_num_size(const struct soap_wsrm_sequence *seq) { int n = 0; if (seq) { struct soap_wsrm_range *p; for (p = seq->ranges; p; p = p->next) n++; } return n; } /** @fn void soap_wsrm_num_free(struct soap *soap, struct soap_wsrm_sequence *seq) @brief Internal function to deallocate all message number ranges from a sequence state. @param soap context @param seq pointer to sequence */ static void soap_wsrm_num_free(struct soap *soap, struct soap_wsrm_sequence *seq) { DBGFUN("soap_wsrm_num_free"); if (seq) { struct soap_wsrm_range *p, *q; for (p = seq->ranges; p; p = q) { q = p->next; SOAP_FREE(soap, p); } } seq->ranges = NULL; } /** @fn void soap_wsrm_msg_free(struct soap *soap, struct soap_wsrm_message *p) @brief Internal function to deallocate all cached messages ranges from a sequence state. @param soap context @param p pointer to message (in a sequence state) */ static void soap_wsrm_msg_free(struct soap *soap, struct soap_wsrm_message *p) { struct soap_wsrm_content *q, *r; DBGFUN1("soap_wsrm_msg_free", "num="SOAP_ULONG_FORMAT, p->num); for (q = p->list; q; q = r) { r = q->next; if (q->buf) SOAP_FREE(soap, q->buf); SOAP_FREE(soap, q); } p->list = p->last = NULL; } #ifdef __cplusplus } #endif