blob: 706ad24ffadb268de38f1476e51a8f85c5baefae [file] [log] [blame]
/*
*
* Copyright (c) 2013-2017 Nest Labs, Inc.
* All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @file
* This file implements objects for a Weave Service Provisioning
* profile unsolicited responder (server).
*
*/
#include <Weave/Core/WeaveCore.h>
#include <Weave/Core/WeaveTLV.h>
#include <Weave/Support/CodeUtils.h>
#include <Weave/Core/WeaveEncoding.h>
#include <Weave/Profiles/WeaveProfiles.h>
#include "ServiceProvisioning.h"
#include <Weave/Profiles/common/CommonProfile.h>
namespace nl {
namespace Weave {
namespace Profiles {
namespace ServiceProvisioning {
using namespace nl::Weave::Encoding;
using namespace nl::Weave::TLV;
ServiceProvisioningServer::ServiceProvisioningServer()
{
FabricState = NULL;
ExchangeMgr = NULL;
mDelegate = NULL;
mCurClientOp = NULL;
mCurClientOpBuf = NULL;
mCurServerOp = NULL;
mServerOpState = kServerOpState_Idle;
}
WEAVE_ERROR ServiceProvisioningServer::Init(WeaveExchangeManager *exchangeMgr)
{
FabricState = exchangeMgr->FabricState;
ExchangeMgr = exchangeMgr;
mDelegate = NULL;
mCurClientOp = NULL;
mCurClientOpBuf = NULL;
mCurServerOp = NULL;
mServerOpState = kServerOpState_Idle;
// Register to receive unsolicited Service Provisioning messages from the exchange manager.
WEAVE_ERROR err =
ExchangeMgr->RegisterUnsolicitedMessageHandler(kWeaveProfile_ServiceProvisioning, HandleClientRequest, this);
return err;
}
WEAVE_ERROR ServiceProvisioningServer::Shutdown()
{
if (ExchangeMgr != NULL)
ExchangeMgr->UnregisterUnsolicitedMessageHandler(kWeaveProfile_ServiceProvisioning);
if (mCurClientOpBuf != NULL)
PacketBuffer::Free(mCurClientOpBuf);
FabricState = NULL;
ExchangeMgr = NULL;
mDelegate = NULL;
mCurClientOp = NULL;
mCurClientOpBuf = NULL;
mCurServerOp = NULL;
mServerOpState = kServerOpState_Idle;
return WEAVE_NO_ERROR;
}
void ServiceProvisioningServer::SetDelegate(ServiceProvisioningDelegate *delegate)
{
mDelegate = delegate;
}
ServiceProvisioningDelegate* ServiceProvisioningServer::GetDelegate(void) const
{
return mDelegate;
}
WEAVE_ERROR ServiceProvisioningServer::SendSuccessResponse()
{
return SendStatusReport(kWeaveProfile_Common, Common::kStatus_Success);
}
WEAVE_ERROR ServiceProvisioningServer::SendStatusReport(uint32_t statusProfileId, uint16_t statusCode, WEAVE_ERROR sysError)
{
WEAVE_ERROR err;
VerifyOrExit(mCurClientOp != NULL, err = WEAVE_ERROR_INCORRECT_STATE);
err = WeaveServerBase::SendStatusReport(mCurClientOp, statusProfileId, statusCode, sysError);
exit:
mServerOpState = kServerOpState_Idle;
if (mCurClientOp != NULL)
{
mCurClientOp->Close();
mCurClientOp = NULL;
}
if (mCurClientOpBuf != NULL)
{
PacketBuffer::Free(mCurClientOpBuf);
mCurClientOpBuf = NULL;
}
return err;
}
WEAVE_ERROR ServiceProvisioningServer::SendPairDeviceToAccountRequest(WeaveConnection *serverCon, uint64_t serviceId, uint64_t fabricId,
const char *accountId, uint16_t accountIdLen,
const uint8_t *pairingToken, uint16_t pairingTokenLen,
const uint8_t *pairingInitData, uint16_t pairingInitDataLen,
const uint8_t *deviceInitData, uint16_t deviceInitDataLen)
{
WEAVE_ERROR err = WEAVE_NO_ERROR;
PairDeviceToAccountMessage msg;
PacketBuffer* msgBuf = NULL;
uint16_t msgLen = sizeof(accountIdLen) + sizeof(pairingTokenLen) + sizeof(pairingInitDataLen) + sizeof(deviceInitDataLen) +
sizeof(serviceId) + sizeof(fabricId) + accountIdLen + pairingTokenLen + pairingInitDataLen + deviceInitDataLen;
if (mServerOpState != kServerOpState_Idle)
return WEAVE_ERROR_INCORRECT_STATE;
msg.ServiceId = serviceId;
msg.FabricId = fabricId;
msg.AccountId = accountId;
msg.AccountIdLen = accountIdLen;
msg.PairingToken = pairingToken;
msg.PairingTokenLen = pairingTokenLen;
msg.PairingInitData = pairingInitData;
msg.PairingInitDataLen = pairingInitDataLen;
msg.DeviceInitData = deviceInitData;
msg.DeviceInitDataLen = deviceInitDataLen;
// Allocate a buffer for the message.
msgBuf = PacketBuffer::NewWithAvailableSize(msgLen);
VerifyOrExit(msgBuf != NULL, err = WEAVE_ERROR_NO_MEMORY);
// Encode the message.
err = msg.Encode(msgBuf);
SuccessOrExit(err);
// Allocate and initialize an exchange context.
mCurServerOp = ExchangeMgr->NewContext(serverCon, this);
VerifyOrExit(mCurServerOp != NULL, err = WEAVE_ERROR_NO_MEMORY);
mCurServerOp->OnMessageReceived = HandleServerResponse;
if (mCurServerOp->ResponseTimeout == 0)
{
mCurServerOp->ResponseTimeout = WEAVE_CONFIG_SERVICE_PROV_RESPONSE_TIMEOUT;
}
mCurServerOp->OnResponseTimeout = HandleServerResponseTimeout;
mCurServerOp->OnConnectionClosed = HandleServerConnectionClosed;
mCurServerOp->OnKeyError = HandleServerKeyError;
// Record that a PairDeviceToAccount request is outstanding.
mServerOpState = kServerOpState_PairDeviceToAccount;
// Send the PairDeviceToAccount message to the service.
err = mCurServerOp->SendMessage(kWeaveProfile_ServiceProvisioning, kMsgType_PairDeviceToAccount, msgBuf, 0);
msgBuf = NULL;
exit:
if (msgBuf != NULL)
PacketBuffer::Free(msgBuf);
if (err != WEAVE_NO_ERROR)
{
if (mCurServerOp != NULL)
{
mCurServerOp->Close();
mCurServerOp = NULL;
}
mServerOpState = kServerOpState_Idle;
}
return err;
}
WEAVE_ERROR ServiceProvisioningServer::SendPairDeviceToAccountRequest(Binding *binding, uint64_t serviceId, uint64_t fabricId,
const char *accountId, uint16_t accountIdLen,
const uint8_t *pairingToken, uint16_t pairingTokenLen,
const uint8_t *pairingInitData, uint16_t pairingInitDataLen,
const uint8_t *deviceInitData, uint16_t deviceInitDataLen)
{
WEAVE_ERROR err = WEAVE_NO_ERROR;
PairDeviceToAccountMessage msg;
PacketBuffer* msgBuf = NULL;
uint16_t flags = 0;
uint16_t msgLen = sizeof(accountIdLen) + sizeof(pairingTokenLen) + sizeof(pairingInitDataLen) + sizeof(deviceInitDataLen) +
sizeof(serviceId) + sizeof(fabricId) + accountIdLen + pairingTokenLen + pairingInitDataLen + deviceInitDataLen;
if (mServerOpState != kServerOpState_Idle)
return WEAVE_ERROR_INCORRECT_STATE;
msg.ServiceId = serviceId;
msg.FabricId = fabricId;
msg.AccountId = accountId;
msg.AccountIdLen = accountIdLen;
msg.PairingToken = pairingToken;
msg.PairingTokenLen = pairingTokenLen;
msg.PairingInitData = pairingInitData;
msg.PairingInitDataLen = pairingInitDataLen;
msg.DeviceInitData = deviceInitData;
msg.DeviceInitDataLen = deviceInitDataLen;
// Allocate a buffer for the message.
msgBuf = PacketBuffer::NewWithAvailableSize(msgLen);
VerifyOrExit(msgBuf != NULL, err = WEAVE_ERROR_NO_MEMORY);
// Encode the message.
err = msg.Encode(msgBuf);
SuccessOrExit(err);
// Allocate and initialize an exchange context.
err = binding->NewExchangeContext(mCurServerOp);
SuccessOrExit(err);
mCurServerOp->AppState = this;
mCurServerOp->OnMessageReceived = HandleServerResponse;
if (mCurServerOp->ResponseTimeout == 0)
{
mCurServerOp->ResponseTimeout = WEAVE_CONFIG_SERVICE_PROV_RESPONSE_TIMEOUT;
}
mCurServerOp->OnResponseTimeout = HandleServerResponseTimeout;
mCurServerOp->OnConnectionClosed = HandleServerConnectionClosed;
mCurServerOp->OnKeyError = HandleServerKeyError;
#if WEAVE_CONFIG_ENABLE_RELIABLE_MESSAGING
mCurServerOp->OnSendError = HandleServerSendError;
#endif // #if WEAVE_CONFIG_ENABLE_RELIABLE_MESSAGING
// Record that a PairDeviceToAccount request is outstanding.
mServerOpState = kServerOpState_PairDeviceToAccount;
// TODO: [TT] Not needed for Jay's new bindings WEAV-1870.
flags = (mCurServerOp->Con == NULL) ? ExchangeContext::kSendFlag_RequestAck : 0;
// Send the PairDeviceToAccount message to the service.
err = mCurServerOp->SendMessage(kWeaveProfile_ServiceProvisioning, kMsgType_PairDeviceToAccount, msgBuf, flags);
msgBuf = NULL;
exit:
if (msgBuf != NULL)
PacketBuffer::Free(msgBuf);
if (err != WEAVE_NO_ERROR)
{
mServerOpState = kServerOpState_Idle;
if (mCurServerOp != NULL)
{
mCurServerOp->Close();
mCurServerOp = NULL;
}
}
return err;
}
void ServiceProvisioningServer::HandleClientRequest(ExchangeContext *ec, const IPPacketInfo *pktInfo, const WeaveMessageInfo *msgInfo,
uint32_t profileId, uint8_t msgType, PacketBuffer *payload)
{
WEAVE_ERROR err = WEAVE_NO_ERROR;
ServiceProvisioningServer *server = (ServiceProvisioningServer *) ec->AppState;
ServiceProvisioningDelegate *delegate = server->mDelegate;
uint16_t dataLen = payload->DataLength();
uint8_t *p = payload->Start();
// Fail messages for the wrong profile. This shouldn't happen, but better safe than sorry.
if (profileId != kWeaveProfile_ServiceProvisioning)
{
WeaveServerBase::SendStatusReport(ec, kWeaveProfile_Common, Common::kStatus_BadRequest, WEAVE_NO_ERROR);
ec->Close();
goto exit;
}
// Call on the delegate to enforce message-level access control. If policy dictates the message should NOT
// be processed, then simply end the exchange and return. If an error response was warranted, the appropriate
// response will have been sent within EnforceAccessControl().
if (!server->EnforceAccessControl(ec, profileId, msgType, msgInfo, server->mDelegate))
{
ec->Close();
ExitNow();
}
// Disallow simultaneous requests.
if (server->mCurClientOp != NULL)
{
WeaveServerBase::SendStatusReport(ec, kWeaveProfile_Common, Common::kStatus_Busy, WEAVE_NO_ERROR);
ec->Close();
goto exit;
}
// Record that we have a request in process.
server->mCurClientOp = ec;
// Decode and dispatch the message.
switch (msgType)
{
case kMsgType_RegisterServicePairAccount:
err = RegisterServicePairAccountMessage::Decode(payload, server->mCurClientOpMsg.RegisterServicePairAccount);
SuccessOrExit(err);
server->mCurClientOpBuf = payload;
payload = NULL;
err = delegate->HandleRegisterServicePairAccount(server->mCurClientOpMsg.RegisterServicePairAccount);
SuccessOrExit(err);
break;
case kMsgType_UpdateService:
err = UpdateServiceMessage::Decode(payload, server->mCurClientOpMsg.UpdateService);
SuccessOrExit(err);
server->mCurClientOpBuf = payload;
payload = NULL;
err = delegate->HandleUpdateService(server->mCurClientOpMsg.UpdateService);
SuccessOrExit(err);
break;
case kMsgType_UnregisterService:
{
uint64_t serviceId;
VerifyOrExit(dataLen == 8, err = WEAVE_ERROR_INVALID_MESSAGE_LENGTH);
serviceId = LittleEndian::Get64(p);
err = delegate->HandleUnregisterService(serviceId);
SuccessOrExit(err);
break;
}
default:
server->SendStatusReport(kWeaveProfile_Common, Common::kStatus_BadRequest);
break;
}
exit:
if (payload != NULL)
PacketBuffer::Free(payload);
if (err != WEAVE_NO_ERROR && server->mCurClientOp != NULL && ec == server->mCurClientOp)
{
uint16_t statusCode = (err == WEAVE_ERROR_INVALID_MESSAGE_LENGTH)
? Common::kStatus_BadRequest
: Common::kStatus_InternalError;
server->SendStatusReport(kWeaveProfile_Common, statusCode, err);
}
}
void ServiceProvisioningServer::HandleServerResponse(ExchangeContext *ec, const IPPacketInfo *pktInfo, const WeaveMessageInfo *msgInfo,
uint32_t profileId, uint8_t msgType, PacketBuffer *payload)
{
WEAVE_ERROR err = WEAVE_NO_ERROR;
WEAVE_ERROR delegateErr = WEAVE_NO_ERROR;
StatusReport statusReport;
ServiceProvisioningServer *server = (ServiceProvisioningServer *) ec->AppState;
// Sanity check that the passed-in exchange context is in fact the one that represents the current
// outstanding server operation. If not, it'll get closed at exit. If it does match, we'll null ec
// to prevent it from getting closed at exit.
VerifyOrExit(ec == server->mCurServerOp, err = WEAVE_NO_ERROR);
ec = NULL;
// Verify the message is expected.
VerifyOrExit((profileId == kWeaveProfile_Common && msgType == nl::Weave::Profiles::Common::kMsgType_StatusReport) ||
(profileId == kWeaveProfile_StatusReport_Deprecated),
err = WEAVE_ERROR_INVALID_MESSAGE_TYPE);
err = StatusReport::parse(payload, statusReport);
SuccessOrExit(err);
// Free the payload here to reduce buffer pressure.
PacketBuffer::Free(payload);
payload = NULL;
delegateErr = (statusReport.mProfileId == kWeaveProfile_Common && statusReport.mStatusCode == Common::kStatus_Success)
? WEAVE_NO_ERROR
: WEAVE_ERROR_STATUS_REPORT_RECEIVED;
server->HandlePairDeviceToAccountResult(delegateErr, statusReport.mProfileId, statusReport.mStatusCode);
exit:
// Free the payload first to reduce buffer pressure.
if (payload != NULL)
PacketBuffer::Free(payload);
if (err != WEAVE_NO_ERROR)
server->HandlePairDeviceToAccountResult(err, 0, 0);
if (ec != NULL)
ec->Close();
}
void ServiceProvisioningServer::HandleServerResponseTimeout(ExchangeContext *ec)
{
ServiceProvisioningServer *server = (ServiceProvisioningServer *) ec->AppState;
// Sanity check that the passed-in exchange context is in fact the one that represents the current
// outstanding server operation. If not, it'll get closed at exit. If it does match, we'll null ec
// to prevent it from getting closed at exit.
VerifyOrExit(ec == server->mCurServerOp, );
ec = NULL;
server->HandlePairDeviceToAccountResult(WEAVE_ERROR_TIMEOUT, 0, 0);
exit:
if (ec != NULL)
ec->Close();
}
void ServiceProvisioningServer::HandleServerConnectionClosed(ExchangeContext *ec, WeaveConnection *con, WEAVE_ERROR conErr)
{
ServiceProvisioningServer *server = (ServiceProvisioningServer *) ec->AppState;
// Sanity check that the passed-in exchange context is in fact the one that represents the current
// outstanding server operation. If not, it'll get closed at exit. If it does match, we'll null ec
// to prevent it from getting closed at exit.
VerifyOrExit(ec == server->mCurServerOp, );
ec = NULL;
// No error on connection close means the service simply closed the connection without responding. In that case
// deliver WEAVE_ERROR_CONNECTION_CLOSED_UNEXPECTEDLY to the delegate.
if (conErr == WEAVE_NO_ERROR)
conErr = WEAVE_ERROR_CONNECTION_CLOSED_UNEXPECTEDLY;
server->HandlePairDeviceToAccountResult(conErr, 0, 0);
exit:
if (ec != NULL)
ec->Close();
}
void ServiceProvisioningServer::HandleServerKeyError(ExchangeContext *ec, WEAVE_ERROR keyErr)
{
ServiceProvisioningServer *server = (ServiceProvisioningServer *) ec->AppState;
// Sanity check that the passed-in exchange context is in fact the one that represents the current
// outstanding server operation. If not, it'll get closed at exit. If it does match, we'll null ec
// to prevent it from getting closed at exit.
VerifyOrExit(ec == server->mCurServerOp, );
ec = NULL;
server->HandlePairDeviceToAccountResult(keyErr, 0, 0);
exit:
if (ec != NULL)
ec->Close();
}
#if WEAVE_CONFIG_ENABLE_RELIABLE_MESSAGING
void ServiceProvisioningServer::HandleServerSendError(ExchangeContext *ec, WEAVE_ERROR err, void *msgCtxt)
{
ServiceProvisioningServer *server = (ServiceProvisioningServer *) ec->AppState;
// Sanity check that the passed-in exchange context is in fact the one that represents the current
// outstanding server operation. If not, it'll get closed at exit. If it does match, we'll null ec
// to prevent it from getting closed at exit.
VerifyOrExit(ec == server->mCurServerOp, );
ec = NULL;
server->HandlePairDeviceToAccountResult(err, 0, 0);
exit:
if (ec != NULL)
ec->Close();
}
#endif // #if WEAVE_CONFIG_ENABLE_RELIABLE_MESSAGING
void ServiceProvisioningServer::HandlePairDeviceToAccountResult(WEAVE_ERROR localErr, uint32_t serverStatusProfileId, uint16_t serverStatusCode)
{
mServerOpState = kServerOpState_Idle;
mCurServerOp->Close();
mCurServerOp = NULL;
if (mDelegate)
{
mDelegate->HandlePairDeviceToAccountResult(localErr, serverStatusProfileId, serverStatusCode);
}
}
bool ServiceProvisioningServer::IsValidServiceConfig(const uint8_t *serviceConfig, uint16_t serviceConfigLen)
{
WEAVE_ERROR err;
nl::Weave::TLV::TLVReader reader;
reader.Init(serviceConfig, serviceConfigLen);
err = reader.Next();
SuccessOrExit(err);
VerifyOrExit(reader.GetTag() == ProfileTag(kWeaveProfile_ServiceProvisioning, kTag_ServiceConfig), err = WEAVE_ERROR_INVALID_TLV_TAG);
{
TLVType topLevelContainer;
bool caCertsPresent = false, dirEndPointPresent = false;
err = reader.EnterContainer(topLevelContainer);
SuccessOrExit(err);
while ((err = reader.Next()) == WEAVE_NO_ERROR)
{
uint64_t elemTag = reader.GetTag();
if (!IsContextTag(elemTag))
continue;
switch (TagNumFromTag(elemTag))
{
case kTag_ServiceConfig_CACerts:
VerifyOrExit(reader.GetType() == kTLVType_Array, err = WEAVE_ERROR_INVALID_TLV_ELEMENT);
caCertsPresent = true;
break;
case kTag_ServiceConfig_DirectoryEndPoint:
VerifyOrExit(reader.GetType() == kTLVType_Structure, err = WEAVE_ERROR_INVALID_TLV_ELEMENT);
dirEndPointPresent = true;
break;
default:
// Ignore unknown elements
break;
}
}
if (err != WEAVE_END_OF_TLV)
ExitNow();
err = reader.ExitContainer(topLevelContainer);
SuccessOrExit(err);
VerifyOrExit(caCertsPresent && dirEndPointPresent, err = WEAVE_ERROR_MISSING_TLV_ELEMENT);
}
exit:
return err == WEAVE_NO_ERROR;
}
void ServiceProvisioningDelegate::EnforceAccessControl(ExchangeContext *ec, uint32_t msgProfileId, uint8_t msgType,
const WeaveMessageInfo *msgInfo, AccessControlResult& result)
{
enum { kServiceProvisioningEndpointId = 0x18B4300200000010ull };
// If the result has not already been determined by a subclass...
if (result == kAccessControlResult_NotDetermined)
{
switch (msgType)
{
#if WEAVE_CONFIG_REQUIRE_AUTH_SERVICE_PROV
case kMsgType_RegisterServicePairAccount:
if (msgInfo->PeerAuthMode == kWeaveAuthMode_PASE_PairingCode && !IsPairedToAccount())
{
result = kAccessControlResult_Accepted;
}
break;
case kMsgType_UpdateService:
if (msgInfo->PeerAuthMode == kWeaveAuthMode_CASE_AccessToken)
{
result = kAccessControlResult_Accepted;
}
break;
case kMsgType_UnregisterService:
if (msgInfo->PeerAuthMode == kWeaveAuthMode_CASE_AccessToken ||
(msgInfo->PeerAuthMode == kWeaveAuthMode_CASE_ServiceEndPoint && msgInfo->SourceNodeId == kServiceProvisioningEndpointId))
{
result = kAccessControlResult_Accepted;
}
break;
#else // WEAVE_CONFIG_REQUIRE_AUTH_SERVICE_PROV
case kMsgType_RegisterServicePairAccount:
case kMsgType_UpdateService:
case kMsgType_UnregisterService:
result = kAccessControlResult_Accepted;
break;
#endif // WEAVE_CONFIG_REQUIRE_AUTH_SERVICE_PROV
default:
WeaveServerBase::SendStatusReport(ec, kWeaveProfile_Common, Common::kStatus_UnsupportedMessage, WEAVE_NO_ERROR);
result = kAccessControlResult_Rejected_RespSent;
break;
}
}
// Call up to the base class.
WeaveServerDelegateBase::EnforceAccessControl(ec, msgProfileId, msgType, msgInfo, result);
}
// TODO: eliminate this method when device code provides appropriate implementations.
bool ServiceProvisioningDelegate::IsPairedToAccount() const
{
return false;
}
} // ServiceProvisioning
} // namespace Profiles
} // namespace Weave
} // namespace nl