blob: e3e52a171abcdaba37d1c28b494ba07cd9f0c3d6 [file] [log] [blame]
/*
*
* Copyright (c) 2020 Project CHIP Authors
*
* 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
*
* Defines a compound transport type (tuple) that can merge several transports
* to look like a single one.
*/
#pragma once
#include <tuple>
#include <type_traits>
#include <transport/raw/Base.h>
namespace chip {
namespace Transport {
/**
* Groups together several transports of different types and presents them as a unified transport.
*
* The usage intent is to be able to group several distinct transport types and make them look
* as a single transport.
*
* Having an example class definition of `Tuple<UDP, UDP, TCP>`
*
* Is equivalent to:
*
* ~~~~~~~~~
* class TupleOfUdpUdpTcp : public BASE {
* private:
* UDP mUdp1;
* UDP mUdp2;
* TCP mTcp3;
* public:
* Init(UDPListenParameters &, UDPListenParameters&, TCPListenParameters) {...}
* CHIP_ERROR SendMessage(...) override;
* bool CanSendToPeer(...) override;
* }
* ~~~~~~~~~
*
* The intent of this is to allow applications to use any transport types without CHIP pre-defining
* popular mappings (like UDP only, UDP and BLE, BLE only etc.) and without using \#ifdefs to create
* a single 'standard transport'.
*
* Transport logic:
* - Every transport can decide if a 'PeerAddress' can be sent over 'self'
*
* - Within a mixed tuple, if several transports support a peer address (e.g. TCP and UDP both
* support IP), the first one found wins.
*
* - Expected message sending logic:
* - BLE transports only support BLE. TCP/UDP support IP addresses
* - Multicasts only supported by UDP
*
* @tparam TransportTypes the ORDERED list of transport types grouped together. Order matters in
* determining what transport is used when multiple transports can reach a Peer.
*
* Transports (TransportTypes) are assumed to have an Init call with EXACTLY one argument and returning
* a CHIP_ERROR, with a signature like:
*
* CHIP_ERROR Init(AnyType);
*
*/
template <typename... TransportTypes>
class Tuple : public Base
{
public:
CHIP_ERROR SendMessage(const PeerAddress & address, System::PacketBufferHandle && msgBuf) override
{
return SendMessageImpl<0>(address, std::move(msgBuf));
}
CHIP_ERROR MulticastGroupJoinLeave(const Transport::PeerAddress & address, bool join) override
{
return MulticastGroupJoinLeaveImpl<0>(address, join);
}
bool CanSendToPeer(const PeerAddress & address) override { return CanSendToPeerImpl<0>(address); }
#if INET_CONFIG_ENABLE_TCP_ENDPOINT
CHIP_ERROR TCPConnect(const PeerAddress & address, Transport::AppTCPConnectionCallbackCtxt * appState,
Transport::ActiveTCPConnectionState ** peerConnState) override
{
return TCPConnectImpl<0>(address, appState, peerConnState);
}
void TCPDisconnect(const PeerAddress & address) override { return TCPDisconnectImpl<0>(address); }
void TCPDisconnect(Transport::ActiveTCPConnectionState * conn, bool shouldAbort = 0) override
{
return TCPDisconnectImpl<0>(conn, shouldAbort);
}
#endif // INET_CONFIG_ENABLE_TCP_ENDPOINT
void Close() override { return CloseImpl<0>(); }
/**
* Initialization method that forwards arguments for initialization to each of the underlying
* transports.
*
* Transports are assumed to have an Init call with EXACTLY one argument. This method MUST initialize
* all underlying transports.
*
* @param delegate the delegate to handle messages.
* @param args initialization arguments, forwarded as-is to the underlying transports.
*/
template <typename... Args, typename std::enable_if<(sizeof...(Args) == sizeof...(TransportTypes))>::type * = nullptr>
CHIP_ERROR Init(RawTransportDelegate * delegate, Args &&... args)
{
return InitImpl(delegate, std::forward<Args>(args)...);
}
private:
/**
* Recursive cansend implementation iterating through transport members.
*
* Will return true if any transport with index N and up can CanSendToPeer(address);
*
* @tparam N the index of the underlying transport to check for CanSendToPeer
*
* @param address what address to check.
*/
template <size_t N, typename std::enable_if<(N < sizeof...(TransportTypes))>::type * = nullptr>
bool CanSendToPeerImpl(const PeerAddress & address)
{
return std::get<N>(mTransports).CanSendToPeer(address) || CanSendToPeerImpl<N + 1>(address);
}
/**
* CanSend template for out of range N. Always returns false.
*/
template <size_t N, typename std::enable_if<(N >= sizeof...(TransportTypes))>::type * = nullptr>
bool CanSendToPeerImpl(const PeerAddress & address)
{
return false;
}
#if INET_CONFIG_ENABLE_TCP_ENDPOINT
/**
* Recursive TCPConnect implementation iterating through transport members.
*
* @tparam N the index of the underlying transport to send disconnect to
*
* @param address what address to connect to.
*/
template <size_t N, typename std::enable_if<(N < sizeof...(TransportTypes))>::type * = nullptr>
CHIP_ERROR TCPConnectImpl(const PeerAddress & address, Transport::AppTCPConnectionCallbackCtxt * appState,
Transport::ActiveTCPConnectionState ** peerConnState)
{
Base * base = &std::get<N>(mTransports);
if (base->CanSendToPeer(address))
{
return base->TCPConnect(address, appState, peerConnState);
}
return TCPConnectImpl<N + 1>(address, appState, peerConnState);
}
/**
* TCPConnectImpl template for out of range N.
*/
template <size_t N, typename std::enable_if<(N >= sizeof...(TransportTypes))>::type * = nullptr>
CHIP_ERROR TCPConnectImpl(const PeerAddress & address, Transport::AppTCPConnectionCallbackCtxt * appState,
Transport::ActiveTCPConnectionState ** peerConnState)
{
return CHIP_ERROR_NO_MESSAGE_HANDLER;
}
/**
* Recursive disconnect implementation iterating through transport members.
*
* @tparam N the index of the underlying transport to send disconnect to
*
* @param address what address to disconnect from.
*/
template <size_t N, typename std::enable_if<(N < sizeof...(TransportTypes))>::type * = nullptr>
void TCPDisconnectImpl(const PeerAddress & address)
{
std::get<N>(mTransports).TCPDisconnect(address);
TCPDisconnectImpl<N + 1>(address);
}
/**
* TCPDisconnectImpl template for out of range N.
*/
template <size_t N, typename std::enable_if<(N >= sizeof...(TransportTypes))>::type * = nullptr>
void TCPDisconnectImpl(const PeerAddress & address)
{}
/**
* Recursive disconnect implementation iterating through transport members.
*
* @tparam N the index of the underlying transport to send disconnect to
*
* @param conn pointer to the connection to the peer.
*/
template <size_t N, typename std::enable_if<(N < sizeof...(TransportTypes))>::type * = nullptr>
void TCPDisconnectImpl(Transport::ActiveTCPConnectionState * conn, bool shouldAbort = 0)
{
std::get<N>(mTransports).TCPDisconnect(conn, shouldAbort);
TCPDisconnectImpl<N + 1>(conn, shouldAbort);
}
/**
* TCPDisconnectImpl template for out of range N.
*/
template <size_t N, typename std::enable_if<(N >= sizeof...(TransportTypes))>::type * = nullptr>
void TCPDisconnectImpl(Transport::ActiveTCPConnectionState * conn, bool shouldAbort = 0)
{}
#endif // INET_CONFIG_ENABLE_TCP_ENDPOINT
/**
* Recursive disconnect implementation iterating through transport members.
*
* @tparam N the index of the underlying transport to send close to
*/
template <size_t N, typename std::enable_if<(N < sizeof...(TransportTypes))>::type * = nullptr>
void CloseImpl()
{
std::get<N>(mTransports).Close();
CloseImpl<N + 1>();
}
/**
* CloseImpl template for out of range N.
*/
template <size_t N, typename std::enable_if<(N >= sizeof...(TransportTypes))>::type * = nullptr>
void CloseImpl()
{}
/**
* Recursive sendmessage implementation iterating through transport members.
*
* Message is sent through the first transport from index N or above, which returns 'CanSendToPeer'
*
* @tparam N the index of the underlying transport to run SendMessage throug.
*
* @param address where to send the message
* @param msgBuf the message to send. Includes all CHIP message fields except optional length.
*/
template <size_t N, typename std::enable_if<(N < sizeof...(TransportTypes))>::type * = nullptr>
CHIP_ERROR SendMessageImpl(const PeerAddress & address, System::PacketBufferHandle && msgBuf)
{
Base * base = &std::get<N>(mTransports);
if (base->CanSendToPeer(address))
{
return base->SendMessage(address, std::move(msgBuf));
}
return SendMessageImpl<N + 1>(address, std::move(msgBuf));
}
/**
* SendMessageImpl when N is out of range. Always returns an error code.
*/
template <size_t N, typename std::enable_if<(N >= sizeof...(TransportTypes))>::type * = nullptr>
CHIP_ERROR SendMessageImpl(const PeerAddress & address, System::PacketBufferHandle msgBuf)
{
return CHIP_ERROR_NO_MESSAGE_HANDLER;
}
/**
* Recursive GroupJoinLeave implementation iterating through transport members.
*
* Listener is activated through the first transport from index N or above, which returns 'CanListenMulticast'
*
* @tparam N the index of the underlying transport to run GroupJoinLeave through.
*
* @param address where to send the message
* @param join a boolean indicating if the transport should join or leave the group
*/
template <size_t N, typename std::enable_if<(N < sizeof...(TransportTypes))>::type * = nullptr>
CHIP_ERROR MulticastGroupJoinLeaveImpl(const Transport::PeerAddress & address, bool join)
{
Base * base = &std::get<N>(mTransports);
if (base->CanListenMulticast())
{
return base->MulticastGroupJoinLeave(address, join);
}
return MulticastGroupJoinLeaveImpl<N + 1>(address, join);
}
/**
* GroupJoinLeave when N is out of range. Always returns an error code.
*/
template <size_t N, typename std::enable_if<(N >= sizeof...(TransportTypes))>::type * = nullptr>
CHIP_ERROR MulticastGroupJoinLeaveImpl(const Transport::PeerAddress & address, bool join)
{
return CHIP_ERROR_NO_MESSAGE_HANDLER;
}
/**
* Recursive init implementation iterating through transport members
*
* Given a set of arguments 'a1, a2, a3, ... aN' will call an Init method on the last N
* transports.
*
* Method is expected to be called initially with exactly sizeof(TransportTypes) to initialize
* all transports.
*
* @param arg the next initialize argument to pass to the transport Init method
* @param rest tail arguments to be passed to the rest of transport Init methods.
*/
template <typename InitArg, typename... Rest>
CHIP_ERROR InitImpl(RawTransportDelegate * delegate, InitArg && arg, Rest &&... rest)
{
auto transport = &std::get<sizeof...(TransportTypes) - sizeof...(Rest) - 1>(mTransports);
CHIP_ERROR err = transport->Init(std::forward<InitArg>(arg));
if (err != CHIP_NO_ERROR)
{
return err;
}
transport->SetDelegate(delegate);
return InitImpl(delegate, std::forward<Rest>(rest)...);
}
/**
* Base case where initialization finishes.
*
* Provided to ensure that recursive InitImpl finishes compiling.
*/
CHIP_ERROR InitImpl(RawTransportDelegate * delegate) { return CHIP_NO_ERROR; }
std::tuple<TransportTypes...> mTransports;
public:
template <size_t i>
auto GetImplAtIndex() -> decltype(std::get<i>(mTransports)) &
{
return std::get<i>(mTransports);
}
std::tuple<TransportTypes...> & GetTransports() { return mTransports; }
};
} // namespace Transport
} // namespace chip