blob: 451b33231cdc224290de03ac32d0dad83b19bcf5 [file] [log] [blame]
/*
*
* Copyright (c) 2020 Project CHIP Authors
* 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 contains implementation of Device class. The objects of this
* class will be used by Controller applications to interact with CHIP
* devices. The class provides mechanism to construct, send and receive
* messages to and from the corresponding CHIP devices.
*/
#include <controller/CHIPDevice.h>
#if CONFIG_DEVICE_LAYER
#include <platform/CHIPDeviceLayer.h>
#endif
#include <core/CHIPCore.h>
#include <core/CHIPEncoding.h>
#include <core/CHIPSafeCasts.h>
#include <support/Base64.h>
#include <support/CHIPMem.h>
#include <support/CodeUtils.h>
#include <support/ErrorStr.h>
#include <support/SafeInt.h>
#include <support/logging/CHIPLogging.h>
using namespace chip::Inet;
using namespace chip::System;
using namespace chip::Callback;
namespace chip {
namespace Controller {
CHIP_ERROR Device::SendMessage(System::PacketBuffer * buffer)
{
CHIP_ERROR err = CHIP_NO_ERROR;
System::PacketBuffer * resend = nullptr;
VerifyOrExit(mSessionManager != nullptr, err = CHIP_ERROR_INCORRECT_STATE);
VerifyOrExit(buffer != nullptr, err = CHIP_ERROR_INVALID_ARGUMENT);
// If there is no secure connection to the device, try establishing it
if (mState != ConnectionState::SecureConnected)
{
err = LoadSecureSessionParameters();
SuccessOrExit(err);
}
else
{
// Secure connection already exists
// Hold on to the buffer, in case session resumption and resend is needed
buffer->AddRef();
resend = buffer;
}
err = mSessionManager->SendMessage(mDeviceId, buffer);
buffer = nullptr;
ChipLogDetail(Controller, "SendMessage returned %d", err);
// The send could fail due to network timeouts (e.g. broken pipe)
// Try sesion resumption if needed
if (err != CHIP_NO_ERROR && resend != nullptr && mState == ConnectionState::SecureConnected)
{
mState = ConnectionState::NotConnected;
err = LoadSecureSessionParameters();
SuccessOrExit(err);
err = mSessionManager->SendMessage(mDeviceId, resend);
resend = nullptr;
ChipLogDetail(Controller, "Re-SendMessage returned %d", err);
SuccessOrExit(err);
}
exit:
if (buffer != nullptr)
{
PacketBuffer::Free(buffer);
}
if (resend != nullptr)
{
PacketBuffer::Free(resend);
}
return err;
}
CHIP_ERROR Device::SendMessage(System::PacketBufferHandle message)
{
return SendMessage(message.Release_ForNow());
}
CHIP_ERROR Device::Serialize(SerializedDevice & output)
{
CHIP_ERROR error = CHIP_NO_ERROR;
uint16_t serializedLen = 0;
SerializableDevice serializable;
nlSTATIC_ASSERT_PRINT(BASE64_ENCODED_LEN(sizeof(serializable)) <= sizeof(output.inner),
"Size of serializable should be <= size of output");
CHIP_ZERO_AT(serializable);
memmove(&serializable.mOpsCreds, &mPairing, sizeof(mPairing));
serializable.mDeviceId = Encoding::LittleEndian::HostSwap64(mDeviceId);
serializable.mDevicePort = Encoding::LittleEndian::HostSwap16(mDevicePort);
nlSTATIC_ASSERT_PRINT(sizeof(serializable.mDeviceAddr) <= INET6_ADDRSTRLEN,
"Size of device address must fit within INET6_ADDRSTRLEN");
mDeviceAddr.ToString(Uint8::to_char(serializable.mDeviceAddr), sizeof(serializable.mDeviceAddr));
serializedLen = chip::Base64Encode(Uint8::to_const_uchar(reinterpret_cast<uint8_t *>(&serializable)),
static_cast<uint16_t>(sizeof(serializable)), Uint8::to_char(output.inner));
VerifyOrExit(serializedLen > 0, error = CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrExit(serializedLen < sizeof(output.inner), error = CHIP_ERROR_INVALID_ARGUMENT);
output.inner[serializedLen] = '\0';
exit:
return error;
}
CHIP_ERROR Device::Deserialize(const SerializedDevice & input)
{
CHIP_ERROR error = CHIP_NO_ERROR;
SerializableDevice serializable;
size_t maxlen = BASE64_ENCODED_LEN(sizeof(serializable));
size_t len = strnlen(Uint8::to_const_char(&input.inner[0]), maxlen);
uint16_t deserializedLen = 0;
VerifyOrExit(len < sizeof(SerializedDevice), error = CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrExit(CanCastTo<uint16_t>(len), error = CHIP_ERROR_INVALID_ARGUMENT);
CHIP_ZERO_AT(serializable);
deserializedLen = Base64Decode(Uint8::to_const_char(input.inner), static_cast<uint16_t>(len),
Uint8::to_uchar(reinterpret_cast<uint8_t *>(&serializable)));
VerifyOrExit(deserializedLen > 0, error = CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrExit(deserializedLen <= sizeof(serializable), error = CHIP_ERROR_INVALID_ARGUMENT);
// The second parameter to FromString takes the strlen value. We are subtracting 1
// from the sizeof(serializable.mDeviceAddr) to account for null termination, since
// strlen doesn't include null character in the size.
VerifyOrExit(
IPAddress::FromString(Uint8::to_const_char(serializable.mDeviceAddr), sizeof(serializable.mDeviceAddr) - 1, mDeviceAddr),
error = CHIP_ERROR_INVALID_ADDRESS);
memmove(&mPairing, &serializable.mOpsCreds, sizeof(mPairing));
mDeviceId = Encoding::LittleEndian::HostSwap64(serializable.mDeviceId);
mDevicePort = Encoding::LittleEndian::HostSwap16(serializable.mDevicePort);
exit:
return error;
}
void Device::OnMessageReceived(const PacketHeader & header, const PayloadHeader & payloadHeader,
const Transport::PeerConnectionState * state, System::PacketBufferHandle msgBuf,
SecureSessionMgr * mgr)
{
if (mState == ConnectionState::SecureConnected)
{
if (mStatusDelegate != nullptr)
{
mStatusDelegate->OnMessage(std::move(msgBuf));
}
// TODO: The following callback processing will need further work
// 1. The response needs to be parsed as per cluster definition. The response callback
// should carry the parsed response values.
// 2. The reports callbacks should also be called with the parsed reports.
// 3. The callbacks would be tracked using exchange context. On receiving the
// message, the exchange context in the message should be matched against
// the registered callbacks.
// GitHub issue: https://github.com/project-chip/connectedhomeip/issues/3910
Cancelable * ca = mResponses.mNext;
while (ca != &mResponses)
{
Callback::Callback<> * cb = Callback::Callback<>::FromCancelable(ca);
// Let's advance to the next cancelable, as the current one will get removed
// from the list (and once removed, its next will point to itself)
ca = ca->mNext;
if (cb != nullptr)
{
ChipLogProgress(Controller, "Dispatching response callback %p", cb);
cb->Cancel();
cb->mCall(cb->mContext);
}
}
ca = mReports.mNext;
while (ca != &mReports)
{
Callback::Callback<> * cb = Callback::Callback<>::FromCancelable(ca);
// Let's advance to the next cancelable, as the current one might get removed
// from the list in the callback (and if removed, its next will point to itself)
ca = ca->mNext;
if (cb != nullptr)
{
ChipLogProgress(Controller, "Dispatching report callback %p", cb);
cb->mCall(cb->mContext);
}
}
}
}
CHIP_ERROR Device::LoadSecureSessionParameters()
{
CHIP_ERROR err = CHIP_NO_ERROR;
SecurePairingSession pairingSession;
if (mSessionManager == nullptr || mState == ConnectionState::SecureConnected)
{
ExitNow(err = CHIP_ERROR_INCORRECT_STATE);
}
err = pairingSession.FromSerializable(mPairing);
SuccessOrExit(err);
err = mTransportMgr->ResetTransport(Transport::UdpListenParameters(mInetLayer).SetAddressType(mDeviceAddr.Type()));
SuccessOrExit(err);
err = mSessionManager->NewPairing(
Optional<Transport::PeerAddress>::Value(Transport::PeerAddress::UDP(mDeviceAddr, mDevicePort, mInterface)), mDeviceId,
&pairingSession);
SuccessOrExit(err);
mState = ConnectionState::SecureConnected;
exit:
if (err != CHIP_NO_ERROR)
{
ChipLogError(Controller, "LoadSecureSessionParameters returning error %d\n", err);
}
return err;
}
bool Device::GetIpAddress(Inet::IPAddress & addr) const
{
if (mState == ConnectionState::SecureConnected)
addr = mDeviceAddr;
return mState == ConnectionState::SecureConnected;
}
void Device::AddResponseHandler(EndpointId endpoint, ClusterId cluster, Callback::Callback<> * onResponse)
{
CallbackInfo info = { endpoint, cluster };
Callback::Cancelable * cancelable = onResponse->Cancel();
nlSTATIC_ASSERT_PRINT(sizeof(info) <= sizeof(cancelable->mInfoScalar), "Size of CallbackInfo should be <= size of mInfoScalar");
cancelable->mInfoScalar = 0;
memmove(&cancelable->mInfoScalar, &info, sizeof(info));
mResponses.Enqueue(cancelable);
}
void Device::AddReportHandler(EndpointId endpoint, ClusterId cluster, Callback::Callback<> * onReport)
{
CallbackInfo info = { endpoint, cluster };
Callback::Cancelable * cancelable = onReport->Cancel();
nlSTATIC_ASSERT_PRINT(sizeof(info) <= sizeof(cancelable->mInfoScalar), "Size of CallbackInfo should be <= size of mInfoScalar");
cancelable->mInfoScalar = 0;
memmove(&cancelable->mInfoScalar, &info, sizeof(info));
mReports.Enqueue(cancelable);
}
} // namespace Controller
} // namespace chip