// Copyright 2005-2016 The Mumble Developers. All rights reserved.
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file at the root of the
// Mumble source tree or at <https://www.mumble.info/LICENSE>.

#ifndef Q_MOC_RUN
# include <boost/function.hpp>
#endif

#include "murmur_pch.h"

#include "Mumble.pb.h"

#include "../Message.h"
#include "../Group.h"
#include "MurmurGRPCImpl.h"
#include "ServerDB.h"
#include "ServerUser.h"
#include "Server.h"
#include "Channel.h"

#include "MurmurRPC.proto.Wrapper.cpp"

// GRPC system overview
// ====================
//
// Definitions:
//
//  - Completion queue: a mechanism used for handling asynchronous grpc
//    requests. Whenever you want to do something with grpc (e.g. accept a
//    remote call, read/write a message), the completion queue manages that
//    request for you.
//
//    When you wish to perform an asynchronous action, you provide the
//    completion queue with a unique "tag". This tag used to identify the
//    request after it has finished.
//
//    To check the status of asynchronous actions that have been added to the
//    completion queue, you call its "Next" method. Next returns when there is
//    a finished action in the completion queue. Next provides two output
//    variables: the completed action's tag, and a boolean, which indicates if
//    the action was successful. All tags in this project are are
//    heap-allocated pointers to "boost::function"s that accept a boolean.
//    After Next returns, the tag pointer is dereferenced, executed with the
//    success variable, and then freed.
//
//  - Wrapper class: each RPC method that is defined in the .proto has had a
//    class generated for it (the generator program is located in scripts/).
//    Each class provides storage for the incoming and outgoing protocol buffer
//    message, as well as helper methods for registering events with the
//    completion queue. Each wrapper class also has an "impl(bool)" method;
//    each one is implemented in this file. This impl method gets executed when
//    the particular RPC method gets invoked by an RPC client.
//
//    Each wrapper class extends RPCCall. Among other things, RPCCall provides
//    reference counting for the object. This is needed for memory management,
//    as the wrapper objects can be used and stored in several locations (this
//    is only really true for streaming grpc calls).
//
// The flow of the grpc system is as follows:
//
//  - GRPCStart() is called from murmur's main().
//  - If an address for the grpc has been set in murmur.ini, the grpc service
//    begins listening on that address for grpc client connections.
//  - A new thread is created which handles the grpc completion queue (defined
//    above). This thread continuously calls the completion queue's Next method
//    to process the next completed event.
//  - The wrapper classes' "create" methods are executed, which makes them
//    invokable by grpc clients.
//  - With the completion queue now running in the background, murmur
//    continues starting up.
//
//  - When a grpc client invokes an RPC method, the previously mentioned thread
//    is notified of this and executes the "tag" (recall, tags are pointers to
//    functions). The wrapper method "impl(bool)" for the corresponding RPC
//    method ends up being called. It is important to note that this impl
//    method gets executed in the main thread. This prevents data corruption
//    of murmur's data structures without the need of locks.
//
//    Additionally, the execution of tags are wrapped with a try-catch. This
//    try-catch catches any grpc::Status that is thrown. If one is caught, the
//    status is automatically sent to the grpc client and the invocation of the
//    current method is stopped.

static MurmurRPCImpl *service;

void GRPCStart() {
	const auto &address = meta->mp.qsGRPCAddress;
	if (address.isEmpty()) {
		return;
	}
	const auto &cert = meta->mp.qsGRPCCert;
	const auto &key = meta->mp.qsGRPCKey;
	std::shared_ptr<::grpc::ServerCredentials> credentials;
	if (cert.isEmpty() || key.isEmpty()) {
		credentials = ::grpc::InsecureServerCredentials();
	} else {
		::grpc::SslServerCredentialsOptions options;
		::grpc::SslServerCredentialsOptions::PemKeyCertPair pair;
		{
			QFile file(cert);
			if (!file.open(QIODevice::ReadOnly)) {
				qFatal("could not open gRPC certificate file: %s", cert.toStdString().c_str());
				return;
			}
			QTextStream stream(&file);
			auto contents = stream.readAll();
			pair.cert_chain = contents.toStdString();
		}
		{
			QFile file(key);
			if (!file.open(QIODevice::ReadOnly)) {
				qFatal("could not open gRPC key file: %s", key.toStdString().c_str());
				return;
			}
			QTextStream stream(&file);
			auto contents = stream.readAll();
			pair.private_key = contents.toStdString();
		}
		options.pem_key_cert_pairs.push_back(pair);
		credentials = ::grpc::SslServerCredentials(options);
	}

	service = new MurmurRPCImpl(address, credentials);

	qWarning("GRPC: lisetning on '%s'", qPrintable(address));
}

void GRPCStop() {
	if (service) {
		delete service;
	}
}

MurmurRPCImpl::MurmurRPCImpl(const QString &address, std::shared_ptr<::grpc::ServerCredentials> credentials) : m_cleanupTimer(this) {
	::grpc::ServerBuilder builder;
	builder.AddListeningPort(u8(address), credentials);
	builder.RegisterService(&m_V1Service);
	m_completionQueue = builder.AddCompletionQueue();
	m_server = builder.BuildAndStart();
	meta->connectListener(this);
	connect(&m_cleanupTimer, SIGNAL(timeout()), this, SLOT(cleanup()));
	m_cleanupTimer.setSingleShot(false);
	m_cleanupTimer.start(1000 * 60);
	start();
}

MurmurRPCImpl::~MurmurRPCImpl() {
}

// This function periodically runs to clean up disconnected listeners. We need
// this because (as of 2015-07-21) the grpc library does not tell us when a
// client disconnects.
void MurmurRPCImpl::cleanup() {
	for (auto i = m_metaServiceListeners.begin(); i != m_metaServiceListeners.end(); ) {
		auto listener = *i;
		if (listener->context.IsCancelled()) {
			i = m_metaServiceListeners.erase(i);
			listener->deref();
		} else {
			++i;
		}
	}

	for (auto i = m_contextActionListeners.begin(); i != m_contextActionListeners.end(); ) {
		auto &ref = i.value();
		for (auto j = ref.begin(); j != ref.end(); ) {
			auto listener = j.value();
			if (listener->context.IsCancelled()) {
				j = ref.erase(j);
				listener->deref();
			} else {
				++j;
			}
		}

		if (ref.isEmpty()) {
			i = m_contextActionListeners.erase(i);
		} else {
			++i;
		}
	}

	for (auto i = m_serverServiceListeners.begin(); i != m_serverServiceListeners.end(); ) {
		auto listener = i.value();
		if (listener->context.IsCancelled()) {
			i = m_serverServiceListeners.erase(i);
			listener->deref();
		} else {
			++i;
		}
	}

	for (auto i = m_authenticators.begin(); i != m_authenticators.end(); ) {
		auto listener = i.value();
		if (listener->context.IsCancelled()) {
			i = m_authenticators.erase(i);
			listener->deref();
		} else {
			++i;
		}
	}
}

// ToRPC/FromRPC methods convert data to/from grpc protocol buffer messages.
void ToRPC(const ::Server *srv, const ::Channel *c, ::MurmurRPC::Channel *rc) {
	rc->mutable_server()->set_id(srv->iServerNum);

	rc->set_id(c->iId);
	rc->set_name(u8(c->qsName));
	if (c->cParent) {
		rc->mutable_parent()->mutable_server()->set_id(srv->iServerNum);
		rc->mutable_parent()->set_id(c->cParent->iId);
	}
	rc->set_description(u8(c->qsDesc));
	rc->set_position(c->iPosition);
	foreach(::Channel *chn, c->qsPermLinks) {
		::MurmurRPC::Channel *linked = rc->add_links();
		linked->mutable_server()->set_id(srv->iServerNum);
		linked->set_id(chn->iId);
	}
	rc->set_temporary(c->bTemporary);
}

void ToRPC(const ::Server *srv, const ::User *u, ::MurmurRPC::User *ru) {
	ru->mutable_server()->set_id(srv->iServerNum);

	ru->set_session(u->uiSession);
	if (u->iId >= 0) {
		ru->set_id(u->iId);
	}
	ru->set_name(u8(u->qsName));
	ru->set_mute(u->bMute);
	ru->set_deaf(u->bDeaf);
	ru->set_suppress(u->bSuppress);
	ru->set_recording(u->bRecording);
	ru->set_priority_speaker(u->bPrioritySpeaker);
	ru->set_self_mute(u->bSelfMute);
	ru->set_self_deaf(u->bSelfDeaf);
	ru->mutable_channel()->mutable_server()->set_id(srv->iServerNum);
	ru->mutable_channel()->set_id(u->cChannel->iId);
	ru->set_comment(u8(u->qsComment));

	const auto su = static_cast<const ServerUser *>(u);
	ru->set_online_secs(su->bwr.onlineSeconds());
	ru->set_bytes_per_sec(su->bwr.bandwidth());
	ru->mutable_version()->set_version(su->uiVersion);
	ru->mutable_version()->set_release(u8(su->qsRelease));
	ru->mutable_version()->set_os(u8(su->qsOS));
	ru->mutable_version()->set_os_version(u8(su->qsOSVersion));
	ru->set_plugin_identity(u8(su->qsIdentity));
	ru->set_plugin_context(su->ssContext);
	ru->set_idle_secs(su->bwr.idleSeconds());
	ru->set_udp_ping_msecs(su->dUDPPingAvg);
	ru->set_tcp_ping_msecs(su->dTCPPingAvg);

	ru->set_tcp_only(QAtomicIntLoad(su->aiUdpFlag) == 0);

	ru->set_address(su->haAddress.toStdString());
}

void ToRPC(const ::Server *srv, const QMap<int, QString> &info, const QByteArray &texture, ::MurmurRPC::DatabaseUser *du) {
	du->mutable_server()->set_id(srv->iServerNum);

	if (info.contains(::ServerDB::User_Name)) {
		du->set_name(u8(info[::ServerDB::User_Name]));
	}
	if (info.contains(::ServerDB::User_Email)) {
		du->set_email(u8(info[::ServerDB::User_Email]));
	}
	if (info.contains(::ServerDB::User_Comment)) {
		du->set_comment(u8(info[::ServerDB::User_Comment]));
	}
	if (info.contains(::ServerDB::User_Hash)) {
		du->set_hash(u8(info[::ServerDB::User_Hash]));
	}
	if (info.contains(::ServerDB::User_Password)) {
		du->set_password(u8(info[::ServerDB::User_Password]));
	}
	if (info.contains(::ServerDB::User_LastActive)) {
		du->set_last_active(u8(info[::ServerDB::User_LastActive]));
	}
	if (!texture.isNull()) {
		du->set_texture(texture.constData(), texture.size());
	}
}

void FromRPC(const ::MurmurRPC::DatabaseUser &du, QMap<int, QString> &info) {
	if (du.has_name()) {
		info.insert(::ServerDB::User_Name, u8(du.name()));
	}
	if (du.has_email()) {
		info.insert(::ServerDB::User_Email, u8(du.email()));
	}
	if (du.has_comment()) {
		info.insert(::ServerDB::User_Comment, u8(du.comment()));
	}
	if (du.has_hash()) {
		info.insert(::ServerDB::User_Hash, u8(du.hash()));
	}
	if (du.has_password()) {
		info.insert(::ServerDB::User_Password, u8(du.password()));
	}
	if (du.has_last_active()) {
		info.insert(::ServerDB::User_LastActive, u8(du.last_active()));
	}
}

void ToRPC(const ::Server *srv, const ::ChanACL *acl, ::MurmurRPC::ACL *ra) {
	ra->set_apply_here(acl->bApplyHere);
	ra->set_apply_subs(acl->bApplySubs);
	ra->set_inherited(false);

	if (acl->iUserId >= 0) {
		ra->mutable_user()->mutable_server()->set_id(srv->iServerNum);
		ra->mutable_user()->set_id(acl->iUserId);
	}
	if (!acl->qsGroup.isEmpty()) {
		ra->mutable_group()->set_name(u8(acl->qsGroup));
	}

	ra->set_allow(MurmurRPC::ACL_Permission(int(acl->pAllow)));
	ra->set_deny(MurmurRPC::ACL_Permission(int(acl->pDeny)));
}

void ToRPC(const ::Server *, const ::Group *g, ::MurmurRPC::ACL_Group *rg) {
	rg->set_name(u8(g->qsName));
	rg->set_inherit(g->bInherit);
	rg->set_inheritable(g->bInheritable);
}

void ToRPC(const ::Server *srv, const ::Ban &ban, ::MurmurRPC::Ban *rb) {
	rb->mutable_server()->set_id(srv->iServerNum);

	rb->set_address(ban.haAddress.toStdString());
	rb->set_bits(ban.iMask);
	rb->set_name(u8(ban.qsUsername));
	rb->set_hash(u8(ban.qsHash));
	rb->set_reason(u8(ban.qsReason));
	rb->set_start(ban.qdtStart.toLocalTime().toTime_t());
	rb->set_duration_secs(ban.iDuration);
}

void FromRPC(const ::Server *, const ::MurmurRPC::Ban &rb, ::Ban &ban) {
	ban.haAddress = HostAddress(rb.address());
	ban.iMask = rb.bits();
	ban.qsUsername = u8(rb.name());
	ban.qsHash = u8(rb.hash());
	ban.qsReason = u8(rb.reason());
	ban.qdtStart = QDateTime::fromTime_t(static_cast<quint32>(rb.start())).toUTC();
	ban.iDuration = rb.duration_secs();
}

void ToRPC(int serverID, const ::ServerDB::LogRecord &log, ::MurmurRPC::Log *rl) {
	rl->mutable_server()->set_id(serverID);

	rl->set_timestamp(log.first);
	rl->set_text(u8(log.second));
}

void ToRPC(const ::Server *srv, const ::User *user, const ::TextMessage &message, ::MurmurRPC::TextMessage *rtm) {
	rtm->mutable_server()->set_id(srv->iServerNum);

	rtm->mutable_actor()->mutable_server()->set_id(srv->iServerNum);
	rtm->mutable_actor()->set_session(user->uiSession);

	foreach(auto session, message.qlSessions) {
		auto target = rtm->add_users();
		target->mutable_server()->set_id(srv->iServerNum);
		target->set_session(session);
	}

	foreach(auto id, message.qlChannels) {
		auto target = rtm->add_channels();
		target->mutable_server()->set_id(srv->iServerNum);
		target->set_id(id);
	}

	foreach(auto id, message.qlTrees) {
		auto target = rtm->add_trees();
		target->mutable_server()->set_id(srv->iServerNum);
		target->set_id(id);
	}

	rtm->set_text(u8(message.qsText));
}

// Sends a meta event to any subscribed listeners.
void MurmurRPCImpl::sendMetaEvent(const ::MurmurRPC::Event &e) {
	auto listeners = m_metaServiceListeners;

	for (auto i = listeners.constBegin(); i != listeners.constEnd(); ++i) {
		auto listener = *i;
		listener->ref();
		auto cb = [this, listener] (::MurmurRPC::Wrapper::V1_Events *, bool ok) {
			if (!ok && m_metaServiceListeners.remove(listener)) {
				listener->deref();
			}
			listener->deref();
		};
		listener->write(e, listener->callback(cb));
	}
}

// Called when a server starts.
void MurmurRPCImpl::started(::Server *server) {
	server->connectListener(this);
	server->connectAuthenticator(this);
	connect(server, SIGNAL(contextAction(const User *, const QString &, unsigned int, int)), this, SLOT(contextAction(const User *, const QString &, unsigned int, int)));
	connect(server, SIGNAL(textMessageFilterSig(int &, const User *, MumbleProto::TextMessage &)), this, SLOT(textMessageFilter(int &, const User *, MumbleProto::TextMessage &)));

	::MurmurRPC::Event rpcEvent;
	rpcEvent.set_type(::MurmurRPC::Event_Type_ServerStarted);
	rpcEvent.mutable_server()->set_id(server->iServerNum);
	sendMetaEvent(rpcEvent);
}

// Called when a server stops.
void MurmurRPCImpl::stopped(::Server *server) {
	removeActiveContextActions(server);

	::MurmurRPC::Event rpcEvent;
	rpcEvent.set_type(::MurmurRPC::Event_Type_ServerStopped);
	rpcEvent.mutable_server()->set_id(server->iServerNum);
	sendMetaEvent(rpcEvent);
}

// Removes a connected text message filter.
void MurmurRPCImpl::removeTextMessageFilter(const ::Server *s) {
	auto filter = m_textMessageFilters.value(s->iServerNum);
	if (!filter) {
		return;
	}
	if (filter->context.IsCancelled()) {
		filter->ref();
		filter->error(::grpc::Status(::grpc::CANCELLED, "filter detached"));
	}
	m_textMessageFilters.remove(s->iServerNum);
	filter->deref();
}

// Removes a connected authenticator.
void MurmurRPCImpl::removeAuthenticator(const ::Server *s) {
	auto authenticator = m_authenticators.value(s->iServerNum);
	if (!authenticator) {
		return;
	}
	if (!authenticator->context.IsCancelled()) {
		authenticator->ref();
		authenticator->error(::grpc::Status(::grpc::CANCELLED, "authenticator detached"));
	}
	m_authenticators.remove(s->iServerNum);
	authenticator->deref();
}

// Called when a connecting user needs to be authenticated.
void MurmurRPCImpl::authenticateSlot(int &res, QString &uname, int sessionId, const QList<QSslCertificate> &certlist, const QString &certhash, bool certstrong, const QString &pw) {
	::Server *s = qobject_cast< ::Server *> (sender());
	auto authenticator = RPCCall::Ref<::MurmurRPC::Wrapper::V1_AuthenticatorStream>(m_authenticators.value(s->iServerNum));
	if (!authenticator) {
		return;
	}

	auto &request = authenticator->response;
	request.Clear();
	request.mutable_authenticate()->set_name(u8(uname));
	if (!pw.isEmpty()) {
		request.mutable_authenticate()->set_password(u8(pw));
	}
	foreach(const auto &cert, certlist) {
		auto data = cert.toDer();
		request.mutable_authenticate()->add_certificates(data.constData(), data.size());
	}
	if (!certhash.isEmpty()) {
		request.mutable_authenticate()->set_certificate_hash(u8(certhash));
		request.mutable_authenticate()->set_strong_certificate(certstrong);
	}

	{
		QMutexLocker l(&qmAuthenticatorsLock);
		if (!authenticator->writeRead()) {
			removeAuthenticator(s);
			res = -1;
			return;
		}
	}

	auto &response = authenticator->request;
	switch (response.authenticate().status()) {
	case ::MurmurRPC::Authenticator_Response_Status_Success:
		if (!response.authenticate().has_id()) {
			res = -3;
			break;
		}
		res = response.authenticate().id();
		if (response.authenticate().has_name()) {
			uname = u8(response.authenticate().name());
		}
		{
			QStringList qsl;
			for (int i = 0; i < response.authenticate().groups_size(); i++) {
				auto &group = response.authenticate().groups(i);
				if (group.has_name()) {
					qsl << u8(group.name());
				}
			}
			if (!qsl.isEmpty()) {
				s->setTempGroups(res, sessionId, NULL, qsl);
			}
		}
		break;
	case ::MurmurRPC::Authenticator_Response_Status_TemporaryFailure:
		res = -3;
		break;
	case ::MurmurRPC::Authenticator_Response_Status_Failure:
		res = -1;
		break;
	default:
		break;
	}
}

// Called when a user is being registered on the server.
void MurmurRPCImpl::registerUserSlot(int &res, const QMap<int, QString> &info) {
	::Server *s = qobject_cast< ::Server *> (sender());
	auto authenticator = RPCCall::Ref<::MurmurRPC::Wrapper::V1_AuthenticatorStream>(m_authenticators.value(s->iServerNum));
	if (!authenticator) {
		return;
	}

	auto &request = authenticator->response;
	request.Clear();
	ToRPC(s, info, QByteArray(), request.mutable_register_()->mutable_user());

	{
		QMutexLocker l(&qmAuthenticatorsLock);
		if (!authenticator->writeRead()) {
			removeAuthenticator(s);
			return;
		}
	}

	auto &response = authenticator->request;
	switch (response.register_().status()) {
	case ::MurmurRPC::Authenticator_Response_Status_Success:
		if (!response.register_().has_user() || !response.register_().user().has_id()) {
			res = -1;
			break;
		}
		res = response.register_().user().id();
		break;
	case ::MurmurRPC::Authenticator_Response_Status_Fallthrough:
		break;
	default:
		res = -1;
		break;
	}
}

// Called when a user is being deregistered on the server.
void MurmurRPCImpl::unregisterUserSlot(int &res, int id) {
	::Server *s = qobject_cast< ::Server *> (sender());
	auto authenticator = RPCCall::Ref<::MurmurRPC::Wrapper::V1_AuthenticatorStream>(m_authenticators.value(s->iServerNum));
	if (!authenticator) {
		return;
	}

	auto &request = authenticator->response;
	request.Clear();
	request.mutable_deregister()->mutable_user()->mutable_server()->set_id(s->iServerNum);
	request.mutable_deregister()->mutable_user()->set_id(id);

	{
		QMutexLocker l(&qmAuthenticatorsLock);
		if (!authenticator->writeRead()) {
			removeAuthenticator(s);
			return;
		}
	}

	auto &response = authenticator->request;

	if (response.deregister().status() != ::MurmurRPC::Authenticator_Response_Status_Fallthrough) {
		res = 0;
	}
}

// Called when a list of registered users is requested.
void MurmurRPCImpl::getRegisteredUsersSlot(const QString &filter, QMap<int, QString> &res) {
	::Server *s = qobject_cast< ::Server *> (sender());
	auto authenticator = RPCCall::Ref<::MurmurRPC::Wrapper::V1_AuthenticatorStream>(m_authenticators.value(s->iServerNum));
	if (!authenticator) {
		return;
	}

	auto &request = authenticator->response;
	request.Clear();
	if (!filter.isEmpty()) {
		request.mutable_query()->set_filter(u8(filter));
	}

	{
		QMutexLocker l(&qmAuthenticatorsLock);
		if (!authenticator->writeRead()) {
			removeAuthenticator(s);
			return;
		}
	}

	auto &response = authenticator->request;

	for (int i = 0; i < response.query().users_size(); i++) {
		const auto &user = response.query().users(i);
		if (!user.has_id() || !user.has_name()) {
			continue;
		}
		res.insert(user.id(), u8(user.name()));
	}
}

// Called when information about a registered user is requested.
void MurmurRPCImpl::getRegistrationSlot(int &res, int id, QMap<int, QString> &info) {
	::Server *s = qobject_cast< ::Server *> (sender());
	auto authenticator = RPCCall::Ref<::MurmurRPC::Wrapper::V1_AuthenticatorStream>(m_authenticators.value(s->iServerNum));
	if (!authenticator) {
		return;
	}

	auto &request = authenticator->response;
	request.Clear();
	request.mutable_find()->set_id(id);

	res = -1;

	{
		QMutexLocker l(&qmAuthenticatorsLock);
		if (!authenticator->writeRead()) {
			removeAuthenticator(s);
			return;
		}
	}

	auto &response = authenticator->request;
	if (response.find().has_user()) {
		FromRPC(response.find().user(), info);
		res = 1;
	}
}

// Called when information about a registered user is being updated.
void MurmurRPCImpl::setInfoSlot(int &res, int id, const QMap<int, QString> &info) {
	::Server *s = qobject_cast< ::Server *> (sender());
	auto authenticator = RPCCall::Ref<::MurmurRPC::Wrapper::V1_AuthenticatorStream>(m_authenticators.value(s->iServerNum));
	if (!authenticator) {
		return;
	}

	auto &request = authenticator->response;
	request.Clear();
	request.mutable_update()->mutable_user()->set_id(id);
	ToRPC(s, info, QByteArray(), request.mutable_update()->mutable_user());

	res = 0;

	{
		QMutexLocker l(&qmAuthenticatorsLock);
		if (!authenticator->writeRead()) {
			removeAuthenticator(s);
			return;
		}
	}

	auto &response = authenticator->request;
	switch (response.update().status()) {
	case ::MurmurRPC::Authenticator_Response_Status_Success:
		res = 1;
		break;
	case ::MurmurRPC::Authenticator_Response_Status_Fallthrough:
		res = -1;
		break;
	default:
		break;
	}
}

// Called when a texture for a registered user is being updated.
void MurmurRPCImpl::setTextureSlot(int &res, int id, const QByteArray &texture) {
	::Server *s = qobject_cast< ::Server *> (sender());
	auto authenticator = RPCCall::Ref<::MurmurRPC::Wrapper::V1_AuthenticatorStream>(m_authenticators.value(s->iServerNum));
	if (!authenticator) {
		return;
	}

	auto &request = authenticator->response;
	request.Clear();
	request.mutable_update()->mutable_user()->set_id(id);
	request.mutable_update()->mutable_user()->set_texture(texture.constData(), texture.size());

	{
		QMutexLocker l(&qmAuthenticatorsLock);
		if (!authenticator->writeRead()) {
			removeAuthenticator(s);
			return;
		}
	}

	auto &response = authenticator->request;
	if (response.update().status() == ::MurmurRPC::Authenticator_Response_Status_Success) {
		res = 1;
	}
}

// Called when a user name needs to be converted to a user ID.
void MurmurRPCImpl::nameToIdSlot(int &res, const QString &name) {
	::Server *s = qobject_cast< ::Server *> (sender());
	auto authenticator = RPCCall::Ref<::MurmurRPC::Wrapper::V1_AuthenticatorStream>(m_authenticators.value(s->iServerNum));
	if (!authenticator) {
		return;
	}

	auto &request = authenticator->response;
	request.Clear();
	request.mutable_find()->set_name(u8(name));

	{
		QMutexLocker l(&qmAuthenticatorsLock);
		if (!authenticator->writeRead()) {
			removeAuthenticator(s);
			return;
		}
	}

	auto &response = authenticator->request;
	if (response.find().has_user() && response.find().user().has_id()) {
		res = response.find().user().id();
	}
}

// Called when a user ID needs to be converted to a user name.
void MurmurRPCImpl::idToNameSlot(QString &res, int id) {
	::Server *s = qobject_cast< ::Server *> (sender());
	auto authenticator = RPCCall::Ref<::MurmurRPC::Wrapper::V1_AuthenticatorStream>(m_authenticators.value(s->iServerNum));
	if (!authenticator) {
		return;
	}

	auto &request = authenticator->response;
	request.Clear();
	request.mutable_find()->set_id(id);

	{
		QMutexLocker l(&qmAuthenticatorsLock);
		if (!authenticator->writeRead()) {
			removeAuthenticator(s);
			return;
		}
	}

	auto &response = authenticator->request;
	if (response.find().has_user() && response.find().user().has_name()) {
		res = u8(response.find().user().name());
	}
}

// Called when a texture for a given registered user is requested.
void MurmurRPCImpl::idToTextureSlot(QByteArray &res, int id) {
	::Server *s = qobject_cast< ::Server *> (sender());
	auto authenticator = RPCCall::Ref<::MurmurRPC::Wrapper::V1_AuthenticatorStream>(m_authenticators.value(s->iServerNum));
	if (!authenticator) {
		return;
	}

	auto &request = authenticator->response;
	request.Clear();
	request.mutable_find()->set_id(id);

	{
		QMutexLocker l(&qmAuthenticatorsLock);
		if (!authenticator->writeRead()) {
			removeAuthenticator(s);
			return;
		}
	}

	auto &response = authenticator->request;
	if (response.find().has_user() && response.find().user().has_texture()) {
		const auto &texture = response.find().user().texture();
		res = QByteArray(texture.data(), texture.size());
	}
}

// Sends a server event to subscribed listeners.
void MurmurRPCImpl::sendServerEvent(const ::Server *s, const ::MurmurRPC::Server_Event &e) {
	auto listeners = m_serverServiceListeners;
	auto serverID = s->iServerNum;
	auto i = listeners.find(serverID);

	for ( ; i != listeners.end() && i.key() == serverID; ++i) {
		auto listener = i.value();
		listener->ref();
		auto cb = [this, listener, serverID] (::MurmurRPC::Wrapper::V1_ServerEvents *, bool ok) {
			if (!ok && m_serverServiceListeners.remove(serverID, listener) > 0) {
				listener->deref();
			}
			listener->deref();
		};
		listener->write(e, listener->callback(cb));
	}
}

// Called when a user's state changes.
void MurmurRPCImpl::userStateChanged(const ::User *user) {
	::Server *s = qobject_cast< ::Server *> (sender());

	::MurmurRPC::Server_Event event;
	event.mutable_server()->set_id(s->iServerNum);
	event.set_type(::MurmurRPC::Server_Event_Type_UserStateChanged);
	ToRPC(s, user, event.mutable_user());
	sendServerEvent(s, event);
}

// Called when a user sends a text message.
void MurmurRPCImpl::userTextMessage(const ::User *user, const ::TextMessage &message) {
	::Server *s = qobject_cast< ::Server *> (sender());

	::MurmurRPC::Server_Event event;
	event.mutable_server()->set_id(s->iServerNum);
	event.set_type(::MurmurRPC::Server_Event_Type_UserTextMessage);
	ToRPC(s, user, event.mutable_user());
	ToRPC(s, user, message, event.mutable_message());
	sendServerEvent(s, event);
}

// Called when a user successfully connects to a server.
void MurmurRPCImpl::userConnected(const ::User *user) {
	::Server *s = qobject_cast< ::Server *> (sender());

	::MurmurRPC::Server_Event event;
	event.mutable_server()->set_id(s->iServerNum);
	event.set_type(::MurmurRPC::Server_Event_Type_UserConnected);
	ToRPC(s, user, event.mutable_user());
	sendServerEvent(s, event);
}

// Called when a user disconnects from a server.
void MurmurRPCImpl::userDisconnected(const ::User *user) {
	::Server *s = qobject_cast< ::Server *> (sender());

	removeUserActiveContextActions(s, user);

	::MurmurRPC::Server_Event event;
	event.mutable_server()->set_id(s->iServerNum);
	event.set_type(::MurmurRPC::Server_Event_Type_UserDisconnected);
	ToRPC(s, user, event.mutable_user());
	sendServerEvent(s, event);
}

// Called when a channel's state changes.
void MurmurRPCImpl::channelStateChanged(const ::Channel *channel) {
	::Server *s = qobject_cast< ::Server *> (sender());

	::MurmurRPC::Server_Event event;
	event.mutable_server()->set_id(s->iServerNum);
	event.set_type(::MurmurRPC::Server_Event_Type_ChannelStateChanged);
	ToRPC(s, channel, event.mutable_channel());
	sendServerEvent(s, event);
}

// Called when a channel is created.
void MurmurRPCImpl::channelCreated(const ::Channel *channel) {
	::Server *s = qobject_cast< ::Server *> (sender());

	::MurmurRPC::Server_Event event;
	event.mutable_server()->set_id(s->iServerNum);
	event.set_type(::MurmurRPC::Server_Event_Type_ChannelCreated);
	ToRPC(s, channel, event.mutable_channel());
	sendServerEvent(s, event);
}

// Called when a channel is removed.
void MurmurRPCImpl::channelRemoved(const ::Channel *channel) {
	::Server *s = qobject_cast< ::Server *> (sender());

	::MurmurRPC::Server_Event event;
	event.mutable_server()->set_id(s->iServerNum);
	event.set_type(::MurmurRPC::Server_Event_Type_ChannelRemoved);
	ToRPC(s, channel, event.mutable_channel());
	sendServerEvent(s, event);
}

// Called when a user sends a text message.
void MurmurRPCImpl::textMessageFilter(int &res, const User *user, MumbleProto::TextMessage &message) {
	::Server *s = qobject_cast< ::Server *> (sender());
	auto filter = RPCCall::Ref<::MurmurRPC::Wrapper::V1_TextMessageFilter>(m_textMessageFilters.value(s->iServerNum));
	if (!filter) {
		return;
	}

	auto &request = filter->response;
	request.Clear();
	request.mutable_server()->set_id(s->iServerNum);
	auto m = request.mutable_message();
	m->mutable_server()->set_id(s->iServerNum);
	m->mutable_actor()->mutable_server()->set_id(s->iServerNum);
	m->mutable_actor()->set_session(user->uiSession);
	for (int i = 0; i < message.session_size(); i++) {
		auto u = m->add_users();
		u->mutable_server()->set_id(s->iServerNum);
		u->set_session(message.session(i));
	}
	for (int i = 0; i < message.channel_id_size(); i++) {
		auto c = m->add_channels();
		c->mutable_server()->set_id(s->iServerNum);
		c->set_id(message.channel_id(i));
	}
	for (int i = 0; i < message.tree_id_size(); i++) {
		auto c = m->add_trees();
		c->mutable_server()->set_id(s->iServerNum);
		c->set_id(message.tree_id(i));
	}
	m->set_text(message.message());

	{
		QMutexLocker l(&qmTextMessageFilterLock);
		if (!filter->writeRead()) {
			removeTextMessageFilter(s);
			return;
		}
	}

	auto &response = filter->request;
	res = response.action();
	switch (response.action()) {
	case ::MurmurRPC::TextMessage_Filter_Action_Accept:
		if (response.has_message() && response.message().has_text()) {
			message.set_message(response.message().text());
		}
		break;
	default:
		break;
	}
}

// Has the user been sent the given context action?
bool MurmurRPCImpl::hasActiveContextAction(const ::Server *s, const ::User *u, const QString &action) {
	const auto &m = m_activeContextActions;
	if (!m.contains(s->iServerNum)) {
		return false;
	}
	const auto &n = m.value(s->iServerNum);
	if (!n.contains(u->uiSession)) {
		return false;
	}
	const auto &o = n.value(u->uiSession);
	if (!o.contains(action)) {
		return false;
	}
	return true;
}

// Add the context action to the user's active context actions.
void MurmurRPCImpl::addActiveContextAction(const ::Server *s, const ::User *u, const QString &action) {
	m_activeContextActions[s->iServerNum][u->uiSession].insert(action);
}

// Remove the context action to the user's active context actions.
void MurmurRPCImpl::removeActiveContextAction(const ::Server *s, const ::User *u, const QString &action) {
	auto &m = m_activeContextActions;
	if (!m.contains(s->iServerNum)) {
		return;
	}
	auto &n = m[s->iServerNum];
	if (!n.contains(u->uiSession)) {
		return;
	}
	auto &o = n[u->uiSession];
	o.remove(action);
}

// Remove all of the user's active context actions.
void MurmurRPCImpl::removeUserActiveContextActions(const ::Server *s, const ::User *u) {
	auto &m = m_activeContextActions;
	if (m.contains(s->iServerNum)) {
		m[s->iServerNum].remove(u->uiSession);
	}
}

// Remove all of the server's active context actions.
void MurmurRPCImpl::removeActiveContextActions(const ::Server *s) {
	auto &m = m_activeContextActions;
	if (m.contains(s->iServerNum)) {
		m.remove(s->iServerNum);
	}
}

// Called when a context action event is triggered.
void MurmurRPCImpl::contextAction(const ::User *user, const QString &action, unsigned int session, int channel) {
	::Server *s = qobject_cast< ::Server *> (sender());

	if (!hasActiveContextAction(s, user, action)) {
		return;
	}

	::MurmurRPC::ContextAction ca;
	ca.mutable_server()->set_id(s->iServerNum);
	ca.mutable_actor()->mutable_server()->set_id(s->iServerNum);
	ca.mutable_actor()->set_session(user->uiSession);
	ca.set_action(u8(action));
	if (session > 0) {
		ca.mutable_user()->mutable_server()->set_id(s->iServerNum);
		ca.mutable_user()->set_session(session);
	}
	if (channel >= 0) {
		ca.mutable_channel()->mutable_server()->set_id(s->iServerNum);
		ca.mutable_channel()->set_id(channel);
	}

	auto serverID = s->iServerNum;
	auto listeners = this->m_contextActionListeners.value(serverID);
	auto i = listeners.find(action);
	for ( ; i != listeners.end() && i.key() == action; ++i) {
		auto listener = i.value();
		listener->ref();
		auto cb = [this, listener, serverID, action] (::MurmurRPC::Wrapper::V1_ContextActionEvents *, bool ok) {
			if (!ok && m_contextActionListeners[serverID].remove(action, listener) > 0) {
				listener->deref();
			}
			listener->deref();
		};
		listener->write(ca, listener->callback(cb));
	}
}

// *Must* functions return the requested value, or throw a ::grpc::Status
// exception.
::ServerUser *MustUser(const Server *server, unsigned int session) {
	auto user = server->qhUsers.value(session);
	if (!user) {
		throw ::grpc::Status(::grpc::NOT_FOUND, "invalid user");
	}
	return user;
}

template <class T>
::ServerUser *MustUser(const ::Server *server, const T &msg) {
	if (!msg.has_user()) {
		throw ::grpc::Status(::grpc::INVALID_ARGUMENT, "missing user");
	}
	return MustUser(server, msg.user().session());
}

template <>
::ServerUser *MustUser(const Server *server, const ::MurmurRPC::User &msg) {
	if (!msg.has_session()) {
		throw ::grpc::Status(::grpc::INVALID_ARGUMENT, "missing user session");
	}
	return MustUser(server, msg.session());
}

::Server *MustServer(unsigned int id) {
	auto server = meta->qhServers.value(id);
	if (!server) {
		throw ::grpc::Status(::grpc::NOT_FOUND, "invalid server");
	}
	return server;
}

::Server *MustServer(int id) {
	auto server = meta->qhServers.value(id);
	if (!server) {
		throw ::grpc::Status(::grpc::NOT_FOUND, "invalid server");
	}
	return server;
}

template <class T>
::Server *MustServer(const T &msg) {
	if (!msg.has_server()) {
		throw ::grpc::Status(::grpc::INVALID_ARGUMENT, "missing server");
	}
	if (!msg.server().has_id()) {
		throw ::grpc::Status(::grpc::INVALID_ARGUMENT, "missing server id");
	}
	return MustServer(msg.server().id());
}

template <>
::Server *MustServer(const ::MurmurRPC::Server &msg) {
	if (!msg.has_id()) {
		throw ::grpc::Status(::grpc::INVALID_ARGUMENT, "missing server id");
	}
	return MustServer(msg.id());
}

template <class T>
int MustServerID(const T &msg) {
	if (!msg.has_server()) {
		throw ::grpc::Status(::grpc::INVALID_ARGUMENT, "missing server");
	}
	if (!msg.server().has_id()) {
		throw ::grpc::Status(::grpc::INVALID_ARGUMENT, "missing server id");
	}
	auto id = msg.server().id();
	if (!ServerDB::serverExists(id)) {
		throw ::grpc::Status(::grpc::NOT_FOUND, "invalid server id");
	}
	return id;
}

int MustServerID(const ::MurmurRPC::Server &msg) {
	if (!msg.has_id()) {
		throw ::grpc::Status(::grpc::INVALID_ARGUMENT, "missing server id");
	}
	auto id = msg.id();
	if (!ServerDB::serverExists(id)) {
		throw ::grpc::Status(::grpc::NOT_FOUND, "invalid server id");
	}
	return id;
}

::Channel *MustChannel(const Server *server, int channel_id) {
	auto channel = server->qhChannels.value(channel_id);
	if (!channel) {
		throw ::grpc::Status(::grpc::NOT_FOUND, "invalid channel");
	}
	return channel;
}

::Channel *MustChannel(const Server *server, unsigned int channel_id) {
	auto channel = server->qhChannels.value(channel_id);
	if (!channel) {
		throw ::grpc::Status(::grpc::NOT_FOUND, "invalid channel");
	}
	return channel;
}

template <class T>
::Channel *MustChannel(const Server *server, const T &msg) {
	if (!msg.has_channel()) {
		throw ::grpc::Status(::grpc::INVALID_ARGUMENT, "missing channel");
	}
	return MustChannel(server, msg.channel());
}

template <>
::Channel *MustChannel(const Server *server, const ::MurmurRPC::Channel &msg) {
	if (!msg.has_id()) {
		throw ::grpc::Status(::grpc::INVALID_ARGUMENT, "missing channel id");
	}
	return MustChannel(server, msg.id());
}

// Qt event listener for RPCExecEvents.
void MurmurRPCImpl::customEvent(QEvent *evt) {
	if (evt->type() == EXEC_QEVENT) {
		auto event = static_cast<RPCExecEvent *>(evt);
		try {
			event->execute();
		} catch (::grpc::Status &ex) {
			event->call->error(ex);
		}
	}
}

// QThread::run() implementation that runs the grpc event loop and executes
// tags as callback functions.
void MurmurRPCImpl::run() {
	MurmurRPC::Wrapper::V1_Init(this, &m_V1Service);

	while (true) {
		void *tag;
		bool ok;
		if (!m_completionQueue->Next(&tag, &ok)) {
			break;
		}
		if (tag != nullptr) {
			auto op = static_cast<boost::function<void(bool)> *>(tag);
			(*op)(ok);
			delete op;
		}
	}
	// TODO(grpc): cleanup allocated memory? not super important, because murmur
	// should be exiting now.
}

// The Wrapper implementation methods are below. Implementation methods are
// executed in the main thread when its corresponding grpc method is invoked.
//
// Since the grpc asynchronous API is used, the implementation methods below
// do not have to complete the call during the lifetime of the method (although
// this is only used for streaming calls).

namespace MurmurRPC {
namespace Wrapper {

void V1_ServerCreate::impl(bool) {
	auto id = ServerDB::addServer();

	::MurmurRPC::Server rpcServer;
	rpcServer.set_id(id);
	end(rpcServer);
}

void V1_ServerQuery::impl(bool) {
	::MurmurRPC::Server_List list;

	foreach(int id, ServerDB::getAllServers()) {
		auto rpcServer = list.add_servers();
		rpcServer->set_id(id);
		try {
			auto server = MustServer(id);
			rpcServer->set_running(true);
			rpcServer->mutable_uptime()->set_secs(server->tUptime.elapsed()/1000000LL);
		} catch (::grpc::Status &ex) {
		}
	}

	end(list);
}

void V1_ServerGet::impl(bool) {
	auto serverID = MustServerID(request);

	::MurmurRPC::Server rpcServer;
	rpcServer.set_id(serverID);
	rpcServer.set_running(false);
	try {
		auto server = MustServer(serverID);
		rpcServer.set_running(true);
		rpcServer.mutable_uptime()->set_secs(server->tUptime.elapsed()/1000000LL);
	} catch (::grpc::Status &ex) {
	}
	end(rpcServer);
}

void V1_ServerStart::impl(bool) {
	auto serverID = MustServerID(request);

	if (!meta->boot(serverID)) {
		throw ::grpc::Status(::grpc::UNKNOWN, "server could not be started, or is already started");
	}

	end();
}

void V1_ServerStop::impl(bool) {
	auto server = MustServer(request);
	meta->kill(server->iServerNum);
	end();
}

void V1_ServerRemove::impl(bool) {
	auto serverID = MustServerID(request);

	if (meta->qhServers.value(serverID)) {
		throw ::grpc::Status(::grpc::FAILED_PRECONDITION, "cannot remove started server");
	}

	ServerDB::deleteServer(serverID);
	end();
}

void V1_ServerEvents::impl(bool) {
	auto server = MustServer(request);
	rpc->m_serverServiceListeners.insert(server->iServerNum, this);
}

void V1_GetUptime::impl(bool) {
	::MurmurRPC::Uptime uptime;
	uptime.set_secs(meta->tUptime.elapsed()/1000000LL);
	end(uptime);
}

void V1_GetVersion::impl(bool) {
	::MurmurRPC::Version version;
	int major, minor, patch;
	QString release;
	Meta::getVersion(major, minor, patch, release);
	version.set_version(major << 16 | minor << 8 | patch);
	version.set_release(u8(release));
	end(version);
}

void V1_Events::impl(bool) {
	rpc->m_metaServiceListeners.insert(this);
}

void V1_ContextActionAdd::impl(bool) {
	auto server = MustServer(request);
	auto user = MustUser(server, request);

	if (!request.has_action()) {
		throw ::grpc::Status(::grpc::INVALID_ARGUMENT, "missing action");
	}
	if (!request.has_text()) {
		throw ::grpc::Status(::grpc::INVALID_ARGUMENT, "missing text");
	}
	if (!request.has_context()) {
		throw ::grpc::Status(::grpc::INVALID_ARGUMENT, "missing context");
	}

	rpc->addActiveContextAction(server, user, u8(request.action()));

	::MumbleProto::ContextActionModify mpcam;
	mpcam.set_action(request.action());
	mpcam.set_text(request.text());
	mpcam.set_context(request.context());
	mpcam.set_operation(::MumbleProto::ContextActionModify_Operation_Add);
	server->sendMessage(user, mpcam);

	end();
}

void V1_ContextActionRemove::impl(bool) {
	auto server = MustServer(request);

	if (!request.has_action()) {
		throw ::grpc::Status(::grpc::INVALID_ARGUMENT, "missing action");
	}

	auto action = u8(request.action());

	::MumbleProto::ContextActionModify mpcam;
	mpcam.set_action(request.action());
	mpcam.set_operation(::MumbleProto::ContextActionModify_Operation_Remove);

	if (request.has_user()) {
		// Remove context action from specific user
		auto user = MustUser(server, request);
		rpc->removeActiveContextAction(server, user, action);
		server->sendMessage(user, mpcam);
	} else {
		// Remove context action from all users
		foreach(::ServerUser *user, server->qhUsers) {
			rpc->removeActiveContextAction(server, user, action);
			server->sendMessage(user, mpcam);
		}
	}

	end();
}

void V1_ContextActionEvents::impl(bool) {
	auto server = MustServer(request);

	if (!request.has_action()) {
		throw ::grpc::Status(::grpc::INVALID_ARGUMENT, "missing action");
	}

	rpc->m_contextActionListeners[server->iServerNum].insert(u8(request.action()), this);
}

void V1_TextMessageSend::impl(bool) {
	auto server = MustServer(request);

	::MumbleProto::TextMessage tm;
	tm.set_message(request.text());
	if (request.has_actor() && request.actor().has_session()) {
		tm.set_actor(request.actor().session());
	}
	for (int i = 0; i < request.users_size(); i++) {
		if (request.users(i).has_session()) {
			tm.add_session(request.users(i).session());
		}
	}
	for (int i = 0; i < request.channels_size(); i++) {
		if (request.channels(i).has_id()) {
			tm.add_channel_id(request.channels(i).id());
		}
	}
	for (int i = 0; i < request.trees_size(); i++) {
		if (request.trees(i).has_id()) {
			tm.add_tree_id(request.trees(i).id());
		}
	}

	server->sendTextMessageGRPC(tm);

	end();
}

void V1_TextMessageFilter::impl(bool) {
	auto onInitialize = [this] (V1_TextMessageFilter *, bool ok) {
		if (!ok) {
			finish(ok);
			return;
		}
		auto server = MustServer(request);
		QMutexLocker l(&rpc->qmTextMessageFilterLock);
		rpc->removeTextMessageFilter(server);
		rpc->m_textMessageFilters.insert(server->iServerNum, this);
	};
	stream.Read(&request, callback(onInitialize));
}

void V1_LogQuery::impl(bool) {
	auto serverID = MustServerID(request);

	int total = ::ServerDB::getLogLen(serverID);
	if (total < 0) {
		throw ::grpc::Status(::grpc::StatusCode::UNAVAILABLE, "could not access database");
	}

	::MurmurRPC::Log_List list;
	list.mutable_server()->set_id(serverID);
	list.set_total(total);

	if (!request.has_min() || !request.has_max()) {
		end(list);
		return;
	}
	list.set_min(request.min());
	list.set_max(request.max());

	auto dblog = ::ServerDB::getLog(serverID, request.min(), request.max());
	foreach(const ::ServerDB::LogRecord &record, dblog) {
		auto rpcLog = list.add_entries();
		ToRPC(serverID, record, rpcLog);
	}

	end(list);
}

void V1_ConfigGet::impl(bool) {
	auto serverID = MustServerID(request);
	auto config = ServerDB::getAllConf(serverID);

	::MurmurRPC::Config rpcConfig;
	rpcConfig.mutable_server()->set_id(serverID);
	auto &fields = *rpcConfig.mutable_fields();
	for (auto i = config.constBegin(); i != config.constEnd(); ++i) {
		fields[u8(i.key())] = u8(i.value());
	}

	end(rpcConfig);
}

void V1_ConfigGetField::impl(bool) {
	auto serverID = MustServerID(request);
	if (!request.has_key()) {
		throw ::grpc::Status(::grpc::INVALID_ARGUMENT, "missing key");
	}
	::MurmurRPC::Config_Field rpcField;
	rpcField.mutable_server()->set_id(serverID);
	rpcField.set_key(request.key());
	rpcField.set_value(u8(ServerDB::getConf(serverID, u8(request.key()), QVariant()).toString()));
	end(rpcField);
}

void V1_ConfigSetField::impl(bool) {
	auto serverID = MustServerID(request);
	if (!request.has_key()) {
		throw ::grpc::Status(::grpc::INVALID_ARGUMENT, "missing key");
	}
	QString key = u8(request.key());
	QString value;
	if (request.has_value()) {
		value = u8(request.value());
	}
	ServerDB::setConf(serverID, key, value);
	try {
		auto server = MustServer(serverID);
		server->setLiveConf(key, value);
	} catch (::grpc::Status &ex) {
	}

	end();
}

void V1_ConfigGetDefault::impl(bool) {
	::MurmurRPC::Config rpcConfig;
	auto &fields = *rpcConfig.mutable_fields();
	for (auto i = meta->mp.qmConfig.constBegin(); i != meta->mp.qmConfig.constEnd(); ++i) {
		fields[u8(i.key())] = u8(i.value());
	}

	end(rpcConfig);
}

void V1_ChannelQuery::impl(bool) {
	auto server = MustServer(request);

	::MurmurRPC::Channel_List list;
	list.mutable_server()->set_id(server->iServerNum);

	foreach(const ::Channel *channel, server->qhChannels) {
		auto rpcChannel = list.add_channels();
		ToRPC(server, channel, rpcChannel);
	}

	end(list);
}

void V1_ChannelGet::impl(bool) {
	auto server = MustServer(request);
	auto channel = MustChannel(server, request);

	::MurmurRPC::Channel rpcChannel;
	ToRPC(server, channel, &rpcChannel);
	end(rpcChannel);
}

void V1_ChannelAdd::impl(bool) {
	auto server = MustServer(request);

	if (!request.has_parent() || !request.has_name()) {
		throw ::grpc::Status(::grpc::INVALID_ARGUMENT, "parent channel and name required");
	}
	::Channel *parent;
	try {
		parent = MustChannel(server, request.parent());
	} catch (::grpc::Status &ex) {
		throw ::grpc::Status(::grpc::INVALID_ARGUMENT, "invalid parent channel");
	}

	if (!server->canNest(parent)) {
		throw ::grpc::Status(::grpc::INVALID_ARGUMENT, "cannot nest channel in given parent");
	}

	QString qsName = u8(request.name());

	::Channel *nc;

	{
		QWriteLocker wl(&server->qrwlVoiceThread);
		nc = server->addChannel(parent, qsName);
	}

	server->updateChannel(nc);
	int newid = nc->iId;

	::MumbleProto::ChannelState mpcs;
	mpcs.set_channel_id(newid);
	mpcs.set_parent(parent->iId);
	mpcs.set_name(request.name());
	server->sendAll(mpcs);

	::MurmurRPC::Channel resChannel;
	ToRPC(server, nc, &resChannel);
	end(resChannel);
}

void V1_ChannelRemove::impl(bool) {
	auto server = MustServer(request);
	auto channel = MustChannel(server, request);

	if (!channel->cParent) {
		throw ::grpc::Status(::grpc::INVALID_ARGUMENT, "cannot remove the root channel");
	}

	{
		QWriteLocker wl(&server->qrwlVoiceThread);
		server->removeChannel(channel);
	}

	end();
}

void V1_ChannelUpdate::impl(bool) {
	auto server = MustServer(request);
	auto channel = MustChannel(server, request);

	::MumbleProto::ChannelState mpcs;
	mpcs.set_channel_id(channel->iId);
	for (int i = 0; i < request.links_size(); i++) {
		if (!request.links(i).has_id()) {
			throw ::grpc::Status(::grpc::INVALID_ARGUMENT, "invalid channel link");
		}
		mpcs.add_links(request.links(i).id());
	}
	if (request.has_parent()) {
		if (!request.parent().has_id()) {
			throw ::grpc::Status(::grpc::INVALID_ARGUMENT, "invalid channel parent");
		}
		mpcs.set_parent(request.parent().id());
	}
	if (request.has_name()) {
		mpcs.set_name(request.name());
	}
	if (request.has_position()) {
		mpcs.set_position(request.position());
	}
	if (request.has_description()) {
		mpcs.set_description(request.description());
	}

	QString err;
	if (!server->setChannelStateGRPC(mpcs, err)) {
		throw ::grpc::Status(::grpc::INVALID_ARGUMENT, u8(err));
	}

	::MurmurRPC::Channel rpcChannel;
	ToRPC(server, channel, &rpcChannel);
	end(rpcChannel);
}

void V1_UserQuery::impl(bool) {
	auto server = MustServer(request);

	::MurmurRPC::User_List list;
	list.mutable_server()->set_id(server->iServerNum);

	foreach(const ::ServerUser *user, server->qhUsers) {
		auto rpcUser = list.add_users();
		ToRPC(server, user, rpcUser);
	}

	end(list);
}

void V1_UserGet::impl(bool) {
	auto server = MustServer(request);

	::MurmurRPC::User rpcUser;

	if (request.has_session()) {
		// Lookup user by session
		auto user = MustUser(server, request);
		ToRPC(server, user, &rpcUser);
		end(rpcUser);
		return;
	} else if (request.has_name()) {
		// Lookup user by name
		QString qsName = u8(request.name());
		foreach(const ::ServerUser *user, server->qhUsers) {
			if (user->qsName == qsName) {
				ToRPC(server, user, &rpcUser);
				end(rpcUser);
				return;
			}
		}
		throw ::grpc::Status(::grpc::NOT_FOUND, "invalid user");
	}

	throw ::grpc::Status(::grpc::INVALID_ARGUMENT, "session or name required");
}

void V1_UserUpdate::impl(bool) {
	auto server = MustServer(request);
	auto user = MustUser(server, request);

	auto channel = user->cChannel;
	if (request.has_channel()) {
		channel = MustChannel(server, request.channel());
	}
	auto mute = user->bMute;
	if (request.has_mute()) {
		mute = request.mute();
	}
	auto deaf = user->bDeaf;
	if (request.has_deaf()) {
		deaf = request.deaf();
	}
	auto suppress = user->bSuppress;
	if (request.has_suppress()) {
		suppress = request.suppress();
	}
	auto prioritySpeaker = user->bPrioritySpeaker;
	if (request.has_priority_speaker()) {
		prioritySpeaker = request.priority_speaker();
	}
	auto name = user->qsName;
	if (request.has_name()) {
		name = u8(request.name());
	}
	auto comment = user->qsComment;
	if (request.has_comment()) {
		comment = u8(request.comment());
	}

	server->setUserState(user, channel, mute, deaf, suppress, prioritySpeaker, name, comment);

	::MurmurRPC::User rpcUser;
	ToRPC(server, user, &rpcUser);
	end(rpcUser);
}

void V1_UserKick::impl(bool) {
	auto server = MustServer(request);
	auto user = MustUser(server, request);

	::MumbleProto::UserRemove mpur;
	mpur.set_session(user->uiSession);
	if (request.has_reason()) {
		mpur.set_reason(request.reason());
	}
	if (request.has_actor() && request.actor().has_session()) {
		mpur.set_actor(request.actor().session());
	}
	server->sendAll(mpur);
	user->disconnectSocket();

	end();
}

void V1_TreeQuery::impl(bool) {
	auto server = MustServer(request);

	auto channel = MustChannel(server, 0);
	::MurmurRPC::Tree root;

	QQueue< QPair<const ::Channel *, ::MurmurRPC::Tree *> > qQueue;
	qQueue.enqueue(qMakePair(channel, &root));

	while (!qQueue.isEmpty()) {
		auto current = qQueue.dequeue();
		auto currentChannel = current.first;
		auto currentTree = current.second;

		ToRPC(server, currentChannel, currentTree->mutable_channel());

		QList< ::User *> users = currentChannel->qlUsers;
		qSort(users.begin(), users.end(), [] (const ::User *a, const ::User *b) -> bool {
			return ::User::lessThan(a, b);
		});
		foreach(const ::User *u, users) {
			auto rpcUser = currentTree->add_users();
			ToRPC(server, u, rpcUser);
		}

		QList< ::Channel *> channels = currentChannel->qlChannels;
		qSort(channels.begin(), channels.end(), [] (const ::Channel *a, const ::Channel *b) -> bool {
			return ::Channel::lessThan(a, b);
		});
		foreach(const ::Channel *subChannel, channels) {
			auto subTree = currentTree->add_children();
			qQueue.enqueue(qMakePair(subChannel, subTree));
		}
	}

	end(root);
}

void V1_BansGet::impl(bool) {
	auto server = MustServer(request);

	::MurmurRPC::Ban_List list;
	list.mutable_server()->set_id(server->iServerNum);
	foreach(const ::Ban &ban, server->qlBans) {
		auto rpcBan = list.add_bans();
		ToRPC(server, ban, rpcBan);
	}

	end(list);
}

void V1_BansSet::impl(bool) {
	auto server = MustServer(request);
	server->qlBans.clear();

	for (int i = 0; i < request.bans_size(); i++) {
		const auto &rpcBan = request.bans(i);
		::Ban ban;
		FromRPC(server, rpcBan, ban);
		server->qlBans << ban;
	}
	server->saveBans();

	end();
}

void V1_ACLGet::impl(bool) {
	auto server = MustServer(request);
	auto channel = MustChannel(server, request);

	::MurmurRPC::ACL_List list;
	list.set_inherit(channel->bInheritACL);

	QStack< ::Channel *> chans;
	ChanACL *acl;
	::Channel *p = channel;
	while (p) {
		chans.push(p);
		if ((p == channel) || p->bInheritACL) {
			p = p->cParent;
		} else {
			p = NULL;
		}
	}

	while (!chans.isEmpty()) {
		p = chans.pop();
		foreach(acl, p->qlACL) {
			if (p == channel || acl->bApplySubs) {
				auto rpcACL = list.add_acls();
				ToRPC(server, acl, rpcACL);
				if (p != channel) {
					rpcACL->set_inherited(true);
				}
			}
		}
	}

	p = channel->cParent;
	const QSet<QString> allnames = ::Group::groupNames(channel);
	foreach(const QString &name, allnames) {
		::Group *g = channel->qhGroups.value(name);
		::Group *pg = p ? ::Group::getGroup(p, name) : NULL;
		if (!g && ! pg) {
			continue;
		}
		auto aclGroup = list.add_groups();
		ToRPC(server, g ? g : pg, aclGroup);
		QSet<int> members;
		if (pg) {
			members = pg->members();
		}
		if (g) {
			foreach(auto id, g->qsAdd) {
				auto rpcUser = aclGroup->add_users_add();
				rpcUser->mutable_server()->set_id(server->iServerNum);
				rpcUser->set_id(id);
			}
			foreach(auto id, g->qsRemove) {
				auto rpcUser = aclGroup->add_users_remove();
				rpcUser->mutable_server()->set_id(server->iServerNum);
				rpcUser->set_id(id);
			}
			aclGroup->set_inherited(false);
			members += g->qsAdd;
			members -= g->qsRemove;
		} else {
			aclGroup->set_inherited(true);
		}
		foreach(auto id, members) {
			auto rpcUser = aclGroup->add_users();
			rpcUser->mutable_server()->set_id(server->iServerNum);
			rpcUser->set_id(id);
		}
	}

	end(list);
}

void V1_ACLSet::impl(bool) {
	auto server = MustServer(request);
	auto channel = MustChannel(server, request);

	::Group *g;
	::ChanACL *acl;

	{
		QWriteLocker wl(&server->qrwlVoiceThread);

		QHash<QString, QSet<int> > hOldTemp;
		foreach(g, channel->qhGroups) {
			hOldTemp.insert(g->qsName, g->qsTemporary);
			delete g;
		}
		foreach(acl, channel->qlACL) {
			delete acl;
		}

		channel->qhGroups.clear();
		channel->qlACL.clear();

		channel->bInheritACL = request.inherit();

		for (int i = 0; i < request.groups_size(); i++) {
			auto &rpcGroup = request.groups(i);
			auto name = u8(rpcGroup.name());
			g = new ::Group(channel, name);
			g->bInherit = rpcGroup.inherit();
			g->bInheritable = rpcGroup.inheritable();
			for (int j = 0; j < rpcGroup.users_add_size(); j++) {
				g->qsAdd.insert(rpcGroup.users_add(j).id());
			}
			for (int j = 0; j < rpcGroup.users_remove_size(); j++) {
				g->qsRemove.insert(rpcGroup.users_remove(j).id());
			}
			g->qsTemporary = hOldTemp.value(name);
		}
		for (int i = 0; i < request.acls_size(); i++) {
			auto &rpcACL = request.acls(i);
			acl = new ChanACL(channel);
			acl->bApplyHere = rpcACL.apply_here();
			acl->bApplySubs = rpcACL.apply_subs();
			if (rpcACL.has_user()) {
				acl->iUserId = rpcACL.user().id();
			}
			if (rpcACL.has_group() && rpcACL.group().has_name()) {
				acl->qsGroup = u8(rpcACL.group().name());
			}
			acl->pDeny = static_cast<ChanACL::Permissions>(rpcACL.deny()) & ChanACL::All;
			acl->pAllow = static_cast<ChanACL::Permissions>(rpcACL.allow()) & ChanACL::All;
		}
	}

	server->clearACLCache();
	server->updateChannel(channel);

	end();
}

void V1_ACLGetEffectivePermissions::impl(bool) {
	auto server = MustServer(request);
	auto user = MustUser(server, request);
	auto channel = MustChannel(server, request);

	auto flags = int(server->effectivePermissions(user, channel));

	::MurmurRPC::ACL rpcACL;
	rpcACL.set_allow(::MurmurRPC::ACL_Permission(flags));
	end(rpcACL);
}

void V1_ACLAddTemporaryGroup::impl(bool) {
	auto server = MustServer(request);
	auto user = MustUser(server, request);
	auto channel = MustChannel(server, request);

	if (!request.has_name()) {
		throw ::grpc::Status(::grpc::INVALID_ARGUMENT, "missing name");
	}

	QString qsgroup = u8(request.name());
	if (qsgroup.isEmpty()) {
		throw ::grpc::Status(::grpc::INVALID_ARGUMENT, "empty name");
	}

	{
		QWriteLocker wl(&server->qrwlVoiceThread);

		::Group *g = channel->qhGroups.value(qsgroup);
		if (!g) {
			g = new ::Group(channel, qsgroup);
		}

		g->qsTemporary.insert(-user->uiSession);
	}

	server->clearACLCache(user);

	end();
}

void V1_ACLRemoveTemporaryGroup::impl(bool) {
	auto server = MustServer(request);
	auto user = MustUser(server, request);
	auto channel = MustChannel(server, request);

	if (!request.has_name()) {
		throw ::grpc::Status(::grpc::INVALID_ARGUMENT, "missing name");
	}

	QString qsgroup = u8(request.name());
	if (qsgroup.isEmpty()) {
		throw ::grpc::Status(::grpc::INVALID_ARGUMENT, "empty name");
	}

	{
		QWriteLocker wl(&server->qrwlVoiceThread);

		::Group *g = channel->qhGroups.value(qsgroup);
		if (!g) {
			g = new ::Group(channel, qsgroup);
		}

		g->qsTemporary.remove(-user->uiSession);
	}

	server->clearACLCache(user);

	end();
}

void V1_AuthenticatorStream::impl(bool) {
	auto onInitialize = [this] (V1_AuthenticatorStream *, bool ok) {
		if (!ok) {
			finish(ok);
			return;
		}
		if (!request.has_initialize()) {
			throw ::grpc::Status(::grpc::INVALID_ARGUMENT, "missing initialize");
		}
		auto server = MustServer(request.initialize());
		QMutexLocker l(&rpc->qmAuthenticatorsLock);
		rpc->removeAuthenticator(server);
		rpc->m_authenticators.insert(server->iServerNum, this);
	};
	stream.Read(&request, callback(onInitialize));
}

void V1_DatabaseUserQuery::impl(bool) {
	auto server = MustServer(request);

	QString filter;
	if (request.has_filter()) {
		filter = u8(request.filter());
	}
	auto users = server->getRegisteredUsers(filter);

	::MurmurRPC::DatabaseUser_List list;
	list.mutable_server()->set_id(server->iServerNum);

	for (auto itr = users.constBegin(); itr != users.constEnd(); ++itr) {
		auto user = list.add_users();
		user->mutable_server()->set_id(server->iServerNum);
		user->set_id(itr.key());
		user->set_name(u8(itr.value()));
	}

	end(list);
}

void V1_DatabaseUserGet::impl(bool) {
	auto server = MustServer(request);

	if (!request.has_id()) {
		throw ::grpc::Status(::grpc::INVALID_ARGUMENT, "missing id");
	}
	auto info = server->getRegistration(request.id());
	if (info.isEmpty()) {
		throw ::grpc::Status(::grpc::INVALID_ARGUMENT, "invalid user");
	}
	auto texture = server->getUserTexture(request.id());

	::MurmurRPC::DatabaseUser rpcDatabaseUser;
	ToRPC(server, info, texture, &rpcDatabaseUser);
	end(rpcDatabaseUser);
}

void V1_DatabaseUserUpdate::impl(bool) {
	auto server = MustServer(request);

	if (!request.has_id()) {
		throw ::grpc::Status(::grpc::INVALID_ARGUMENT, "missing id");
	}
	if (!server->isUserId(request.id())) {
		throw ::grpc::Status(::grpc::INVALID_ARGUMENT, "invalid user");
	}

	QMap<int, QString> info;
	FromRPC(request, info);

	if (!server->setInfo(request.id(), info)) {
		throw ::grpc::Status(::grpc::INVALID_ARGUMENT, "invalid user");
	}
	if (request.has_texture()) {
		server->setTexture(request.id(), QByteArray(request.texture().c_str(), request.texture().size()));
	}

	if (info.contains(ServerDB::User_Name) || info.contains(ServerDB::User_Comment)) {
		foreach(::ServerUser *u, server->qhUsers) {
			if (static_cast<unsigned int>(u->iId) == request.id()) {
				QString name = u->qsName;
				QString comment = u->qsComment;
				if (info.contains(ServerDB::User_Name)) {
					comment = info.value(ServerDB::User_Name);
				}
				if (info.contains(ServerDB::User_Comment)) {
					comment = info.value(ServerDB::User_Comment);
				}
				server->setUserState(u, u->cChannel, u->bMute, u->bDeaf, u->bSuppress, u->bPrioritySpeaker, name, comment);
				break;
			}
		}
	}

	end();
}

void V1_DatabaseUserRegister::impl(bool) {
	auto server = MustServer(request);

	QMap<int, QString> info;
	FromRPC(request, info);

	auto userid = server->registerUser(info);
	if (userid < 0) {
		throw ::grpc::Status(::grpc::INVALID_ARGUMENT, "invalid user");
	}
	QByteArray texture;
	if (request.has_texture()) {
		texture = QByteArray(request.texture().c_str(), request.texture().size());
		server->setTexture(request.id(), texture);
	}

	::MurmurRPC::DatabaseUser rpcDatabaseUser;
	rpcDatabaseUser.set_id(userid);
	ToRPC(server, info, texture, &rpcDatabaseUser);
	end(rpcDatabaseUser);
}

void V1_DatabaseUserDeregister::impl(bool) {
	auto server = MustServer(request);

	if (!request.has_id()) {
		throw ::grpc::Status(::grpc::INVALID_ARGUMENT, "missing id");
	}
	if (!server->unregisterUser(request.id())) {
		throw ::grpc::Status(::grpc::INVALID_ARGUMENT, "invalid user");
	}

	end();
}

void V1_DatabaseUserVerify::impl(bool) {
	auto server = MustServer(request);

	if (!request.has_name()) {
		throw ::grpc::Status(::grpc::INVALID_ARGUMENT, "missing name");
	}
	if (!request.has_password()) {
		throw ::grpc::Status(::grpc::INVALID_ARGUMENT, "missing password");
	}

	auto name = u8(request.name());
	auto password = u8(request.password());

	auto ret = server->authenticate(name, password);
	switch (ret) {
	case -1:
		throw ::grpc::Status(::grpc::INVALID_ARGUMENT, "invalid username and/or password");
	case -2:
		throw ::grpc::Status(::grpc::NOT_FOUND, "unknown user");
	case -3:
		throw ::grpc::Status(::grpc::UNAVAILABLE, "authenticator temporarily unavailable");
	}

	::MurmurRPC::DatabaseUser rpcDatabaseUser;
	rpcDatabaseUser.mutable_server()->set_id(server->iServerNum);
	rpcDatabaseUser.set_id(ret);
	end(rpcDatabaseUser);
}

void V1_RedirectWhisperGroupAdd::impl(bool) {
	auto server = MustServer(request);
	auto user = MustUser(server, request);

	if (!request.source().has_name()) {
		throw ::grpc::Status(::grpc::INVALID_ARGUMENT, "missing source name");
	}
	if (!request.target().has_name()) {
		throw ::grpc::Status(::grpc::INVALID_ARGUMENT, "missing target name");
	}

	QString qssource = u8(request.source().name());
	QString qstarget = u8(request.target().name());

	{
		QWriteLocker wl(&server->qrwlVoiceThread);
		user->qmWhisperRedirect.insert(qssource, qstarget);
	}

	server->clearACLCache(user);

	end();
}

void V1_RedirectWhisperGroupRemove::impl(bool) {
	auto server = MustServer(request);
	auto user = MustUser(server, request);

	if (!request.source().has_name()) {
		throw ::grpc::Status(::grpc::INVALID_ARGUMENT, "missing source name");
	}

	QString qssource = u8(request.source().name());

	{
		QWriteLocker wl(&server->qrwlVoiceThread);
		user->qmWhisperRedirect.remove(qssource);
	}

	server->clearACLCache(user);

	end();
}

}
}
