blob: dc25b16d845a87b97dcdfe70d26d0863ff894c80 [file] [log] [blame]
// Copyright 2015 The Android Open Source Project
//
// This software is licensed under the terms of the GNU General Public
// License version 2, as published by the Free Software Foundation, and
// may be copied, distributed, and modified under those terms.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
#include "android/emulation/qemud/android_qemud_client.h"
#include "android/emulation/android_pipe_host.h"
#include "android/emulation/qemud/android_qemud_common.h"
#include "android/emulation/qemud/android_qemud_multiplexer.h"
#include "android/utils/bufprint.h"
#include <assert.h>
#include <errno.h>
#include <stdlib.h>
QemudClient* qemud_client_new(QemudService* service,
int channelId,
const char* client_param,
void* clie_opaque,
QemudClientRecv clie_recv,
QemudClientClose clie_close,
QemudClientSave clie_save,
QemudClientLoad clie_load) {
QemudMultiplexer* m = qemud_multiplexer;
QemudClient* c = qemud_client_alloc(channelId,
client_param,
clie_opaque,
clie_recv,
clie_close,
clie_save,
clie_load,
m->serial,
&m->clients);
qemud_service_add_client(service, c);
return c;
}
static QemudPipeMessage* _qemud_pipe_alloc_msg(int msglen) {
auto buf = (QemudPipeMessage*) malloc(msglen + sizeof(QemudPipeMessage));
if (!buf) {
return nullptr;
}
/* Message starts right after the descriptor. */
buf->message = (uint8_t*) buf + sizeof(QemudPipeMessage);
buf->size = msglen;
buf->offset = 0;
buf->next = NULL;
return buf;
}
static void _qemud_pipe_append_msg(QemudClient* client, QemudPipeMessage* buf) {
if (client->ProtocolSelector.Pipe.last_msg) {
client->ProtocolSelector.Pipe.last_msg->next = buf;
client->ProtocolSelector.Pipe.last_msg = buf;
} else {
client->ProtocolSelector.Pipe.last_msg =
client->ProtocolSelector.Pipe.messages = buf;
}
}
void _qemud_pipe_send(QemudClient* client, const uint8_t* msg, int msglen) {
int avail, len = msglen;
int framing = client->framing;
if (msglen <= 0)
return;
D("%s: len=%3d '%s'",
__FUNCTION__, msglen, quote_bytes((const char*) msg, msglen));
if (framing) {
len += FRAME_HEADER_SIZE;
}
/* packetize the payload for the serial MTU */
while (len > 0) {
avail = len;
if (avail > MAX_SERIAL_PAYLOAD)
avail = MAX_SERIAL_PAYLOAD;
/* insert frame header when needed */
QemudPipeMessage* frame_msg;
QemudPipeMessage* pipe_msg;
if (framing) {
frame_msg = _qemud_pipe_alloc_msg(FRAME_HEADER_SIZE);
int2hex(frame_msg->message, FRAME_HEADER_SIZE, msglen);
_qemud_pipe_append_msg(client, frame_msg);
avail -= FRAME_HEADER_SIZE;
len -= FRAME_HEADER_SIZE;
framing = 0;
}
pipe_msg = _qemud_pipe_alloc_msg(avail);
assert(pipe_msg);
memcpy(pipe_msg->message, msg, avail);
T("%s: '%.*s'", __func__, avail, msg);
_qemud_pipe_append_msg(client, pipe_msg);
android_pipe_host_signal_wake(
client->ProtocolSelector.Pipe.qemud_pipe->hwpipe,
PIPE_WAKE_READ);
msg += avail;
len -= avail;
}
}
void qemud_client_send(QemudClient* client, const uint8_t* msg, int msglen) {
if (qemud_is_pipe_client(client)) {
_qemud_pipe_send(client, msg, msglen);
} else {
qemud_serial_send(client->ProtocolSelector.Serial.serial,
client->ProtocolSelector.Serial.channel,
client->framing != 0, msg, msglen);
}
}
void qemud_client_set_framing(QemudClient* client, int framing) {
/* release dynamic buffer if we're disabling framing */
if (client->framing) {
if (!client->need_header) {
AFREE(client->payload->buff);
client->need_header = 1;
}
}
client->framing = !!framing;
}
/* this can be used by a service implementation to close a
* specific client connection.
*/
void qemud_client_close(QemudClient* client) {
qemud_client_disconnect(client, 0);
}
bool qemud_is_pipe_client(QemudClient* client) {
return client->protocol == QEMUD_PROTOCOL_PIPE;
}
/* remove a QemudClient from global list */
void qemud_client_remove(QemudClient* c) {
c->pref[0] = c->next;
if (c->next)
c->next->pref = c->pref;
c->next = NULL;
c->pref = &c->next;
}
/* add a QemudClient to global list */
void qemud_client_prepend(QemudClient* c, QemudClient** plist) {
c->next = *plist;
c->pref = plist;
*plist = c;
if (c->next)
c->next->pref = &c->next;
}
/* receive a new message from a client, and dispatch it to
* the real service implementation.
*/
void qemud_client_recv(void* opaque, uint8_t* msg, int msglen) {
QemudClient* c = static_cast<QemudClient*>(opaque);
/* no framing, things are simple */
if (!c->framing) {
if (c->clie_recv)
c->clie_recv(c->clie_opaque, msg, msglen, c);
return;
}
/* framing */
#if 1
/* special case, in 99% of cases, everything is in
* the incoming message, and we can do all we need
* directly without dynamic allocation.
*/
if (msglen > FRAME_HEADER_SIZE &&
c->need_header == 1 &&
qemud_sink_needed(c->header) == 0) {
int len = hex2int(msg, FRAME_HEADER_SIZE);
if (len >= 0 && msglen == len + FRAME_HEADER_SIZE) {
if (c->clie_recv)
c->clie_recv(c->clie_opaque,
msg + FRAME_HEADER_SIZE,
msglen - FRAME_HEADER_SIZE, c);
return;
}
}
#endif
while (msglen > 0) {
uint8_t* data;
/* read the header */
if (c->need_header) {
int frame_size;
uint8_t* data;
if (!qemud_sink_fill(c->header, (const uint8_t**) &msg, &msglen))
break;
frame_size = hex2int(c->header0, 4);
if (frame_size == 0) {
D("%s: ignoring empty frame", __FUNCTION__);
continue;
}
if (frame_size < 0) {
D("%s: ignoring corrupted frame header '.*s'",
__FUNCTION__, FRAME_HEADER_SIZE, c->header0);
continue;
}
/* +1 for terminating zero */
data = static_cast<uint8_t*>(
_android_array_alloc(sizeof(*data), frame_size + 1));
qemud_sink_reset(c->payload, frame_size, data);
c->need_header = 0;
c->header->used = 0;
}
/* read the payload */
if (!qemud_sink_fill(c->payload, (const uint8_t**) &msg, &msglen))
break;
c->payload->buff[c->payload->size] = 0;
c->need_header = 1;
data = c->payload->buff;
/* Technically, calling 'clie_recv' can destroy client object 'c'
* if it decides to close the connection, so ensure we don't
* use/dereference it after the call. */
if (c->clie_recv)
c->clie_recv(c->clie_opaque, c->payload->buff, c->payload->size, c);
AFREE(data);
}
}
/* Frees memory allocated for the qemud client.
*/
void _qemud_client_free(QemudClient* c) {
if (c != NULL) {
if (qemud_is_pipe_client(c)) {
/* Free outstanding messages. */
QemudPipeMessage** msg_list = &c->ProtocolSelector.Pipe.messages;
while (*msg_list != NULL) {
QemudPipeMessage* to_free = *msg_list;
*msg_list = to_free->next;
free(to_free);
}
c->ProtocolSelector.Pipe.last_msg = NULL;
}
if (c->param != NULL) {
free(c->param);
}
AFREE(c);
}
}
/* disconnect a client. this automatically frees the QemudClient.
* note that this also removes the client from the global list
* and from its service's list, if any.
* Param:
* opaque - QemuClient instance
* guest_close - For pipe clients control whether or not the disconnect is
* caused by guest closing the pipe handle (in which case 1 is passed in
* this parameter). For serial clients this parameter is ignored.
*/
void qemud_client_disconnect(void* opaque, int guest_close) {
QemudClient* c = static_cast<QemudClient*>(opaque);
if (c->closing) { /* recursive call, exit immediately */
return;
}
if (qemud_is_pipe_client(c) && !guest_close) {
android_pipe_host_close(c->ProtocolSelector.Pipe.qemud_pipe->hwpipe);
return;
}
c->closing = 1;
/* remove from current list */
qemud_client_remove(c);
if (qemud_is_pipe_client(c)) {
/* We must NULL the client reference in the QemuPipe for this connection,
* so if a sudden receive request comes after client has been closed, we
* don't blow up. */
c->ProtocolSelector.Pipe.qemud_pipe->client = NULL;
} else if (c->ProtocolSelector.Serial.channel > 0) {
/* send a disconnect command to the daemon */
char tmp[128], * p = tmp, * end = p + sizeof(tmp);
p = bufprint(tmp, end, "disconnect:%02x",
c->ProtocolSelector.Serial.channel);
qemud_serial_send(c->ProtocolSelector.Serial.serial, 0, 0, (uint8_t*) tmp, p - tmp);
}
/* call the client close callback */
if (c->clie_close) {
c->clie_close(c->clie_opaque);
c->clie_close = NULL;
}
c->clie_recv = NULL;
/* remove from service list, if any */
if (c->service) {
qemud_service_remove_client(c->service, c);
c->service = NULL;
}
_qemud_client_free(c);
}
/* allocate a new QemudClient object
* NOTE: channel_id valie is used as a selector between serial and pipe clients.
* Since channel_id < 0 is an invalid value for a serial client, it would
* indicate that creating client is a pipe client. */
QemudClient* qemud_client_alloc(int channel_id,
const char* client_param,
void* clie_opaque,
QemudClientRecv clie_recv,
QemudClientClose clie_close,
QemudClientSave clie_save,
QemudClientLoad clie_load,
QemudSerial* serial,
QemudClient** pclients) {
QemudClient* c = static_cast<QemudClient*>(android_alloc0(sizeof(*c)));
if (channel_id < 0) {
/* Allocating a pipe client. */
c->protocol = QEMUD_PROTOCOL_PIPE;
c->ProtocolSelector.Pipe.messages = NULL;
c->ProtocolSelector.Pipe.last_msg = NULL;
c->ProtocolSelector.Pipe.qemud_pipe = NULL;
} else {
/* Allocating a serial client. */
c->protocol = QEMUD_PROTOCOL_SERIAL;
c->ProtocolSelector.Serial.serial = serial;
c->ProtocolSelector.Serial.channel = channel_id;
}
c->param = client_param ? ASTRDUP(client_param) : NULL;
c->clie_opaque = clie_opaque;
c->clie_recv = clie_recv;
c->clie_close = clie_close;
c->clie_save = clie_save;
c->clie_load = clie_load;
c->service = NULL;
c->next_serv = NULL;
c->next = NULL;
c->framing = 0;
c->need_header = 1;
qemud_sink_reset(c->header, FRAME_HEADER_SIZE, c->header0);
qemud_client_prepend(c, pclients);
return c;
}
/* Saves the client state needed to re-establish connections on load.
* Note that we save only serial clients here. The pipe clients will be
* saved along with the pipe to which they are attached.
*/
void qemud_serial_client_save(Stream* f, QemudClient* c) {
/* save generic information */
qemud_service_save_name(f, c->service);
stream_put_string(f, c->param);
stream_put_be32(f, c->ProtocolSelector.Serial.channel);
/* save client-specific state */
if (c->clie_save)
c->clie_save(f, c, c->clie_opaque);
/* save framing configuration */
stream_put_be32(f, c->framing);
if (c->framing) {
stream_put_be32(f, c->need_header);
/* header sink always connected to c->header0, no need to save */
stream_put_be32(f, FRAME_HEADER_SIZE);
stream_write(f, c->header0, FRAME_HEADER_SIZE);
/* payload sink */
qemud_sink_save(f, c->payload);
stream_write(f, c->payload->buff, c->payload->size);
}
}
/* Loads client state from file, then starts a new client connected to the
* corresponding service.
* Note that we load only serial clients here. The pipe clients will be
* loaded along with the pipe to which they were attached.
*/
int qemud_serial_client_load(Stream* f,
QemudService* current_services,
int version) {
char* service_name = qemud_service_load_name(f);
if (service_name == NULL)
return -EIO;
char* param = stream_get_string(f);
/* get current service instance */
QemudService* sv = qemud_service_find(current_services, service_name);
if (sv == NULL) {
D("%s: load failed: unknown service \"%s\"\n",
__FUNCTION__, service_name);
return -EIO;
}
int channel = stream_get_be32(f);
if (channel == 0) {
D("%s: illegal snapshot: client for control channel must no be saved\n",
__FUNCTION__);
return -EIO;
}
/* re-connect client */
QemudClient* c = qemud_service_connect_client(sv, channel, param);
if (c == NULL)
return -EIO;
/* load client-specific state */
int ret;
if (c->clie_load) if ((ret = c->clie_load(f, c, c->clie_opaque)))
return ret; /* load failure */
/* load framing configuration */
c->framing = stream_get_be32(f);
if (c->framing) {
/* header buffer */
c->need_header = stream_get_be32(f);
int header_size = stream_get_be32(f);
if (header_size > FRAME_HEADER_SIZE) {
D("%s: load failed: payload buffer requires %d bytes, %d available\n",
__FUNCTION__, header_size, FRAME_HEADER_SIZE);
return -EIO;
}
int ret;
if ((ret = stream_read(f, c->header0, header_size)) != header_size) {
D("%s: frame header buffer load failed: expected %d bytes, got %d\n",
__FUNCTION__, header_size, ret);
return -EIO;
}
/* payload sink */
if ((ret = qemud_sink_load(f, c->payload)))
return ret;
/* replace payload buffer by saved data */
if (c->payload->buff) {
AFREE(c->payload->buff);
}
/* +1 for terminating zero */
c->payload->buff = static_cast<uint8_t*>(
_android_array_alloc(sizeof(*c->payload->buff), c->payload->size + 1));
if ((ret = stream_read(f, c->payload->buff, c->payload->size)) !=
c->payload->size) {
D("%s: frame payload buffer load failed: expected %d bytes, got %d\n",
__FUNCTION__, c->payload->size, ret);
AFREE(c->payload->buff);
return -EIO;
}
}
return 0;
}