From 27aed0b2ac327eca5b2416ed45b74a761701d7f3 Mon Sep 17 00:00:00 2001 From: lahiker42 Date: Fri, 19 Dec 2008 02:41:32 +0000 Subject: [PATCH] initial commit. this is NOT WORKING by a long shot. git-svn-id: https://protobuf-c.googlecode.com/svn/trunk@104 00440858-1255-0410-a3e6-75ea37f81c3a --- src/sctp-rpc/example-client.c | 98 ++++++ src/sctp-rpc/example-server-2.c | 33 ++ src/sctp-rpc/example-server.c | 60 ++++ src/sctp-rpc/example-word-funcs-service.c | 61 ++++ src/sctp-rpc/example-word-funcs-service.h | 8 + src/sctp-rpc/example.proto | 11 + src/sctp-rpc/protobuf-c-sctp.h | 65 ++++ src/sctp-rpc/render-rfc | 12 + src/sctp-rpc/rfc-protobuf-sctp.ms | 371 ++++++++++++++++++++++ src/sctp-rpc/sctp-channel.c | 8 + 10 files changed, 727 insertions(+) create mode 100644 src/sctp-rpc/example-client.c create mode 100644 src/sctp-rpc/example-server-2.c create mode 100644 src/sctp-rpc/example-server.c create mode 100644 src/sctp-rpc/example-word-funcs-service.c create mode 100644 src/sctp-rpc/example-word-funcs-service.h create mode 100644 src/sctp-rpc/example.proto create mode 100644 src/sctp-rpc/protobuf-c-sctp.h create mode 100755 src/sctp-rpc/render-rfc create mode 100644 src/sctp-rpc/rfc-protobuf-sctp.ms create mode 100644 src/sctp-rpc/sctp-channel.c diff --git a/src/sctp-rpc/example-client.c b/src/sctp-rpc/example-client.c new file mode 100644 index 0000000..b13861c --- /dev/null +++ b/src/sctp-rpc/example-client.c @@ -0,0 +1,98 @@ +#include "example-word-funcs-service.h" + +static void +word_closure_to_ptr_word (const Example__Word *message, + void *closure_data) +{ + const char *rv = message == NULL || message->word == NULL + ? "*error*" : message->word; + * (char **) closure_data = strdup (rv); +} + +int main (int argc, char **argv) +{ + Example__WordFuncs_Service *service = NULL; + const char *name = "word_funcs"; + const char *addr = NULL; + + dispatch = protobuf_c_sctp_dispatch_new (); + + /* Create service (either local or remote) */ + for (i = 1; i < argc; i++) + { + if (strcmp (argv[i], "--local") == 0) + { + example_word_funcs_service_init (); + service = example_word_funcs_service; + name = ""; + } + else if (strncmp (argv[i], "--addr=", 7) == 0) + addr = argv[i] + 7; + else if (strncmp (argv[i], "--name=", 7) == 0) + name = argv[i] + 7; + else + usage (argv[0]); + } + if (service == NULL && addr == NULL) + die ("missing --addr="); + if (service == NULL && addr != NULL) + { + channel = protobuf_c_sctp_channel_ipv4_connect_by_hostport (addr, dispatch); + service = protobuf_c_sctp_channel_add_remote_service (channel, + name, + &example__word_funcs__descriptor); + } + + /* main loop: invoke service methods (either local or remote) */ + for (;;) + { + fprintf (stderr, "%s >> ", name); + if (fgets (buf, sizeof (buf), stdin) == NULL) + break; + /* parse command */ + at = buf; + while (*at && isspace (*at)) + at++; + if (*at == 0) + continue; + cmd = at; + while (*at && !isspace (*at)) + at++; + if (*at != 0) + { + *at++ = 0; + while (*at && *isspace (*at)) + at++; + } + + if (strcmp (cmd, "uppercase") == 0 + || strcmp (cmd, "lowercase") == 0) + { + /* functions mapping string => string can be handled via this + mechanism */ + char *rv = NULL; + Example__Word input = EXAMPLE__WORD__INIT; + input.word = at; + if (strcmp (cmd, "uppercase") == 0) + example__word_funcs__uppercase (service, + &input, + word_closure_to_ptr_word, + &rv); + else if (strcmp (cmd, "lowercase") == 0) + example__word_funcs__lowercase (service, + &input, + word_closure_to_ptr_word, + &rv); + else + assert (0); + while (rv == NULL) + protobuf_c_sctp_dispatch_run (dispatch); + + printf ("%s\n", rv); + fflush (stdout); + free (rv); + } + } + + return 0; +} diff --git a/src/sctp-rpc/example-server-2.c b/src/sctp-rpc/example-server-2.c new file mode 100644 index 0000000..bdd93d2 --- /dev/null +++ b/src/sctp-rpc/example-server-2.c @@ -0,0 +1,33 @@ +#include "protobuf-c-sctp.h" + +void usage (const char *prog_name) +{ + die ("usage: %s --port=PORT [--name=NAME]\n" + "Run an example word-funcs service.\n", prog_name); +} + +int main (int argc, char **argv) +{ + int port = 0; + ProtobufC_SCTP_LocalService local_services[1] = { { "word_funcs", NULL } }; + for (i = 1; i < argc; i++) + { + if (strncmp (argv[i], "--port=", 7) == 0) + port = atoi (argv[i] + 7); + else if (strncmp (argv[i], "--name=", 7) == 0) + local_services[0].name = argv[i] + 7; + else + usage (argv[0]); + } + if (port == 0) + die ("--port=PORT is required"); + + /* initialize our our actual service implementation */ + example_word_funcs_service_init (); + local_services[0].service = example_word_funcs_service; + + /* run the server */ + protobuf_c_sctp_server_run (1, local_services, 0, NULL, port); + + return 0; +} diff --git a/src/sctp-rpc/example-server.c b/src/sctp-rpc/example-server.c new file mode 100644 index 0000000..0933f01 --- /dev/null +++ b/src/sctp-rpc/example-server.c @@ -0,0 +1,60 @@ +#include "protobuf-c-sctp.h" + +typedef struct _StringFuncsImpl StringFuncsImpl; +struct _StringFuncsImpl +{ + Example__WordFuncs_Service base_service; +}; + +void usage (const char *prog_name) +{ + die ("usage: %s --port=PORT [--name=NAME]\n" + "Run an example word-funcs service.\n", prog_name); +} + +typedef struct _ChannelConfigData ChannelConfigData; +struct _ChannelConfigData +{ + const char *name; + ProtobufCService *service; +}; + +/* Set up newly-accepted connection */ +static void +init_channel (ProtobufC_SCTP_Channel *channel, + void *data) +{ + ChannelConfigData *config = data; + protobuf_c_sctp_channel_add_local_service (channel, + config->name, + config->service); +} + +int main (int argc, char **argv) +{ + int port = 0; + StringFuncsImpl impl; + ChannelConfigData config = { "word_funcs", &impl.base_service.base }; + for (i = 1; i < argc; i++) + { + if (strncmp (argv[i], "--port=", 7) == 0) + port = atoi (argv[i] + 7); + else if (strncmp (argv[i], "--name=", 7) == 0) + config.name = argv[i] + 7; + else + usage (argv[0]); + } + if (port == 0) + die ("--port=PORT is required"); + + /* initialize our our actual service implementation */ + example__word_funcs__init (&impl.base_service); + impl.base_service.uppercase = implement_uppercase; + + listener = protobuf_c_sctp_listener_new_ipv4 (NULL, port, dispatch, + init_channel, &config); + for (;;) + protobuf_c_sctp_dispatch_run (dispatch, -1); + + return 0; +} diff --git a/src/sctp-rpc/example-word-funcs-service.c b/src/sctp-rpc/example-word-funcs-service.c new file mode 100644 index 0000000..f56c28f --- /dev/null +++ b/src/sctp-rpc/example-word-funcs-service.c @@ -0,0 +1,61 @@ +#include "example-word-funcs-service.h" + +/* note: the implementations here only handle ascii characters-- + this is a tutorial about RPC, + not utf8 encoding or unicode normalization */ + +Example__WordFuncs_Service *example_word_funcs_service; + +static Example__WordFuncs_Service word_funcs_service; + +static void +implement_uppercase (Example__WordFuncs_Service *service, + const Example__Word *input, + Example__Word_Closure closure, + void *closure_data) +{ + char *rv = strdup (input->word); + Example__Word output = EXAMPLE__WORD__INIT; + char *at; + for (at = rv; *at; at++) + if ('a' <= *at && *at <= 'z') + *at -= ('a' - 'A'); + output.word = rv; + closure (&output, closure_data); + free (rv); +} + +static void +implement_lowercase (Example__WordFuncs_Service *service, + const Example__Word *input, + Example__Word_Closure closure, + void *closure_data) +{ + char *rv = strdup (input->word); + Example__Word output = EXAMPLE__WORD__INIT; + char *at; + for (at = rv; *at; at++) + if ('A' <= *at && *at <= 'Z') + *at += ('a' - 'A'); + output.word = rv; + closure (&output, closure_data); + free (rv); +} + +#if 0 /* you could consider an init function like this instead */ +void example_word_funcs_service_init (void) +{ + example_word_funcs_service = &word_funcs_service; + example__word_funcs__init (&word_funcs_service); + word_funcs_service.uppercase = implement_uppercase; + word_funcs_service.lowercase = implement_lowercase; +} +#endif + +/* TODO: provide a c99 example */ +Example__WordFuncs_Service example__word_funcs__service__global = +{ + EXAMPLE__WORD_FUNCS__SERVICE__BASE_INIT, + implement_uppercase, + implement_lowercase +}; diff --git a/src/sctp-rpc/example-word-funcs-service.h b/src/sctp-rpc/example-word-funcs-service.h new file mode 100644 index 0000000..923d287 --- /dev/null +++ b/src/sctp-rpc/example-word-funcs-service.h @@ -0,0 +1,8 @@ +#include "generated/example.pb-c.h" + +extern Example__WordFuncs_Service example__word_funcs__service__global; + +/* extern ProtobufCService *example__word_funcs__service; */ +#define example__word_funcs__service \ + ((ProtobufCService *) (&example__word_funcs__service__global)) + diff --git a/src/sctp-rpc/example.proto b/src/sctp-rpc/example.proto new file mode 100644 index 0000000..423c5f0 --- /dev/null +++ b/src/sctp-rpc/example.proto @@ -0,0 +1,11 @@ +package example; + +message Word +{ + required string word = 1; +}; + +service WordFuncs { + rpc Uppercase (Word) returns (Word); + rpc Lowercase (Word) returns (Word); +} diff --git a/src/sctp-rpc/protobuf-c-sctp.h b/src/sctp-rpc/protobuf-c-sctp.h new file mode 100644 index 0000000..5083946 --- /dev/null +++ b/src/sctp-rpc/protobuf-c-sctp.h @@ -0,0 +1,65 @@ +#include "../google/protobuf-c/protobuf-c.h" + +typedef struct _ProtobufC_SCTP_Dispatch ProtobufC_SCTP_Dispatch; +typedef struct _ProtobufC_SCTP_Channel ProtobufC_SCTP_Channel; +typedef struct _ProtobufC_SCTP_Listener ProtobufC_SCTP_Listener; + +/* For servers and clients, you can get at the array of + remote service objects from the closure-data of your local-service's + invocation method. */ +ProtobufCService ** +protobuf_c_sctp_closure_data_get_remote_services (void *closure_data); + +typedef struct { + const char *name; + ProtobufCService *service; +} ProtobufC_SCTP_LocalService; +typedef struct { + const char *name; + ProtobufCServiceDescriptor *descriptor; +} ProtobufC_SCTP_RemoteService; + +ProtobufC_SCTP_Config * +protobuf_c_sctp_config_new (size_t n_local_services, + ProtobufC_SCTP_LocalService *local_services, + size_t n_remote_services, + ProtobufC_SCTP_RemoteService *remote_services); + +/* a channel: an sctp association (technically sctp allows multiple + streams within a single association (unlike tcp), but we do not + use that ability. when created as a client, this does automatic + reconnecting. */ +ProtobufC_SCTP_Channel * + protobuf_c_sctp_client_new_ipv4(const uint8_t *addr, + uint16_t port, + ProtobufC_SCTP_Config *config, + ProtobufC_Dispatch *dispatch); +ProtobufC_SCTP_Channel * + protobuf_c_sctp_client_new_ipv4_dns (const char *host, + uint16_t port, + ProtobufC_SCTP_Config *config, + ProtobufC_Dispatch *dispatch); +ProtobufC_SCTP_Channel * + protobuf_c_sctp_client_new_ipv4_hostport(const char *host_port, + ProtobufC_SCTP_Config *config, + ProtobufC_SCTP_Dispatch *dispatch); +void protobuf_c_sctp_channel_set_error_handler (ProtobufC_SCTP_Channel *channel, + ProtobufC_SCTP_ErrorFunc func, + void *data, + ProtobufC_SCTP_Destroy destroy); +ProtobufCService * + protobuf_c_sctp_channel_peek_remote_service(ProtobufC_SCTP_Channel *channel, + unsigned index); +void protobuf_c_sctp_channel_shutdown (ProtobufC_SCTP_Channel *channel); +void protobuf_c_sctp_channel_destroy (ProtobufC_SCTP_Channel *channel); + + + +/* a listener: a passive sctp socket awaiting new connections */ +ProtobufC_SCTP_Server * + protobuf_c_sctp_server_new_ipv4 (const uint8_t *bind_addr, + uint16_t port, + ProtobufC_SCTP_Config *config); +void protobuf_c_sctp_server_destroy (ProtobufC_SCTP_Server *server); + + diff --git a/src/sctp-rpc/render-rfc b/src/sctp-rpc/render-rfc new file mode 100755 index 0000000..fd0cead --- /dev/null +++ b/src/sctp-rpc/render-rfc @@ -0,0 +1,12 @@ +#! /bin/sh + +nroff -t -ms rfc-protobuf-sctp.ms | + perl -e ' + undef $/; # Read whole files in a single gulp. + while (<>) { # Read the entire input file. + s/FORMFEED(\[Page\s+\d+\])\s+/ \1\n\f\n/sg; + s/\f\n$//; # Want no formfeed at end? + print; # Print the resultant file. + }' > rfc-protobuf-sctp.txt + +groff -t -ms rfc-protobuf-sctp.ms > rfc-protobuf-sctp.ps diff --git a/src/sctp-rpc/rfc-protobuf-sctp.ms b/src/sctp-rpc/rfc-protobuf-sctp.ms new file mode 100644 index 0000000..62cdd81 --- /dev/null +++ b/src/sctp-rpc/rfc-protobuf-sctp.ms @@ -0,0 +1,371 @@ +.pl 10.0i +.po 0 +.ll 7.2i +.lt 7.2i +.nr LL 7.2i +.nr LT 7.2i +.ds LF Benson +.ds RF FORMFEED[Page %] +.ds CF +.ds LH PROPOSAL +.ds RH 7 December 2008 +.ds CH Protobuf over SCTP +.hy 0 +.ad l +.in 0 +.ce +Proposal for Implementing Protocol-Buffer RPC over SCTP + +.ti 0 +Status of this Memo + +.fi +.in 3 +This memo describes a proposed method for the encapsulation of +Protocol-Buffer Datagrams via the Stream Control Transmission Protocol. +This is an experimental, not recommended standard. +Distribution of this memo is unlimited. + +.ti 0 +Overview and Rationale + +Google's Protobuf Buffer package (or "protobuf", for short) +provides a language for +specifying the format binary-data. In that language, a "message" +in that language defines a specific binary-data format. +The language also defines a "service": a service has +an array of named "methods", each of which takes a specific +type of message as input, and gives a specific +type of message as output. + +SCTP is a reliable datagram-oriented protocol, +parallel to UDP or TCP, and riding atop the IP layer +(either IPv4 or IPv6). + +Protocol-Buffer RPC over SCTP (or protobuf-sctp for short), +defines a way of implementing RPCs to one of a set of named services. + +The general inplementation of the server and client +is very similar, since both the server and client can +implement local services and invoke remote services. +Usually, a server provides a local service and the client +accesses it, but we provide a more symmetric model. + +.ti 0 +Why Services and not Messages? + +One possible alternate scheme for using protobuf is to encapsulate messages +without any regard to the request/response cycle. +This could be quite beneficial at times, especially when the RPC does not +need to get a response message. Despite the possibility of eliminating +many round-trips, we do not offer any protocol but the rpc-method technique. + +Services are the fundamental unit of RPC in protocol buffers, +so it makes sense to use them as the basis of a protocol. +It is possible that a string name is not the most efficient way +to identify the service, but many languages are well-optimized +for string handling, so it's unclear if base-128 encoding is really +better. In any event, strings are sent on every request for both +the name of the service, and the name of the method. + +.ti 0 +Why SCTP and not a generalized reliable datagram layer? + +In principle, this specification could be generalized +to cover any reliable datagram passing mechanism. +We chose to limit our attention to SCTP for +the following reasons: +.IP +.RS +.IP * +We can analyze the performance of our design choices. +.RE +.RS +.IP * +SCTP boasts a rich set of features, like the ability to +specify ordered or unordered delivery. +.RE + +.ti 0 +Definitions + +.KS +.IP +Datagram +.RS +.IP +a piece of binary-data, an array of bytes. +.RE +.KE + +.KS +.IP +SCTP Connection +.RS +.IP +a single stream of datagrams. +.RE +.KE + +.KS +.IP +Handshake +.RS +.IP +the sequence of datagrams to establish client/server validity. +.RE +.KE + +.KS +.IP +Uninitialized Connection +.RS +.IP +an SCTP connection before the handshake has completed. +.RE +.KE + +.KS +.IP +Request ID +.RS +.IP +a 64-bit number allocated by the end that begins an RPC request. +It is recommend that the allocation strategy simply use an incrementing 64-bit +counter. +.RE +.KE + +.KS +.IP +Datagram Type +.RS +.IP +is a single byte that is the first byte in every message. +It can be used to distinguish the type of message. +.RE +.KE + +.ti 0 +Overview of the Protocol + +The session always begins with ordered delivery of three +messages, the handshake. Only after the handshake is completed +may unordered messages be sent. + +For each remote-procedure call, the caller must allocate a request_id. +The request_id is necessary because we want +to support unordered delivery and because we would like to support +concurrent backend service invocations. + +.ti 0 +Initial handshake + +The first packet is sent by the client, +the active end of the connection. + +.KS +.TS +tab(:); +l s +| c | c | +| l | l | . +HANDSHAKE_REQUEST += +format:name +_ +byte:datagram_type (HANDSHAKE_REQUEST=1) +HandshakeRequest:request +_ +.TE +.KE + +The HandshakeRequest message format is defined +by the following protobuf file fragment: +.DS L + message HandshakeService + { + required string name = 1; + optional string service_type_name = 2; + } + message HandshakeRequest + { + // lists the services provided by the client to the server + repeated HandshakeService services = 1; + } +.DE + +Once the request has been received, +the server should respond with a message +.KS +.TS +tab(:); +l s +| c | c | +| l | l | . +HANDSHAKE_RESPONSE += +format:name +_ +byte:datagram_type (HANDSHAKE_RESPONSE=2) +HandshakeResponse:request +_ +.TE +.KE +where HandshakeResponse is defined by the +following fragment, which uses the definition of +HandshakeService above: +.DS L + message HandshakeResponse + { + // lists the services provided by the client to the server + repeated HandshakeService services = 1; + } +.DE +This message gives a list of services provided by the +server (which can be called by the client). + +Once the response has been received, +the client should respond with the final handshake message: +.KS +.TS +tab(:); +l s +| c | c | +| l | l | . +HANDSHAKE_COMPLETED += +format:name +_ +byte:datagram_type (HANDSHAKE_COMPLETED=3) +_ +.TE +.KE + +As far as the client is concerned, the handshake is completed +when it received the HandshakeRespond message; +for the server, the handshake is completed slightly later: +once the HandshakeCompleted message is received. + +For pipelineing purposes, it is nice to be able to send +requests before completing the handshake. +But until the handshake is complete, all packets +must be sent in ordered fashion, to ensure that the handshake +is the first packet processed. (This is the point of the HandshakeCompleted +message: to ensure that all sends will be ordered until +the client has received the respond.) + + +.ti 0 +The RPC Protocol + +The format of a remote-procedure call (RPC) transaction +is that a request is sent from the caller to +the caller. Eventually, the called resource +trasmits a response packet. The protocol specified here +permits responses to be received out of order. + +Out-of-order receipt is implemented by requiring the caller +(or, more precisely, its RPC implementation) +to allocate a request_id for each request. +The caller which has multiple outstanding requests must then +match the request id to the appropriate response. +The caller may throw an error if a response with an invalid request_id +is encountered. + +The remaining sections will describe the wire-format of the messages +sent for a request/response pair. + +.ti 0 +Request Format + +.KS +.TS +tab(:); +l s +| c | c | +| l | l | . +REQUEST += +format:name +_ +byte:datagram_type (REQUEST=4) +uint64:request_id +NUL-terminated string:service_name +NUL-terminated string:method_name +protobuf:request +_ +.TE +.KE + +The format of "request" is specified by the service-definition. + +.ti 0 +Response Format + +.KS +.TS +tab(:); +l s +| c | c | +| l | l | . +RESPONSE += +format:name +_ +byte:datagram_type (RESPONSE=5) +uint64:request_id +protobuf:response +_ +.TE +.KE + + +.ti 0 +Discussion + +Multiple types of service can be provided with a prioritized pecking +order. An additional property is built-in worm detection and +eradication. Because IP only guarantees best effort delivery, loss of +a carrier can be tolerated. With time, the carriers are +self-regenerating. While broadcasting is not specified, storms can +cause data loss. There is persistent delivery retry, until the +carrier drops. Audit trails are automatically generated, and can +often be found on logs and cable trays. + +.ti 0 +Security Considerations + +.in 3 +Messages are sent unencrypted, so this encapsulation cannot +be used safely on the broader internet. + +.KS +.ti 0 +Author's Address + +.nf +David Benson + +EMail: daveb@ffem.org +.KE + +.KS +.ti 0 +Appendix: table of datagram types + +.TS +tab(:); +c | c +| l | l | . +value:datagram type += +1:HANDSHAKE_REQUEST +2:HANDSHAKE_RESPONSE +3:HANDSHAKE_COMPLETED +4:REQUEST +5:RESPONSE +_ +.TE +.KE + diff --git a/src/sctp-rpc/sctp-channel.c b/src/sctp-rpc/sctp-channel.c new file mode 100644 index 0000000..b986d2c --- /dev/null +++ b/src/sctp-rpc/sctp-channel.c @@ -0,0 +1,8 @@ + +struct _ProtobufC_SCTP_Channel +{ + int fd; + protobuf_c_boolean is_passive; /* ie the server */ +}; + +