blob: afcb875f9ac2e39772a387b701e806a97c524f52 [file]
/*
*
* Copyright (c) 2026 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.
*/
#include <transport/raw/ProxyTransport.h>
#include <lib/support/logging/CHIPLogging.h>
#include <transport/raw/MessageHeader.h>
namespace chip {
namespace Transport {
namespace {
// Exchange Flags, Protocol Opcode, Exchange ID and Protocol ID: the fields every Protocol Header carries.
constexpr size_t kMinPayloadHeaderLength = 6;
} // namespace
CHIP_ERROR ProxyTransportBase::Init(const ProxyListenParameters &)
{
// No network resources to allocate; the transport stays inert until Activate().
return CHIP_NO_ERROR;
}
CHIP_ERROR ProxyTransportBase::Activate(uint16_t sessionId, ProxyTransportDelegate * delegate)
{
VerifyOrReturnError(delegate != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrReturnError(!mActive, CHIP_ERROR_INCORRECT_STATE);
ChipLogProgress(Inet, "ProxyTransport: activating session %u", sessionId);
mSessionId = sessionId;
mDelegate = delegate;
mPaseLocalSessionId.reset();
mActive = true;
return CHIP_NO_ERROR;
}
void ProxyTransportBase::Deactivate()
{
ChipLogProgress(Inet, "ProxyTransport: deactivating session %u", mSessionId);
mActive = false;
mDelegate = nullptr;
mSessionId = 0;
mPaseLocalSessionId.reset();
}
CHIP_ERROR ProxyTransportBase::SetPaseLocalSessionId(uint16_t sessionId, uint16_t localSessionId)
{
VerifyOrReturnError(mActive && sessionId == mSessionId, CHIP_ERROR_INCORRECT_STATE);
mPaseLocalSessionId = localSessionId;
return CHIP_NO_ERROR;
}
void ProxyTransportBase::Close()
{
Deactivate();
}
bool ProxyTransportBase::CanSendToPeer(const PeerAddress & address)
{
// Only claim a kProxy address for the active session. A PeerAddress built from a
// previous session id can outlive that session in a SessionHolder or an exchange;
// claiming it would tunnel traffic to the wrong commissionee.
return mActive && address.GetTransportType() == Type::kProxy && address.GetProxySessionId() == mSessionId;
}
CHIP_ERROR ProxyTransportBase::SendMessage(const PeerAddress & address, System::PacketBufferHandle && msgBuf)
{
VerifyOrReturnError(address.GetTransportType() == Type::kProxy, CHIP_ERROR_INCORRECT_STATE);
VerifyOrReturnError(!msgBuf.IsNull(), CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrReturnError(mActive, CHIP_ERROR_INCORRECT_STATE);
VerifyOrReturnError(mDelegate != nullptr, CHIP_ERROR_INCORRECT_STATE);
// Same reasoning as CanSendToPeer: never forward under a session id other than the
// active one.
uint16_t sessionId = address.GetProxySessionId();
VerifyOrReturnError(sessionId == mSessionId, CHIP_ERROR_INCORRECT_STATE);
ChipLogDetail(Inet, "ProxyTransport: forwarding %u bytes for session %u", static_cast<unsigned>(msgBuf->DataLength()),
sessionId);
return mDelegate->SendProxyMessage(sessionId, ByteSpan(msgBuf->Start(), msgBuf->DataLength()));
}
CHIP_ERROR ProxyTransportBase::OnProxyMessageReceived(uint16_t sessionId, ByteSpan message)
{
VerifyOrReturnError(mActive && sessionId == mSessionId, CHIP_ERROR_INCORRECT_STATE,
ChipLogError(Inet, "ProxyTransport: received message for unknown session %u (active=%d, expected=%u)",
sessionId, (int) mActive, mSessionId));
// NewWithData() memcpy()s from message.data(), which an empty span leaves null, and a
// zero-length packet carries nothing for the stack to parse.
VerifyOrReturnError(!message.empty(), CHIP_ERROR_INVALID_ARGUMENT,
ChipLogError(Inet, "ProxyTransport: empty message for session %u", sessionId));
PacketHeader header;
uint16_t headerLength = 0;
CHIP_ERROR err = header.Decode(message.data(), message.size(), &headerLength);
if (err != CHIP_NO_ERROR)
{
ChipLogError(Inet, "ProxyTransport: message for session %u is not a Matter message: %" CHIP_ERROR_FORMAT, sessionId,
err.Format());
return err;
}
// An encrypted Protocol Header cannot be decoded here, so require room for the smallest one and the MIC.
if (message.size() - headerLength < kMinPayloadHeaderLength + header.MICTagLength())
{
ChipLogError(Inet, "ProxyTransport: message for session %u is too short for a Matter message", sessionId);
return CHIP_ERROR_INVALID_MESSAGE_LENGTH;
}
// Unsecured messages are accepted at any time. An encrypted message is accepted only for the
// unicast PASE session established through this proxy session, so group messages are always rejected.
if (header.IsEncrypted())
{
bool allowed = header.IsUnicastSession() && mPaseLocalSessionId == header.GetSessionId();
if (!allowed)
{
ChipLogError(Inet, "ProxyTransport: message for session %u is for Session ID %u, not this proxy session's", sessionId,
header.GetSessionId());
return CHIP_ERROR_KEY_NOT_FOUND;
}
}
System::PacketBufferHandle buf = System::PacketBufferHandle::NewWithData(message.data(), message.size());
VerifyOrReturnError(!buf.IsNull(), CHIP_ERROR_NO_MEMORY,
ChipLogError(Inet, "ProxyTransport: out of memory for received message"));
ChipLogDetail(Inet, "ProxyTransport: injecting %u bytes for session %u into Matter stack",
static_cast<unsigned>(message.size()), sessionId);
HandleMessageReceived(PeerAddress::Proxy(sessionId), std::move(buf));
return CHIP_NO_ERROR;
}
} // namespace Transport
} // namespace chip