blob: 95d21878357090b34ac7b81aa27a8da007982c88 [file] [log] [blame]
/*
*
* Copyright (c) 2019 Google LLC.
* Copyright (c) 2018 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.
*/
#include <Weave/DeviceLayer/internal/WeaveDeviceLayerInternal.h>
#include <Weave/DeviceLayer/internal/ServiceProvisioningServer.h>
#include <Weave/Profiles/common/CommonProfile.h>
using namespace ::nl;
using namespace ::nl::Weave;
using namespace ::nl::Weave::TLV;
using namespace ::nl::Weave::Profiles;
using namespace ::nl::Weave::Profiles::ServiceProvisioning;
using namespace ::nl::Weave::Profiles::Security;
namespace nl {
namespace Weave {
namespace DeviceLayer {
namespace Internal {
using ::nl::Weave::Platform::Security::MemoryAlloc;
using ::nl::Weave::Platform::Security::MemoryFree;
ServiceProvisioningServer ServiceProvisioningServer::sInstance;
WEAVE_ERROR ServiceProvisioningServer::Init(void)
{
WEAVE_ERROR err;
// Call init on the server base class.
err = ServerBaseClass::Init(&::nl::Weave::DeviceLayer::ExchangeMgr);
SuccessOrExit(err);
// Set the pointer to the delegate object.
SetDelegate(this);
mProvServiceBinding = NULL;
mWaitingForServiceConnectivity = false;
exit:
return err;
}
WEAVE_ERROR ServiceProvisioningServer::HandleRegisterServicePairAccount(RegisterServicePairAccountMessage & msg)
{
WEAVE_ERROR err;
uint64_t curServiceId;
// Check if a service is already provisioned. If so respond with "Too Many Services".
err = ConfigurationMgr().GetServiceId(curServiceId);
if (err == WEAVE_NO_ERROR)
{
err = sInstance.SendStatusReport(kWeaveProfile_ServiceProvisioning,
(curServiceId == msg.ServiceId) ? kStatusCode_ServiceAlreadyRegistered : kStatusCode_TooManyServices);
ExitNow();
}
if (err == WEAVE_DEVICE_ERROR_CONFIG_NOT_FOUND)
{
err = WEAVE_NO_ERROR;
}
SuccessOrExit(err);
// Validate the service config. We don't want to get any further along before making sure the data is good.
if (!ServiceProvisioningServer::IsValidServiceConfig(msg.ServiceConfig, msg.ServiceConfigLen))
{
err = sInstance.SendStatusReport(kWeaveProfile_ServiceProvisioning, kStatusCode_InvalidServiceConfig);
ExitNow();
}
WeaveLogProgress(DeviceLayer, "Registering new service: %" PRIx64 " (account id %*s)", msg.ServiceId, (int)msg.AccountIdLen, msg.AccountId);
// Store the service id and the service config in persistent storage.
err = ConfigurationMgr().StoreServiceProvisioningData(msg.ServiceId, msg.ServiceConfig, msg.ServiceConfigLen, NULL, 0);
SuccessOrExit(err);
// Post an event alerting other subsystems to the change in the service provisioning state.
{
WeaveDeviceEvent event;
event.Type = DeviceEventType::kServiceProvisioningChange;
event.ServiceProvisioningChange.IsServiceProvisioned = true;
event.ServiceProvisioningChange.ServiceConfigUpdated = false;
PlatformMgr().PostEvent(&event);
}
#if WEAVE_DEVICE_CONFIG_ENABLE_OPERATIONAL_DEVICE_CREDENTIALS
// Initiate the process of sending a GetCertificateRequest to the Certificate Provisioning service.
PlatformMgr().ScheduleWork(AsyncStartCertificateProvisioning);
#elif !WEAVE_DEVICE_CONFIG_DISABLE_ACCOUNT_PAIRING
// Initiate the process of sending a PairDeviceToAccount request to the Service Provisioning service.
PlatformMgr().ScheduleWork(AsyncStartPairDeviceToAccount);
#else // !WEAVE_DEVICE_CONFIG_DISABLE_ACCOUNT_PAIRING
// Store the account id in persistent storage.
err = ConfigurationMgr().StorePairedAccountId(msg.AccountId, msg.AccountIdLen);
SuccessOrExit(err);
// Post an event alerting other subsystems that the device is now paired to an account.
{
WeaveDeviceEvent event;
event.Type = DeviceEventType::kAccountPairingChange;
event.AccountPairingChange.IsPairedToAccount = true;
PlatformMgr().PostEvent(&event);
}
// Send a success StatusReport for the RegisterServicePairDevice request.
SendSuccessResponse();
#endif // !WEAVE_DEVICE_CONFIG_DISABLE_ACCOUNT_PAIRING
exit:
return err;
}
WEAVE_ERROR ServiceProvisioningServer::HandleUpdateService(UpdateServiceMessage& msg)
{
WEAVE_ERROR err;
uint64_t curServiceId;
// Verify that the service id matches the existing service. If not respond with "No Such Service".
err = ConfigurationMgr().GetServiceId(curServiceId);
if (err == WEAVE_DEVICE_ERROR_CONFIG_NOT_FOUND || curServiceId != msg.ServiceId)
{
err = sInstance.SendStatusReport(kWeaveProfile_ServiceProvisioning, kStatusCode_NoSuchService);
ExitNow();
}
SuccessOrExit(err);
// Validate the service config. We don't want to get any further along before making sure the data is good.
if (!ServiceProvisioningServer::IsValidServiceConfig(msg.ServiceConfig, msg.ServiceConfigLen))
{
err = sInstance.SendStatusReport(kWeaveProfile_ServiceProvisioning, kStatusCode_InvalidServiceConfig);
ExitNow();
}
// Save the new service configuration in device persistent storage, replacing the existing value.
err = ConfigurationMgr().StoreServiceConfig(msg.ServiceConfig, msg.ServiceConfigLen);
SuccessOrExit(err);
// Post an event alerting other subsystems that the service config has changed.
{
WeaveDeviceEvent event;
event.Type = DeviceEventType::kServiceProvisioningChange;
event.ServiceProvisioningChange.IsServiceProvisioned = true;
event.ServiceProvisioningChange.ServiceConfigUpdated = true;
PlatformMgr().PostEvent(&event);
}
// Send "Success" back to the requestor.
err = sInstance.SendSuccessResponse();
SuccessOrExit(err);
exit:
return err;
}
WEAVE_ERROR ServiceProvisioningServer::HandleUnregisterService(uint64_t serviceId)
{
WEAVE_ERROR err;
uint64_t curServiceId;
// Verify that the service id matches the existing service. If not respond with "No Such Service".
err = ConfigurationMgr().GetServiceId(curServiceId);
if (err == WEAVE_DEVICE_ERROR_CONFIG_NOT_FOUND || curServiceId != serviceId)
{
err = sInstance.SendStatusReport(kWeaveProfile_ServiceProvisioning, kStatusCode_NoSuchService);
ExitNow();
}
SuccessOrExit(err);
// Clear the persisted service provisioning data, if present.
err = ConfigurationMgr().ClearServiceProvisioningData();
SuccessOrExit(err);
// Send "Success" back to the requestor.
err = sInstance.SendSuccessResponse();
SuccessOrExit(err);
exit:
return err;
}
bool ServiceProvisioningServer::IsPairedToAccount(void) const
{
return ConfigurationMgr().IsServiceProvisioned() && ConfigurationMgr().IsPairedToAccount();
}
void ServiceProvisioningServer::OnPlatformEvent(const WeaveDeviceEvent * event)
{
#if !WEAVE_DEVICE_CONFIG_DISABLE_ACCOUNT_PAIRING
// If a tunnel to the service has been established...
// OR if service connectivity has been established (e.g. via Thread)...
if ((event->Type == DeviceEventType::kServiceTunnelStateChange &&
event->ServiceTunnelStateChange.Result == kConnectivity_Established) ||
(event->Type == DeviceEventType::kServiceConnectivityChange &&
event->ServiceConnectivityChange.Overall.Result == kConnectivity_Established))
{
// If a RegisterServicePairAccount request is pending and the system is waiting for
// the service connectivity to be established, initiate the PairDeviceToAccount request now.
if (mCurClientOp != NULL && mWaitingForServiceConnectivity)
{
StartPairDeviceToAccount();
}
}
#endif // !WEAVE_DEVICE_CONFIG_DISABLE_ACCOUNT_PAIRING
}
#if !WEAVE_DEVICE_CONFIG_DISABLE_ACCOUNT_PAIRING
void ServiceProvisioningServer::StartPairDeviceToAccount(void)
{
WEAVE_ERROR err = WEAVE_NO_ERROR;
// If the system does not currently have a tunnel established with the service,
// AND the system does not have service connectivity by some other means (e.g. Thread)
// wait a period of time for connectivity to be established.
if (!ConnectivityMgr().HaveServiceConnectivity() && !ConnectivityMgr().IsServiceTunnelConnected())
{
mWaitingForServiceConnectivity = true;
err = SystemLayer.StartTimer(WEAVE_DEVICE_CONFIG_SERVICE_PROVISIONING_CONNECTIVITY_TIMEOUT,
HandleServiceConnectivityTimeout,
NULL);
SuccessOrExit(err);
ExitNow();
WeaveLogProgress(DeviceLayer, "Waiting for service connectivity to complete RegisterServicePairDevice action");
}
mWaitingForServiceConnectivity = false;
SystemLayer.CancelTimer(HandleServiceConnectivityTimeout, NULL);
WeaveLogProgress(DeviceLayer, "Initiating communication with Service Provisioning service");
// Create a binding and begin the process of preparing it for talking to the Service Provisioning
// service. When this completes HandleProvServiceBindingEvent will be called with a BindingReady event.
mProvServiceBinding = ExchangeMgr->NewBinding(HandleProvServiceBindingEvent, NULL);
VerifyOrExit(mProvServiceBinding != NULL, err = WEAVE_ERROR_NO_MEMORY);
err = mProvServiceBinding->BeginConfiguration()
.Target_ServiceEndpoint(WEAVE_DEVICE_CONFIG_SERVICE_PROVISIONING_ENDPOINT_ID)
.Transport_UDP_WRM()
.Exchange_ResponseTimeoutMsec(WEAVE_DEVICE_CONFIG_SERVICE_PROVISIONING_REQUEST_TIMEOUT)
.Security_SharedCASESession()
.PrepareBinding();
SuccessOrExit(err);
exit:
if (err != WEAVE_NO_ERROR)
{
HandlePairDeviceToAccountResult(err, kWeaveProfile_Common, Profiles::Common::kStatus_InternalServerProblem);
}
}
void ServiceProvisioningServer::SendPairDeviceToAccountRequest(void)
{
WEAVE_ERROR err = WEAVE_NO_ERROR;
const RegisterServicePairAccountMessage & regServiceMsg = mCurClientOpMsg.RegisterServicePairAccount;
uint8_t devDesc[100]; // TODO: make configurable
size_t devDescLen;
// Generate a device descriptor for the local device in TLV.
err = ConfigurationMgr().GetDeviceDescriptorTLV(devDesc, sizeof(devDesc), devDescLen);
SuccessOrExit(err);
// Call up to a helper function the server base class to encode and send a PairDeviceToAccount request to
// the Service Provisioning service. This will ultimately result in a call to HandlePairDeviceToAccountResult
// with the result.
//
// Pass through the values for Service Id, Account Id, Pairing Token and Pairing Init Data that
// were received in the Register Service message. For Device Init Data, pass the encoded device
// descriptor. Finally, pass the id of the Weave fabric for which the device is a member.
//
WeaveLogProgress(DeviceLayer, "Sending PairDeviceToAccount request to Service Provisioning service");
err = ServerBaseClass::SendPairDeviceToAccountRequest(mProvServiceBinding,
regServiceMsg.ServiceId, FabricState->FabricId,
regServiceMsg.AccountId, regServiceMsg.AccountIdLen,
regServiceMsg.PairingToken, regServiceMsg.PairingTokenLen,
regServiceMsg.PairingInitData, regServiceMsg.PairingInitDataLen,
devDesc, devDescLen);
SuccessOrExit(err);
exit:
if (err != WEAVE_NO_ERROR)
{
HandlePairDeviceToAccountResult(err, kWeaveProfile_Common, Profiles::Common::kStatus_InternalServerProblem);
}
}
void ServiceProvisioningServer::HandlePairDeviceToAccountResult(WEAVE_ERROR err, uint32_t statusReportProfileId, uint16_t statusReportStatusCode)
{
// Close the binding if necessary.
if (mProvServiceBinding != NULL)
{
mProvServiceBinding->Close();
mProvServiceBinding = NULL;
}
#if WEAVE_DEVICE_CONFIG_ENABLE_OPERATIONAL_DEVICE_CREDENTIALS
// If necessary, free memory allocated for the certificate provisioning engine.
if (mCertProvClient != NULL)
{
MemoryFree(mCertProvClient);
mCertProvClient = NULL;
}
#endif // WEAVE_DEVICE_CONFIG_ENABLE_OPERATIONAL_DEVICE_CREDENTIALS
// Return immediately if for some reason the client's RegisterServicePairAccount request
// is no longer pending. Note that, even if the PairDeviceToAccount request succeeded,
// the device must clear the persisted service configuration in this case because it has
// lost access to the account id (which was in the RegisterServicePairAccount message)
// and therefore cannot complete the process of registering the service.
if (mCurClientOp == NULL)
{
ExitNow(err = WEAVE_ERROR_INCORRECT_STATE);
}
// If the PairDeviceToAccount request was successful...
if (err == WEAVE_NO_ERROR)
{
const RegisterServicePairAccountMessage & regServiceMsg = mCurClientOpMsg.RegisterServicePairAccount;
// Store the account id in persistent storage. This is the final step of registering a
// service and marks that the device is properly associated with a user's account.
err = ConfigurationMgr().StorePairedAccountId(regServiceMsg.AccountId, regServiceMsg.AccountIdLen);
SuccessOrExit(err);
// Post an event alerting other subsystems that the device is now paired to an account.
{
WeaveDeviceEvent event;
event.Type = DeviceEventType::kAccountPairingChange;
event.AccountPairingChange.IsPairedToAccount = true;
PlatformMgr().PostEvent(&event);
}
WeaveLogProgress(DeviceLayer, "PairDeviceToAccount request completed successfully");
// Send a success StatusReport back to the client.
err = SendSuccessResponse();
SuccessOrExit(err);
}
exit:
if (err != WEAVE_NO_ERROR)
{
WeaveLogError(DeviceLayer, "PairDeviceToAccount request failed with %s: %s",
(err == WEAVE_ERROR_STATUS_REPORT_RECEIVED) ? "status report from service" : "local error",
(err == WEAVE_ERROR_STATUS_REPORT_RECEIVED)
? ::nl::StatusReportStr(statusReportProfileId, statusReportStatusCode)
: ::nl::ErrorStr(err));
// Since we're failing the RegisterServicePairDevice request, clear the persisted service configuration.
ConfigurationMgr().ClearServiceProvisioningData();
// Choose an appropriate StatusReport to return if not already given.
if (statusReportProfileId == 0 && statusReportStatusCode == 0)
{
if (err == WEAVE_ERROR_TIMEOUT)
{
statusReportProfileId = kWeaveProfile_ServiceProvisioning;
statusReportStatusCode = Profiles::ServiceProvisioning::kStatusCode_ServiceCommunicationError;
}
else
{
statusReportProfileId = kWeaveProfile_Common;
statusReportStatusCode = Profiles::Common::kStatus_InternalServerProblem;
}
}
// Send an error StatusReport back to the client. Only include the local error code if it isn't
// WEAVE_ERROR_STATUS_REPORT_RECEIVED.
SendStatusReport(statusReportProfileId, statusReportStatusCode,
(err != WEAVE_ERROR_STATUS_REPORT_RECEIVED) ? err : WEAVE_NO_ERROR);
}
}
#if WEAVE_DEVICE_CONFIG_ENABLE_OPERATIONAL_DEVICE_CREDENTIALS
void ServiceProvisioningServer::AsyncStartCertificateProvisioning(intptr_t arg)
{
WEAVE_ERROR err = WEAVE_NO_ERROR;
// Allocate temporary memory to hold the certificate provisioning client.
sInstance.mCertProvClient = static_cast<CertificateProvisioningClient *>(MemoryAlloc(sizeof(CertificateProvisioningClient)));
VerifyOrExit(sInstance.mCertProvClient != NULL, err = WEAVE_ERROR_NO_MEMORY);
// Initialize certificate provisioning client.
err = sInstance.mCertProvClient->Init(WeaveCertProvEngine::kReqType_GetInitialOpDeviceCert, sInstance.EncodeGetCertificateRequestAuthInfo);
SuccessOrExit(err);
// Start certificate provisioning.
sInstance.mCertProvClient->StartCertificateProvisioning();
exit:
if (err != WEAVE_NO_ERROR)
{
MemoryFree(sInstance.mCertProvClient);
sInstance.mCertProvClient = NULL;
sInstance.HandlePairDeviceToAccountResult(err, kWeaveProfile_Common, Profiles::Common::kStatus_InternalServerProblem);
}
}
#endif // WEAVE_DEVICE_CONFIG_ENABLE_OPERATIONAL_DEVICE_CREDENTIALS
void ServiceProvisioningServer::AsyncStartPairDeviceToAccount(intptr_t arg)
{
sInstance.StartPairDeviceToAccount();
}
void ServiceProvisioningServer::HandleServiceConnectivityTimeout(System::Layer * /* unused */, void * /* unused */, System::Error /* unused */)
{
sInstance.HandlePairDeviceToAccountResult(WEAVE_ERROR_TIMEOUT, 0, 0);
}
void ServiceProvisioningServer::HandleProvServiceBindingEvent(void * appState, Binding::EventType eventType,
const Binding::InEventParam & inParam, Binding::OutEventParam & outParam)
{
uint32_t statusReportProfileId;
uint16_t statusReportStatusCode;
switch (eventType)
{
case Binding::kEvent_BindingReady:
sInstance.SendPairDeviceToAccountRequest();
break;
case Binding::kEvent_PrepareFailed:
if (inParam.PrepareFailed.StatusReport != NULL)
{
statusReportProfileId = inParam.PrepareFailed.StatusReport->mProfileId;
statusReportStatusCode = inParam.PrepareFailed.StatusReport->mStatusCode;
}
else
{
statusReportProfileId = kWeaveProfile_ServiceProvisioning;
statusReportStatusCode = Profiles::ServiceProvisioning::kStatusCode_ServiceCommunicationError;
}
sInstance.HandlePairDeviceToAccountResult(inParam.PrepareFailed.Reason,
statusReportProfileId, statusReportStatusCode);
break;
default:
Binding::DefaultEventHandler(appState, eventType, inParam, outParam);
break;
}
}
WEAVE_ERROR ServiceProvisioningServer::EncodeGetCertificateRequestAuthInfo(TLVWriter & writer)
{
WEAVE_ERROR err = WEAVE_NO_ERROR;
const RegisterServicePairAccountMessage & regServiceMsg = sInstance.mCurClientOpMsg.RegisterServicePairAccount;
// Encode pairing token.
err = writer.PutBytes(ContextTag(kTag_GetCertReqMsg_Authorize_PairingToken), regServiceMsg.PairingToken, regServiceMsg.PairingTokenLen);
SuccessOrExit(err);
// Encode pairing initialization data.
err = writer.PutBytes(ContextTag(kTag_GetCertReqMsg_Authorize_PairingInitData), regServiceMsg.PairingInitData, regServiceMsg.PairingInitDataLen);
SuccessOrExit(err);
exit:
return err;
}
#else // !WEAVE_DEVICE_CONFIG_DISABLE_ACCOUNT_PAIRING
void ServiceProvisioningServer::HandlePairDeviceToAccountResult(WEAVE_ERROR err, uint32_t statusReportProfileId, uint16_t statusReportStatusCode)
{
}
#endif // !WEAVE_DEVICE_CONFIG_DISABLE_ACCOUNT_PAIRING
#if WEAVE_CONFIG_ENABLE_IFJ_SERVICE_FABRIC_JOIN
void ServiceProvisioningServer::HandleIFJServiceFabricJoinResult(WEAVE_ERROR err, uint32_t statusReportProfileId, uint16_t statusReportStatusCode)
{
}
#endif // WEAVE_CONFIG_ENABLE_IFJ_SERVICE_FABRIC_JOIN
} // namespace Internal
} // namespace DeviceLayer
} // namespace Weave
} // namespace nl