| /* |
| * |
| * 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::Profiles; |
| using namespace ::nl::Weave::Profiles::ServiceProvisioning; |
| |
| namespace nl { |
| namespace Weave { |
| namespace DeviceLayer { |
| namespace Internal { |
| |
| 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_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[Profiles::DeviceDescription::WeaveDeviceDescriptor::kMaxEncodedTLVLength]; // 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; |
| } |
| |
| // 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); |
| } |
| } |
| |
| 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; |
| } |
| } |
| |
| #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 |