blob: 9667f1307ab24bbcb7ffbf8540908712b3e732de [file] [log] [blame]
/*
*
* Copyright (c) 2025 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
* This file implements a WiFiPAF endpoint abstraction for CHIP over WiFiPAF (CHIPoPAF)
* Public Action Frame Transport Protocol (PAFTP).
*
*/
#include "WiFiPAFEndPoint.h"
#include <cstdint>
#include <cstring>
#include <utility>
#include <lib/support/BitFlags.h>
#include <lib/support/BufferReader.h>
#include <lib/support/CodeUtils.h>
#include <lib/support/logging/CHIPLogging.h>
#include <system/SystemClock.h>
#include <system/SystemLayer.h>
#include <system/SystemPacketBuffer.h>
#include "WiFiPAFConfig.h"
#include "WiFiPAFError.h"
#include "WiFiPAFLayer.h"
#include "WiFiPAFTP.h"
// Define below to enable extremely verbose, WiFiPAF end point-specific debug logging.
#undef CHIP_WIFIPAF_END_POINT_DEBUG_LOGGING_ENABLED
#define CHIP_WIFIPAF_END_POINT_DEBUG_LOGGING_LEVEL 0
#ifdef CHIP_WIFIPAF_END_POINT_DEBUG_LOGGING_ENABLED
#define ChipLogDebugWiFiPAFEndPoint_L0(MOD, MSG, ...) ChipLogDetail(MOD, MSG, ##__VA_ARGS__)
#if (CHIP_WIFIPAF_END_POINT_DEBUG_LOGGING_LEVEL == 0)
#define ChipLogDebugWiFiPAFEndPoint(MOD, MSG, ...)
#else
#define ChipLogDebugWiFiPAFEndPoint(MOD, MSG, ...) ChipLogDetail(MOD, MSG, ##__VA_ARGS__)
#endif // CHIP_WIFIPAF_END_POINT_DEBUG_LOGGING_LEVEL
#define ChipLogDebugBufferWiFiPAFEndPoint(MOD, BUF) \
ChipLogByteSpan(MOD, ByteSpan((BUF)->Start(), ((BUF)->DataLength() < 8 ? (BUF)->DataLength() : 8u)))
#else
#define ChipLogDebugWiFiPAFEndPoint(MOD, MSG, ...)
#define ChipLogDebugBufferWiFiPAFEndPoint(MOD, BUF)
#endif
/**
* @def WIFIPAF_CONFIG_IMMEDIATE_ACK_WINDOW_THRESHOLD
*
* @brief
* If an end point's receive window drops equal to or below this value, it will send an immediate acknowledgement
* packet to re-open its window instead of waiting for the send-ack timer to expire.
*
*/
#define WIFIPAF_CONFIG_IMMEDIATE_ACK_WINDOW_THRESHOLD 1
#define WIFIPAF_ACK_SEND_TIMEOUT_MS 2500
#define WIFIPAF_WAIT_RES_TIMEOUT_MS 1000
// Drop the connection if network resources remain unavailable for the period.
#define WIFIPAF_MAX_RESOURCE_BLOCK_COUNT (PAFTP_CONN_IDLE_TIMEOUT_MS / WIFIPAF_WAIT_RES_TIMEOUT_MS)
/**
* @def WIFIPAF_WINDOW_NO_ACK_SEND_THRESHOLD
*
* @brief
* Data fragments may only be sent without piggybacked acks if receiver's window size is above this threshold.
*
*/
#define WIFIPAF_WINDOW_NO_ACK_SEND_THRESHOLD 1
namespace chip {
namespace WiFiPAF {
CHIP_ERROR WiFiPAFEndPoint::StartConnect()
{
CHIP_ERROR err = CHIP_NO_ERROR;
PAFTransportCapabilitiesRequestMessage req;
PacketBufferHandle buf;
constexpr uint8_t numVersions =
CHIP_PAF_TRANSPORT_PROTOCOL_MAX_SUPPORTED_VERSION - CHIP_PAF_TRANSPORT_PROTOCOL_MIN_SUPPORTED_VERSION + 1;
static_assert(numVersions <= NUM_PAFTP_SUPPORTED_PROTOCOL_VERSIONS, "Incompatibly protocol versions");
// Ensure we're in the correct state.
VerifyOrExit(mState == kState_Ready, err = CHIP_ERROR_INCORRECT_STATE);
mState = kState_Connecting;
// Build PAF transport protocol capabilities request.
buf = System::PacketBufferHandle::New(System::PacketBuffer::kMaxSize);
VerifyOrExit(!buf.IsNull(), err = CHIP_ERROR_NO_MEMORY);
// Zero-initialize PAF transport capabilities request.
memset(&req, 0, sizeof(req));
req.mMtu = CHIP_PAF_DEFAULT_MTU;
req.mWindowSize = PAF_MAX_RECEIVE_WINDOW_SIZE;
// Populate request with highest supported protocol versions
for (uint8_t i = 0; i < numVersions; i++)
{
req.SetSupportedProtocolVersion(i, static_cast<uint8_t>(CHIP_PAF_TRANSPORT_PROTOCOL_MAX_SUPPORTED_VERSION - i));
}
err = req.Encode(buf);
SuccessOrExit(err);
// Start connect timer. Canceled when end point freed or connection established.
err = StartConnectTimer();
SuccessOrExit(err);
// Send PAF transport capabilities request to peripheral.
// Add reference to message fragment. CHIP retains partial ownership of message fragment's packet buffer,
// since this is the same buffer as that of the whole message, just with a fragmenter-modified payload offset
// and data length, by a Retain() on the handle when calling this function.
err = SendWrite(buf.Retain());
SuccessOrExit(err);
// Free request buffer on write confirmation. Stash a reference to it in mSendQueue, which we don't use anyway
// until the connection has been set up.
QueueTx(std::move(buf), kType_Data);
exit:
// If we failed to initiate the connection, close the end point.
if (err != CHIP_NO_ERROR)
{
StopConnectTimer();
DoClose(kWiFiPAFCloseFlag_AbortTransmission, err);
}
return err;
}
CHIP_ERROR WiFiPAFEndPoint::HandleConnectComplete()
{
CHIP_ERROR err = CHIP_NO_ERROR;
mState = kState_Connected;
// Cancel the connect timer.
StopConnectTimer();
// We've successfully completed the PAF transport protocol handshake, so let the application know we're open for business.
if (mWiFiPafLayer != nullptr)
{
// Indicate connect complete to next-higher layer.
mWiFiPafLayer->OnEndPointConnectComplete(this, CHIP_NO_ERROR);
}
else
{
// If no connect complete callback has been set up, close the end point.
err = WIFIPAF_ERROR_NO_CONNECT_COMPLETE_CALLBACK;
}
return err;
}
bool WiFiPAFEndPoint::IsConnected(uint8_t state) const
{
return (state == kState_Connected || state == kState_Closing);
}
void WiFiPAFEndPoint::DoClose(uint8_t flags, CHIP_ERROR err)
{
uint8_t oldState = mState;
// If end point is not closed or closing, OR end point was closing gracefully, but tx abort has been specified...
if ((mState != kState_Closed && mState != kState_Closing) ||
(mState == kState_Closing && (flags & kWiFiPAFCloseFlag_AbortTransmission)))
{
// Cancel Connect and ReceiveConnect timers if they are running.
// Check role first to avoid needless iteration over timer pool.
if (mRole == kWiFiPafRole_Subscriber)
{
StopConnectTimer();
}
// Free the packets in re-order queue if ones exist
for (uint8_t qidx = 0; qidx < PAFTP_REORDER_QUEUE_SIZE; qidx++)
{
if (ReorderQueue[qidx] != nullptr)
{
ReorderQueue[qidx] = nullptr;
ItemsInReorderQueue--;
}
}
// If transmit buffer is empty or a transmission abort was specified...
if (mPafTP.TxState() == WiFiPAFTP::kState_Idle || (flags & kWiFiPAFCloseFlag_AbortTransmission))
{
FinalizeClose(oldState, flags, err);
}
else
{
// Wait for send queue and fragmenter's tx buffer to become empty, to ensure all pending messages have been
// sent. Only free end point and tell platform it can throw away the underlying connection once all
// pending messages have been sent and acknowledged by the remote CHIPoPAF stack, or once the remote stack
// closes the CHIPoPAF connection.
//
// In so doing, WiFiPAFEndPoint attempts to emulate the level of reliability afforded by TCPEndPoint and TCP
// sockets in general with a typical default SO_LINGER option. That said, there is no hard guarantee that
// pending messages will be sent once (Do)Close() is called, so developers should use application-level
// messages to confirm the receipt of all data sent prior to a Close() call.
mState = kState_Closing;
if ((flags & kWiFiPAFCloseFlag_SuppressCallback) == 0)
{
DoCloseCallback(oldState, flags, err);
}
}
}
}
void WiFiPAFEndPoint::FinalizeClose(uint8_t oldState, uint8_t flags, CHIP_ERROR err)
{
mState = kState_Closed;
// Ensure transmit queue is empty and set to NULL.
mSendQueue = nullptr;
// Clear the session information
ChipLogProgress(WiFiPAF, "Shutdown PAF session (%u, %u)", mSessionInfo.id, mSessionInfo.role);
mWiFiPafLayer->mWiFiPAFTransport->WiFiPAFCloseSession(mSessionInfo);
memset(&mSessionInfo, 0, sizeof(mSessionInfo));
// Fire application's close callback if we haven't already, and it's not suppressed.
if (oldState != kState_Closing && (flags & kWiFiPAFCloseFlag_SuppressCallback) == 0)
{
DoCloseCallback(oldState, flags, err);
}
// If underlying WiFiPAF connection has closed, connection object is invalid, so just free the end point and return.
if (err == WIFIPAF_ERROR_REMOTE_DEVICE_DISCONNECTED || err == WIFIPAF_ERROR_APP_CLOSED_CONNECTION)
{
Free();
}
else // Otherwise, try to signal close to remote device before end point releases WiFiPAF connection and frees itself.
{
if (mRole == kWiFiPafRole_Subscriber)
{
// Cancel send and receive-ack timers, if running.
StopAckReceivedTimer();
StopSendAckTimer();
StopWaitResourceTimer();
mConnStateFlags.Set(ConnectionStateFlag::kOperationInFlight);
}
else
{
Free();
}
}
ClearAll();
}
void WiFiPAFEndPoint::DoCloseCallback(uint8_t state, uint8_t flags, CHIP_ERROR err)
{
// Callback fires once per end point lifetime.
mOnPafSubscribeComplete = nullptr;
mOnPafSubscribeError = nullptr;
OnConnectionClosed = nullptr;
}
void WiFiPAFEndPoint::Free()
{
// Clear fragmentation and reassembly engine's Tx and Rx buffers. Counters will be reset by next engine init.
FreePAFtpEngine();
// Clear pending ack buffer, if any.
mAckToSend = nullptr;
// Cancel all timers.
StopConnectTimer();
StopAckReceivedTimer();
StopSendAckTimer();
StopWaitResourceTimer();
// Clear callbacks.
mOnPafSubscribeComplete = nullptr;
mOnPafSubscribeError = nullptr;
OnMessageReceived = nullptr;
OnConnectionClosed = nullptr;
}
void WiFiPAFEndPoint::FreePAFtpEngine()
{
// Free transmit disassembly buffer
mPafTP.ClearTxPacket();
// Free receive reassembly buffer
mPafTP.ClearRxPacket();
}
CHIP_ERROR WiFiPAFEndPoint::Init(WiFiPAFLayer * WiFiPafLayer, WiFiPAFSession & SessionInfo)
{
// Fail if already initialized.
VerifyOrReturnError(mWiFiPafLayer == nullptr, CHIP_ERROR_INCORRECT_STATE);
// Validate args.
VerifyOrReturnError(WiFiPafLayer != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
// If end point plays subscriber role, expect ack as last step of PAFTP handshake.
// If being publisher, subscriber's handshake indication 'ack's write sent by publisher to kick off the PAFTP handshake.
bool expectInitialAck = (SessionInfo.role == kWiFiPafRole_Publisher);
CHIP_ERROR err = mPafTP.Init(this, expectInitialAck);
if (err != CHIP_NO_ERROR)
{
ChipLogError(WiFiPAF, "WiFiPAFTP init failed");
return err;
}
mWiFiPafLayer = WiFiPafLayer;
// WiFiPAF EndPoint data members:
memcpy(&mSessionInfo, &SessionInfo, sizeof(mSessionInfo));
mRole = SessionInfo.role;
mTimerStateFlags.ClearAll();
mLocalReceiveWindowSize = 0;
mRemoteReceiveWindowSize = 0;
mReceiveWindowMaxSize = 0;
mSendQueue = nullptr;
mAckToSend = nullptr;
ChipLogDebugWiFiPAFEndPoint(WiFiPAF, "initialized local rx window, size = %u", mLocalReceiveWindowSize);
// End point is ready.
mState = kState_Ready;
return CHIP_NO_ERROR;
}
CHIP_ERROR WiFiPAFEndPoint::SendCharacteristic(PacketBufferHandle && buf)
{
CHIP_ERROR err = CHIP_NO_ERROR;
SuccessOrExit(err = SendWrite(std::move(buf)));
// Write succeeded, so shrink remote receive window counter by 1.
mRemoteReceiveWindowSize = static_cast<SequenceNumber_t>(mRemoteReceiveWindowSize - 1);
ChipLogDebugWiFiPAFEndPoint(WiFiPAF, "decremented remote rx window, new size = %u", mRemoteReceiveWindowSize);
exit:
return err;
}
/*
* Routine to queue the Tx packet with a packet type
* kType_Data(0) - data packet
* kType_Control(1) - control packet
*/
void WiFiPAFEndPoint::QueueTx(PacketBufferHandle && data, PacketType_t type)
{
if (mSendQueue.IsNull())
{
mSendQueue = std::move(data);
ChipLogDebugWiFiPAFEndPoint(WiFiPAF, "%s: Set data as new mSendQueue %p, type %d", __FUNCTION__, mSendQueue->Start(), type);
}
else
{
mSendQueue->AddToEnd(std::move(data));
ChipLogDebugWiFiPAFEndPoint(WiFiPAF, "%s: Append data to mSendQueue %p, type %d", __FUNCTION__, mSendQueue->Start(), type);
}
}
CHIP_ERROR WiFiPAFEndPoint::Send(PacketBufferHandle && data)
{
CHIP_ERROR err = CHIP_NO_ERROR;
VerifyOrExit(!data.IsNull(), err = CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrExit(IsConnected(mState), err = CHIP_ERROR_INCORRECT_STATE);
// Ensure outgoing message fits in a single contiguous packet buffer, as currently required by the
// message fragmentation and reassembly engine.
if (data->HasChainedBuffer())
{
data->CompactHead();
if (data->HasChainedBuffer())
{
err = CHIP_ERROR_OUTBOUND_MESSAGE_TOO_BIG;
ExitNow();
}
}
// Add new message to send queue.
QueueTx(std::move(data), kType_Data);
// Send first fragment of new message, if we can.
err = DriveSending();
SuccessOrExit(err);
exit:
if (err != CHIP_NO_ERROR)
{
DoClose(kWiFiPAFCloseFlag_AbortTransmission, err);
}
return err;
}
bool WiFiPAFEndPoint::PrepareNextFragment(PacketBufferHandle && data, bool & sentAck)
{
// If we have a pending fragment acknowledgement to send, piggyback it on the fragment we're about to transmit.
if (mTimerStateFlags.Has(TimerStateFlag::kSendAckTimerRunning))
{
// Reset local receive window counter.
mLocalReceiveWindowSize = mReceiveWindowMaxSize;
ChipLogDebugWiFiPAFEndPoint(WiFiPAF, "reset local rx window on piggyback ack tx, size = %u", mLocalReceiveWindowSize);
// Tell caller AND fragmenter we have an ack to piggyback.
sentAck = true;
}
else
{
// No ack to piggyback.
sentAck = false;
}
return mPafTP.HandleCharacteristicSend(std::move(data), sentAck);
}
CHIP_ERROR WiFiPAFEndPoint::SendNextMessage()
{
// Get the first queued packet to send
PacketBufferHandle data = mSendQueue.PopHead();
// Hand whole message payload to the fragmenter.
bool sentAck;
VerifyOrReturnError(PrepareNextFragment(std::move(data), sentAck), WIFIPAF_ERROR_CHIPPAF_PROTOCOL_ABORT);
ReturnErrorOnFailure(SendCharacteristic(mPafTP.BorrowTxPacket()));
if (sentAck)
{
// If sent piggybacked ack, stop send-ack timer.
StopSendAckTimer();
}
// Start ack received timer, if it's not already running.
return StartAckReceivedTimer();
}
CHIP_ERROR WiFiPAFEndPoint::ContinueMessageSend()
{
bool sentAck;
if (!PrepareNextFragment(nullptr, sentAck))
{
// Log PAFTP error
ChipLogError(WiFiPAF, "paftp fragmenter error on send!");
mPafTP.LogState();
return WIFIPAF_ERROR_CHIPPAF_PROTOCOL_ABORT;
}
ReturnErrorOnFailure(SendCharacteristic(mPafTP.BorrowTxPacket()));
if (sentAck)
{
// If sent piggybacked ack, stop send-ack timer.
StopSendAckTimer();
}
// Start ack received timer, if it's not already running.
return StartAckReceivedTimer();
}
CHIP_ERROR WiFiPAFEndPoint::HandleHandshakeConfirmationReceived()
{
// Free capabilities request/response payload.
mSendQueue.FreeHead();
return CHIP_NO_ERROR;
}
CHIP_ERROR WiFiPAFEndPoint::HandleFragmentConfirmationReceived(bool result)
{
CHIP_ERROR err = CHIP_NO_ERROR;
// Ensure we're in correct state to receive confirmation of non-handshake GATT send.
VerifyOrExit(IsConnected(mState), err = CHIP_ERROR_INCORRECT_STATE);
if (mConnStateFlags.Has(ConnectionStateFlag::kStandAloneAckInFlight))
{
// If confirmation was received for stand-alone ack, free its tx buffer.
mAckToSend = nullptr;
mConnStateFlags.Clear(ConnectionStateFlag::kStandAloneAckInFlight);
}
if (result != true)
{
// Something wrong in writing packets
ChipLogError(WiFiPAF, "Failed to send PAF packet");
err = CHIP_ERROR_SENDING_BLOCKED;
StopAckReceivedTimer();
SuccessOrExit(err);
}
// If local receive window size has shrunk to or below immediate ack threshold, AND a message fragment is not
// pending on which to piggyback an ack, send immediate stand-alone ack.
//
// This check covers the case where the local receive window has shrunk between transmission and confirmation of
// the stand-alone ack, and also the case where a window size < the immediate ack threshold was detected in
// Receive(), but the stand-alone ack was deferred due to a pending outbound message fragment.
if (mLocalReceiveWindowSize <= WIFIPAF_CONFIG_IMMEDIATE_ACK_WINDOW_THRESHOLD && mSendQueue.IsNull() &&
mPafTP.TxState() != WiFiPAFTP::kState_InProgress)
{
err = DriveStandAloneAck(); // Encode stand-alone ack and drive sending.
SuccessOrExit(err);
}
else
{
err = DriveSending();
SuccessOrExit(err);
}
exit:
if (err != CHIP_NO_ERROR)
{
DoClose(kWiFiPAFCloseFlag_AbortTransmission, err);
}
return err;
}
CHIP_ERROR WiFiPAFEndPoint::HandleSendConfirmationReceived(bool result)
{
// Mark outstanding operation as finished.
mConnStateFlags.Clear(ConnectionStateFlag::kOperationInFlight);
// If confirmation was for outbound portion of PAFTP connect handshake...
if (!mConnStateFlags.Has(ConnectionStateFlag::kCapabilitiesConfReceived))
{
mConnStateFlags.Set(ConnectionStateFlag::kCapabilitiesConfReceived);
return HandleHandshakeConfirmationReceived();
}
return HandleFragmentConfirmationReceived(result);
}
CHIP_ERROR WiFiPAFEndPoint::DriveStandAloneAck()
{
// Stop send-ack timer if running.
StopSendAckTimer();
// If stand-alone ack not already pending, allocate new payload buffer here.
if (mAckToSend.IsNull())
{
mAckToSend = System::PacketBufferHandle::New(kTransferProtocolStandaloneAckHeaderSize);
VerifyOrReturnError(!mAckToSend.IsNull(), CHIP_ERROR_NO_MEMORY);
}
// Attempt to send stand-alone ack.
return DriveSending();
}
CHIP_ERROR WiFiPAFEndPoint::DoSendStandAloneAck()
{
ChipLogDebugWiFiPAFEndPoint(WiFiPAF, "sending stand-alone ack");
// Encode and transmit stand-alone ack.
mPafTP.EncodeStandAloneAck(mAckToSend);
ReturnErrorOnFailure(SendCharacteristic(mAckToSend.Retain()));
// Reset local receive window counter.
mLocalReceiveWindowSize = mReceiveWindowMaxSize;
ChipLogDebugWiFiPAFEndPoint(WiFiPAF, "reset local rx window on stand-alone ack tx, size = %u", mLocalReceiveWindowSize);
mConnStateFlags.Set(ConnectionStateFlag::kStandAloneAckInFlight);
// Start ack received timer, if it's not already running.
return StartAckReceivedTimer();
}
CHIP_ERROR WiFiPAFEndPoint::DriveSending()
{
// If receiver's window is almost closed and we don't have an ack to send, OR we do have an ack to send but
// receiver's window is completely empty, OR another operation is in flight, awaiting confirmation...
if ((mRemoteReceiveWindowSize <= WIFIPAF_WINDOW_NO_ACK_SEND_THRESHOLD &&
!mTimerStateFlags.Has(TimerStateFlag::kSendAckTimerRunning) && mAckToSend.IsNull()) ||
(mRemoteReceiveWindowSize == 0) || (mConnStateFlags.Has(ConnectionStateFlag::kOperationInFlight)))
{
if (mRemoteReceiveWindowSize <= WIFIPAF_WINDOW_NO_ACK_SEND_THRESHOLD &&
!mTimerStateFlags.Has(TimerStateFlag::kSendAckTimerRunning) && mAckToSend.IsNull())
{
ChipLogDebugWiFiPAFEndPoint(WiFiPAF, "NO SEND: receive window almost closed, and no ack to send");
}
if (mRemoteReceiveWindowSize == 0)
{
ChipLogDebugWiFiPAFEndPoint(WiFiPAF, "NO SEND: remote receive window closed");
}
if (mConnStateFlags.Has(ConnectionStateFlag::kOperationInFlight))
{
ChipLogDebugWiFiPAFEndPoint(WiFiPAF, "NO SEND: Operation in flight");
}
// Can't send anything.
return CHIP_NO_ERROR;
}
if (!mWiFiPafLayer->mWiFiPAFTransport->WiFiPAFResourceAvailable() && (!mAckToSend.IsNull() || !mSendQueue.IsNull()))
{
// Resource is currently unavailable, send packets later
StartWaitResourceTimer();
return CHIP_NO_ERROR;
}
mResourceWaitCount = 0;
// Otherwise, let's see what we can send.
if ((!mAckToSend.IsNull()) && !mConnStateFlags.Has(ConnectionStateFlag::kStandAloneAckInFlight))
{
// If immediate, stand-alone ack is pending, send it.
ChipLogProgress(WiFiPAF, "Send the pending stand-alone ack");
ReturnErrorOnFailure(DoSendStandAloneAck());
}
else if (mPafTP.TxState() == WiFiPAFTP::kState_Idle) // Else send next message fragment, if any.
{
// Fragmenter's idle, let's see what's in the send queue...
if (!mSendQueue.IsNull())
{
// Transmit first fragment of next whole message in send queue.
ReturnErrorOnFailure(SendNextMessage());
}
else
{
// Nothing to send!
ChipLogDebugWiFiPAFEndPoint(WiFiPAF, "=> No pending packets, nothing to send!");
}
}
else if (mPafTP.TxState() == WiFiPAFTP::kState_InProgress)
{
// Send next fragment of message currently held by fragmenter.
ChipLogDebugWiFiPAFEndPoint(WiFiPAF, "Send the next fragment");
ReturnErrorOnFailure(ContinueMessageSend());
}
else if (mPafTP.TxState() == WiFiPAFTP::kState_Complete)
{
// Clear fragmenter's pointer to sent message buffer and reset its Tx state.
// Buffer will be freed at scope exit.
PacketBufferHandle sentBuf = mPafTP.TakeTxPacket();
if (!mSendQueue.IsNull())
{
ChipLogDebugWiFiPAFEndPoint(WiFiPAF, "Send the next pkt");
// Transmit first fragment of next whole message in send queue.
ReturnErrorOnFailure(SendNextMessage());
}
else if (mState == kState_Closing && !mPafTP.ExpectingAck()) // and mSendQueue is NULL, per above...
{
ChipLogDebugWiFiPAFEndPoint(WiFiPAF, "Closing and no expect ack!");
// If end point closing, got last ack, and got out-of-order confirmation for last send, finalize close.
FinalizeClose(mState, kWiFiPAFCloseFlag_SuppressCallback, CHIP_NO_ERROR);
}
else
{
// Nothing to send!
ChipLogDebugWiFiPAFEndPoint(WiFiPAF, "No more packets to send");
}
}
else
{
ChipLogError(WiFiPAF, "Unknown TxState: %u", mPafTP.TxState());
}
return CHIP_NO_ERROR;
}
CHIP_ERROR WiFiPAFEndPoint::HandleCapabilitiesRequestReceived(PacketBufferHandle && data)
{
PAFTransportCapabilitiesRequestMessage req;
PAFTransportCapabilitiesResponseMessage resp;
uint16_t mtu;
VerifyOrReturnError(!data.IsNull(), CHIP_ERROR_INVALID_ARGUMENT);
mState = kState_Connecting;
// Decode PAFTP capabilities request.
ReturnErrorOnFailure(PAFTransportCapabilitiesRequestMessage::Decode(data, req));
PacketBufferHandle responseBuf = System::PacketBufferHandle::New(kCapabilitiesResponseLength);
VerifyOrReturnError(!responseBuf.IsNull(), CHIP_ERROR_NO_MEMORY);
if (req.mMtu > 0) // If MTU was observed and provided by central...
{
mtu = req.mMtu; // Accept central's observation of the MTU.
}
else
{
mtu = CHIP_PAF_DEFAULT_MTU;
}
// Select fragment size for connection based on MTU.
resp.mFragmentSize = std::min(static_cast<uint16_t>(mtu), WiFiPAFTP::sMaxFragmentSize);
// Select local and remote max receive window size based on local resources available for both incoming writes
mRemoteReceiveWindowSize = mLocalReceiveWindowSize = mReceiveWindowMaxSize =
std::min(req.mWindowSize, static_cast<uint8_t>(PAF_MAX_RECEIVE_WINDOW_SIZE));
resp.mWindowSize = mReceiveWindowMaxSize;
ChipLogProgress(WiFiPAF, "local and remote recv window sizes = %u", resp.mWindowSize);
// Select PAF transport protocol version from those supported by central, or none if no supported version found.
resp.mSelectedProtocolVersion = WiFiPAFLayer::GetHighestSupportedProtocolVersion(req);
ChipLogProgress(WiFiPAF, "selected PAFTP version %d", resp.mSelectedProtocolVersion);
if (resp.mSelectedProtocolVersion == kWiFiPAFTransportProtocolVersion_None)
{
// If WiFiPAF transport protocol versions incompatible, prepare to close connection after capabilities response
// has been sent.
ChipLogError(WiFiPAF, "incompatible PAFTP versions; peripheral expected between %d and %d",
CHIP_PAF_TRANSPORT_PROTOCOL_MIN_SUPPORTED_VERSION, CHIP_PAF_TRANSPORT_PROTOCOL_MAX_SUPPORTED_VERSION);
mState = kState_Aborting;
}
else
{
// Set Rx and Tx fragment sizes to the same value
mPafTP.SetRxFragmentSize(resp.mFragmentSize);
mPafTP.SetTxFragmentSize(resp.mFragmentSize);
}
ChipLogProgress(WiFiPAF, "using PAFTP fragment sizes rx %d / tx %d.", mPafTP.GetRxFragmentSize(), mPafTP.GetTxFragmentSize());
ReturnErrorOnFailure(resp.Encode(responseBuf));
CHIP_ERROR err;
err = SendWrite(responseBuf.Retain());
SuccessOrExit(err);
// Stash capabilities response payload
QueueTx(std::move(responseBuf), kType_Data);
// Response has been sent
return HandleConnectComplete();
exit:
return err;
}
CHIP_ERROR WiFiPAFEndPoint::HandleCapabilitiesResponseReceived(PacketBufferHandle && data)
{
PAFTransportCapabilitiesResponseMessage resp;
VerifyOrReturnError(!data.IsNull(), CHIP_ERROR_INVALID_ARGUMENT);
// Decode PAFTP capabilities response.
ReturnErrorOnFailure(PAFTransportCapabilitiesResponseMessage::Decode(data, resp));
VerifyOrReturnError(resp.mFragmentSize > 0, WIFIPAF_ERROR_INVALID_FRAGMENT_SIZE);
ChipLogProgress(WiFiPAF, "Publisher chose PAFTP version %d; subscriber expected between %d and %d",
resp.mSelectedProtocolVersion, CHIP_PAF_TRANSPORT_PROTOCOL_MIN_SUPPORTED_VERSION,
CHIP_PAF_TRANSPORT_PROTOCOL_MAX_SUPPORTED_VERSION);
if ((resp.mSelectedProtocolVersion < CHIP_PAF_TRANSPORT_PROTOCOL_MIN_SUPPORTED_VERSION) ||
(resp.mSelectedProtocolVersion > CHIP_PAF_TRANSPORT_PROTOCOL_MAX_SUPPORTED_VERSION))
{
return WIFIPAF_ERROR_INCOMPATIBLE_PROTOCOL_VERSIONS;
}
// Set fragment size as minimum of (reported ATT MTU, BTP characteristic size)
resp.mFragmentSize = std::min(resp.mFragmentSize, WiFiPAFTP::sMaxFragmentSize);
mPafTP.SetRxFragmentSize(resp.mFragmentSize);
mPafTP.SetTxFragmentSize(resp.mFragmentSize);
ChipLogProgress(WiFiPAF, "using PAFTP fragment sizes rx %d / tx %d.", mPafTP.GetRxFragmentSize(), mPafTP.GetTxFragmentSize());
// Select local and remote max receive window size based on local resources available for both incoming indications
mRemoteReceiveWindowSize = mLocalReceiveWindowSize = mReceiveWindowMaxSize = resp.mWindowSize;
ChipLogProgress(WiFiPAF, "local and remote recv window size = %u", resp.mWindowSize);
// Shrink local receive window counter by 1, since connect handshake indication requires acknowledgement.
mLocalReceiveWindowSize = static_cast<SequenceNumber_t>(mLocalReceiveWindowSize - 1);
ChipLogDebugWiFiPAFEndPoint(WiFiPAF, "decremented local rx window, new size = %u", mLocalReceiveWindowSize);
// Send ack for connection handshake indication when timer expires. Sequence numbers always start at 0,
// and the reassembler's "last received seq num" is initialized to 0 and updated when new fragments are
// received from the peripheral, so we don't need to explicitly mark the ack num to send here.
ReturnErrorOnFailure(StartSendAckTimer());
// We've sent a capabilities request write and received a compatible response, so the connect
// operation has completed successfully.
return HandleConnectComplete();
}
// Returns number of open slots in remote receive window given the input values.
SequenceNumber_t WiFiPAFEndPoint::AdjustRemoteReceiveWindow(SequenceNumber_t lastReceivedAck, SequenceNumber_t maxRemoteWindowSize,
SequenceNumber_t newestUnackedSentSeqNum)
{
// Assumption: SequenceNumber_t is uint8_t.
// Assumption: Maximum possible sequence number value is UINT8_MAX.
// Assumption: Sequence numbers incremented past maximum value wrap to 0.
// Assumption: newest unacked sent sequence number never exceeds current (and by extension, new and un-wrapped)
// window boundary, so it never wraps relative to last received ack, if new window boundary would not
// also wrap.
// Define new window boundary (inclusive) as uint16_t, so its value can temporarily exceed UINT8_MAX.
uint16_t newRemoteWindowBoundary = static_cast<uint16_t>(lastReceivedAck + maxRemoteWindowSize);
if (newRemoteWindowBoundary > UINT8_MAX && newestUnackedSentSeqNum < lastReceivedAck)
{
// New window boundary WOULD wrap, and latest unacked seq num already HAS wrapped, so add offset to difference.
return static_cast<uint8_t>(newRemoteWindowBoundary - (newestUnackedSentSeqNum + UINT8_MAX));
}
// Neither values would or have wrapped, OR new boundary WOULD wrap but latest unacked seq num does not, so no
// offset required.
return static_cast<uint8_t>(newRemoteWindowBoundary - newestUnackedSentSeqNum);
}
CHIP_ERROR WiFiPAFEndPoint::GetPktSn(Encoding::LittleEndian::Reader & reader, uint8_t * pHead, SequenceNumber_t & seqNum)
{
CHIP_ERROR err;
BitFlags<WiFiPAFTP::HeaderFlags> rx_flags;
size_t SnOffset = 0;
SequenceNumber_t * pSn;
err = reader.Read8(rx_flags.RawStorage()).StatusCode();
if (rx_flags.Has(WiFiPAFTP::HeaderFlags::kHankshake))
{
// Handkshake message => No ack/sn
return CHIP_ERROR_INTERNAL;
}
// Always has header flag
SnOffset += kTransferProtocolHeaderFlagsSize;
if (rx_flags.Has(WiFiPAFTP::HeaderFlags::kManagementOpcode)) // Has Mgmt_Op
{
SnOffset += kTransferProtocolMgmtOpSize;
}
if (rx_flags.Has(WiFiPAFTP::HeaderFlags::kFragmentAck)) // Has ack
{
SnOffset += kTransferProtocolAckSize;
}
pSn = pHead + SnOffset;
seqNum = *pSn;
return CHIP_NO_ERROR;
}
CHIP_ERROR WiFiPAFEndPoint::DebugPktAckSn(const PktDirect_t PktDirect, Encoding::LittleEndian::Reader & reader, uint8_t * pHead)
{
#ifdef CHIP_WIFIPAF_END_POINT_DEBUG_LOGGING_ENABLED
BitFlags<WiFiPAFTP::HeaderFlags> rx_flags;
CHIP_ERROR err;
uint8_t * pAct = nullptr;
char AckBuff[4];
uint8_t * pSn;
size_t SnOffset = 0;
err = reader.Read8(rx_flags.RawStorage()).StatusCode();
SuccessOrExit(err);
if (rx_flags.Has(WiFiPAFTP::HeaderFlags::kHankshake))
{
// Handkshake message => No ack/sn
return CHIP_NO_ERROR;
}
// Always has header flag
SnOffset += kTransferProtocolHeaderFlagsSize;
if (rx_flags.Has(WiFiPAFTP::HeaderFlags::kManagementOpcode)) // Has Mgmt_Op
{
SnOffset += kTransferProtocolMgmtOpSize;
}
if (rx_flags.Has(WiFiPAFTP::HeaderFlags::kFragmentAck)) // Has ack
{
pAct = pHead + kTransferProtocolHeaderFlagsSize;
SnOffset += kTransferProtocolAckSize;
}
pSn = pHead + SnOffset;
if (pAct == nullptr)
{
strcpy(AckBuff, " ");
}
else
{
snprintf(AckBuff, sizeof(AckBuff), "%02hhu", *pAct);
}
if (PktDirect == PktDirect_t::kTx)
{
ChipLogDebugWiFiPAFEndPoint_L0(WiFiPAF, "==>[tx] [Sn, Ack] = [ %02u, -- %s]", *pSn, AckBuff);
}
else if (PktDirect == PktDirect_t::kRx)
{
ChipLogDebugWiFiPAFEndPoint_L0(WiFiPAF, "<==[rx] [Ack, Sn] = [-- %s, %02u]", AckBuff, *pSn);
}
exit:
return err;
#else
return CHIP_NO_ERROR;
#endif
}
CHIP_ERROR WiFiPAFEndPoint::Receive(PacketBufferHandle && data)
{
SequenceNumber_t ExpRxNextSeqNum = mPafTP.GetRxNextSeqNum();
SequenceNumber_t seqNum;
Encoding::LittleEndian::Reader reader(data->Start(), data->DataLength());
CHIP_ERROR err = CHIP_NO_ERROR;
err = GetPktSn(reader, data->Start(), seqNum);
if (err != CHIP_NO_ERROR)
{
// Failed to get SeqNum. => Pass down to PAFTP engine directly
return RxPacketProcess(std::move(data));
}
/*
If reorder-queue is not empty => Need to queue the packet whose SeqNum is the next one at
offset 0 to fill the hole.
*/
if ((ExpRxNextSeqNum == seqNum) && (ItemsInReorderQueue == 0))
return RxPacketProcess(std::move(data));
ChipLogError(WiFiPAF, "Reorder the packet: [%u, %u]", ExpRxNextSeqNum, seqNum);
// Start reordering packets
SequenceNumber_t offset = OffsetSeqNum(seqNum, ExpRxNextSeqNum);
if (offset >= PAFTP_REORDER_QUEUE_SIZE)
{
// Offset is too big
// => It may be the unexpected packet or duplicate packet => drop it
ChipLogError(WiFiPAF, "Offset (%u) is too big => drop the packet", offset);
ChipLogDebugBufferWiFiPAFEndPoint(WiFiPAF, data);
return CHIP_NO_ERROR;
}
// Save the packet to the reorder-queue
if (ReorderQueue[offset] == nullptr)
{
ReorderQueue[offset] = std::move(data).UnsafeRelease();
ItemsInReorderQueue++;
}
// Consume the packets in the reorder queue if no hole exists
if (ReorderQueue[0] == nullptr)
{
// The hole still exists => Can't continue
ChipLogError(WiFiPAF, "The hole still exists. Packets in reorder-queue: %u", ItemsInReorderQueue);
return CHIP_NO_ERROR;
}
uint8_t qidx;
for (qidx = 0; qidx < PAFTP_REORDER_QUEUE_SIZE; qidx++)
{
// The head slots should have been filled. => Do rx processing
if (ReorderQueue[qidx] == nullptr)
{
// Stop consuming packets until the hole or no packets
break;
}
// Consume the saved packets
ChipLogProgress(WiFiPAF, "Rx processing from the re-order queue [%u]", qidx);
err = RxPacketProcess(System::PacketBufferHandle::Adopt(ReorderQueue[qidx]));
ReorderQueue[qidx] = nullptr;
ItemsInReorderQueue--;
}
// Has reached the 1st hole in the queue => move the rest items forward
// Note: It's to continue => No need to reinit "i"
for (uint8_t newId = 0; qidx < PAFTP_REORDER_QUEUE_SIZE; qidx++, newId++)
{
if (ReorderQueue[qidx] != nullptr)
{
ReorderQueue[newId] = ReorderQueue[qidx];
ReorderQueue[qidx] = nullptr;
}
}
return err;
}
CHIP_ERROR WiFiPAFEndPoint::RxPacketProcess(PacketBufferHandle && data)
{
ChipLogDebugBufferWiFiPAFEndPoint(WiFiPAF, data);
CHIP_ERROR err = CHIP_NO_ERROR;
SequenceNumber_t receivedAck = 0;
uint8_t closeFlags = kWiFiPAFCloseFlag_AbortTransmission;
bool didReceiveAck = false;
BitFlags<WiFiPAFTP::HeaderFlags> rx_flags;
Encoding::LittleEndian::Reader reader(data->Start(), data->DataLength());
DebugPktAckSn(PktDirect_t::kRx, reader, data->Start());
{ // This is a special handling on the first CHIPoPAF data packet, the CapabilitiesRequest.
// If we're receiving the first inbound packet of a PAF transport connection handshake...
if (!mConnStateFlags.Has(ConnectionStateFlag::kCapabilitiesMsgReceived))
{
if (mRole == kWiFiPafRole_Subscriber) // If we're a central receiving a capabilities response indication...
{
// Ensure end point's in the right state before continuing.
VerifyOrExit(mState == kState_Connecting, err = CHIP_ERROR_INCORRECT_STATE);
mConnStateFlags.Set(ConnectionStateFlag::kCapabilitiesMsgReceived);
err = HandleCapabilitiesResponseReceived(std::move(data));
SuccessOrExit(err);
}
else // Or, a peripheral receiving a capabilities request write...
{
// Ensure end point's in the right state before continuing.
VerifyOrExit(mState == kState_Ready, err = CHIP_ERROR_INCORRECT_STATE);
mConnStateFlags.Set(ConnectionStateFlag::kCapabilitiesMsgReceived);
err = HandleCapabilitiesRequestReceived(std::move(data));
if (err != CHIP_NO_ERROR)
{
// If an error occurred decoding and handling the capabilities request, release the BLE connection.
// Central's connect attempt will time out if peripheral's application decides to keep the BLE
// connection open, or fail immediately if the application closes the connection.
closeFlags = closeFlags | kWiFiPAFCloseFlag_SuppressCallback;
ExitNow();
}
}
// If received data was handshake packet, don't feed it to message reassembler.
ExitNow();
}
} // End handling the CapabilitiesRequest
err = reader.Read8(rx_flags.RawStorage()).StatusCode();
SuccessOrExit(err);
if (rx_flags.Has(WiFiPAFTP::HeaderFlags::kHankshake))
{
ChipLogDebugWiFiPAFEndPoint(WiFiPAF, "Unexpected handshake packet => drop");
ExitNow();
}
ChipLogDebugWiFiPAFEndPoint(WiFiPAF, "PAFTP about to rx characteristic, state before:");
mPafTP.LogStateDebug();
// Pass received packet into PAFTP protocol engine.
err = mPafTP.HandleCharacteristicReceived(std::move(data), receivedAck, didReceiveAck);
ChipLogDebugWiFiPAFEndPoint(WiFiPAF, "PAFTP rx'd characteristic, state after:");
mPafTP.LogStateDebug();
SuccessOrExit(err);
// Protocol engine accepted the fragment, so shrink local receive window counter by 1.
mLocalReceiveWindowSize = static_cast<SequenceNumber_t>(mLocalReceiveWindowSize - 1);
ChipLogDebugWiFiPAFEndPoint(WiFiPAF, "decremented local rx window, new size = %u", mLocalReceiveWindowSize);
// Respond to received ack, if any.
if (didReceiveAck)
{
ChipLogDebugWiFiPAFEndPoint(WiFiPAF, "got paftp ack = %u", receivedAck);
// If ack was rx'd for newest unacked sent fragment, stop ack received timer.
if (!mPafTP.ExpectingAck())
{
ChipLogDebugWiFiPAFEndPoint(WiFiPAF, "got ack for last outstanding fragment");
StopAckReceivedTimer();
if (mState == kState_Closing && mSendQueue.IsNull() && mPafTP.TxState() == WiFiPAFTP::kState_Idle)
{
// If end point closing, got confirmation for last send, and waiting for last ack, finalize close.
FinalizeClose(mState, kWiFiPAFCloseFlag_SuppressCallback, CHIP_NO_ERROR);
ExitNow();
}
}
else // Else there are still sent fragments for which acks are expected, so restart ack received timer.
{
ChipLogDebugWiFiPAFEndPoint(WiFiPAF, "still expecting ack(s), restarting timer...");
err = RestartAckReceivedTimer();
SuccessOrExit(err);
}
ChipLogDebugWiFiPAFEndPoint(
WiFiPAF, "about to adjust remote rx window; got ack num = %u, newest unacked sent seq num = %u, \
old window size = %u, max window size = %u",
receivedAck, mPafTP.GetNewestUnackedSentSequenceNumber(), mRemoteReceiveWindowSize, mReceiveWindowMaxSize);
// Open remote device's receive window according to sequence number it just acknowledged.
mRemoteReceiveWindowSize =
AdjustRemoteReceiveWindow(receivedAck, mReceiveWindowMaxSize, mPafTP.GetNewestUnackedSentSequenceNumber());
ChipLogDebugWiFiPAFEndPoint(WiFiPAF, "adjusted remote rx window, new size = %u", mRemoteReceiveWindowSize);
// Restart message transmission if it was previously paused due to window exhaustion.
err = DriveSending();
SuccessOrExit(err);
}
// The previous DriveSending() might have generated a piggyback acknowledgement if there was
// previously un-acked data. Otherwise, prepare to send acknowledgement for newly received fragment.
//
// If local receive window is below immediate ack threshold, AND there is no previous stand-alone ack in
// flight, AND there is no pending outbound message fragment on which the ack can and will be piggybacked,
// send immediate stand-alone ack to reopen window for sender.
//
// The "operation in flight" check below covers "pending outbound message fragment" by extension, as when
// a message has been passed to the end point via Send(), its next outbound fragment must either be in flight
// itself, or awaiting the completion of another in-flight operation.
//
// If any operation is in flight that is NOT a stand-alone ack, the window size will be checked against
// this threshold again when the operation is confirmed.
if (mPafTP.HasUnackedData())
{
if (mLocalReceiveWindowSize <= WIFIPAF_CONFIG_IMMEDIATE_ACK_WINDOW_THRESHOLD &&
!mConnStateFlags.Has(ConnectionStateFlag::kOperationInFlight))
{
ChipLogDebugWiFiPAFEndPoint(WiFiPAF, "sending immediate ack");
err = DriveStandAloneAck();
SuccessOrExit(err);
}
else
{
ChipLogDebugWiFiPAFEndPoint(WiFiPAF, "starting send-ack timer");
// Send ack when timer expires.
err = StartSendAckTimer();
SuccessOrExit(err);
}
}
// If we've reassembled a whole message...
if (mPafTP.RxState() == WiFiPAFTP::kState_Complete)
{
// Take ownership of message buffer
System::PacketBufferHandle full_packet = mPafTP.TakeRxPacket();
ChipLogDebugWiFiPAFEndPoint(WiFiPAF, "reassembled whole msg, len = %u", static_cast<unsigned>(full_packet->DataLength()));
// If we have a message received callback, and end point is not closing...
if (mWiFiPafLayer != nullptr && mState != kState_Closing)
{
// Pass received message up the stack.
err = mWiFiPafLayer->OnWiFiPAFMsgRxComplete(mSessionInfo, std::move(full_packet));
}
}
exit:
if (err != CHIP_NO_ERROR)
{
DoClose(closeFlags, err);
}
return err;
}
CHIP_ERROR WiFiPAFEndPoint::SendWrite(PacketBufferHandle && buf)
{
mConnStateFlags.Set(ConnectionStateFlag::kOperationInFlight);
ChipLogDebugBufferWiFiPAFEndPoint(WiFiPAF, buf);
Encoding::LittleEndian::Reader reader(buf->Start(), buf->DataLength());
DebugPktAckSn(PktDirect_t::kTx, reader, buf->Start());
mWiFiPafLayer->mWiFiPAFTransport->WiFiPAFMessageSend(mSessionInfo, std::move(buf));
return CHIP_NO_ERROR;
}
CHIP_ERROR WiFiPAFEndPoint::StartConnectTimer()
{
const CHIP_ERROR timerErr = mWiFiPafLayer->mSystemLayer->StartTimer(System::Clock::Milliseconds32(PAFTP_CONN_RSP_TIMEOUT_MS),
HandleConnectTimeout, this);
ReturnErrorOnFailure(timerErr);
mTimerStateFlags.Set(TimerStateFlag::kConnectTimerRunning);
return CHIP_NO_ERROR;
}
CHIP_ERROR WiFiPAFEndPoint::StartAckReceivedTimer()
{
if (!mTimerStateFlags.Has(TimerStateFlag::kAckReceivedTimerRunning))
{
const CHIP_ERROR timerErr = mWiFiPafLayer->mSystemLayer->StartTimer(System::Clock::Milliseconds32(PAFTP_ACK_TIMEOUT_MS),
HandleAckReceivedTimeout, this);
ReturnErrorOnFailure(timerErr);
mTimerStateFlags.Set(TimerStateFlag::kAckReceivedTimerRunning);
}
return CHIP_NO_ERROR;
}
CHIP_ERROR WiFiPAFEndPoint::RestartAckReceivedTimer()
{
VerifyOrReturnError(mTimerStateFlags.Has(TimerStateFlag::kAckReceivedTimerRunning), CHIP_ERROR_INCORRECT_STATE);
StopAckReceivedTimer();
return StartAckReceivedTimer();
}
CHIP_ERROR WiFiPAFEndPoint::StartSendAckTimer()
{
if (!mTimerStateFlags.Has(TimerStateFlag::kSendAckTimerRunning))
{
ChipLogDebugWiFiPAFEndPoint(WiFiPAF, "starting new SendAckTimer");
const CHIP_ERROR timerErr = mWiFiPafLayer->mSystemLayer->StartTimer(
System::Clock::Milliseconds32(WIFIPAF_ACK_SEND_TIMEOUT_MS), HandleSendAckTimeout, this);
ReturnErrorOnFailure(timerErr);
mTimerStateFlags.Set(TimerStateFlag::kSendAckTimerRunning);
}
return CHIP_NO_ERROR;
}
CHIP_ERROR WiFiPAFEndPoint::StartWaitResourceTimer()
{
mResourceWaitCount++;
if (mResourceWaitCount >= WIFIPAF_MAX_RESOURCE_BLOCK_COUNT)
{
ChipLogError(WiFiPAF, "Network resource has been unavailable for a long time");
mResourceWaitCount = 0;
DoClose(kWiFiPAFCloseFlag_AbortTransmission, CHIP_ERROR_NOT_CONNECTED);
return CHIP_NO_ERROR;
}
if (!mTimerStateFlags.Has(TimerStateFlag::kWaitResTimerRunning))
{
ChipLogDebugWiFiPAFEndPoint(WiFiPAF, "starting new WaitResTimer");
const CHIP_ERROR timerErr = mWiFiPafLayer->mSystemLayer->StartTimer(
System::Clock::Milliseconds32(WIFIPAF_WAIT_RES_TIMEOUT_MS), HandleWaitResourceTimeout, this);
ReturnErrorOnFailure(timerErr);
mTimerStateFlags.Set(TimerStateFlag::kWaitResTimerRunning);
}
return CHIP_NO_ERROR;
}
void WiFiPAFEndPoint::StopConnectTimer()
{
// Cancel any existing connect timer.
mWiFiPafLayer->mSystemLayer->CancelTimer(HandleConnectTimeout, this);
mTimerStateFlags.Clear(TimerStateFlag::kConnectTimerRunning);
}
void WiFiPAFEndPoint::StopAckReceivedTimer()
{
// Cancel any existing ack-received timer.
mWiFiPafLayer->mSystemLayer->CancelTimer(HandleAckReceivedTimeout, this);
mTimerStateFlags.Clear(TimerStateFlag::kAckReceivedTimerRunning);
}
void WiFiPAFEndPoint::StopSendAckTimer()
{
// Cancel any existing send-ack timer.
mWiFiPafLayer->mSystemLayer->CancelTimer(HandleSendAckTimeout, this);
mTimerStateFlags.Clear(TimerStateFlag::kSendAckTimerRunning);
}
void WiFiPAFEndPoint::StopWaitResourceTimer()
{
// Cancel any existing wait-resource timer.
mWiFiPafLayer->mSystemLayer->CancelTimer(HandleWaitResourceTimeout, this);
mTimerStateFlags.Clear(TimerStateFlag::kWaitResTimerRunning);
}
void WiFiPAFEndPoint::HandleConnectTimeout(chip::System::Layer * systemLayer, void * appState)
{
WiFiPAFEndPoint * ep = static_cast<WiFiPAFEndPoint *>(appState);
// Check for event-based timer race condition.
if (ep->mTimerStateFlags.Has(TimerStateFlag::kConnectTimerRunning))
{
ChipLogError(WiFiPAF, "connect handshake timed out, closing ep %p", ep);
ep->mTimerStateFlags.Clear(TimerStateFlag::kConnectTimerRunning);
ep->DoClose(kWiFiPAFCloseFlag_AbortTransmission, WIFIPAF_ERROR_CONNECT_TIMED_OUT);
}
}
void WiFiPAFEndPoint::HandleAckReceivedTimeout(chip::System::Layer * systemLayer, void * appState)
{
WiFiPAFEndPoint * ep = static_cast<WiFiPAFEndPoint *>(appState);
// Check for event-based timer race condition.
if (ep->mTimerStateFlags.Has(TimerStateFlag::kAckReceivedTimerRunning))
{
ChipLogError(WiFiPAF, "ack recv timeout, closing ep %p", ep);
ep->mPafTP.LogStateDebug();
ep->mTimerStateFlags.Clear(TimerStateFlag::kAckReceivedTimerRunning);
ep->DoClose(kWiFiPAFCloseFlag_AbortTransmission, WIFIPAF_ERROR_FRAGMENT_ACK_TIMED_OUT);
}
}
void WiFiPAFEndPoint::HandleSendAckTimeout(chip::System::Layer * systemLayer, void * appState)
{
WiFiPAFEndPoint * ep = static_cast<WiFiPAFEndPoint *>(appState);
// Check for event-based timer race condition.
if (ep->mTimerStateFlags.Has(TimerStateFlag::kSendAckTimerRunning))
{
ep->mTimerStateFlags.Clear(TimerStateFlag::kSendAckTimerRunning);
// If previous stand-alone ack isn't still in flight...
if (!ep->mConnStateFlags.Has(ConnectionStateFlag::kStandAloneAckInFlight))
{
CHIP_ERROR sendErr = ep->DriveStandAloneAck();
if (sendErr != CHIP_NO_ERROR)
{
ep->DoClose(kWiFiPAFCloseFlag_AbortTransmission, sendErr);
}
}
}
}
void WiFiPAFEndPoint::HandleWaitResourceTimeout(chip::System::Layer * systemLayer, void * appState)
{
WiFiPAFEndPoint * ep = static_cast<WiFiPAFEndPoint *>(appState);
// Check for event-based timer race condition.
if (ep->mTimerStateFlags.Has(TimerStateFlag::kWaitResTimerRunning))
{
ep->mTimerStateFlags.Clear(TimerStateFlag::kWaitResTimerRunning);
CHIP_ERROR sendErr = ep->DriveSending();
if (sendErr != CHIP_NO_ERROR)
{
ep->DoClose(kWiFiPAFCloseFlag_AbortTransmission, sendErr);
}
}
}
void WiFiPAFEndPoint::ClearAll()
{
memset(reinterpret_cast<uint8_t *>(this), 0, sizeof(WiFiPAFEndPoint));
return;
}
} /* namespace WiFiPAF */
} /* namespace chip */