--- /dev/null
+
+0. Introduction
+---------------
+
+This document describes the simple JSON-based protocol used between
+the Winthorpe W3C Speech plugin and an external entity talking to it.
+In this document the term 'server' is generally used to refer to the
+Winthorpe W3C Speech plugin while the term 'client' is used to refer
+to any external entity using the protocol described here to interact
+with the plugin. A client is typically but not necessarily an entity
+within a Web Runtime or a Web Browser that wishes to use Winthorpe as
+the backend for rendering speech recognition and synthesis services.
+
+
+1. Message Encoding, Framing, And IPC
+-------------------------------------
+
+The Winthorpe W3C protocol uses JSON to encode messages. Connection-
+oriented stream sockets are used for the actual transmission of the
+bytes comprising an encoded messages. A simple framing scheme is used
+to help the receiving end to stay in sync with message boundaries for
+decoding. Every message transmitted over the wire is prefixed with a
+4-byte header containing the length of the following encoded message
+in network byte order, as depicted below.
+
+
+ +---------------------------------------------------------------------+
+ | frame #0 | frame #1 | | frame #N |
+ +---------------------------------------------------------------------+
+ | size0 : message#0 | size1 : message#1 |...| sizeN : message#N |
+ +---------------------------------------------------------------------+
+ | | size0 bytes | | size1 bytes | | | sizeN bytes |
+ +---------------------------------------------------------------------+
+
+
+2. Message Types
+----------------
+
+Messages can be logically divided into three categories:
+
+ - requests
+ - replies
+ - events
+
+A request is a message that is sent by a client to the server. For
+example, to create a new speech recognizer instance a client sends a
+request of the appropriate type to Winthorpe. A reply is a message
+that Winthorpe sends to a client in response to a request. For example,
+when a speech recognizer instance has been succefully created, the
+server sends a reply with recognizer ID to the client. Events are
+unsolicited messages that Winthorpe sends to clients to notify them
+about events that the client might be interested in. For example, when
+a speech utterance has been succesfully recognized, Winthorpe sends a
+corresponding event to the client that has started speech recognition.
+
+
+3. Message Structure
+--------------------
+
+Every message consists of a fixed number of common, type-agnostic
+fields and a variable number of type-specific fields. The type-agnostic
+fields are the following.
+
+Request Number (reqno)
+
+The request number is a monotonically increasing integer assigned by
+the requestor, IOW the client. It is used to correlate between requests
+and replies. A reply to a request will always have the same request
+number as the request itself. Events will always have 0 as their request
+number.
+
+Message Type (type)
+
+The message type field indicates what type of information the message is
+carrying. It can be one of
+
+Object Creation Request (create)
+
+An object creation request creates an object of the specified type on
+the server side and optionally initializes it with a set of initial
+variables which could alternatively be also set later with an Attribute
+Set Request (set, see below). Upon success, the server replies with a
+successful status message that contains the object ID of the newly
+created object. Upon failure, the server responds with an error status
+reply. Here is a sample create request:
+ {
+ reqno: 1,
+ type: 'create',
+ object: 'recognizer',
+ set: {
+ grammars: [ { src: 'winthorpe://player', weight: 1.0 } ],
+ lang: 'en-GB',
+ continuous: true,
+ },
+ }
+
+Object Deletion Request (delete)
+
+An object deletion request deletes the object with specified ID from
+the server. Upon success, the server replies with a success status
+reply, upon error with an error status reply. Here is a sample delete
+request:
+ {
+ reqno: 1,
+ type: 'delete',
+ id: 1,
+ }
+
+Attribute Setting Request (set)
+
+And object configuration request changes the given parameters of the
+given object. Upon success, the server replies with a success status
+reply. Upon error the server responds with an error status reply.
+Here is a sample set request:
+ {
+ reqno: 1,
+ type: 'set',
+ id: 1,
+ set: {
+ grammars: [ { src: 'winthorpe://player', weight: 1.0 } ],
+ lang: 'en-GB',
+ continuous: true,
+ },
+ }
+
+
+Reply Message (status)
+
+The server responds to all requests with a status reply. Successful
+requests are responded to with a success status reply. Unsuccessful
+requests are respondes to with an error status reply. Here is a
+sample success reply:
+ {
+ reqno: 1,
+ type: 'status',
+ status: 0,
+ message: 'recognizer instance created'
+ }
+
+Here is a sample error status reply:
+ {
+ reqno: 1,
+ type: 'status',
+ status: 5,
+ error: 'bad-grammar',
+ message: 'Unknown grammar player requested.'
+ }
+
+
+Method Invocation Request (invoke)
+
+A method invocation request asks the server to invoke the specified
+method on the given object with the given argument list. Upon succesful
+completion the server responds with a success status reply. Upon error
+the server responds with an error status reply. Here is a sample method
+invocation call request:
+ {
+ reqno: 5,
+ type: 'invoke',
+ id: 1,
+ method: 'start',
+ args: []
+ }
+
+Event Message (event)
+
+Event messages are sent by the server to notify clients about events of
+potential interest. Clients then decide whether and how to react to the
+events they received. Events are never replied to. From the servers point
+of view they are fire-and-forget type of messages. Here is a sample
+event:
+ {
+ reqno: 0,
+ type: 'event',
+ id: 0,
+ event: 'match',
+ timestamp: { sec: 1412792206, usec: 742317 },
+ match: {
+ final: true,
+ length: 1,
+ results: [ { confidence: 0.89, transcript: "play next" } ]
+ }
+ }
+
+
+Timestamp Request (timestamp)
+
+A timestamp request asks the server for its notion of time used in
+timestamps. The server responds to a timestamp request with a success
+status reply containing the timestamp obtained (right before) at the
+moment of sending the reply. Here is a sample timestamp request:
+ {
+ reqno: 5,
+ type: 'typestamp'
+ }
+
+And here is a sample reply:
+ {
+ reqno: 5,
+ type: 'status',
+ status: 0,
+ timestamp: { sec: 1412792206, usec: 742317 },
+ }
+
+The timestamp request can be used to establish a correlation between
+the clock on the server side and the clock on the client side, for
+instance in the events to be emitted in a WRT JS engine).
+
+The timestamp request can also be used to estimate/compensate for the
+communication delay associated with message delivery, although in the
+case of using unix domain sockets or TCP sockets over the loopback
+device this delay should probably be neglible from the JS engine point
+of view for all practical purposes.
+
+
+4. Speech Recognition Protocol
+------------------------------
+
+4.1 Creating An Instance
+
+A speech recognition instance can be created by sending a 'create'
+request with object type 'recognizer'.
+
+ {
+ reqno: 1,
+ type: 'create',
+ object: 'recognizer',
+ set: {
+ lang: 'en-GB',
+ grammars: [ { src: 'winthorpe://player', weight: 1.0 } ],
+ continuous: true,
+ interimResults: false,
+ maxAlternatives: 1,
+ events: [ 'result', 'nomatch', 'error', 'start', 'end' ]
+ shared: true,
+ }
+ }
+
+Although the server allows all standard attributes to be set, currently
+the interimResults, maxAlternatives and serviceURI attributes are silently
+ignored.
+
+In addition to the standard attributes, Winthorpe allows the following non-
+standard ones to be set:
+
+ - events: The set of events the client wants to be notified about. This
+ should reflect which event handlers have been set up for the related
+ JS object in the WRT.
+
+ - shared: Go for shared Winthorpe voice focus, instead of exclusive one.
+
+Upon successful creation, the server responds with a sucess status reply
+containing the object ID of the newly created recognizer.
+
+
+ {
+ reqno: 1,
+ type: 'status',
+ status: 0,
+ id: 1,
+ message: 'recognizer 1 created'
+ }
+
+
+This ID needs to be used in all subsequent requests releated to this
+instance.
+
+Upon error the server responds with an error status reply, containing an
+error status code, an error string, and optionally a verbose error message
+further clarifying to reason for failure.
+
+4.2 Explicit Deletion
+
+An existing recognizer instance can be explicitly deleted by sending a
+delete request to the server with the object ID of the recognizer.
+
+ {
+ reqno: 2,
+ type: 'delete',
+ id: 1,
+ }
+
+The server responds to the deletion request with a status reply.
+
+ {
+ reqno: 2,
+ type: 'status',
+ status: 0,
+ }
+
+4.3 Setting Recognizer Attributes
+
+Recognizer attributes can either be set during creation of the instance,
+or later using an attribute setting request. For a list of settable
+attributes, see the recognizer creation request above. The server might
+not be able to change certain attributes, depending on the state of the
+recognizer. If setting an attribute fails, the server will respond with
+an error status reply to the request.
+
+4.4 Starting, Stopping, And Aborting The Recognizer
+
+The recognizer start, stop, and abort methods can be invoked using a
+method invocation request.
+
+ {
+ reqno: 15,
+ type: 'invoke',
+ id: 1,
+ method: 'start', (or 'stop', or 'abort')
+ args: []
+ }
+
+The server responds with a status reply.
+
+4.5 Recognition Events
+
+Winthorpe delivers recognition events to the clients using a 'match'
+event message.
+
+ {
+ reqno: 0,
+ type: 'event',
+ id: 1,
+ event: 'match',
+ match: {
+ final: true,
+ length: 1,
+ results: [
+ {
+ transcript: 'hal open the pod bay doors',
+ confidence: 0.89,
+ }
+ ]
+ }
+ }
+
+The server will not provide any 'EMMA' or 'interpretation' attributes for
+the recognition event.
+
+4.6 Status Codes and Errors
+
+TBD.
+
+
+5. Synthesizer Protocol
+-----------------------
+
+5.1 Creating a Synthesizer Instance
+
+The speech synthesizer object is an implicitly create per-connection
+singleton object. It does not need to and cannot be explicitly created.
+The implicit singleton synthesizer object always exists with object ID
+0.
+
+
+5.2 Deleting the Synthesizer Instance
+
+The implicitly created singleton speech synthesizer object cannot be
+explicitly deleted.
+
+5.3 Creating an Utterance
+
+An utterance can be created by sending an utterance creation request
+to the server.
+
+ {
+ reqno: 16,
+ type: 'create',
+ object: 'utterance',
+ set: {
+ text: 'this is a test message',
+ lang: 'en-GB',
+ events: [ 'start', 'end', 'error' ],
+ volume: 1.0,
+ rate: 1.0,
+ }
+ }
+
+Although the server allows all standard attributes to be set, currently
+volume is always ignored and depending on the backend, rate and pitch
+might be ignore.
+
+In addition to the standard attributes, Winthirpe allows the following
+non-standard ones to be set:
+
+ - events: the set of events the client wants to be notified about. This
+ should reflect which event handlers have been set up for the related
+ JS object in the WRT. Currently only the start, stop and error events
+ are generated by the server, but rendering progress events will be
+ added in the soon future.
+
+ - timeout: timeout after which the utterance gets automatically cancelled
+ if its rendering has not been started. The timeout period is specified
+ in milliseconds.
+
+Upon sucessful creation the server responds with a success status reply
+containing the object ID of the newly created utterance.
+
+ {
+ reqno: 16,
+ type: 'status',
+ status: '0',
+ id: 3
+ }
+
+The supplied ID needs to be used in all subsequent requests related to this
+utterance instance.
+
+5.4 Setting Utterance Attributes
+
+Utterance attributes can either be set during creation of the instance,
+or later using an attribute setting request. For the list of settable
+attributes, see the utterance creation request above. The server might
+not be able to change certain attributes, depending on the state of
+the utterance. If setting an attribute fails, the server will respond with
+an error status reply to the request, otherwise with a sucessful status
+reply with status 0.
+
+5.5 Rendering an Utterance
+
+An utterance can be rendered by invoking the speak method of the singleton
+synthesizer object (with ID 0) and passing it the ID of the utterance in
+the utterance field.
+
+ {
+ reqno: 17,
+ type: 'invoke',
+ id: 0,
+ method: 'speak'
+ utterance: 3
+ }
+
+Upon success, the server will respond with a success status reply.
+
+ {
+ reqno: 17,
+ type: 'status',
+ status: 0
+ }
+
+5.6 Cancelling All Utterances
+
+All utterance can be cancelled by invoking the cancel method of the singleton
+synthesizer object (ID 0).
+
+ {
+ reqno: 18,
+ type: 'invoke',
+ id: 0,
+ method: 'cancel'
+ }
+
+This will cancel all queued utterances. Upon success the server responds with
+a sucess status reply.
+
+ {
+ reqno: 18,
+ type: 'status',
+ status: 0
+ }
+
+5.7 Pausing and Resuming Utterance Rendering
+
+Rendering of pending utterances can be paused and resumed by invoking the
+pause and resume methods of the singleton synthesizer object (ID 0).
+
+ {
+ reqno: 19,
+ type: 'invoke',
+ id: 0,
+ method: 'pause' (or resume)
+ }
+
+Once paused, rendering will not resume until the resume method is invoked.
+Upon success the server responds with a sucess status reply.
+
+ {
+ reqno: 19,
+ type: 'status',
+ status: 0
+ }
+
+5.8 Listing Available Voices
+
+The set of available voices can be queried by invoking the get-voices method
+of the singleton synthesizer objec (ID 0).
+
+ {
+ reqno: 20,
+ type: 'invoke',
+ id: 0,
+ method: 'get-voices'
+ }
+
+Upon success the server replies with the set of available voices.
+ {
+ reqno: 20,
+ type: 'status',
+ status: 0,
+ voices: [
+ {
+ voiceURI: 'english-male',
+ lang: 'English',
+ name: 'english-male',
+ localService: true,
+ default: false
+ },
+ {
+ voiceURI: 'english-british-male-1',
+ lang: 'English',
+ name: 'english-british-male-1',
+ localService: true,
+ default: false
+ },
+ {
+ voiceURI: 'english-scottish-male',
+ lang: 'English',
+ name: 'english-scottish-male',
+ localService: true,
+ default: false
+ }
+ ]
+ }
+
+5.9 Emitted Events
+
+The server emits event whenever the speaking, pending, or paused
+state of the server changes, ie. whenever
+ - rendering starts,
+ - rendering stops,
+ - the first utterance is inserted into an empty utterance queue,
+ - the last utterance is removed from the utterance queue,
+ - the utterance queue is paused, or
+ - the utterance queue is resumed
+
+These events are emitted with the ID (0) of the global singleton synthesizer
+object.
+
+Utterance specific events (start, end, error) are emitted with the utterance
+ID as the object id of the event. Rendering progress events will be similarly
+emitted in the future.
+
+
+6. Object Lifecycle
+-------------------
+
+The lifecycle of the objects created on the server side is tied to the
+lifecycle of the socket connection via which the objects have been
+created. If the client closes the connection, all objects associated with
+the connection/client will be automatically destroyed on the server side.
+
+
+7. Other Considerations
+-----------------------
+
+Attributes can optionally be initialized during object creation, or they
+can be set later using an attribute set request. In principle it does not
+matter whether any attributes are set during construction. You can always
+create an empty object and configure it with subsequent attribute setting
+requests.
+
+However, most objects are not possible/meaningful to use or sometimes even
+to construct on the backend side before certain attributes have been set.
+For instance, a recognition object is practically inactive from the clients
+point of view before an omatch handler has been set on it. Similarly, the
+recognition object cannot be constructed on the backend side before the
+grammars attribute has been set.
+
+For the sake of completeness and to allow implementation to attempt
+opportunistic optimizations of object creation and message exchange, the
+protocol supports attribute initialization during object creation. If it
+wishes to do so, an implementation can cache locally attribute settings
+until it sees fit to construct the object (once enough attributes has been
+set for the object to become fully operational) and only then create and
+initialize the object with a single message exchange in one go.
+
+However, it is also perfectly fine to not do so. The gains are mostly in
+doing less communication for the cost of more complex client-side object-
+state handling logic. Whether this makes sense and whether it is a real
+gain depends on the implementation details case by case.
--- /dev/null
+/*
+ * Copyright (c) 2012-2014, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of Intel Corporation nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <getopt.h>
+#include <stdarg.h>
+#include <ctype.h>
+
+#include <murphy/common/macros.h>
+#include <murphy/common/debug.h>
+#include <murphy/common/log.h>
+#include <murphy/common/mm.h>
+#include <murphy/common/mainloop.h>
+#include <murphy/common/transport.h>
+#include <murphy/common/json.h>
+
+#include <breedline/breedline-murphy.h>
+
+#define DEFAULT_SERVER "unxs:@winthorpe.w3c-speech"
+
+/*
+ * a W3C test client
+ */
+
+typedef struct {
+ mrp_mainloop_t *ml; /* our mainloop */
+ brl_t *brl; /* breedline for terminal I/O */
+ int log_mask; /* log verbosity mask */
+ char *server; /* server (transport) address */
+ const char *atype; /* resolved type */
+ mrp_sockaddr_t addr; /* resolved address */
+ socklen_t alen; /* resolved length */
+ mrp_transport_t *t; /* transport to server */
+ mrp_timer_t *conntmr; /* connection timer */
+ int connected : 1; /* whether we have a connection */
+ uint32_t reqno; /* request number */
+} client_t;
+
+
+typedef struct {
+ const char *command;
+ int (*handler)(client_t *c, int narg, char **args);
+} command_t;
+
+static int transport_connect(client_t *c);
+static void transport_destroy(client_t *c);
+static void connection_timer_start(client_t *c);
+static void connection_timer_stop(client_t *c);
+static void mainloop_quit(client_t *c, int exit_code);
+
+static void execute_command(client_t *c, int narg, char **args);
+
+static void set_prompt(client_t *c, const char *prompt)
+{
+ brl_set_prompt(c->brl, prompt);
+}
+
+
+static void show_prompt(client_t *c)
+{
+ if (c->brl != NULL)
+ brl_show_prompt(c->brl);
+}
+
+
+static void hide_prompt(client_t *c)
+{
+ if (c->brl != NULL)
+ brl_hide_prompt(c->brl);
+}
+
+
+static void print(client_t *c, const char *format, ...)
+{
+ va_list ap;
+
+ hide_prompt(c);
+
+ va_start(ap, format);
+ vfprintf(stdout, format, ap);
+ fputc('\n', stdout);
+ fflush(stdout);
+ va_end(ap);
+
+ show_prompt(c);
+}
+
+
+static void client_set_defaults(client_t *c, const char *argv0)
+{
+ MRP_UNUSED(c);
+ MRP_UNUSED(argv0);
+
+ c->server = mrp_strdup(DEFAULT_SERVER);
+ c->log_mask = MRP_LOG_UPTO(MRP_LOG_INFO);
+}
+
+
+static void client_connected(client_t *c)
+{
+ c->connected = true;
+ hide_prompt(c);
+ set_prompt(c, "w3c-client");
+ show_prompt(c);
+}
+
+
+static void client_disconnected(client_t *c)
+{
+ c->connected = false;
+ hide_prompt(c);
+ set_prompt(c, "disconnected");
+ show_prompt(c);
+}
+
+
+static client_t *client_create(const char *argv0)
+{
+ client_t *c;
+
+ c = mrp_allocz(sizeof(*c));
+
+ if (c != NULL)
+ client_set_defaults(c, argv0);
+
+ return c;
+}
+
+
+static void client_destroy(client_t *c)
+{
+ mrp_free(c);
+}
+
+
+static int mainloop_create(client_t *c)
+{
+ c->ml = mrp_mainloop_create();
+
+ if (c->ml != NULL)
+ return 0;
+ else {
+ fprintf(stderr, "Failed to create mainloop.");
+ exit(1);
+ }
+}
+
+
+static int mainloop_run(client_t *c)
+{
+ if (c != NULL && c->ml != NULL)
+ return mrp_mainloop_run(c->ml);
+ else {
+ errno = EINVAL;
+ return -1;
+ }
+}
+
+
+static void mainloop_quit(client_t *c, int exit_code)
+{
+ if (c != NULL && c->ml != NULL)
+ mrp_mainloop_quit(c->ml, exit_code);
+ else
+ exit(exit_code);
+}
+
+
+static void mainloop_destroy(client_t *c)
+{
+ mrp_mainloop_destroy(c->ml);
+ c->ml = NULL;
+}
+
+
+static void transport_recv_evt(mrp_transport_t *t, mrp_json_t *msg,
+ void *user_data)
+{
+ client_t *c = (client_t *)user_data;
+ const char *s;
+
+ MRP_UNUSED(t);
+ MRP_UNUSED(c);
+
+ s = mrp_json_object_to_string(msg);
+
+ print(c, "received message:");
+ print(c, " %s", s);
+}
+
+
+static void transport_recvfrom_evt(mrp_transport_t *t, mrp_json_t *msg,
+ mrp_sockaddr_t *addr, socklen_t addrlen,
+ void *user_data)
+{
+ MRP_UNUSED(t);
+ MRP_UNUSED(msg);
+ MRP_UNUSED(addr);
+ MRP_UNUSED(addrlen);
+ MRP_UNUSED(user_data);
+}
+
+
+static void transport_closed_evt(mrp_transport_t *t, int error, void *user_data)
+{
+ client_t *c = (client_t *)user_data;
+
+ MRP_UNUSED(t);
+
+ if (error != 0)
+ print(c, "Connection to server closed with error %d (%s).", error,
+ strerror(error));
+ else
+ print(c, "Connection to server closed.");
+
+ transport_destroy(c);
+ connection_timer_start(c);
+}
+
+
+static int transport_connect(client_t *c)
+{
+ static mrp_transport_evt_t evt = {
+ { .recvjson = transport_recv_evt, },
+ { .recvjsonfrom = transport_recvfrom_evt },
+ .closed = transport_closed_evt,
+ .connection = NULL
+ };
+
+ mrp_sockaddr_t addr;
+ socklen_t alen;
+ const char *atype;
+ int flags;
+
+ alen = mrp_transport_resolve(NULL, c->server, &addr, sizeof(addr), &atype);
+
+ if (alen <= 0) {
+ print(c, "Failed to resolve transport address '%s'.", c->server);
+ return -1;
+ }
+
+ flags = MRP_TRANSPORT_MODE_JSON | MRP_TRANSPORT_REUSEADDR;
+
+ if (c->t == NULL)
+ c->t = mrp_transport_create(c->ml, atype, &evt, c, flags);
+
+ if (c->t == NULL) {
+ print(c, "Failed to create transport (for '%s').", c->server);
+ return -1;
+ }
+
+ if (!mrp_transport_connect(c->t, &addr, alen))
+ return -1;
+ else {
+ client_connected(c);
+ return 0;
+ }
+}
+
+
+static void transport_destroy(client_t *c)
+{
+ mrp_transport_destroy(c->t);
+ c->t = NULL;
+ client_disconnected(c);
+}
+
+
+static void try_connect(mrp_timer_t *t, void *user_data)
+{
+ client_t *c = (client_t *)user_data;
+
+ if (transport_connect(c) == 0) {
+ mrp_del_timer(t);
+ c->conntmr = NULL;
+ }
+}
+
+
+static int transport_send(client_t *c, mrp_json_t *msg)
+{
+ if (mrp_transport_sendjson(c->t, msg))
+ return 0;
+ else {
+ errno = EIO;
+ return -1;
+ }
+}
+
+
+static void connection_timer_start(client_t *c)
+{
+ if (transport_connect(c) < 0)
+ c->conntmr = mrp_add_timer(c->ml, 1500, try_connect, c);
+}
+
+
+static void connection_timer_stop(client_t *c)
+{
+ mrp_del_timer(c->conntmr);
+ c->conntmr = NULL;
+}
+
+
+static void sighandler(mrp_sighandler_t *h, int signum, void *user_data)
+{
+ client_t *c = (client_t *)user_data;
+
+ MRP_UNUSED(h);
+
+ switch (signum) {
+ case SIGINT:
+ printf("Received SIGINT, exiting...");
+ mainloop_quit(c, 0);
+ break;
+
+ case SIGTERM:
+ printf("Received SIGTERM, exiting...");
+ mainloop_quit(c, 0);
+ break;
+ }
+}
+
+
+static void setup_signals(client_t *c)
+{
+ mrp_add_sighandler(c->ml, SIGINT , sighandler, c);
+ mrp_add_sighandler(c->ml, SIGTERM, sighandler, c);
+}
+
+
+static int split_input(char *input, int narg, char **args)
+{
+ int n, l;
+ char *p, *e;
+
+ n = 0;
+ p = input;
+ e = NULL;
+
+ while (*p) {
+ while (*p == ' ' || *p == '\t')
+ p++;
+
+ args[n++] = p;
+
+ if (n >= narg) {
+ errno = EOVERFLOW;
+ return -1;
+ }
+
+ while (*p && ((e && *p != *e) || (!e && (*p != ' ' && *p != '\t')))) {
+ if (*p == '\\') {
+ if ((l = strlen(p)) > 1) {
+ memmove(p, p + 1, l - 1);
+ goto next;
+ }
+ else {
+ *p = '\0';
+ break;
+ }
+ }
+
+ if (!e && (*p == '\'' || *p == '"'))
+ e = p;
+
+ next:
+ p++;
+ }
+
+ if (e && *p == *e)
+ p++;
+
+ if (*p)
+ *p++ = '\0';
+
+ e = NULL;
+
+ mrp_debug("arg #%d: '%s'\n", n, args[n-1]);
+ }
+
+ return n;
+}
+
+
+static void terminal_cb(brl_t *brl, const char *input, void *user_data)
+{
+ client_t *c = (client_t *)user_data;
+ int len = input ? strlen(input) + 1 : 0;
+ char buf[len], *args[64];
+ int narg;
+
+ if (len > 1) {
+ brl_add_history(brl, input);
+ hide_prompt(c);
+
+ strcpy(buf, input);
+ narg = split_input(buf, MRP_ARRAY_SIZE(args), args);
+ if (narg > 0)
+ execute_command(c, narg, &args[0]);
+ else
+ print(c, "failed to parse input '%s'", input);
+
+ show_prompt(c);
+ }
+}
+
+
+static void terminal_setup(client_t *c)
+{
+ if ((c->brl = brl_create_with_murphy(fileno(stdin), "disconnected", c->ml,
+ terminal_cb, c)) != NULL)
+ brl_show_prompt(c->brl);
+ else {
+ fprintf(stderr, "Failed to set up terminal input.");
+ exit(1);
+ }
+}
+
+
+static void terminal_cleanup(client_t *c)
+{
+ if (c != NULL && c->brl != NULL) {
+ brl_destroy(c->brl);
+ c->brl = NULL;
+ }
+}
+
+
+static void print_usage(const char *argv0, int exit_code, const char *fmt, ...)
+{
+ va_list ap;
+ const char *exe;
+
+ if (fmt && *fmt) {
+ va_start(ap, fmt);
+ vprintf(fmt, ap);
+ va_end(ap);
+ }
+
+ exe = strrchr(argv0, '/');
+
+ printf("usage: %s [options]\n\n"
+ "The possible options are:\n"
+ " -v, --verbose increase logging verbosity\n"
+ " -d, --debug enable debug messages\n"
+ " -h, --help show help on usage\n", exe);
+ printf("\n");
+
+ if (exit_code < 0)
+ return;
+ else
+ exit(exit_code);
+}
+
+
+static void parse_cmdline(client_t *c, int argc, char **argv)
+{
+# define OPTIONS "s:vd:h"
+ struct option options[] = {
+ { "server" , required_argument, NULL, 's' },
+ { "verbose" , no_argument , NULL, 'v' },
+ { "debug" , required_argument, NULL, 'd' },
+ { "help" , no_argument , NULL, 'h' },
+ { NULL, 0, NULL, 0 }
+ };
+
+ int opt;
+
+ while ((opt = getopt_long(argc, argv, OPTIONS, options, NULL)) != -1) {
+ switch (opt) {
+ case 's':
+ mrp_free(c->server);
+ c->server = mrp_strdup(optarg);
+ break;
+
+ case 'v':
+ c->log_mask <<= 1;
+ c->log_mask |= 1;
+ mrp_log_set_mask(c->log_mask);
+ break;
+
+ case 'd':
+ mrp_debug_set_config(optarg);
+ mrp_debug_enable(TRUE);
+ break;
+
+ case 'h':
+ print_usage(argv[0], -1, "");
+ exit(0);
+ break;
+
+ default:
+ print_usage(argv[0], EINVAL, "invalid option '%c'", opt);
+ }
+ }
+}
+
+
+static int check_connection(client_t *c, int notify)
+{
+ if (!c->connected) {
+ if (notify)
+ print(c, "Connection to server is down.");
+
+ errno = ENOTCONN;
+ return -1;
+ }
+
+ return 0;
+}
+
+
+static int cmd_get_timestamp(client_t *c, int narg, char **args)
+{
+ mrp_json_t *req;
+ int status;
+
+ MRP_UNUSED(args);
+
+ if (check_connection(c, TRUE) < 0)
+ return -1;
+
+ if (narg > 0)
+ print(c, "Ignoring unused arguments...");
+
+ if ((req = mrp_json_create(MRP_JSON_OBJECT)) != NULL) {
+ mrp_json_add_integer(req, "reqno", c->reqno++);
+ mrp_json_add_string (req, "type" , "timestamp");
+
+ if (transport_send(c, req) < 0)
+ status = -1;
+ else
+ status = 0;
+
+ mrp_json_unref(req);
+ }
+
+ return status;
+}
+
+
+static int parse_set(client_t *c, mrp_json_t *set, int narg, char **args)
+{
+ char var[64], *val;
+ int i, l;
+
+ for (i = 0; i < narg; i++) {
+ val = strchr(args[i], '=');
+
+ if (val == NULL) {
+ print(c, "Invalid variable initializer/setting '%s'.", args[i]);
+
+ errno = EINVAL;
+ return -1;
+ }
+
+ snprintf(var, sizeof(var), "%*.*s",
+ (int)(val - args[i]), (int)(val - args[i]), args[i]);
+ val++;
+
+ mrp_debug("* '%s' = '%s'\n", var, val);
+
+ if (!strcmp(var, "grammars")) {
+ mrp_json_t *arr = mrp_json_create(MRP_JSON_ARRAY);
+ mrp_json_t *grm;
+ char *p, *n;
+
+ for (p = val; p && *p; p = n) {
+ n = strchr(p, ',');
+ l = n ? (int)(n - p) : (int)strlen(p);
+
+ mrp_json_array_append(arr,
+ grm = mrp_json_create(MRP_JSON_OBJECT));
+
+ mrp_json_add_string_slice(grm, "src" , p, l);
+ mrp_json_add_double (grm, "weight", 1.0);
+
+ n = n ? n + 1 : n;
+ }
+
+ mrp_json_add(set, var, arr);
+ }
+ else if (!strncmp(val, "int:", l=4))
+ mrp_json_add_integer(set, var, strtol(val+l, NULL, 10));
+ else if (!strncmp(val, "bln:", l=4))
+ mrp_json_add_boolean(set, var, var[l] == '1' || var[l] == 't');
+ else if (!strncmp(val, "str:", l=4))
+ mrp_json_add_string(set, var, val+l);
+ else if (!strncmp(val, "dbl:", l=4))
+ mrp_json_add_double(set, var, strtod(val+l, NULL));
+ else if (val[0] == '\'' || val[0] == '\"')
+ mrp_json_add_string_slice(set, var, val + 1, strlen(val) - 2);
+ else if (!strcasecmp(val, "true") || (!strcasecmp(val, "false")))
+ mrp_json_add_boolean(set, var, *val == 't');
+ else if (!strncmp(val, "strarr:", l=7)) {
+ mrp_json_t *arr = mrp_json_create(MRP_JSON_ARRAY);
+ char *p, *n;
+
+ for (p = val + l; p && *p; p = n) {
+ n = strchr(p, ',');
+ l = n ? (int)(n - p) : (int)strlen(p);
+
+ mrp_json_array_append_string_slice(arr, p, l);
+
+ n = n ? n + 1 : n;
+ }
+
+ mrp_json_add(set, var, arr);
+ }
+ else if (!strncmp(val, "intarr:", l=7)) {
+ mrp_json_t *arr = mrp_json_create(MRP_JSON_ARRAY);
+ char *p, *n;
+
+ for (p = val + l; p && *p; p = n) {
+ n = strchr(p, ',');
+ l = n ? (int)(n - p) : (int)strlen(p);
+
+ mrp_json_array_append_integer(arr, strtol(p, NULL, 10));
+
+ n = n ? n + 1 : n;
+ }
+
+ mrp_json_add(set, var, arr);
+ }
+ else if (!strncmp(val, "dblarr:", l=7)) {
+ mrp_json_t *arr = mrp_json_create(MRP_JSON_ARRAY);
+ char *p, *n;
+
+ for (p = val + l; p && *p; p = n) {
+ n = strchr(p, ',');
+ l = n ? (int)(n - p) : (int)strlen(p);
+
+ mrp_json_array_append_double(arr, strtod(p, NULL));
+
+ n = n ? n + 1 : n;
+ }
+
+ mrp_json_add(set, var, arr);
+ }
+ else if (!strncmp(val, "blnarr:", l=7)) {
+ mrp_json_t *arr = mrp_json_create(MRP_JSON_ARRAY);
+ char *p, *n;
+
+ for (p = val + l; p && *p; p = n) {
+ n = strchr(p, ',');
+ l = n ? (int)(n - p) : (int)strlen(p);
+
+ mrp_json_array_append_boolean(arr, *p == 't');
+
+ n = n ? n + 1 : n;
+ }
+
+ mrp_json_add(set, var, arr);
+ }
+ else if (isdigit(*val) || *val == '-' || *val == '+') {
+ int iv;
+ double dv;
+ char *end;
+
+ iv = strtol(val, &end, 10);
+
+ if (end && !*end) {
+ mrp_json_add_integer(set, var, iv);
+ continue;
+ }
+
+ if (end && *end == '.') {
+ dv = strtod(val, &end);
+
+ if (end && !*end) {
+ mrp_json_add_double(set, var, dv);
+ continue;
+ }
+ }
+
+ mrp_json_add_string(set, var, val);
+ }
+ else
+ mrp_json_add_string(set, var, val);
+ }
+
+ return 0;
+}
+
+
+static int cmd_create_recognizer(client_t *c, int narg, char **args)
+{
+ mrp_json_t *req, *set;
+ int status;
+
+ if (check_connection(c, TRUE) < 0)
+ return -1;
+
+ if ((req = mrp_json_create(MRP_JSON_OBJECT)) != NULL) {
+ mrp_json_add_integer(req, "reqno" , c->reqno++);
+ mrp_json_add_string (req, "type" , "create");
+ mrp_json_add_string (req, "object", "recognizer");
+
+ if (narg > 0) {
+ mrp_json_add(req, "set", set = mrp_json_create(MRP_JSON_OBJECT));
+
+ if (parse_set(c, set, narg, args) < 0)
+ goto fail;
+ }
+
+ if (transport_send(c, req) < 0)
+ status = -1;
+ else
+ status = 0;
+
+ mrp_json_unref(req);
+
+ return status;
+ }
+
+ fail:
+ return -1;
+}
+
+
+static int cmd_delete_recognizer(client_t *c, int narg, char **args)
+{
+ mrp_json_t *req;
+ int i, id, status;
+
+ if (narg <= 0) {
+ print(c, "Can't delete recognizer, no ID given.");
+ errno = EINVAL;
+
+ return -1;
+ }
+
+ if (check_connection(c, TRUE) < 0)
+ return -1;
+
+ status = 0;
+ for (i = 0; i < narg; i++) {
+ id = strtoul(args[i], NULL, 10);
+
+ if ((req = mrp_json_create(MRP_JSON_OBJECT)) != NULL) {
+ mrp_json_add_integer(req, "reqno", c->reqno++);
+ mrp_json_add_string (req, "type" , "delete");
+ mrp_json_add_integer(req, "id" , id);
+
+ if (transport_send(c, req) < 0)
+ status = -1;
+
+ mrp_json_unref(req);
+ }
+ }
+
+ return status;
+}
+
+
+static int cmd_set_recognizer(client_t *c, int narg, char **args)
+{
+ mrp_json_t *req, *set;
+ int id, status;
+
+ if (narg < 2) {
+ print(c, "Can't set variable, need ID, and variable assignment.");
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (check_connection(c, TRUE) < 0)
+ return -1;
+
+ id = strtoul(args[0], NULL, 10);
+ args++;
+ narg--;
+
+ if ((req = mrp_json_create(MRP_JSON_OBJECT)) == NULL)
+ return -1;
+
+ mrp_json_add_integer(req, "reqno", c->reqno++);
+ mrp_json_add_string (req, "type" , "set");
+ mrp_json_add_integer(req, "id" , id);
+ mrp_json_add (req, "set" , set = mrp_json_create(MRP_JSON_OBJECT));
+
+ if (parse_set(c, set, narg, args) < 0) {
+ errno = EINVAL;
+ status = -1;
+ }
+ else {
+ if (transport_send(c, req) < 0)
+ status = -1;
+ else
+ status = 0;
+ }
+
+ mrp_json_unref(req);
+
+ return status;
+}
+
+
+static int cmd_start_recognizer(client_t *c, int narg, char **args)
+{
+ mrp_json_t *req;
+ int i, id, status;
+
+ if (check_connection(c, TRUE) < 0)
+ return -1;
+
+ if (narg <= 0) {
+ print(c, "Can't start recognizer, no ID given.");
+ errno = EINVAL;
+
+ return -1;
+ }
+
+ status = 0;
+ for (i = 0; i < narg; i++) {
+ id = strtoul(args[i], NULL, 10);
+
+ if ((req = mrp_json_create(MRP_JSON_OBJECT)) != NULL) {
+ mrp_json_add_integer(req, "reqno" , c->reqno++);
+ mrp_json_add_string (req, "type" , "invoke");
+ mrp_json_add_string (req, "method", "start");
+ mrp_json_add_integer(req, "id" , id);
+
+ if (transport_send(c, req) < 0)
+ status = -1;
+
+ mrp_json_unref(req);
+ }
+ }
+
+ return status;
+}
+
+
+static int cmd_stop_recognizer(client_t *c, int narg, char **args)
+{
+ mrp_json_t *req;
+ int i, id, status;
+
+ if (check_connection(c, TRUE) < 0)
+ return -1;
+
+ if (narg <= 0) {
+ print(c, "Can't start recognizer, no ID given.");
+ errno = EINVAL;
+
+ return -1;
+ }
+
+ status = 0;
+ for (i = 0; i < narg; i++) {
+ id = strtoul(args[i], NULL, 10);
+
+ if ((req = mrp_json_create(MRP_JSON_OBJECT)) != NULL) {
+ mrp_json_add_integer(req, "reqno" , c->reqno++);
+ mrp_json_add_string (req, "type" , "invoke");
+ mrp_json_add_string (req, "method", "stop");
+ mrp_json_add_integer(req, "id" , id);
+
+ if (transport_send(c, req) < 0)
+ status = -1;
+
+ mrp_json_unref(req);
+ }
+ }
+
+ return status;
+}
+
+
+static int cmd_abort_recognizer(client_t *c, int narg, char **args)
+{
+ mrp_json_t *req;
+ int i, id, status;
+
+ if (check_connection(c, TRUE) < 0)
+ return -1;
+
+ if (narg <= 0) {
+ print(c, "Can't abort recognizer, no ID given.");
+ errno = EINVAL;
+
+ return -1;
+ }
+
+ status = 0;
+ for (i = 0; i < narg; i++) {
+ id = strtoul(args[i], NULL, 10);
+
+ if ((req = mrp_json_create(MRP_JSON_OBJECT)) != NULL) {
+ mrp_json_add_integer(req, "reqno", c->reqno++);
+ mrp_json_add_string (req, "type" , "invoke");
+ mrp_json_add_string (req, "method", "abort");
+ mrp_json_add_integer(req, "id" , id);
+
+ if (transport_send(c, req) < 0)
+ status = -1;
+
+ mrp_json_unref(req);
+ }
+ }
+
+ return status;
+}
+
+
+static int cmd_get_voices(client_t *c, int narg, char **args)
+{
+ mrp_json_t *req;
+ const char *lang;
+ int status;
+
+ switch (narg) {
+ case 0: lang = NULL; break;
+ case 1: lang = args[0]; break;
+ default:
+ print(c, "list-voices expects either a single or no arguments.");
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (check_connection(c, TRUE) < 0)
+ return -1;
+
+ if ((req = mrp_json_create(MRP_JSON_OBJECT)) == NULL)
+ return -1;
+
+ mrp_json_add_integer(req, "reqno" , c->reqno++);
+ mrp_json_add_string (req, "type" , "invoke");
+ mrp_json_add_integer(req, "id" , 0);
+ mrp_json_add_string (req, "method", "get-voices");
+ if (lang != NULL)
+ mrp_json_add_string(req, "lang", lang);
+
+ if (transport_send(c, req) < 0)
+ status = -1;
+ else
+ status = 0;
+
+ mrp_json_unref(req);
+
+
+ return status;
+}
+
+
+static int cmd_create_utterance(client_t *c, int narg, char **args)
+{
+ mrp_json_t *req, *set;
+ int status;
+
+ if (check_connection(c, TRUE) < 0)
+ return -1;
+
+ if ((req = mrp_json_create(MRP_JSON_OBJECT)) != NULL) {
+ mrp_json_add_integer(req, "reqno" , c->reqno++);
+ mrp_json_add_string (req, "type" , "create");
+ mrp_json_add_string (req, "object", "utterance");
+
+ if (narg > 0) {
+ mrp_json_add(req, "set", set = mrp_json_create(MRP_JSON_OBJECT));
+
+ if (parse_set(c, set, narg, args) < 0)
+ goto fail;
+ }
+
+ if (transport_send(c, req) < 0)
+ status = -1;
+ else
+ status = 0;
+
+ mrp_json_unref(req);
+
+ return status;
+ }
+
+ fail:
+ return -1;
+}
+
+
+static int cmd_delete_utterance(client_t *c, int narg, char **args)
+{
+ mrp_json_t *req;
+ int i, id, status;
+
+ if (narg <= 0) {
+ print(c, "Can't delete utterance, no ID given.");
+ errno = EINVAL;
+
+ return -1;
+ }
+
+ if (check_connection(c, TRUE) < 0)
+ return -1;
+
+ status = 0;
+ for (i = 0; i < narg; i++) {
+ id = strtoul(args[i], NULL, 10);
+
+ if ((req = mrp_json_create(MRP_JSON_OBJECT)) != NULL) {
+ mrp_json_add_integer(req, "reqno", c->reqno++);
+ mrp_json_add_string (req, "type" , "delete");
+ mrp_json_add_integer(req, "id" , id);
+
+ if (transport_send(c, req) < 0)
+ status = -1;
+
+ mrp_json_unref(req);
+ }
+ }
+
+ return status;
+}
+
+
+static int cmd_set_utterance(client_t *c, int narg, char **args)
+{
+ mrp_json_t *req, *set;
+ int id, status;
+
+ if (narg < 2) {
+ print(c, "Can't set variable, need ID, and variable assignment.");
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (check_connection(c, TRUE) < 0)
+ return -1;
+
+ id = strtoul(args[0], NULL, 10);
+ args++;
+ narg--;
+
+ if ((req = mrp_json_create(MRP_JSON_OBJECT)) == NULL)
+ return -1;
+
+ mrp_json_add_integer(req, "reqno", c->reqno++);
+ mrp_json_add_string (req, "type" , "set");
+ mrp_json_add_integer(req, "id" , id);
+ mrp_json_add (req, "set" , set = mrp_json_create(MRP_JSON_OBJECT));
+
+ if (parse_set(c, set, narg, args) < 0) {
+ errno = EINVAL;
+ status = -1;
+ }
+ else {
+ if (transport_send(c, req) < 0)
+ status = -1;
+ else
+ status = 0;
+ }
+
+ mrp_json_unref(req);
+
+ return status;
+}
+
+
+static int cmd_speak_utterance(client_t *c, int narg, char **args)
+{
+ mrp_json_t *req;
+ int i, uid, status;
+
+ if (check_connection(c, TRUE) < 0)
+ return -1;
+
+ if (narg <= 0) {
+ print(c, "Can't speak utterance, no ID given.");
+ errno = EINVAL;
+
+ return -1;
+ }
+
+ status = 0;
+ for (i = 0; i < narg; i++) {
+ uid = strtoul(args[i], NULL, 10);
+
+ if ((req = mrp_json_create(MRP_JSON_OBJECT)) != NULL) {
+ mrp_json_add_integer(req, "reqno" , c->reqno++);
+ mrp_json_add_string (req, "type" , "invoke");
+ mrp_json_add_string (req, "method" , "speak");
+ mrp_json_add_integer(req, "id" , 0);
+ mrp_json_add_integer(req, "utterance", uid);
+
+ if (transport_send(c, req) < 0)
+ status = -1;
+
+ mrp_json_unref(req);
+ }
+ }
+
+ return status;
+}
+
+
+static int cmd_cancel_utterance(client_t *c, int narg, char **args)
+{
+ mrp_json_t *req;
+ int i, uid, status;
+
+ if (check_connection(c, TRUE) < 0)
+ return -1;
+
+#if 0
+ if (narg <= 0) {
+ print(c, "Can't cancel utterance, no ID given.");
+ errno = EINVAL;
+
+ return -1;
+ }
+#endif
+
+ status = 0;
+
+ if (narg == 0) {
+ if ((req = mrp_json_create(MRP_JSON_OBJECT)) != NULL) {
+ mrp_json_add_integer(req, "reqno" , c->reqno++);
+ mrp_json_add_string (req, "type" , "invoke");
+ mrp_json_add_integer(req, "id" , 0);
+ mrp_json_add_string (req, "method" , "cancel");
+
+ if (transport_send(c, req) < 0)
+ status = -1;
+
+ mrp_json_unref(req);
+ }
+ }
+ else {
+ for (i = 0; i < narg; i++) {
+ uid = strtoul(args[i], NULL, 10);
+
+ if ((req = mrp_json_create(MRP_JSON_OBJECT)) != NULL) {
+ mrp_json_add_integer(req, "reqno" , c->reqno++);
+ mrp_json_add_string (req, "type" , "invoke");
+ mrp_json_add_integer(req, "id" , 0);
+ mrp_json_add_string (req, "method" , "cancel");
+ mrp_json_add_integer(req, "utterance", uid);
+
+ if (transport_send(c, req) < 0)
+ status = -1;
+
+ mrp_json_unref(req);
+ }
+ }
+ }
+
+ return status;
+}
+
+
+static int cmd_pause_utterance(client_t *c, int narg, char **args)
+{
+ mrp_json_t *req;
+ int status;
+
+ MRP_UNUSED(args);
+
+ if (check_connection(c, TRUE) < 0)
+ return -1;
+
+ if (narg != 0)
+ print(c, "Warning: ignoring extra pause arguments.");
+
+ if ((req = mrp_json_create(MRP_JSON_OBJECT)) != NULL) {
+ mrp_json_add_integer(req, "reqno" , c->reqno++);
+ mrp_json_add_string (req, "type" , "invoke");
+ mrp_json_add_string (req, "method", "pause");
+ mrp_json_add_integer(req, "id" , 0);
+
+ status = transport_send(c, req);
+
+ mrp_json_unref(req);
+ }
+
+ return status;
+}
+
+
+static int cmd_resume_utterance(client_t *c, int narg, char **args)
+{
+ mrp_json_t *req;
+ int status;
+
+ MRP_UNUSED(args);
+
+ if (check_connection(c, TRUE) < 0)
+ return -1;
+
+ if (narg != 0)
+ print(c, "Warning: ignoring extra resume arguments.");
+
+ if ((req = mrp_json_create(MRP_JSON_OBJECT)) != NULL) {
+ mrp_json_add_integer(req, "reqno" , c->reqno++);
+ mrp_json_add_string (req, "type" , "invoke");
+ mrp_json_add_string (req, "method", "resume");
+ mrp_json_add_integer(req, "id" , 0);
+
+ status = transport_send(c, req);
+
+ mrp_json_unref(req);
+ }
+
+ return status;
+}
+
+
+static int cmd_connect(client_t *c, int narg, char **args)
+{
+ MRP_UNUSED(narg);
+ MRP_UNUSED(args);
+
+ if (!c->connected)
+ if (c->conntmr == NULL)
+ connection_timer_start(c);
+
+ return 0;
+}
+
+
+static int cmd_disconnect(client_t *c, int narg, char **args)
+{
+ MRP_UNUSED(narg);
+ MRP_UNUSED(args);
+
+ if (c->connected)
+ transport_destroy(c);
+
+ return 0;
+}
+
+
+static int cmd_quit(client_t *c, int narg, char **args)
+{
+ MRP_UNUSED(narg);
+ MRP_UNUSED(args);
+
+ print(c, "Exiting...");
+ mrp_mainloop_quit(c->ml, 0);
+
+ return 0;
+}
+
+
+static void execute_command(client_t *c, int narg, char **args)
+{
+ static command_t commands[] = {
+ { "get-timestamp" , cmd_get_timestamp },
+ { "create-recognizer" , cmd_create_recognizer },
+ { "delete-recognizer" , cmd_delete_recognizer },
+ { "set-recognizer" , cmd_set_recognizer },
+ { "start-recognizer" , cmd_start_recognizer },
+ { "stop-recognizer" , cmd_stop_recognizer },
+ { "abort-recognizer" , cmd_abort_recognizer },
+ { "get-voices" , cmd_get_voices },
+ { "create-utterance" , cmd_create_utterance },
+ { "delete-utterance" , cmd_delete_utterance },
+ { "set-utterance" , cmd_set_utterance },
+ { "speak-utterance" , cmd_speak_utterance },
+ { "cancel-utterance" , cmd_cancel_utterance },
+ { "pause-utterance" , cmd_pause_utterance },
+ { "resume-utterance" , cmd_resume_utterance },
+ { "connect" , cmd_connect },
+ { "disconnect" , cmd_disconnect },
+ { "quit" , cmd_quit },
+ { "exit" , cmd_quit },
+ { NULL, NULL }
+ };
+
+ command_t *cmd;
+ size_t len;
+
+ len = strlen(args[0]);
+ for (cmd = commands; cmd->command != NULL; cmd++) {
+ if (!strncmp(args[0], cmd->command, len)) {
+ cmd->handler(c, narg - 1, args + 1);
+ return;
+ }
+ }
+
+ print(c, "Unknown command '%s'...", args[0]);
+}
+
+
+int main(int argc, char *argv[])
+{
+ client_t *c;
+
+ if ((c = client_create(argv[0])) != NULL) {
+ parse_cmdline(c, argc, &argv[0]);
+
+ mainloop_create(c);
+ setup_signals(c);
+ terminal_setup(c);
+ connection_timer_start(c);
+
+ mainloop_run(c);
+
+ connection_timer_stop(c);
+ terminal_cleanup(c);
+ mainloop_destroy(c);
+
+ client_destroy(c);
+ }
+
+ return 0;
+}
--- /dev/null
+/*
+ * Copyright (c) 2014, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of Intel Corporation nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <errno.h>
+#include <stdarg.h>
+
+#include <murphy/common/macros.h>
+#include <murphy/common/debug.h>
+#include <murphy/common/log.h>
+#include <murphy/common/mm.h>
+#include <murphy/common/mainloop.h>
+#include <murphy/common/transport.h>
+#include <murphy/common/json.h>
+
+#include "srs/daemon/plugin.h"
+#include "srs/daemon/client.h"
+
+#include "w3c-server.h"
+#include "w3c-message.h"
+#include "w3c-protocol.h"
+
+#define W3C_PLUGIN "w3c-speech"
+#define W3C_DESCR "W3C speech API plugin for Winthorpe."
+#define W3C_AUTHORS "Krisztian Litkey <kli@iki.fi>"
+#define W3C_VERSION "0.0.1"
+
+#define W3C_TYPE_SYNTHESIZER 0x0
+#define W3C_TYPE_RECOGNIZER 0x1
+#define W3C_TYPE_UTTERANCE 0x2
+#define W3C_OBJECT_ID(type, cnt) ((cnt << 2) | (type))
+#define W3C_OBJECT_TYPE(id) ((id) & 0x3)
+
+
+typedef struct w3c_client_s w3c_client_t;
+typedef struct w3c_synthesizer_s w3c_synthesizer_t;
+typedef struct w3c_attrdef_s w3c_attrdef_t;
+
+
+/*
+ * W3C server (plugin runtime context)
+ */
+
+typedef struct {
+ srs_plugin_t *self; /* our plugin instance */
+ const char *address; /* transport address to listen on */
+ int sock; /* or existing socket for transport */
+ const char *grammar_dir; /* grammar directory */
+ mrp_transport_t *lt; /* transport we listen on */
+ mrp_list_hook_t clients; /* connected clients */
+ int next_id; /* next client id */
+} w3c_server_t;
+
+
+/*
+ * W3C client events
+ */
+
+typedef enum {
+ W3C_EVENT_NONE = 0x0000,
+ W3C_EVENT_START = 0x0001,
+ W3C_EVENT_END = 0x0002,
+ W3C_EVENT_RESULT = 0x0004,
+ W3C_EVENT_NOMATCH = 0x0008,
+ W3C_EVENT_ERROR = 0x0010,
+ W3C_EVENT_AUDIOSTART = 0x0020,
+ W3C_EVENT_AUDIOEND = 0x0040,
+ W3C_EVENT_SOUNDSTART = 0x0080,
+ W3C_EVENT_SOUNDEND = 0x0100,
+ W3C_EVENT_SPEECHSTART = 0x0200,
+ W3C_EVENT_SPEECHEND = 0x0400,
+ W3C_EVENT_PAUSE = 0x0800,
+ W3C_EVENT_RESUME = 0x1000,
+ W3C_EVENT_MARK = 0x2000,
+ W3C_EVENT_BOUNDARY = 0x4000,
+} w3c_event_t;
+
+
+/*
+ * W3C client requests
+ */
+
+typedef enum {
+ W3C_REQUEST_NONE = 0,
+ W3C_REQUEST_START,
+ W3C_REQUEST_STOP,
+ W3C_REQUEST_ABORT,
+ W3C_REQUEST_PAUSE,
+ W3C_REQUEST_CANCEL,
+ W3C_REQUEST_RESUME,
+} w3c_request_t;
+
+
+/*
+ * W3C backend state
+ */
+
+typedef enum {
+ W3C_BACKEND_STOPPED = 0,
+ W3C_BACKEND_STARTED,
+ W3C_BACKEND_RENDERING
+} w3c_backend_t;
+
+
+/*
+ * a W3C client
+ */
+
+struct w3c_client_s {
+ mrp_list_hook_t hook; /* to list of clients */
+ int id; /* client id */
+ w3c_server_t *s; /* back pointer to W3C server/plugin */
+ mrp_transport_t *t; /* transport to this client */
+ w3c_synthesizer_t *syn; /* singleton per-client synthesizer */
+ mrp_list_hook_t recognizers; /* recognizer instances */
+ int next_id; /* next recognizer/utterance id */
+};
+
+
+/*
+ * a W3C recognizer instance
+ */
+
+typedef struct {
+ char *name; /* backend client name */
+ char *appclass; /* backend client application class */
+ uint32_t events; /* mask of events of interest */
+ char **grammars; /* recognizer grammars */
+ int ngrammar; /* number of grammars */
+ char *lang; /* recognizer language */
+ bool continuous; /* whether in continuous mode */
+ bool interim; /* whether to deliver interim results */
+ int max_alt; /* max. alternatives to deliver */
+ char *service; /* recognizer service URI */
+ bool shared; /* whether use shared focus */
+ char **commands; /* commands from grammars */
+ int ncommand; /* number of commands */
+} w3c_rec_attr_t;
+
+
+typedef struct {
+ mrp_list_hook_t hook; /* to list of recognizers */
+ w3c_client_t *c; /* client we belong to */
+ int id; /* recognizer object id */
+ w3c_rec_attr_t attr; /* recognizer attributes */
+ uint32_t mask; /* attribute mask */
+ srs_client_t *srsc; /* associated backend client */
+ int request; /* W3C client request */
+ int backend; /* W3C backend state */
+} w3c_recognizer_t;
+
+
+/*
+ * a W3C synthesizer instance (per-client singleton)
+ */
+
+typedef struct {
+ char *name; /* backend client name */
+ char *appclass; /* backend client application class */
+} w3c_syn_attr_t;
+
+
+struct w3c_synthesizer_s {
+ w3c_client_t *c; /* client we belong to */
+ w3c_syn_attr_t attr; /* synthesizer attributes */
+ srs_client_t *srsc; /* associated backend client */
+ mrp_list_hook_t utterances; /* existing utterances */
+ mrp_list_hook_t pending; /* pending utterances */
+ bool paused; /* whether paused */
+};
+
+
+/*
+ * a W3C utterance instance
+ */
+
+typedef struct {
+ char *text; /* text to synthesize */
+ char *lang; /* language to use */
+ char *voice; /* voice to use for synthesis */
+ double volume; /* volume to use for synthesis */
+ double rate; /* rate to use for synthesis */
+ double pitch; /* pitch to use for synthesis */
+ uint32_t events; /* events to deliver to client */
+ int timeout; /* rendering timeout */
+} w3c_utt_attr_t;
+
+
+typedef struct {
+ mrp_list_hook_t hook; /* to utterance list */
+ mrp_list_hook_t pending; /* to pending list */
+ w3c_synthesizer_t *syn; /* associated synthesizer */
+ int id; /* (W3C) utterance object id */
+ w3c_utt_attr_t attr; /* utterance attributes */
+ uint32_t vid; /* backend id */
+} w3c_utterance_t;
+
+
+/*
+ * an attribute definition
+ */
+
+typedef int (*w3c_attr_parser_t)(mrp_json_t *attr, void *obj, size_t base,
+ w3c_attrdef_t *def, int *errc,
+ const char **errs);
+typedef int (*w3c_attr_check_t)(void *obj, w3c_attrdef_t *def, void *value,
+ int *errc, const char **errs);
+
+struct w3c_attrdef_s {
+ const char *name; /* attribute name */
+ int type; /* attribute type */
+ size_t offs; /* attribute offset */
+ int mask; /* change mask bit */
+ w3c_attr_parser_t parser; /* attribute parser */
+ w3c_attr_check_t check; /* attribute checker */
+};
+
+typedef enum {
+ W3C_ATTR_NAME = (0x1 << 0),
+ W3C_ATTR_APPCLASS = (0x1 << 1),
+ W3C_ATTR_EVENTS = (0x1 << 2),
+ W3C_ATTR_GRAMMARS = (0x1 << 3),
+ W3C_ATTR_LANG = (0x1 << 4),
+ W3C_ATTR_CONTINUOUS = (0x1 << 5),
+ W3C_ATTR_INTERIM = (0x1 << 6),
+ W3C_ATTR_MAXALT = (0x1 << 7),
+ W3C_ATTR_SERVICE = (0x1 << 8),
+ W3C_ATTR_TEXT = (0x1 << 9),
+ W3C_ATTR_VOICE = (0x1 << 10),
+ W3C_ATTR_VOLUME = (0x1 << 11),
+ W3C_ATTR_RATE = (0x1 << 12),
+ W3C_ATTR_PITCH = (0x1 << 13),
+ W3C_ATTR_SHARED = (0x1 << 14),
+ W3C_ATTR_TIMEOUT = (0x1 << 15),
+} w3c_attrmask_t;
+
+
+/*
+ * a client request handler
+ */
+
+typedef int (*request_handler_t)(w3c_client_t *c, int reqno, mrp_json_t *req);
+
+
+static int create_synthesizer(w3c_client_t *c);
+static void destroy_synthesizer(w3c_synthesizer_t *syn);
+static void destroy_recognizer(w3c_recognizer_t *rec);
+static w3c_utterance_t *lookup_utterance(w3c_client_t *c, int id, uint32_t vid);
+static void destroy_utterance(w3c_utterance_t *utt);
+
+
+static w3c_client_t *w3c_client_create(w3c_server_t *s)
+{
+ w3c_client_t *c;
+ int flags;
+
+ if ((c = mrp_allocz(sizeof(*c))) == NULL) {
+ mrp_transport_destroy(mrp_transport_accept(s->lt, NULL, 0));
+ return NULL;
+ }
+
+ mrp_list_init(&c->hook);
+ mrp_list_init(&c->recognizers);
+
+ if (create_synthesizer(c) < 0) {
+ mrp_transport_destroy(mrp_transport_accept(s->lt, NULL, 0));
+ mrp_free(c);
+ return NULL;
+ }
+
+ flags = MRP_TRANSPORT_REUSEADDR;
+
+ if ((c->t = mrp_transport_accept(s->lt, c, flags)) == NULL) {
+ mrp_free(c);
+ return NULL;
+ }
+
+ c->id = s->next_id++;
+ c->s = s;
+ c->next_id = 1;
+
+ mrp_list_append(&s->clients, &c->hook);
+
+ mrp_log_info("Created W3C client #%d.", c->id);
+
+ return c;
+}
+
+
+static void w3c_client_destroy(w3c_client_t *c)
+{
+ mrp_list_hook_t *p, *n;
+ w3c_recognizer_t *rec;
+
+ mrp_log_info("Destroying W3C client #%d...", c->id);
+
+ mrp_list_foreach(&c->recognizers, p, n) {
+ rec = mrp_list_entry(p, typeof(*rec), hook);
+ destroy_recognizer(rec);
+ }
+
+ destroy_synthesizer(c->syn);
+
+ mrp_list_delete(&c->hook);
+ mrp_transport_destroy(c->t);
+ mrp_free(c);
+}
+
+
+static void connection_evt(mrp_transport_t *lt, void *user_data)
+{
+ w3c_server_t *s = (w3c_server_t *)user_data;
+ w3c_client_t *c;
+
+ MRP_UNUSED(lt);
+
+ if ((c = w3c_client_create(s)) == NULL)
+ mrp_log_error("Failed to create new W3C client.");
+ else
+ mrp_log_info("Accepted connection from W3C client #%d.", c->id);
+}
+
+
+static void closed_evt(mrp_transport_t *t, int error, void *user_data)
+{
+ w3c_client_t *c = (w3c_client_t *)user_data;
+
+ MRP_UNUSED(t);
+
+ if (error != 0)
+ mrp_log_error("W3C speech connection closed with error %d (%s).",
+ error, strerror(error));
+ else
+ mrp_log_info("W3C speech connection closed.");
+
+ w3c_client_destroy(c);
+}
+
+
+static inline void dump_request(mrp_json_t *req)
+{
+ const char *str;
+
+#if 1
+ str = mrp_json_object_to_string(req);
+#else
+ str = json_object_to_json_string_ext(req, JSON_C_TO_STRING_PRETTY);
+#endif
+
+ mrp_log_info("received W3C speech request:");
+ mrp_log_info(" %s", str);
+}
+
+
+static mrp_json_t *add_json_timestamp(mrp_json_t *msg)
+{
+ mrp_json_t *ts;
+ struct timeval tv;
+
+ if (gettimeofday(&tv, NULL) < 0)
+ return NULL;
+
+ if (msg != NULL)
+ ts = mrp_json_add_member(msg, "timestamp", MRP_JSON_OBJECT);
+ else
+ msg = ts = mrp_json_create(MRP_JSON_OBJECT);
+
+ if (ts == NULL)
+ return NULL;
+
+ mrp_json_add_integer(ts, "sec" , tv.tv_sec);
+ mrp_json_add_integer(ts, "usec", tv.tv_usec);
+
+ return msg;
+}
+
+
+static int _reply_status(mrp_transport_t *t, int reqno, int status, ...)
+{
+ mrp_json_t *rpl;
+ va_list ap;
+ const char *key;
+ int type;
+
+ if ((rpl = mrp_json_create(MRP_JSON_OBJECT)) == NULL)
+ return -1;
+
+ mrp_json_add_integer(rpl, "reqno" , reqno);
+ mrp_json_add_string (rpl, "type" , "status");
+ mrp_json_add_integer(rpl, "status", status);
+
+ va_start(ap, status);
+
+ while ((key = va_arg(ap, const char *)) != NULL) {
+ switch ((type = va_arg(ap, int))) {
+ case MRP_JSON_BOOLEAN:
+ mrp_json_add_boolean(rpl, key, va_arg(ap, int));
+ break;
+ case MRP_JSON_STRING:
+ mrp_json_add_string(rpl, key, va_arg(ap, const char *));
+ break;
+ case MRP_JSON_INTEGER:
+ mrp_json_add_integer(rpl, key, va_arg(ap, int));
+ break;
+ case MRP_JSON_DOUBLE:
+ mrp_json_add_double(rpl, key, va_arg(ap, double));
+ break;
+ case MRP_JSON_OBJECT:
+ mrp_json_add(rpl, key, va_arg(ap, void *));
+ break;
+ case MRP_JSON_ARRAY:
+ mrp_json_add(rpl, key, va_arg(ap, void *));
+ break;
+ default:
+ mrp_json_unref(rpl);
+ errno = EINVAL;
+
+ va_end(ap);
+
+ return -1;
+ }
+ }
+
+ va_end(ap);
+
+ if (mrp_transport_sendjson(t, rpl))
+ status = 0;
+ else {
+ errno = EIO;
+ status = -1;
+ }
+
+ mrp_json_unref(rpl);
+
+ return status;
+}
+
+#define reply_status(...) _reply_status(__VA_ARGS__, NULL)
+
+
+static int reply_error(mrp_transport_t *t, int reqno, int status,
+ const char *error, mrp_json_t *req, const char *fmt, ...)
+{
+ const char *request;
+ char msg[4096];
+ va_list ap;
+
+ va_start(ap, fmt);
+ vsnprintf(msg, sizeof(msg), fmt ? fmt : "", ap);
+ va_end(ap);
+
+ if (req != NULL)
+ request = "request";
+ else
+ request = NULL;
+
+ if (reqno < 0)
+ mrp_json_get_integer(req, "reqno", &reqno);
+
+ return reply_status(t, reqno, status,
+ "error" , MRP_JSON_STRING, error,
+ "message", MRP_JSON_STRING, msg ,
+ request , MRP_JSON_OBJECT, req );
+}
+
+
+static int _send_event(mrp_transport_t *t, int id, const char *event, ...)
+{
+ mrp_json_t *evt;
+ va_list ap;
+ const char *key;
+ int type, status;
+
+ if ((evt = mrp_json_create(MRP_JSON_OBJECT)) != NULL) {
+ mrp_json_add_integer(evt, "reqno", 0);
+ mrp_json_add_string (evt, "type" , "event");
+ mrp_json_add_integer(evt, "id" , id);
+ add_json_timestamp (evt);
+ mrp_json_add_string (evt, "event", event);
+
+ va_start(ap, event);
+
+ while ((key = va_arg(ap, const char *)) != NULL) {
+ switch ((type = va_arg(ap, int))) {
+ case MRP_JSON_BOOLEAN:
+ mrp_json_add_boolean(evt, key, va_arg(ap, int));
+ break;
+ case MRP_JSON_STRING:
+ mrp_json_add_string(evt, key, va_arg(ap, const char *));
+ break;
+ case MRP_JSON_INTEGER:
+ mrp_json_add_integer(evt, key, va_arg(ap, int));
+ break;
+ case MRP_JSON_DOUBLE:
+ mrp_json_add_double(evt, key, va_arg(ap, double));
+ break;
+ case MRP_JSON_OBJECT:
+ mrp_json_add(evt, key, va_arg(ap, void *));
+ break;
+ case MRP_JSON_ARRAY:
+ mrp_json_add(evt, key, va_arg(ap, void *));
+ break;
+ default:
+ errno = EINVAL;
+ mrp_json_unref(evt);
+ return -1;
+ }
+ }
+
+ va_end(ap);
+
+ if (mrp_transport_sendjson(t, evt))
+ status = 0;
+ else {
+ errno = EIO;
+ status = -1;
+ }
+
+ mrp_json_unref(evt);
+ }
+ else
+ status = -1;
+
+ return status;
+}
+
+#define send_event(...) _send_event(__VA_ARGS__, NULL)
+
+
+static int malformed_request(mrp_transport_t *t, mrp_json_t *req,
+ const char *fmt, ...)
+{
+ int reqno;
+ char msg[4096];
+ va_list ap;
+
+ if (!mrp_json_get_integer(req, "reqno", &reqno))
+ reqno = 0;
+
+ va_start(ap, fmt);
+ vsnprintf(msg, sizeof(msg), fmt, ap);
+ va_end(ap);
+
+ if (reqno > 0)
+ return reply_error(t, reqno, EINVAL, W3C_MALFORMED, req, "%s", msg);
+ else
+ return send_event(t, 0, "error",
+ "errorCode", MRP_JSON_STRING, W3C_MALFORMED,
+ "message" , MRP_JSON_STRING, msg);
+}
+
+
+static inline int update_speaking(w3c_synthesizer_t *syn, bool state)
+{
+ w3c_client_t *c = syn->c;
+
+ return send_event(c->t, 0, "speaking", "state", MRP_JSON_BOOLEAN, state);
+}
+
+
+static inline int update_pending(w3c_synthesizer_t *syn, bool prev)
+{
+ w3c_client_t *c = syn->c;
+ bool curr = !mrp_list_empty(&c->syn->pending);
+
+ if (curr != prev)
+ return send_event(c->t, 0, "pending", "state", MRP_JSON_BOOLEAN, curr);
+ else
+ return 0;
+}
+
+
+static inline int update_paused(w3c_synthesizer_t *syn, bool state)
+{
+ w3c_client_t *c = syn->c;
+
+ return send_event(c->t, 0, "paused", "state", MRP_JSON_BOOLEAN, state);
+}
+
+
+static int w3c_focus_notify(srs_client_t *c, srs_voice_focus_t focus)
+{
+ w3c_recognizer_t *rec = (w3c_recognizer_t *)c->user_data;
+
+ mrp_log_info("W3C-recognizer#%d has now %s focus", rec->id,
+ (focus == SRS_VOICE_FOCUS_NONE ? "no" :
+ (focus == SRS_VOICE_FOCUS_SHARED ? "shared" : "exclusive")));
+
+ if (focus == SRS_VOICE_FOCUS_NONE) {
+ switch (rec->request) {
+ case W3C_REQUEST_START:
+ send_event(rec->c->t, rec->id, "error",
+ "error" , MRP_JSON_STRING, "aborted",
+ "message", MRP_JSON_STRING, "voice focus lost");
+ break;
+ case W3C_REQUEST_STOP:
+ case W3C_REQUEST_ABORT:
+ send_event(rec->c->t, rec->id, "stopped");
+ break;
+ default:
+ break;
+ }
+ rec->backend = W3C_BACKEND_STOPPED;
+ }
+ else {
+ switch (rec->request) {
+ case W3C_REQUEST_START:
+ send_event(rec->c->t, rec->id, "started");
+ break;
+ default:
+ break;
+ }
+ rec->backend = W3C_BACKEND_STARTED;
+ }
+
+ return 0;
+}
+
+
+static char *concat_tokens(char *buf, size_t size, int ntoken, char **tokens)
+{
+ char *p;
+ int l, n, i;
+
+ p = buf;
+ l = size;
+
+ for (i = 0; i < ntoken; i++) {
+ n = snprintf(p, l, "%s%s", i ? " " : "", tokens[i]);
+ p += n;
+ l -= n;
+
+ if (l <= 0) {
+ buf[size - 1] = '\0';
+ break;
+ }
+ }
+
+ return buf;
+}
+
+
+static int w3c_command_notify(srs_client_t *c, int idx, int ntoken,
+ char **tokens, uint32_t *start, uint32_t *end,
+ srs_audiobuf_t *audio)
+{
+ w3c_recognizer_t *rec = (w3c_recognizer_t *)c->user_data;
+ mrp_json_t *results, *r;
+ char text[16*1024];
+
+ MRP_UNUSED(idx);
+ MRP_UNUSED(start);
+ MRP_UNUSED(end);
+ MRP_UNUSED(audio);
+
+ r = results = NULL;
+
+ if ((results = mrp_json_create(MRP_JSON_ARRAY)) == NULL ||
+ (r = mrp_json_create(MRP_JSON_OBJECT)) == NULL) {
+ mrp_json_unref(results);
+ mrp_json_unref(r);
+
+ return -1;
+ }
+
+ concat_tokens(text, sizeof(text), ntoken, tokens);
+
+ mrp_json_add_double(r, "confidence", 0.89);
+ mrp_json_add_string(r, "transcript", text);
+
+ if (mrp_json_array_append(results, r)) {
+ send_event(rec->c->t, rec->id, "match",
+ "final" , MRP_JSON_BOOLEAN, true,
+ "length" , MRP_JSON_INTEGER, 1,
+ "results", MRP_JSON_OBJECT , results);
+
+ return 0;
+ }
+ else {
+ mrp_json_unref(results);
+ mrp_json_unref(r);
+
+ return -1;
+ }
+}
+
+
+static int no_voice_notify(srs_client_t *c, srs_voice_event_t *event)
+{
+ MRP_UNUSED(c);
+ MRP_UNUSED(event);
+
+ return 0;
+}
+
+
+static int create_recognizer_client(w3c_recognizer_t *rec, int *errc,
+ const char **errs)
+{
+ static srs_client_ops_t ops = {
+ .notify_focus = w3c_focus_notify,
+ .notify_command = w3c_command_notify,
+ .notify_render = no_voice_notify
+ };
+
+ srs_context_t *srs = rec->c->s->self->srs;
+ char **commands = rec->attr.commands;
+ int ncommand = rec->attr.ncommand;
+ const char *name = rec->attr.name;
+ const char *appclass = rec->attr.appclass;
+ char cid[256];
+
+ if (rec->srsc != NULL)
+ return 0;
+
+ if (!commands) {
+ *errc = EINVAL;
+ *errs = W3C_BADGRAMMAR;
+ return -1;
+ }
+
+ snprintf(cid, sizeof(cid), "W3C-client #%d.%d", rec->c->id, rec->id);
+
+ if (name == NULL)
+ name = cid;
+
+ if (appclass == NULL)
+ appclass = "player";
+
+ rec->srsc = client_create(srs, SRS_CLIENT_TYPE_EXTERNAL, name, appclass,
+ commands, ncommand, cid, &ops, rec);
+
+ if (rec->srsc == NULL) {
+ *errc = EINVAL;
+ *errs = W3C_FAILED;
+
+ return -1;
+ }
+
+ rec->request = W3C_REQUEST_NONE;
+ rec->backend = W3C_BACKEND_STOPPED;
+
+ return 0;
+}
+
+
+static void destroy_recognizer_client(w3c_recognizer_t *rec)
+{
+ client_destroy(rec->srsc);
+
+ mrp_log_info("Destroying recognizer #%d.%d...", rec->c->id, rec->id);
+
+ rec->srsc = NULL;
+ rec->backend = W3C_BACKEND_STOPPED;
+}
+
+
+static int start_recognizer_client(w3c_recognizer_t *rec, int *errc,
+ const char **errs)
+{
+ int focus;
+
+ if (rec->attr.shared)
+ focus = SRS_VOICE_FOCUS_SHARED;
+ else
+ focus = SRS_VOICE_FOCUS_EXCLUSIVE;
+
+ if (client_request_focus(rec->srsc, focus))
+ return 0;
+ else {
+ *errc = EINVAL;
+ *errs = W3C_FAILED;
+
+ return -1;
+ }
+}
+
+
+static int stop_recognizer_client(w3c_recognizer_t *rec, int *errc,
+ const char **errs)
+{
+ if (client_request_focus(rec->srsc, SRS_VOICE_FOCUS_NONE))
+ return 0;
+ else {
+ *errc = EINVAL;
+ *errs = W3C_FAILED;
+
+ return -1;
+ }
+}
+
+
+static int w3c_voice_notify(srs_client_t *c, srs_voice_event_t *e)
+{
+ w3c_synthesizer_t *syn = (w3c_synthesizer_t *)c->user_data;
+ w3c_utterance_t *utt = lookup_utterance(syn->c, -1, e->id);
+ int mask = 1 << e->type;
+
+ switch (e->type) {
+ case SRS_VOICE_EVENT_STARTED:
+ if (utt->attr.events & W3C_EVENT_START)
+ send_event(syn->c->t, utt->id, "start");
+ break;
+ case SRS_VOICE_EVENT_COMPLETED:
+ if (utt->attr.events & W3C_EVENT_END)
+ send_event(syn->c->t, utt->id, "end");
+ break;
+ case SRS_VOICE_EVENT_TIMEOUT:
+ if (utt->attr.events & W3C_EVENT_ERROR)
+ send_event(syn->c->t, utt->id, "error",
+ "error", MRP_JSON_STRING, "timeout while queued");
+ break;
+ case SRS_VOICE_EVENT_ABORTED:
+ if (utt->attr.events & W3C_EVENT_ERROR)
+ send_event(syn->c->t, utt->id, "error",
+ "error", MRP_JSON_STRING, "aborted");
+ break;
+ case SRS_VOICE_EVENT_PROGRESS:
+ break;
+ default:
+ break;
+ }
+
+ if (mask & SRS_VOICE_MASK_STARTED) {
+ update_speaking(utt->syn, true);
+ }
+ else if (mask & SRS_VOICE_MASK_DONE) {
+ utt->vid = SRS_VOICE_INVALID;
+ mrp_list_delete(&utt->pending);
+
+ update_speaking(utt->syn, false);
+ update_pending(utt->syn, true);
+ }
+
+ return 0;
+}
+
+
+static int create_synthesizer_client(w3c_synthesizer_t *syn, int *errc,
+ const char **errs)
+{
+ static srs_client_ops_t ops = {
+ .notify_focus = NULL,
+ .notify_command = NULL,
+ .notify_render = w3c_voice_notify
+ };
+
+ srs_context_t *srs = syn->c->s->self->srs;
+ const char *name = syn->attr.name;
+ const char *appclass = syn->attr.appclass;
+ char cid[256];
+
+ if (syn->srsc != NULL)
+ return 0;
+
+ snprintf(cid, sizeof(cid), "W3C-renderer #%d", syn->c->id);
+
+ if (name == NULL)
+ name = cid;
+
+ if (appclass == NULL)
+ appclass = "player";
+
+ syn->srsc = client_create(srs, SRS_CLIENT_TYPE_EXTERNAL, name, appclass,
+ NULL, 0, cid, &ops, syn);
+
+ if (syn->srsc == NULL) {
+ *errc = EINVAL;
+ *errs = W3C_FAILED;
+
+ return -1;
+ }
+
+ return 0;
+}
+
+
+static void destroy_synthesizer_client(w3c_synthesizer_t *syn)
+{
+ client_destroy(syn->srsc);
+ syn->srsc = NULL;
+}
+
+
+static int create_synthesizer(w3c_client_t *c)
+{
+ w3c_synthesizer_t *syn;
+
+ if (c->syn != NULL)
+ return 0;
+
+ if ((syn = mrp_allocz(sizeof(*syn))) == 0)
+ return -1;
+
+ mrp_list_init(&syn->utterances);
+ mrp_list_init(&syn->pending);
+
+ syn->c = c;
+ c->syn = syn;
+
+ return 0;
+}
+
+
+static void destroy_synthesizer(w3c_synthesizer_t *syn)
+{
+ mrp_list_hook_t *p, *n;
+ w3c_utterance_t *utt;
+
+ if (syn == NULL)
+ return;
+
+ destroy_synthesizer_client(syn);
+
+ syn->c->syn = NULL;
+
+ mrp_list_foreach(&syn->utterances, p, n) {
+ utt = mrp_list_entry(p, typeof(*utt), hook);
+ destroy_utterance(utt);
+
+ }
+
+ mrp_free(syn);
+}
+
+
+static w3c_utterance_t *create_utterance(w3c_synthesizer_t *syn)
+{
+ w3c_utterance_t *utt;
+
+ if ((utt = mrp_allocz(sizeof(*utt))) != NULL) {
+ mrp_list_init(&utt->hook);
+ mrp_list_init(&utt->pending);
+
+ utt->syn = syn;
+ utt->id = W3C_OBJECT_ID(W3C_TYPE_UTTERANCE, syn->c->next_id++);
+ utt->vid = SRS_VOICE_INVALID;
+
+ utt->attr.volume = 1.0;
+ utt->attr.rate = 1.0;
+ utt->attr.pitch = 1.0;
+ utt->attr.timeout = -1;
+
+ mrp_list_append(&syn->utterances, &utt->hook);
+ }
+
+ return utt;
+}
+
+
+static void destroy_utterance(w3c_utterance_t *utt)
+{
+ mrp_list_delete(&utt->hook);
+ mrp_list_delete(&utt->pending);
+
+ mrp_free(utt->attr.text);
+ mrp_free(utt->attr.lang);
+ mrp_free(utt->attr.voice);
+
+ mrp_free(utt);
+}
+
+
+static w3c_utterance_t *lookup_utterance(w3c_client_t *c, int id, uint32_t vid)
+{
+ mrp_list_hook_t *p, *n;
+ w3c_utterance_t *utt;
+
+ mrp_list_foreach(&c->syn->utterances, p, n) {
+ utt = mrp_list_entry(p, typeof(*utt), hook);
+
+ if (id != -1 && utt->id == id)
+ return utt;
+
+ if (id == -1 && utt->vid == vid)
+ return utt;
+ }
+
+ return NULL;
+}
+
+
+static int activate_utterance(w3c_utterance_t *utt)
+{
+ const char *msg, *voice;
+ double rate, pitch;
+ int timeout, events;
+
+ if (utt->vid != SRS_VOICE_INVALID)
+ return 0;
+
+ msg = utt->attr.text;
+ voice = utt->attr.voice ? utt->attr.voice : utt->attr.lang;
+ rate = utt->attr.rate;
+ pitch = utt->attr.pitch;
+ timeout = utt->attr.timeout;
+
+ events = SRS_VOICE_MASK_ALL;
+
+ if (!utt->syn->paused) {
+ utt->vid = client_render_voice(utt->syn->srsc, msg, voice, rate, pitch,
+ timeout, events);
+
+ if (utt->vid == SRS_VOICE_INVALID) {
+ errno = EINVAL;
+ return -1;
+ }
+ }
+
+ mrp_list_delete(&utt->pending);
+ mrp_list_append(&utt->syn->pending, &utt->pending);
+
+ return 0;
+}
+
+
+static int cancel_utterance(w3c_utterance_t *utt)
+{
+ client_cancel_voice(utt->syn->srsc, utt->vid);
+ utt->vid = SRS_VOICE_INVALID;
+ mrp_list_delete(&utt->pending);
+
+ return 0;
+}
+
+
+static int pause_utterance(w3c_utterance_t *utt)
+{
+ /* XXX TODO: not right pause since will restart from the start. */
+ client_cancel_voice(utt->syn->srsc, utt->vid);
+ utt->vid = SRS_VOICE_INVALID;
+
+ return 0;
+}
+
+
+static int resume_utterance(w3c_utterance_t *utt)
+{
+ /* XXX TODO: since we can't resume ATM, for now we restart from scratch */
+ return activate_utterance(utt);
+}
+
+
+static int parse_string_attr(mrp_json_t *attr, void *obj, size_t base,
+ w3c_attrdef_t *def, int *errc, const char **errs)
+{
+ char **valuep = (char **)(((char *)obj) + base + def->offs);
+ const char *value;
+ int status;
+
+ value = mrp_json_string_value(attr);
+ status = def->check(obj, def, (void *)&value, errc, errs);
+
+ if (status < 0)
+ return -(errno = -status);
+
+ mrp_free(*valuep);
+ *valuep = mrp_strdup(value);
+ mrp_debug("string attribute '%s' set to '%s'", def->name, value);
+
+ return 0;
+}
+
+
+static int parse_boolean_attr(mrp_json_t *attr, void *obj, size_t base,
+ w3c_attrdef_t *def, int *errc, const char **errs)
+{
+ bool *valuep = (bool *)(((char *)obj) + base + def->offs);
+ bool value;
+ int status;
+
+ value = !!mrp_json_boolean_value(attr);
+ status = def->check(obj, def, (void *)&value, errc, errs);
+
+ if (status < 0)
+ return -(errno = -status);
+
+ *valuep = value;
+ mrp_debug("boolean attribute '%s' set to %s", def->name,
+ value ? "true" : "false");
+
+ return 0;
+}
+
+
+static int parse_integer_attr(mrp_json_t *attr, void *obj, size_t base,
+ w3c_attrdef_t *def, int *errc, const char **errs)
+{
+ int *valuep = (int *)(((char *)obj) + base + def->offs);
+ int value, status;
+
+ value = (int)mrp_json_integer_value(attr);
+ status = def->check(obj, def, (void *)&value, errc, errs);
+
+ if (status < 0)
+ return -(errno = -status);
+
+ *valuep = value;
+ mrp_debug("integer attribute '%s' set to %d", def->name, value);
+
+ return 0;
+}
+
+
+static int parse_double_attr(mrp_json_t *attr, void *obj, size_t base,
+ w3c_attrdef_t *def, int *errc, const char **errs)
+{
+ double *valuep = (double *)(((char *)obj) + base + def->offs);
+ double value;
+ int status;
+
+ value = (double)mrp_json_double_value(attr);
+ status = def->check(obj, def, (void *)&value, errc, errs);
+
+ if (status < 0)
+ return -(errno = -status);
+
+ *valuep = value;
+ mrp_debug("double attribute '%s' set to %f", def->name, value);
+
+ return 0;
+}
+
+
+static int parse_events(mrp_json_t *attr, void *obj, size_t base,
+ w3c_attrdef_t *def, int *errc, const char **errs)
+{
+ static struct {
+ const char *name;
+ int mask;
+ } events[] = {
+#define E(_n, _m) { _n, W3C_EVENT_##_m }
+ E("start" , START ),
+ E("end" , END ),
+ E("result" , RESULT ),
+ E("nomatch" , NOMATCH ),
+ E("error" , ERROR ),
+ E("audiostart" , AUDIOSTART ),
+ E("audioend" , AUDIOEND ),
+ E("soundstart" , SOUNDSTART ),
+ E("soundend" , SOUNDEND ),
+ E("speechstart" , SPEECHSTART ),
+ E("speechend" , SPEECHEND ),
+ E("pause" , PAUSE ),
+ E("resume" , RESUME ),
+ E("mark" , MARK ),
+ E("boundary" , BOUNDARY ),
+#undef E
+ { NULL, 0 }
+ }, *e;
+
+ const char *name;
+ int *valuep = (int *)(((char *)obj) + base + def->offs);
+ int mask, len, i, status;
+
+ len = mrp_json_array_length(attr);
+ mask = W3C_EVENT_NONE;
+
+ for (i = 0; i < len; i++) {
+ if (!mrp_json_array_get_string(attr, i, &name)) {
+ errno = EINVAL;
+ *errc = EINVAL;
+ *errs = W3C_BADEVENTS;
+ return -1;
+ }
+
+ for (e = events; e->name; e++) {
+ if (!strcmp(e->name, name)) {
+ mask |= e->mask;
+ break;
+ }
+ }
+
+ if (e->name == NULL) {
+ mrp_log_error("Unknown W3C event '%s' requested", name);
+ errno = EINVAL;
+ *errc = EINVAL;
+ *errs = W3C_BADEVENT;
+ return -1;
+ }
+ }
+
+ status = def->check(obj, def, (void *)&mask, errc, errs);
+
+ if (status < 0)
+ return -(errno = -status);
+
+ *valuep = mask;
+ mrp_debug("events attribute '%s' set to 0x%x", def->name, *valuep);
+
+ return 0;
+}
+
+
+static int parse_grammars(mrp_json_t *attr, void *obj, size_t base,
+ w3c_attrdef_t *def, int *errc, const char **errs)
+{
+ char ***grammarsp = (char ***)(((char *)obj) + base + def->offs);
+ int *ngrammarp;
+ char **grammars;
+ int ngrammar, len, i, status;
+ mrp_json_t *grm;
+
+ if (grammarsp != (char ***)(((char *)obj) +
+ MRP_OFFSET(w3c_recognizer_t, attr) +
+ MRP_OFFSET(w3c_rec_attr_t, grammars))) {
+ *errc = EINVAL;
+ *errs = W3C_SERVERERR;
+ return -1;
+ }
+
+ ngrammarp = (int *)(((char *)obj) +
+ MRP_OFFSET(w3c_recognizer_t, attr) +
+ MRP_OFFSET(w3c_rec_attr_t, ngrammar));
+
+ grammars = NULL;
+ ngrammar = 0;
+ len = mrp_json_array_length(attr);
+
+ for (i = 0; i < len; i++) {
+ if (!mrp_reallocz(grammars, ngrammar, ngrammar + 1)) {
+ *errc = ENOMEM;
+ *errs = W3C_NOMEM;
+ return -1;
+ }
+
+ if (!mrp_json_array_get_item(attr, i, MRP_JSON_OBJECT, &grm))
+ goto invalid_grammars;
+
+ if (!mrp_json_get_string(grm, "src", grammars + i))
+ goto invalid_grammars;
+
+ grammars[i] = mrp_strdup(grammars[i]);
+ ngrammar++;
+ }
+
+ if (!mrp_reallocz(grammars, ngrammar, ngrammar + 1)) {
+ *errc = ENOMEM;
+ *errs = W3C_NOMEM;
+ return -1;
+ }
+
+ status = def->check(obj, def, (void *)grammars, errc, errs);
+
+ if (status < 0) {
+ errno = -status;
+ goto invalid_grammars;
+ }
+
+ for (i = 0; i < *ngrammarp; i++)
+ mrp_free((*grammarsp)[i]);
+ mrp_free(*grammarsp);
+
+ *grammarsp = grammars;
+ *ngrammarp = ngrammar;
+
+ mrp_debug("grammar attribute '%s' set to:", def->name);
+ for (i = 0; i < ngrammar; i++)
+ mrp_debug(" #%d: '%s'", i, grammars[i]);
+
+ return 0;
+
+ invalid_grammars:
+ for (i = 0; i < ngrammar; i++)
+ mrp_free(grammars[i]);
+ mrp_free(grammars);
+
+ *errc = EINVAL;
+ *errs = W3C_BADGRAMMAR;
+
+ return -1;
+}
+
+
+static int w3c_set_attributes(mrp_json_t *set, void *obj, size_t base,
+ w3c_attrdef_t *defs, int *errc, const char **errs)
+{
+ w3c_attrdef_t *d;
+ mrp_json_t *v;
+ int i, mask, vtype;
+
+ if (set == NULL)
+ return 0;
+
+ /*
+ * Notes:
+ *
+ * Currently unsupported attributes in set are silently ignored.
+ * Detecting those would require to replace the loop below with
+ * two nested loops, the outer of which loopinh through all key-
+ * value pairs in the set and the inner looking for the descriptor
+ * for key.
+ */
+
+ mask = 0;
+ for (i = 0, d = defs; d->name != NULL; d++, i++) {
+ v = mrp_json_get(set, d->name);
+
+ if (v == NULL)
+ continue;
+
+ if ((vtype = (int)mrp_json_get_type(v)) != d->type) {
+ if (!(vtype = MRP_JSON_INTEGER && d->type == MRP_JSON_DOUBLE)) {
+ *errc = EINVAL;
+ *errs = W3C_MALFORMED;
+ return -(i + 1);
+ }
+ }
+
+ if (d->parser(v, obj, base, d, errc, errs) < 0)
+ return -(i + 1);
+
+ mask |= d->mask;
+ }
+
+ return mask;
+}
+
+
+static w3c_recognizer_t *create_recognizer(w3c_client_t *c)
+{
+ w3c_recognizer_t *rec;
+
+ if ((rec = mrp_allocz(sizeof(*rec))) != NULL) {
+ mrp_list_init(&rec->hook);
+ mrp_list_append(&c->recognizers, &rec->hook);
+
+ rec->c = c;
+ rec->id = W3C_OBJECT_ID(W3C_TYPE_RECOGNIZER, c->next_id++);
+ }
+
+ mrp_log_info("Created W3C recognizer #%d.%d.", rec->c->id, rec->id);
+
+ return rec;
+}
+
+
+static void destroy_recognizer(w3c_recognizer_t *rec)
+{
+ int i;
+
+ if (rec == NULL)
+ return;
+
+ mrp_log_info("Destroying W3C recognizer #%d.%d.", rec->c->id, rec->id);
+
+ mrp_list_delete(&rec->hook);
+
+ destroy_recognizer_client(rec);
+
+ mrp_free(rec->attr.name);
+ mrp_free(rec->attr.appclass);
+ mrp_free(rec->attr.lang);
+ mrp_free(rec->attr.service);
+
+ for (i = 0; i < rec->attr.ngrammar; i++)
+ mrp_free(rec->attr.grammars[i]);
+ mrp_free(rec->attr.grammars);
+
+
+ mrp_free(rec);
+}
+
+
+static w3c_recognizer_t *lookup_recognizer(w3c_client_t *c, int id)
+{
+ mrp_list_hook_t *p, *n;
+ w3c_recognizer_t *rec;
+
+ mrp_list_foreach(&c->recognizers, p, n) {
+ rec = mrp_list_entry(p, typeof(*rec), hook);
+
+ if (rec->id == id)
+ return rec;
+ }
+
+ return NULL;
+}
+
+
+
+static int check_recognizer_attr(void *obj, w3c_attrdef_t *def, void *val,
+ int *errc, const char **errs)
+{
+ w3c_recognizer_t *rec = (w3c_recognizer_t *)obj;
+
+ MRP_UNUSED(val);
+
+ switch (def->mask) {
+ case W3C_ATTR_NAME:
+ case W3C_ATTR_APPCLASS:
+ case W3C_ATTR_GRAMMARS:
+ case W3C_ATTR_LANG:
+ if (rec->srsc == NULL)
+ return 0;
+
+ *errc = EBUSY;
+ *errs = W3C_BUSY;
+ return -EBUSY;
+
+ case W3C_ATTR_EVENTS: return 0;
+ case W3C_ATTR_CONTINUOUS: return 0;
+ case W3C_ATTR_INTERIM: return 0;
+ case W3C_ATTR_MAXALT: return 0;
+ case W3C_ATTR_SERVICE: return 0;
+ case W3C_ATTR_SHARED: return 0;
+ default:
+ *errc = EINVAL;
+ *errs = W3C_MALFORMED;
+ return -EINVAL;
+ }
+}
+
+
+static int check_utterance_attr(void *obj, w3c_attrdef_t *def, void *val,
+ int *errc, const char **errs)
+{
+ w3c_utterance_t *utt = (w3c_utterance_t *)obj;
+
+ MRP_UNUSED(val);
+
+ switch (def->mask) {
+ case W3C_ATTR_TEXT:
+ case W3C_ATTR_LANG:
+ case W3C_ATTR_VOICE:
+ if (utt->vid == SRS_VOICE_INVALID)
+ return 0;
+
+ *errc = EBUSY;
+ *errs = W3C_BUSY;
+ return -EBUSY;
+
+ case W3C_ATTR_VOLUME:
+ case W3C_ATTR_RATE:
+ case W3C_ATTR_PITCH:
+ case W3C_ATTR_TIMEOUT:
+ if (utt->vid == SRS_VOICE_INVALID)
+ return 0;
+
+ *errc = EBUSY;
+ *errs = W3C_BUSY;
+ return -EBUSY;
+
+ case W3C_ATTR_EVENTS:
+ return 0;
+
+ default:
+ *errc = EINVAL;
+ *errs = W3C_MALFORMED;
+ return -EINVAL;
+ }
+}
+
+
+static inline w3c_attrdef_t *recognizer_attributes(void)
+{
+#define DEFAULT NULL
+#define BOOLEAN(_n, _an, _m, _p) \
+ { _n, MRP_JSON_BOOLEAN, MRP_OFFSET(w3c_rec_attr_t, _an), W3C_ATTR_##_m, \
+ _p ? _p : parse_boolean_attr, check_recognizer_attr }
+#define STRING(_n, _an, _m, _p) \
+ { _n, MRP_JSON_STRING , MRP_OFFSET(w3c_rec_attr_t, _an), W3C_ATTR_##_m, \
+ _p ? _p : parse_string_attr , check_recognizer_attr }
+#define INTEGER(_n, _an, _m, _p) \
+ { _n, MRP_JSON_INTEGER, MRP_OFFSET(w3c_rec_attr_t, _an), W3C_ATTR_##_m, \
+ _p ? _p : parse_integer_attr, check_recognizer_attr }
+#define ARRAY(_n, _an, _m, _p) \
+ { _n, MRP_JSON_ARRAY , MRP_OFFSET(w3c_rec_attr_t, _an), W3C_ATTR_##_m, \
+ _p, check_recognizer_attr }
+
+ static w3c_attrdef_t def[] = {
+ STRING ("name" , name , NAME , DEFAULT ),
+ STRING ("appclass" , appclass , APPCLASS , DEFAULT ),
+ ARRAY ("events" , events , EVENTS , parse_events ),
+ ARRAY ("grammars" , grammars , GRAMMARS , parse_grammars),
+ STRING ("lang" , lang , LANG , DEFAULT ),
+ BOOLEAN("continuous" , continuous, CONTINUOUS, DEFAULT ),
+ BOOLEAN("interimResults" , interim , INTERIM , DEFAULT ),
+ INTEGER("maxAlternatives", max_alt , MAXALT , DEFAULT ),
+ STRING ("serviceURI" , service , SERVICE , DEFAULT ),
+ BOOLEAN("shared" , shared , SHARED , DEFAULT ),
+ { NULL, 0, 0, 0, NULL, NULL }
+ };
+
+#undef DEFAULT
+#undef BOOLEAN
+#undef STRING
+#undef INTEGER
+#undef ARRAY
+
+ return def;
+}
+
+
+static inline w3c_attrdef_t *utterance_attributes(void)
+{
+#define DEFAULT NULL
+#define BOOLEAN(_n, _an, _m, _p) \
+ { _n, MRP_JSON_BOOLEAN, MRP_OFFSET(w3c_utt_attr_t, _an), W3C_ATTR_##_m, \
+ _p ? _p : parse_boolean_attr, check_utterance_attr }
+#define STRING(_n, _an, _m, _p) \
+ { _n, MRP_JSON_STRING , MRP_OFFSET(w3c_utt_attr_t, _an), W3C_ATTR_##_m, \
+ _p ? _p : parse_string_attr , check_utterance_attr }
+#define INTEGER(_n, _an, _m, _p) \
+ { _n, MRP_JSON_INTEGER, MRP_OFFSET(w3c_utt_attr_t, _an), W3C_ATTR_##_m, \
+ _p ? _p : parse_integer_attr, check_utterance_attr }
+#define DOUBLE(_n, _an, _m, _p) \
+ { _n, MRP_JSON_DOUBLE, MRP_OFFSET(w3c_utt_attr_t, _an), W3C_ATTR_##_m, \
+ _p ? _p : parse_double_attr, check_utterance_attr }
+#define ARRAY(_n, _an, _m, _p) \
+ { _n, MRP_JSON_ARRAY , MRP_OFFSET(w3c_utt_attr_t, _an), W3C_ATTR_##_m, \
+ _p, check_utterance_attr }
+
+ static w3c_attrdef_t def[] = {
+ STRING ("text" , text , TEXT , DEFAULT ),
+ STRING ("lang" , lang , LANG , DEFAULT ),
+ STRING ("voiceURI" , voice , VOICE , DEFAULT ),
+ DOUBLE ("volume" , volume , VOLUME , DEFAULT ),
+ DOUBLE ("rate" , rate , RATE , DEFAULT ),
+ DOUBLE ("pitch" , pitch , PITCH , DEFAULT ),
+ INTEGER("timeout" , timeout , TIMEOUT , DEFAULT ),
+ ARRAY ("events" , events , EVENTS , parse_events ),
+ { NULL, 0, 0, 0, NULL, NULL }
+ };
+
+#undef DEFAULT
+#undef BOOLEAN
+#undef STRING
+#undef INTEGER
+#undef DOUBLE
+#undef ARRAY
+
+ return def;
+}
+
+
+
+static char *strip_whitespace(char *buf)
+{
+ char *e;
+
+ if (buf == NULL)
+ return NULL;
+
+ while (*buf == ' ' || *buf == '\t')
+ buf++;
+
+ e = buf + strlen(buf) - 1;
+ while (e >= buf && (*e == '\n' || *e == ' ' || *e == '\t'))
+ *e-- = '\0';
+
+ return buf;
+}
+
+
+static FILE *open_grammar(w3c_server_t *s, const char *URI)
+{
+ char path[PATH_MAX];
+ FILE *fp;
+
+ if (strncmp(URI, W3C_URI, sizeof(W3C_URI) - 1)) {
+ mrp_log_error("W3C: invalid grammar '%s'.", URI);
+ errno = EINVAL;
+ return NULL;
+ }
+
+ URI += sizeof(W3C_URI) - 1;
+ snprintf(path, sizeof(path), "%s/%s", s->grammar_dir, URI);
+
+ fp = fopen(path, "r");
+
+ if (fp != NULL)
+ mrp_debug("W3C: grammar '%s' -> '%s'", URI, path);
+
+ return fp;
+}
+
+
+int read_grammars(w3c_recognizer_t *rec, const char **errs)
+{
+ char **cmds, *cmd, buf[4096];
+ int n, i;
+ FILE *fp;
+
+ cmds = NULL;
+ n = 0;
+
+ for (i = 0; i < rec->attr.ngrammar; i++) {
+ fp = open_grammar(rec->c->s, rec->attr.grammars[i]);
+
+ if (fp == NULL) {
+ *errs = W3C_BADGRAMMAR;
+ goto fail;
+ }
+
+ while (fgets(buf, sizeof(buf), fp) != NULL) {
+ cmd = strip_whitespace(buf);
+
+ if (!*cmd)
+ continue;
+
+ if (mrp_reallocz(cmds, n, n + 1) == NULL ||
+ (cmds[n] = mrp_strdup(cmd)) == NULL) {
+ *errs = W3C_NOMEM;
+ goto fail;
+ }
+
+ mrp_debug("command #%d: '%s'", n, cmd);
+
+ n++;
+ }
+
+ fclose(fp);
+ }
+
+ for (i = 0; i < rec->attr.ncommand; i++)
+ mrp_free(rec->attr.commands[i]);
+ mrp_free(rec->attr.commands);
+
+ rec->attr.commands = cmds;
+ rec->attr.ncommand = n;
+
+ return 0;
+
+ fail:
+ for (i = 0; i < n; i++)
+ mrp_free(cmds[i]);
+ mrp_free(cmds);
+
+ return -1;
+}
+
+
+static int check_id(w3c_client_t *c, mrp_json_t *req, int *idp)
+{
+ if (mrp_json_get_integer(req, "id", idp))
+ return 0;
+ else {
+ malformed_request(c->t, req, "missing object ID");
+ return -1;
+ }
+}
+
+
+static w3c_recognizer_t *check_recognizer(w3c_client_t *c, mrp_json_t *req,
+ int id)
+{
+ w3c_recognizer_t *rec;
+
+ if (id < 0)
+ if (check_id(c, req, &id) < 0)
+ return NULL;
+
+ rec = lookup_recognizer(c, id);
+
+ if (rec != NULL)
+ return rec;
+
+ reply_error(c->t, -1, ENOENT, W3C_NOTFOUND, req,
+ "recognizer object not found");
+ return NULL;
+}
+
+
+static w3c_utterance_t *check_utterance(w3c_client_t *c, mrp_json_t *req, int id)
+{
+ w3c_utterance_t *utt;
+
+ if (id < 0)
+ if (check_id(c, req, &id) < 0)
+ return NULL;
+
+ utt = lookup_utterance(c, id, -1);
+
+ if (utt != NULL)
+ return utt;
+
+ reply_error(c->t, -1, ENOENT, W3C_NOTFOUND, req,
+ "utterance object not found");
+ return NULL;
+}
+
+
+
+
+static int w3c_create_recognizer(w3c_client_t *c, int reqno, mrp_json_t *req)
+{
+ w3c_attrdef_t *def = recognizer_attributes();
+ w3c_recognizer_t *rec;
+ mrp_json_t *set;
+ size_t base;
+ int mask;
+ int errc;
+ const char *errs;
+
+ if ((rec = create_recognizer(c)) == NULL) {
+ return reply_error(c->t, reqno, ENOMEM, W3C_FAILED, req,
+ "failed to create new recognizer instance");
+ }
+
+ set = mrp_json_get(req, "set");
+
+ if (set != NULL) {
+ base = MRP_OFFSET(w3c_recognizer_t, attr);
+ errc = 0;
+ errs = NULL;
+ mask = w3c_set_attributes(set, rec, base, def, &errc, &errs);
+ }
+ else
+ mask = 0;
+
+ if (mask < 0) {
+ reply_error(c->t, reqno, errc, errs, req,
+ "failed to set recognizer attributes");
+ destroy_recognizer(rec);
+ return -1;
+ }
+
+ if (mask & W3C_ATTR_GRAMMARS) {
+ if (read_grammars(rec, &errs) < 0) {
+ errc = errno;
+ reply_error(c->t, reqno, errc, errs, req,
+ "failed to locate/parse given grammars");
+ destroy_recognizer(rec);
+ return -1;
+ }
+ }
+
+ return reply_status(c->t, reqno, 0, "id", MRP_JSON_INTEGER, rec->id);
+}
+
+
+static int w3c_delete_object(w3c_client_t *c, int reqno, mrp_json_t *req)
+{
+ int id;
+ w3c_recognizer_t *rec;
+ w3c_utterance_t *utt;
+
+ if (check_id(c, req, &id) < 0)
+ return -1;
+
+ switch (W3C_OBJECT_TYPE(id)) {
+ case W3C_TYPE_RECOGNIZER:
+ rec = check_recognizer(c, req, id);
+
+ if (rec != NULL)
+ destroy_recognizer(rec);
+
+ return reply_status(c->t, reqno, 0);
+
+ case W3C_TYPE_UTTERANCE:
+ utt = check_utterance(c, req, id);
+
+ if (utt != NULL)
+ destroy_utterance(utt);
+
+ return reply_status(c->t, reqno, 0);
+
+ default:
+ break;
+ }
+
+ return -1;
+}
+
+
+static int w3c_set_attribute(w3c_client_t *c, int reqno, mrp_json_t *req)
+{
+ int id;
+ w3c_recognizer_t *rec;
+ w3c_utterance_t *utt;
+ mrp_json_t *set;
+ w3c_attrdef_t *defs;
+ size_t base;
+ int mask;
+ int errc;
+ const char *errs;
+
+ if (check_id(c, req, &id) < 0)
+ return -1;
+
+ if ((set = mrp_json_get(req, "set")) == NULL)
+ return malformed_request(c->t, req, "missing attributes");
+
+ if (mrp_json_get_type(set) != MRP_JSON_OBJECT)
+ return malformed_request(c->t, req, "invalid attributes");
+
+ switch (W3C_OBJECT_TYPE(id)) {
+ case W3C_TYPE_RECOGNIZER:
+ if ((rec = check_recognizer(c, req, id)) == NULL)
+ return 0;
+
+ defs = recognizer_attributes();
+ base = MRP_OFFSET(w3c_recognizer_t, attr);
+ mask = w3c_set_attributes(set, rec, base, defs, &errc, &errs);
+
+ if (mask < 0)
+ return reply_error(c->t, reqno, errc, errs, req,
+ "failed to set attribute #%d", -(mask - 1));
+
+ if (mask & W3C_ATTR_GRAMMARS) {
+ if (read_grammars(rec, &errs) < 0) {
+ errc = errno;
+ return reply_error(c->t, reqno, errc, errs, req,
+ "failed to locate/parse some given grammar");
+ }
+ }
+
+ return reply_status(c->t, reqno, 0);
+
+ case W3C_TYPE_UTTERANCE:
+ if ((utt = check_utterance(c, req, id)) == NULL)
+ return 0;
+
+ defs = utterance_attributes();
+ base = MRP_OFFSET(w3c_utterance_t, attr);
+ mask = w3c_set_attributes(set, utt, base, defs, &errc, &errs);
+
+ if (mask < 0)
+ return reply_error(c->t, reqno, errc, errs, req,
+ "failed to set attribute #%d", -(mask - 1));
+ else
+ return reply_status(c->t, reqno, 0);
+
+ default:
+ break;
+ }
+
+ return -1;
+}
+
+
+static int w3c_get_timestamp(w3c_client_t *c, int reqno, mrp_json_t *req)
+{
+ mrp_log_info("%s(#%d: %s)", __FUNCTION__, reqno,
+ mrp_json_object_to_string(req));
+
+ return reply_status(c->t, reqno, 0,
+ "timestamp", MRP_JSON_OBJECT, add_json_timestamp(NULL));
+}
+
+
+static int w3c_start_recognizer(w3c_client_t *c, int reqno, mrp_json_t *req)
+{
+ w3c_recognizer_t *rec;
+ int id, errc;
+ const char *errs;
+
+ if (check_id(c, req, &id) < 0)
+ return -1;
+
+ if ((rec = check_recognizer(c, req, id)) == NULL)
+ return -1;
+
+ if (rec->srsc == NULL) {
+ if (create_recognizer_client(rec, &errc, &errs) < 0) {
+ reply_error(c->t, reqno, errc, errs, req,
+ "failed to create backend client");
+ return -1;
+ }
+ }
+
+ if (start_recognizer_client(rec, &errc, &errs) < 0) {
+ reply_error(c->t, reqno, errc, errs, req,
+ "failed to start backend client");
+ return -1;
+ }
+
+ rec->request = W3C_REQUEST_START;
+
+ return reply_status(c->t, reqno, 0);
+}
+
+
+static int w3c_stop_recognizer(w3c_client_t *c, int reqno, mrp_json_t *req)
+{
+ w3c_recognizer_t *rec;
+ int id, errc;
+ const char *errs;
+
+ if (check_id(c, req, &id) < 0)
+ return -1;
+
+ if ((rec = check_recognizer(c, req, id)) == NULL)
+ return -1;
+
+ if (stop_recognizer_client(rec, &errc, &errs) < 0) {
+ reply_error(c->t, reqno, errc, errs, req,
+ "failed to stop backend client");
+ return -1;
+ }
+
+ rec->request = W3C_REQUEST_STOP;
+
+ return reply_status(c->t, reqno, 0);
+}
+
+
+static int w3c_abort_recognizer(w3c_client_t *c, int reqno, mrp_json_t *req)
+{
+ w3c_recognizer_t *rec;
+ int id, errc;
+ const char *errs;
+
+ if (check_id(c, req, &id) < 0)
+ return -1;
+
+ if ((rec = check_recognizer(c, req, id)) == NULL)
+ return -1;
+
+ if (stop_recognizer_client(rec, &errc, &errs) < 0) {
+ reply_error(c->t, reqno, errc, errs, req,
+ "failed to stop backend client");
+ return -1;
+ }
+
+ rec->request = W3C_REQUEST_ABORT;
+
+ return reply_status(c->t, reqno, 0);
+}
+
+
+static int w3c_create_utterance(w3c_client_t *c, int reqno, mrp_json_t *req)
+{
+ w3c_synthesizer_t *syn = c->syn;
+ w3c_attrdef_t *def = utterance_attributes();
+ w3c_utterance_t *utt;
+ mrp_json_t *set;
+ size_t base;
+ int mask;
+ int errc;
+ const char *errs;
+
+ if ((utt = create_utterance(syn)) == NULL)
+ return reply_error(c->t, reqno, ENOMEM, W3C_FAILED, req,
+ "failed to create new utterance instance");
+
+ if ((set = mrp_json_get(req, "set")) != NULL) {
+ base = MRP_OFFSET(w3c_utterance_t, attr);
+ errc = 0;
+ errs = NULL;
+ mask = w3c_set_attributes(set, utt, base, def, &errc, &errs);
+ }
+ else
+ mask = 0;
+
+ if (mask < 0) {
+ reply_error(c->t, reqno, errc, errs, req,
+ "failed to set utterance attribute (#%d)", -mask);
+ destroy_utterance(utt);
+ return -1;
+ }
+
+ return reply_status(c->t, reqno, 0, "id", MRP_JSON_INTEGER, utt->id);
+}
+
+
+static int w3c_speak_utterance(w3c_client_t *c, int reqno, mrp_json_t *req)
+{
+ w3c_utterance_t *utt;
+ w3c_synthesizer_t *syn;
+ int id, uid;
+ int errc;
+ const char *errs;
+ bool had_pending;
+
+ if (check_id(c, req, &id) < 0)
+ return -1;
+
+ if (id != 0)
+ return reply_error(c->t, reqno, EINVAL, W3C_MALFORMED, req,
+ "speak must use implicit ID 0");
+
+ if (!mrp_json_get_integer(req, "utterance", &uid))
+ return malformed_request(c->t, req, "missing utterace ID");
+
+ if ((utt = check_utterance(c, req, uid)) == NULL)
+ return -1;
+
+ if (utt->vid != SRS_VOICE_INVALID) {
+ reply_error(c->t, reqno, EBUSY, W3C_BUSY, req,
+ "utterance is already being played/queued");
+ return -1;
+ }
+
+ if (utt->attr.text == NULL) {
+ reply_error(c->t, reqno, EINVAL, W3C_FAILED, req,
+ "utterance text not set");
+ return -1;
+ }
+
+ if (utt->attr.lang == NULL && utt->attr.voice == NULL) {
+ reply_error(c->t, reqno, EINVAL, W3C_FAILED, req,
+ "neither voice nor language is set");
+ return -1;
+ }
+
+ syn = utt->syn;
+
+ if (syn->srsc == NULL) {
+ if (create_synthesizer_client(syn, &errc, &errs) < 0) {
+ reply_error(c->t, reqno, errc, errs, req,
+ "failed to create backend client");
+ return -1;
+ }
+ }
+
+ had_pending = !mrp_list_empty(&syn->pending);
+
+ if (activate_utterance(utt) < 0) {
+ reply_error(c->t, reqno, EINVAL, W3C_FAILED, req,
+ "synthesizer backend failed");
+ return -1;
+ }
+
+ reply_status(c->t, reqno, 0);
+ update_pending(syn, had_pending);
+
+ return 0;
+}
+
+
+static int w3c_cancel_utterance(w3c_client_t *c, int reqno, mrp_json_t *req)
+{
+ w3c_synthesizer_t *syn = c->syn;
+ w3c_utterance_t *utt;
+ mrp_list_hook_t *p, *n;
+ int id, uid;
+ bool cancelled;
+
+ if (check_id(c, req, &id) < 0)
+ return -1;
+
+ cancelled = false;
+
+ if (id != 0)
+ return reply_error(c->t, reqno, EINVAL, W3C_MALFORMED, req,
+ "cancel must use implicit ID 0");
+
+ if (mrp_json_get_integer(req, "utterance", &uid)) {
+ if ((utt = check_utterance(c, req, uid)) == NULL)
+ return -1;
+
+ cancel_utterance(utt);
+ }
+ else {
+ mrp_list_foreach(&syn->pending, p, n) {
+ utt = mrp_list_entry(p, typeof(*utt), pending);
+
+ cancel_utterance(utt);
+ cancelled = true;
+ }
+ }
+
+ reply_status(c->t, reqno, 0);
+ update_pending(syn, cancelled);
+
+ return 0;
+}
+
+
+static int w3c_pause_utterance(w3c_client_t *c, int reqno, mrp_json_t *req)
+{
+ w3c_synthesizer_t *syn = c->syn;
+ w3c_utterance_t *utt;
+ mrp_list_hook_t *p, *n;
+ int id;
+
+ if (check_id(c, req, &id) < 0)
+ return -1;
+
+ if (id != 0)
+ return reply_error(c->t, reqno, EINVAL, W3C_MALFORMED, req,
+ "pause must use implicit ID 0");
+
+ mrp_list_foreach(&syn->pending, p, n) {
+ utt = mrp_list_entry(p, typeof(*utt), pending);
+
+ pause_utterance(utt);
+ }
+
+ reply_status(c->t, reqno, 0);
+
+ syn->paused = true;
+ update_paused(syn, true);
+
+ return 0;
+}
+
+
+static int w3c_resume_utterance(w3c_client_t *c, int reqno, mrp_json_t *req)
+{
+ w3c_synthesizer_t *syn = c->syn;
+ w3c_utterance_t *utt;
+ mrp_list_hook_t *p, *n;
+ int id;
+ bool first;
+
+ if (check_id(c, req, &id) < 0)
+ return -1;
+
+ if (id != 0)
+ return reply_error(c->t, reqno, EINVAL, W3C_MALFORMED, req,
+ "pause must use implicit ID 0");
+
+ syn->paused = false;
+
+ first = true;
+ mrp_list_foreach(&syn->pending, p, n) {
+ utt = mrp_list_entry(p, typeof(*utt), pending);
+
+ if (first)
+ resume_utterance(utt);
+ else
+ activate_utterance(utt);
+
+ first = false;
+ }
+
+ reply_status(c->t, reqno, 0);
+ update_paused(syn, false);
+
+ return 0;
+}
+
+
+static int w3c_get_voices(w3c_client_t *c, int reqno, mrp_json_t *req)
+{
+ srs_context_t *srs = c->s->self->srs;
+ mrp_json_t *voices = NULL;
+ const char *lang = NULL;
+ srs_voice_actor_t *actors, *a;
+ int nactor, i;
+ mrp_json_t *actor;
+
+ mrp_json_get_string(req, "lang", &lang);
+
+ nactor = srs_query_voices(srs, lang, &actors);
+
+ if (nactor < 0) {
+ reply_error(c->t, reqno, EINVAL, W3C_FAILED, req,
+ "failed to query backend voices");
+ return -1;
+ }
+
+ voices = mrp_json_create(MRP_JSON_ARRAY);
+
+ if (voices == NULL)
+ goto oom;
+
+ for (i = 0, a = actors; i < nactor; i++, a++) {
+ actor = mrp_json_create(MRP_JSON_OBJECT);
+
+ if (actor == NULL)
+ goto oom;
+
+ if (!mrp_json_array_append(voices, actor)) {
+ mrp_json_unref(actor);
+ goto oom;
+ }
+
+ mrp_json_add_string (actor, "voiceURI" , a->name);
+ mrp_json_add_string (actor, "lang" , a->lang);
+ mrp_json_add_string (actor, "name" , a->name);
+ mrp_json_add_boolean(actor, "localService", true);
+ mrp_json_add_boolean(actor, "default" , false);
+ }
+
+ return reply_status(c->t, reqno, 0,
+ "voices", MRP_JSON_ARRAY, voices);
+
+ oom: /* Rrright... surely this is going to succeed then. */
+ reply_error(c->t, reqno, ENOMEM, W3C_NOMEM, req, "out of memory");
+ srs_free_queried_voices(actors);
+ mrp_json_unref(voices);
+ return -1;
+}
+
+
+static request_handler_t get_handler(w3c_client_t *c, mrp_json_t *req,
+ int *reqnop)
+{
+ static struct {
+ const char *type;
+ const char *key;
+ const char *value;
+ request_handler_t handler;
+ } *h, handlers[] = {
+ { "create" , "object" , "recognizer", w3c_create_recognizer },
+ { "create" , "object" , "utterance" , w3c_create_utterance },
+ { "delete" , NULL , NULL , w3c_delete_object },
+ { "set" , NULL , NULL , w3c_set_attribute },
+ { "timestamp", NULL , NULL , w3c_get_timestamp },
+ { "invoke" , "method" , "start" , w3c_start_recognizer },
+ { "invoke" , "method" , "stop" , w3c_stop_recognizer },
+ { "invoke" , "method" , "abort" , w3c_abort_recognizer },
+ { "invoke" , "method" , "speak" , w3c_speak_utterance },
+ { "invoke" , "method" , "cancel" , w3c_cancel_utterance },
+ { "invoke" , "method" , "pause" , w3c_pause_utterance },
+ { "invoke" , "method" , "resume" , w3c_resume_utterance },
+ { "invoke" , "method" , "get-voices", w3c_get_voices },
+ { NULL, NULL, NULL, NULL }
+ };
+
+ const char *type, *key, *value;
+ int reqno;
+
+ if (!mrp_json_get_string (req, "type" , &type)) {
+ malformed_request(c->t, req, "missing request type");
+ goto fail;
+ }
+
+ if (!mrp_json_get_integer(req, "reqno", &reqno)) {
+ malformed_request(c->t, req, "missing request number");
+ goto fail;
+ }
+
+ if (reqnop != NULL)
+ *reqnop = reqno;
+
+ key = NULL;
+ value = NULL;
+
+ for (h = handlers; h->type != NULL; h++) {
+ if (strcmp(type, h->type) != 0)
+ continue;
+
+ if (h->key == NULL)
+ break;
+
+ if (key == NULL || strcmp(key, h->key) != 0) {
+ if (!mrp_json_get_string(req, (key = h->key), &value)) {
+ malformed_request(c->t, req, "missing request %s", key);
+ goto fail;
+ }
+ }
+
+ if (!strcmp(value, h->value))
+ break;
+ }
+
+ if (h->type != NULL)
+ return h->handler;
+ else
+ malformed_request(c->t, req, "unknown request type");
+
+ fail:
+ w3c_client_destroy(c);
+
+ return NULL;
+}
+
+
+static void recv_evt(mrp_transport_t *t, mrp_json_t *req, void *user_data)
+{
+ w3c_client_t *c = (w3c_client_t *)user_data;
+ request_handler_t h;
+ int reqno;
+
+ MRP_UNUSED(t);
+
+ dump_request(req);
+
+ if ((h = get_handler(c, req, &reqno)) != NULL)
+ h(c, reqno, req);
+ else
+ mrp_log_error("Failed to find request handler for request %s.",
+ mrp_json_object_to_string(req));
+}
+
+
+static int transport_create(w3c_server_t *s)
+{
+ static mrp_transport_evt_t evt = {
+ { .recvjson = recv_evt },
+ { .recvjsonfrom = NULL },
+ .connection = connection_evt,
+ .closed = closed_evt,
+ };
+
+ srs_context_t *srs = s->self->srs;
+ mrp_sockaddr_t addr;
+ socklen_t alen;
+ const char *type;
+ int flags, state;
+
+ flags = MRP_TRANSPORT_NONBLOCK | MRP_TRANSPORT_MODE_JSON | \
+ MRP_TRANSPORT_REUSEADDR;
+
+ alen = mrp_transport_resolve(NULL, s->address, &addr, sizeof(addr), &type);
+
+ if (alen <= 0)
+ goto resolve_failed;
+
+ if (s->sock >= 0) {
+ state = MRP_TRANSPORT_LISTENED;
+ s->lt = mrp_transport_create_from(srs->ml, type, &s->sock, &evt,
+ s, flags, state);
+
+ if (s->lt == NULL)
+ goto create_failed;
+
+ mrp_log_info("Using socket %d for W3C speech transport.", s->sock);
+ }
+ else {
+ s->lt = mrp_transport_create(srs->ml, type, &evt, s, flags);
+
+ if (s->lt == NULL)
+ goto create_failed;
+
+ if (!mrp_transport_bind(s->lt, &addr, alen))
+ goto bind_failed;
+
+ if (!mrp_transport_listen(s->lt, 0))
+ goto bind_failed;
+
+ mrp_log_info("Listening on W3C speech transport '%s'.", s->address);
+ }
+
+ return 0;
+
+ resolve_failed:
+ mrp_log_error("Can't resolve W3C speech transport '%s'.", s->address);
+ goto cleanup_fail;
+
+ create_failed:
+ mrp_log_error("Can't create W3C speech transport.");
+ goto cleanup_fail;
+
+ bind_failed:
+ mrp_log_error("Can't bind/listen W3C speech transport '%s'.", s->address);
+ /* fallthru */
+
+ cleanup_fail:
+ mrp_transport_destroy(s->lt);
+ s->lt = NULL;
+
+ return -1;
+}
+
+
+static void transport_destroy(w3c_server_t *s)
+{
+ mrp_transport_destroy(s->lt);
+ s->lt = NULL;
+}
+
+
+static int w3c_create(srs_plugin_t *plugin)
+{
+ w3c_server_t *s;
+
+ mrp_debug("creating W3C Speech API plugin");
+
+ s = mrp_allocz(sizeof(*s));
+
+ if (s == NULL)
+ return FALSE;
+
+ mrp_list_init(&s->clients);
+ s->self = plugin;
+
+ plugin->plugin_data = s;
+
+ return TRUE;
+}
+
+
+static int w3c_config(srs_plugin_t *plugin, srs_cfg_t *cfg)
+{
+ w3c_server_t *s = (w3c_server_t *)plugin->plugin_data;
+
+ mrp_debug("configuring W3C speech plugin");
+
+ s->address = srs_config_get_string(cfg, CONFIG_ADDRESS, DEFAULT_ADDRESS);
+ s->sock = srs_config_get_int32(cfg, CONFIG_SOCKET, DEFAULT_SOCKET);
+
+ if (s->sock < 0)
+ mrp_log_info("Using W3C speech transport '%s'.", s->address);
+ else
+ mrp_log_info("Using W3C speech socket %d.", s->sock);
+
+ s->grammar_dir = srs_config_get_string(cfg, CONFIG_GRAMMARDIR,
+ DEFAULT_GRAMMARDIR);
+
+ mrp_log_info("Looking for W3C grammar files in '%s'.", s->grammar_dir);
+
+ return TRUE;
+}
+
+
+static int w3c_start(srs_plugin_t *plugin)
+{
+ w3c_server_t *s = (w3c_server_t *)plugin->plugin_data;
+
+ if (transport_create(s) < 0)
+ return FALSE;
+ else
+ return TRUE;
+}
+
+
+static void w3c_stop(srs_plugin_t *plugin)
+{
+ MRP_UNUSED(plugin);
+}
+
+
+static void w3c_destroy(srs_plugin_t *plugin)
+{
+ w3c_server_t *s = (w3c_server_t *)plugin->plugin_data;
+
+ transport_destroy(s);
+ mrp_free(s);
+}
+
+
+SRS_DECLARE_PLUGIN(W3C_PLUGIN, W3C_DESCR, W3C_AUTHORS, W3C_VERSION,
+ w3c_create, w3c_config, w3c_start, w3c_stop, w3c_destroy);