diff --git a/src/app/server/CommissioningWindowManager.cpp b/src/app/server/CommissioningWindowManager.cpp index 0d8adc364eab41..62126113d7f8b3 100644 --- a/src/app/server/CommissioningWindowManager.cpp +++ b/src/app/server/CommissioningWindowManager.cpp @@ -73,17 +73,8 @@ void CommissioningWindowManager::OnPlatformEvent(const DeviceLayer::ChipDeviceEv mServer->GetBleLayerObject()->CloseAllBleConnections(); #endif #if CHIP_DEVICE_CONFIG_ENABLE_WIFIPAF - chip::WiFiPAF::WiFiPAFLayer::GetWiFiPAFLayer()->Shutdown([](uint32_t id, WiFiPAF::WiFiPAFSession::PAFRole role) { - switch (role) - { - case WiFiPAF::WiFiPAFSession::PAFRole::publisher: - DeviceLayer::ConnectivityMgr().WiFiPAFCancelPublish(id); - break; - case WiFiPAF::WiFiPAFSession::PAFRole::subscriber: - DeviceLayer::ConnectivityMgr().WiFiPAFCancelSubscribe(id); - break; - } - }); + chip::WiFiPAF::WiFiPAFLayer::GetWiFiPAFLayer()->Shutdown( + [](uint32_t id, WiFiPAF::WiFiPafRole role) { DeviceLayer::ConnectivityMgr().WiFiPAFShutdown(id, role); }); #endif } else if (event->Type == DeviceLayer::DeviceEventType::kFailSafeTimerExpired) diff --git a/src/controller/CHIPDeviceController.cpp b/src/controller/CHIPDeviceController.cpp index 6e2bf5a4679636..49bd27be73ed61 100644 --- a/src/controller/CHIPDeviceController.cpp +++ b/src/controller/CHIPDeviceController.cpp @@ -568,17 +568,8 @@ void DeviceCommissioner::Shutdown() } #endif // CHIP_DEVICE_CONFIG_ENABLE_COMMISSIONER_DISCOVERY #if CHIP_DEVICE_CONFIG_ENABLE_WIFIPAF - WiFiPAF::WiFiPAFLayer::GetWiFiPAFLayer()->Shutdown([](uint32_t id, WiFiPAF::WiFiPAFSession::PAFRole role) { - switch (role) - { - case WiFiPAF::WiFiPAFSession::PAFRole::publisher: - DeviceLayer::ConnectivityMgr().WiFiPAFCancelPublish(id); - break; - case WiFiPAF::WiFiPAFSession::PAFRole::subscriber: - DeviceLayer::ConnectivityMgr().WiFiPAFCancelSubscribe(id); - break; - } - }); + WiFiPAF::WiFiPAFLayer::GetWiFiPAFLayer()->Shutdown( + [](uint32_t id, WiFiPAF::WiFiPafRole role) { DeviceLayer::ConnectivityMgr().WiFiPAFShutdown(id, role); }); #endif // Release everything from the commissionee device pool here. @@ -715,13 +706,13 @@ CHIP_ERROR DeviceCommissioner::EstablishPASEConnection(NodeId remoteDeviceId, co #if CHIP_DEVICE_CONFIG_ENABLE_WIFIPAF static void StopSignalHandler(int signum) { - WiFiPAF::WiFiPAFLayer::GetWiFiPAFLayer()->Shutdown([](uint32_t id, WiFiPAF::WiFiPAFSession::PAFRole role) { + WiFiPAF::WiFiPAFLayer::GetWiFiPAFLayer()->Shutdown([](uint32_t id, WiFiPAF::WiFiPafRole role) { switch (role) { - case WiFiPAF::WiFiPAFSession::PAFRole::publisher: + case WiFiPAF::WiFiPafRole::kWiFiPafRole_Publisher: DeviceLayer::ConnectivityMgr().WiFiPAFCancelPublish(id); break; - case WiFiPAF::WiFiPAFSession::PAFRole::subscriber: + case WiFiPAF::WiFiPafRole::kWiFiPafRole_Subscriber: DeviceLayer::ConnectivityMgr().WiFiPAFCancelSubscribe(id); break; } diff --git a/src/include/platform/CHIPDeviceEvent.h b/src/include/platform/CHIPDeviceEvent.h index 2e23ed7c5cb088..300826af384436 100644 --- a/src/include/platform/CHIPDeviceEvent.h +++ b/src/include/platform/CHIPDeviceEvent.h @@ -282,9 +282,10 @@ enum InternalEventTypes */ kCHIPoBLEConnectionError, kCHIPoBLENotifyConfirm, - kCHIPoWiFiPAFWriteReceived, + kCHIPoWiFiPAFReceived, kCHIPoWiFiPAFConnected, kCHIPoWiFiPAFCancelConnect, + kCHIPoWiFiPAFWriteDone, }; static_assert(kEventTypeNotSet == 0, "kEventTypeNotSet must be defined as 0"); @@ -388,6 +389,7 @@ typedef void (*AsyncWorkFunct)(intptr_t arg); #include #include #include +#include namespace chip { namespace DeviceLayer { @@ -498,10 +500,8 @@ struct ChipDeviceEvent final struct { chip::System::PacketBuffer * Data; - uint32_t id; - uint32_t peer_id; - uint8_t peer_addr[6]; - } CHIPoWiFiPAFWriteReceived; + chip::WiFiPAF::WiFiPAFSession SessionInfo; + } CHIPoWiFiPAFReceived; #endif struct { diff --git a/src/include/platform/ConnectivityManager.h b/src/include/platform/ConnectivityManager.h index a647113acbfdb6..bbcc35c2d4396b 100644 --- a/src/include/platform/ConnectivityManager.h +++ b/src/include/platform/ConnectivityManager.h @@ -189,6 +189,7 @@ class ConnectivityManager CHIP_ERROR WiFiPAFSend(const WiFiPAF::WiFiPAFSession & TxInfo, System::PacketBufferHandle && msgBuf); WiFiPAF::WiFiPAFLayer * GetWiFiPAF(); void WiFiPafSetApFreq(const uint16_t freq); + CHIP_ERROR WiFiPAFShutdown(uint32_t id, WiFiPAF::WiFiPafRole role); #endif // WiFi AP methods @@ -472,6 +473,11 @@ inline CHIP_ERROR ConnectivityManager::WiFiPAFSend(const WiFiPAF::WiFiPAFSession { return static_cast(this)->_WiFiPAFSend(TxInfo, std::move(msgBuf)); } + +inline CHIP_ERROR ConnectivityManager::WiFiPAFShutdown(uint32_t id, WiFiPAF::WiFiPafRole role) +{ + return static_cast(this)->_WiFiPAFShutdown(id, role); +} #endif inline bool ConnectivityManager::IsThreadEnabled() diff --git a/src/include/platform/internal/GenericPlatformManagerImpl.ipp b/src/include/platform/internal/GenericPlatformManagerImpl.ipp index fc9a2637e40b21..49ebddc6a5f310 100644 --- a/src/include/platform/internal/GenericPlatformManagerImpl.ipp +++ b/src/include/platform/internal/GenericPlatformManagerImpl.ipp @@ -153,17 +153,8 @@ void GenericPlatformManagerImpl::_Shutdown() #if CHIP_DEVICE_CONFIG_ENABLE_WIFIPAF ChipLogProgress(DeviceLayer, "WiFi-PAF Layer shutdown"); - WiFiPAF::WiFiPAFLayer::GetWiFiPAFLayer()->Shutdown([](uint32_t id, WiFiPAF::WiFiPAFSession::PAFRole role) { - switch (role) - { - case WiFiPAF::WiFiPAFSession::PAFRole::publisher: - DeviceLayer::ConnectivityMgr().WiFiPAFCancelPublish(id); - break; - case WiFiPAF::WiFiPAFSession::PAFRole::subscriber: - DeviceLayer::ConnectivityMgr().WiFiPAFCancelSubscribe(id); - break; - } - }); + WiFiPAF::WiFiPAFLayer::GetWiFiPAFLayer()->Shutdown( + [](uint32_t id, WiFiPAF::WiFiPafRole role) { DeviceLayer::ConnectivityMgr().WiFiPAFShutdown(id, role); }); #endif ChipLogProgress(DeviceLayer, "System Layer shutdown"); diff --git a/src/lib/support/logging/Constants.h b/src/lib/support/logging/Constants.h index badc112202c277..78b37e64382e85 100644 --- a/src/lib/support/logging/Constants.h +++ b/src/lib/support/logging/Constants.h @@ -61,6 +61,7 @@ enum LogModule kLogModule_CASESessionManager, kLogModule_ICD, kLogModule_FabricSync, + kLogModule_WiFiPAF, kLogModule_Max }; @@ -236,6 +237,10 @@ enum LogModule #define CHIP_CONFIG_LOG_MODULE_FabricSync 1 #endif +#ifndef CHIP_CONFIG_LOG_MODULE_WiFiPAF +#define CHIP_CONFIG_LOG_MODULE_WiFiPAF 1 +#endif + /** * @enum LogCategory * diff --git a/src/lib/support/logging/TextOnlyLogging.cpp b/src/lib/support/logging/TextOnlyLogging.cpp index 0e420140524662..220ce185bf28d9 100644 --- a/src/lib/support/logging/TextOnlyLogging.cpp +++ b/src/lib/support/logging/TextOnlyLogging.cpp @@ -151,6 +151,7 @@ static const char ModuleNames[kLogModule_Max][kMaxModuleNameLen + 1] = { "CSM", // CASESessionManager "ICD", // ICD "FS", // FabricSync + "PAF", // WiFiPAF }; } // namespace diff --git a/src/platform/Linux/ConnectivityManagerImpl.cpp b/src/platform/Linux/ConnectivityManagerImpl.cpp index dd27d2891cc79d..a57870f39e63c1 100644 --- a/src/platform/Linux/ConnectivityManagerImpl.cpp +++ b/src/platform/Linux/ConnectivityManagerImpl.cpp @@ -2,6 +2,7 @@ * * Copyright (c) 2020-2022 Project CHIP Authors * Copyright (c) 2019 Nest Labs, Inc. + * Copyright (c) 2025 NXP * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -145,6 +146,7 @@ CHIP_ERROR ConnectivityManagerImpl::_Init() #endif #if CHIP_DEVICE_CONFIG_ENABLE_WIFIPAF pmWiFiPAF = WiFiPAF::WiFiPAFLayer::GetWiFiPAFLayer(); + pmWiFiPAF->Init(&DeviceLayer::SystemLayer()); #endif return CHIP_NO_ERROR; @@ -159,21 +161,18 @@ void ConnectivityManagerImpl::_OnPlatformEvent(const ChipDeviceEvent * event) #if CHIP_DEVICE_CONFIG_ENABLE_WIFIPAF switch (event->Type) { - case DeviceEventType::kCHIPoWiFiPAFWriteReceived: { - ChipLogProgress(DeviceLayer, "WiFi-PAF: event: kCHIPoWiFiPAFWriteReceived"); - WiFiPAF::WiFiPAFSession RxInfo{ .id = event->CHIPoWiFiPAFWriteReceived.id, - .peer_id = event->CHIPoWiFiPAFWriteReceived.peer_id }; - memcpy(RxInfo.peer_addr, event->CHIPoWiFiPAFWriteReceived.peer_addr, sizeof(uint8_t) * 6); - _GetWiFiPAF()->OnWiFiPAFMessageReceived(RxInfo, System::PacketBufferHandle::Adopt(event->CHIPoWiFiPAFWriteReceived.Data)); + case DeviceEventType::kCHIPoWiFiPAFReceived: { + ChipLogProgress(DeviceLayer, "WiFi-PAF: event: kCHIPoWiFiPAFReceived"); + WiFiPAF::WiFiPAFSession RxInfo; + memcpy(&RxInfo, &event->CHIPoWiFiPAFReceived.SessionInfo, sizeof(WiFiPAF::WiFiPAFSession)); + _GetWiFiPAF()->OnWiFiPAFMessageReceived(RxInfo, System::PacketBufferHandle::Adopt(event->CHIPoWiFiPAFReceived.Data)); break; } case DeviceEventType::kCHIPoWiFiPAFConnected: { ChipLogProgress(DeviceLayer, "WiFi-PAF: event: kCHIPoWiFiPAFConnected"); - if (mOnPafSubscribeComplete != nullptr) - { - mOnPafSubscribeComplete(mAppState); - mOnPafSubscribeComplete = nullptr; - } + WiFiPAF::WiFiPAFSession SessionInfo; + memcpy(&SessionInfo, &event->CHIPoWiFiPAFReceived.SessionInfo, sizeof(WiFiPAF::WiFiPAFSession)); + _GetWiFiPAF()->HandleTransportConnectionInitiated(SessionInfo, mOnPafSubscribeComplete, mAppState, mOnPafSubscribeError); break; } case DeviceEventType::kCHIPoWiFiPAFCancelConnect: { @@ -185,6 +184,13 @@ void ConnectivityManagerImpl::_OnPlatformEvent(const ChipDeviceEvent * event) } break; } + case DeviceEventType::kCHIPoWiFiPAFWriteDone: { + ChipLogProgress(DeviceLayer, "WiFi-PAF: event: kCHIPoWiFiPAFWriteDone"); + WiFiPAF::WiFiPAFSession TxInfo; + memcpy(&TxInfo, &event->CHIPoWiFiPAFReceived.SessionInfo, sizeof(WiFiPAF::WiFiPAFSession)); + _GetWiFiPAF()->HandleWriteConfirmed(TxInfo); + break; + } } #endif // CHIP_DEVICE_CONFIG_ENABLE_WIFIPAF @@ -1458,16 +1464,15 @@ void ConnectivityManagerImpl::OnDiscoveryResult(GVariant * discov_info) peer_addr[3], peer_addr[4], peer_addr[5]); ChipLogProgress(DeviceLayer, "\t DevInfo: %x", pPublishSSI->DevInfo); - pPafInfo->role = WiFiPAF::WiFiPAFSession::subscriber; + pPafInfo->role = WiFiPAF::WiFiPafRole::kWiFiPafRole_Subscriber; pPafInfo->id = subscribe_id; pPafInfo->peer_id = peer_publish_id; memcpy(pPafInfo->peer_addr, peer_addr, sizeof(uint8_t) * 6); - /* Indicate the connection event */ - ChipDeviceEvent event; - event.Type = DeviceEventType::kCHIPoWiFiPAFConnected; + ChipDeviceEvent event{ .Type = DeviceEventType::kCHIPoWiFiPAFConnected }; + memcpy(&event.CHIPoWiFiPAFReceived.SessionInfo, pPafInfo, sizeof(chip::WiFiPAF::WiFiPAFSession)); PlatformMgr().PostEventOrDie(&event); } @@ -1535,7 +1540,7 @@ void ConnectivityManagerImpl::OnReplied(GVariant * reply_info) ChipLogError(DeviceLayer, "WiFi-PAF: OnReplied, no valid session with publish_id: %d", publish_id); return; } - if ((pPafInfo->role = WiFiPAF::WiFiPAFSession::publisher) && (pPafInfo->peer_id == peer_subscribe_id) && + if ((pPafInfo->role == WiFiPAF::WiFiPafRole::kWiFiPafRole_Publisher) && (pPafInfo->peer_id == peer_subscribe_id) && !memcmp(pPafInfo->peer_addr, peer_addr, sizeof(uint8_t) * 6)) { ChipLogError(DeviceLayer, "WiFi-PAF: OnReplied, reentrance, publish_id: %u ", publish_id); @@ -1548,13 +1553,13 @@ void ConnectivityManagerImpl::OnReplied(GVariant * reply_info) ChipLogProgress(DeviceLayer, "\t (publish_id, peer_subscribe_id): (%u, %u)", publish_id, peer_subscribe_id); ChipLogProgress(DeviceLayer, "\t peer_addr: [%02x:%02x:%02x:%02x:%02x:%02x]", peer_addr[0], peer_addr[1], peer_addr[2], peer_addr[3], peer_addr[4], peer_addr[5]); - ChipLogProgress(DeviceLayer, "\t DevInfo: %x", pPublishSSI->DevInfo); - pPafInfo->role = WiFiPAF::WiFiPAFSession::publisher; + pPafInfo->role = WiFiPAF::WiFiPafRole::kWiFiPafRole_Publisher; pPafInfo->id = publish_id; pPafInfo->peer_id = peer_subscribe_id; memcpy(pPafInfo->peer_addr, peer_addr, sizeof(uint8_t) * 6); + _GetWiFiPAF()->HandleTransportConnectionInitiated(*pPafInfo); } void ConnectivityManagerImpl::OnNanReceive(GVariant * obj) @@ -1596,10 +1601,9 @@ void ConnectivityManagerImpl::OnNanReceive(GVariant * obj) buf = System::PacketBufferHandle::NewWithData(rxbuf, bufferLen); // Post an event to the Chip queue to deliver the data into the Chip stack. - ChipDeviceEvent event{ .Type = DeviceEventType::kCHIPoWiFiPAFWriteReceived, - .CHIPoWiFiPAFWriteReceived = { - .Data = std::move(buf).UnsafeRelease(), .id = RxInfo.id, .peer_id = RxInfo.peer_id } }; - memcpy(event.CHIPoWiFiPAFWriteReceived.peer_addr, RxInfo.peer_addr, sizeof(uint8_t) * 6); + ChipDeviceEvent event{ .Type = DeviceEventType::kCHIPoWiFiPAFReceived, + .CHIPoWiFiPAFReceived = { .Data = std::move(buf).UnsafeRelease() } }; + memcpy(&event.CHIPoWiFiPAFReceived.SessionInfo, &RxInfo, sizeof(WiFiPAF::WiFiPAFSession)); PlatformMgr().PostEventOrDie(&event); } @@ -1676,7 +1680,7 @@ CHIP_ERROR ConnectivityManagerImpl::_WiFiPAFSubscribe(const SetupDiscriminator & if (pPafInfo != nullptr) { pPafInfo->id = subscribe_id; - pPafInfo->role = WiFiPAF::WiFiPAFSession::subscriber; + pPafInfo->role = WiFiPAF::WiFiPafRole::kWiFiPafRole_Subscriber; } g_signal_connect(mWpaSupplicant.iface, "nandiscovery-result", @@ -1766,12 +1770,33 @@ CHIP_ERROR ConnectivityManagerImpl::_WiFiPAFSend(const WiFiPAF::WiFiPAFSession & g_variant_builder_add(&builder, "{sv}", "peer_addr", g_variant_new_string(peer_mac)); g_variant_builder_add(&builder, "{sv}", "ssi", ssi_array_variant); args = g_variant_builder_end(&builder); - wpa_fi_w1_wpa_supplicant1_interface_call_nantransmit_sync(mWpaSupplicant.iface, args, nullptr, &err.GetReceiver()); - + gboolean result = + wpa_fi_w1_wpa_supplicant1_interface_call_nantransmit_sync(mWpaSupplicant.iface, args, nullptr, &err.GetReceiver()); + if (!result) + { + ChipLogError(DeviceLayer, "WiFi-PAF: Failed to send message: %s", err == nullptr ? "unknown error" : err->message); + } ChipLogProgress(Controller, "WiFi-PAF: Outbound message (%lu) done", msgBuf->DataLength()); + + // Post an event to the Chip queue to deliver the data into the Chip stack. + ChipDeviceEvent event{ .Type = DeviceEventType::kCHIPoWiFiPAFWriteDone }; + memcpy(&event.CHIPoWiFiPAFReceived.SessionInfo, &TxInfo, sizeof(chip::WiFiPAF::WiFiPAFSession)); + PlatformMgr().PostEventOrDie(&event); return ret; } +CHIP_ERROR ConnectivityManagerImpl::_WiFiPAFShutdown(uint32_t id, WiFiPAF::WiFiPafRole role) +{ + switch (role) + { + case WiFiPAF::WiFiPafRole::kWiFiPafRole_Publisher: + return _WiFiPAFCancelPublish(id); + case WiFiPAF::WiFiPafRole::kWiFiPafRole_Subscriber: + return _WiFiPAFCancelSubscribe(id); + } + return CHIP_ERROR_INTERNAL; +} + #endif // CHIP_DEVICE_CONFIG_ENABLE_WIFIPAF void ConnectivityManagerImpl::_ConnectWiFiNetworkAsyncCallback(GObject * sourceObject, GAsyncResult * res) diff --git a/src/platform/Linux/ConnectivityManagerImpl.h b/src/platform/Linux/ConnectivityManagerImpl.h index a92c1332e7ad55..34c0c93a113b00 100644 --- a/src/platform/Linux/ConnectivityManagerImpl.h +++ b/src/platform/Linux/ConnectivityManagerImpl.h @@ -49,6 +49,7 @@ #include #if CHIP_DEVICE_CONFIG_ENABLE_WIFIPAF +#include #include #endif #endif @@ -153,6 +154,7 @@ class ConnectivityManagerImpl final : public ConnectivityManager, CHIP_ERROR _WiFiPAFSend(const WiFiPAF::WiFiPAFSession & TxInfo, chip::System::PacketBufferHandle && msgBuf); WiFiPAF::WiFiPAFLayer * _GetWiFiPAF(); void _WiFiPafSetApFreq(const uint16_t freq) { mApFreq = freq; } + CHIP_ERROR _WiFiPAFShutdown(uint32_t id, WiFiPAF::WiFiPafRole role); #endif void PostNetworkConnect(); @@ -236,6 +238,7 @@ class ConnectivityManagerImpl final : public ConnectivityManager, OnConnectionCompleteFunct mOnPafSubscribeComplete; OnConnectionErrorFunct mOnPafSubscribeError; WiFiPAF::WiFiPAFLayer * pmWiFiPAF; + WiFiPAF::WiFiPAFEndPoint mWiFiPAFEndPoint; void * mAppState; uint16_t mApFreq; CHIP_ERROR _WiFiPAFPublish(WiFiPAFAdvertiseParam & args); diff --git a/src/transport/SecureSession.h b/src/transport/SecureSession.h index 574d5f40ccfe5a..02c72c42c29562 100644 --- a/src/transport/SecureSession.h +++ b/src/transport/SecureSession.h @@ -158,11 +158,7 @@ class SecureSession : public Session, public ReferenceCountedSendMessage(*pTxInfo, std::move(msgBuf)); return CHIP_NO_ERROR; } @@ -124,6 +124,14 @@ void WiFiPAFBase::OnWiFiPAFMessageReceived(chip::WiFiPAF::WiFiPAFSession & RxInf HandleMessageReceived(Transport::PeerAddress(Transport::Type::kWiFiPAF, pPafInfo->nodeId), std::move(buffer)); } +CHIP_ERROR WiFiPAFBase::WiFiPAFMessageSend(chip::WiFiPAF::WiFiPAFSession & TxInfo, System::PacketBufferHandle && msgBuf) +{ + VerifyOrReturnError(mWiFiPAFLayer->GetWiFiPAFState() != chip::WiFiPAF::State::kNotReady, CHIP_ERROR_INCORRECT_STATE); + DeviceLayer::ConnectivityMgr().WiFiPAFSend(TxInfo, std::move(msgBuf)); + + return CHIP_NO_ERROR; +} + CHIP_ERROR WiFiPAFBase::SendAfterConnect(System::PacketBufferHandle && msg) { CHIP_ERROR err = CHIP_ERROR_NO_MEMORY; diff --git a/src/transport/raw/WiFiPAF.h b/src/transport/raw/WiFiPAF.h index 6168f4a82ced07..05992d8ae72c06 100644 --- a/src/transport/raw/WiFiPAF.h +++ b/src/transport/raw/WiFiPAF.h @@ -53,11 +53,18 @@ class DLL_EXPORT WiFiPAFBase : public Base, public WiFiPAF::WiFiPAFLayerDelegate * @param param Wi-Fi-PAF configuration parameters for this transport */ CHIP_ERROR Init(const WiFiPAFListenParameters & param); - CHIP_ERROR SendMessage(const Transport::PeerAddress & address, System::PacketBufferHandle && msgBuf) override; bool CanSendToPeer(const Transport::PeerAddress & address) override; - void OnWiFiPAFMessageReceived(WiFiPAF::WiFiPAFSession & RxInfo, System::PacketBufferHandle && buffer) override; void SetWiFiPAFLayerTransportToSelf() { mWiFiPAFLayer->mWiFiPAFTransport = this; } bool IsWiFiPAFLayerTransportSetToSelf() { return mWiFiPAFLayer->mWiFiPAFTransport == this; } + /** + * Interface of Base + */ + CHIP_ERROR SendMessage(const Transport::PeerAddress & address, System::PacketBufferHandle && msgBuf) override; + /** + * Interfaces of WiFiPAFLayerDelegate + */ + void OnWiFiPAFMessageReceived(WiFiPAF::WiFiPAFSession & RxInfo, System::PacketBufferHandle && buffer) override; + CHIP_ERROR WiFiPAFMessageSend(WiFiPAF::WiFiPAFSession & TxInfo, System::PacketBufferHandle && msg) override; private: /** diff --git a/src/wifipaf/BUILD.gn b/src/wifipaf/BUILD.gn index c58a774a936303..092f5112c47e2f 100644 --- a/src/wifipaf/BUILD.gn +++ b/src/wifipaf/BUILD.gn @@ -1,4 +1,4 @@ -# Copyright (c) 2024 Project CHIP Authors +# 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. @@ -21,9 +21,11 @@ if (chip_device_config_enable_wifipaf) { output_name = "libWiFiPAFLayer" sources = [ + "WiFiPAFEndPoint.cpp", "WiFiPAFLayer.cpp", "WiFiPAFLayer.h", "WiFiPAFLayerDelegate.h", + "WiFiPAFTP.cpp", ] cflags = [ "-Wconversion" ] diff --git a/src/wifipaf/WiFiPAFConfig.h b/src/wifipaf/WiFiPAFConfig.h new file mode 100644 index 00000000000000..8d907f12f3850a --- /dev/null +++ b/src/wifipaf/WiFiPAFConfig.h @@ -0,0 +1,142 @@ +/* + * + * 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 defines default compile-time configuration constants + * for the CHIP PAFTP layer. + * + */ + +#pragma once + +#include + +/** + * @def WIFIPAF_LAYER_NUM_PAF_ENDPOINTS + * + * @brief + * This defines the number of WiFiPAFEndPoint objects allocated for use by the + * WiFiPAFLayer subsystem. Value should be defined as the minimum of (max number + * of simultaneous PAF connections the system supports, max number of + * simultaneous PAF connections the application will establish). + */ +#ifndef WIFIPAF_LAYER_NUM_PAF_ENDPOINTS +#define WIFIPAF_LAYER_NUM_PAF_ENDPOINTS 1 +#endif // WIFIPAF_LAYER_NUM_PAF_ENDPOINTS + +#if (WIFIPAF_LAYER_NUM_PAF_ENDPOINTS < 1) +#error "WIFIPAF_LAYER_NUM_PAF_ENDPOINTS must be greater than 0. configure options may be used to disable chip over PAF." +#endif + +/** + * @def WIFIPAF_CONNECTION_OBJECT + * + * @brief + * This defines the type of WIFIPAF_CONNECTION_OBJECT parameters passed between + * PAF platform code and the WiFiPAFLayer subsystem. + * + * Most platforms should be able to retain this type's default definition as + * (void *), and pass [pointers to] connection handles generated by their + * platform interface where WIFIPAF_CONNECTION_OBJECT arguments are required by + * WiFiPAFLayer input functions. + * + */ +#ifndef WIFIPAF_CONNECTION_OBJECT +#define WIFIPAF_CONNECTION_OBJECT void * +#endif // WIFIPAF_CONNECTION_OBJECT + +/** + * @def WIFIPAF_CONNECTION_UNINITIALIZED + * + * @brief + * This defines the value of an uninitialized WIFIPAF_CONNECTION_OBJECT. + * + */ +#ifndef WIFIPAF_CONNECTION_UNINITIALIZED +#define WIFIPAF_CONNECTION_UNINITIALIZED nullptr +#endif // WIFIPAF_CONNECTION_UNINITIALIZED + +/** + * @def PAF_MAX_RECEIVE_WINDOW_SIZE + * + * @brief + * This is the maximum allowed size of a PAF end point's receive window, defined as the number of fragments the + * end point may reliably receive without PAF-layer acknowledgement. This value should be no larger than the floor + * of ONE-HALF the total number of slots or buffers reserved. The PAFTP layer reserves all of these buffers for its + * own use - one half for incoming writes or indications, and the other half for incoming confirmations. + * + * This value must be greater than 1, or race condition avoidance logic will prevent send the on remote device. This + * logic prevents any send with no piggybacked ack when the receiver's window has only 1 slot open. Without this + * logic, simultaneous data transmissions could fill both receiver's windows, leaving no room for the acks required + * to re-open them. Both senders would wedge, and the PAFTP connection would stall. + * + * This value must also exceed (WIFIPAF_CONFIG_IMMEDIATE_ACK_WINDOW_THRESHOLD + 1), or ***immediate*** stand-alone + * acks will forever be sent without delay in response to one another as each peer's window size dips below + * WIFIPAF_CONFIG_IMMEDIATE_ACK_WINDOW_THRESHOLD with receipt of any single message fragment. + * + * Default value of 3 is absolute minimum for stable performance, and an attempt to ensure safe window sizes on new + * platforms. + * + */ +#ifndef PAF_MAX_RECEIVE_WINDOW_SIZE +#define PAF_MAX_RECEIVE_WINDOW_SIZE 6 +#endif + +#if (PAF_MAX_RECEIVE_WINDOW_SIZE < 3) +#error "PAF_MAX_RECEIVE_WINDOW_SIZE must be greater than 2 for PAF transport protocol stability." +#endif + +/** + * @def PAFTP_CONN_RSP_TIMEOUT_MS + * + * @brief + * Maximum amount of time, in milliseconds, after sending or receiving a PAFTP Session Handshake + * request to wait for connection establishment. + */ +#ifndef PAFTP_CONN_RSP_TIMEOUT_MS +#define PAFTP_CONN_RSP_TIMEOUT_MS 5000 +#endif // PAFTP_CONN_RSP_TIMEOUT_MS + +/** + * @def PAFTP_ACK_TIMEOUT_MS + * + * @brief + * Maximum amount of time, in milliseconds, after sending a PAFTP packet to wait for + * an acknowledgement. When the ack is not received within this period the PAFTP session is closed. + */ +#ifndef PAFTP_ACK_TIMEOUT_MS +#define PAFTP_ACK_TIMEOUT_MS 15000 +#endif // PAFTP_ACK_TIMEOUT_MS + +/** + * @def PAFTP_CONN_IDLE_TIMEOUT_MS + * + * @brief + * Maximum amount of time, in milliseconds, no unique data has been sent over PAFTP session + * before the Commissioner must close the PAFTP session + */ +#ifndef PAFTP_CONN_IDLE_TIMEOUT_MS +#define PAFTP_CONN_IDLE_TIMEOUT_MS 30000 +#endif // PAFTP_CONN_IDLE_TIMEOUT_MS + +/* + * Ref: [4.748] Supported Maximum Service Specific Info Length + */ +#define CHIP_PAF_DEFAULT_MTU 350 + +#include diff --git a/src/wifipaf/WiFiPAFEndPoint.cpp b/src/wifipaf/WiFiPAFEndPoint.cpp new file mode 100644 index 00000000000000..2cd4565622d985 --- /dev/null +++ b/src/wifipaf/WiFiPAFEndPoint.cpp @@ -0,0 +1,1143 @@ +/* + * + * 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 +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#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 + +/** + * @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(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(); + } + + // 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; + + // 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(); + mConnStateFlags.Set(ConnectionStateFlag::kOperationInFlight); + } + else + { + Free(); + } + } +} + +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(); + + // 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(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) +{ + ChipLogDebugWiFiPAFEndPoint(WiFiPAF, "entered Send"); + + 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: + ChipLogDebugWiFiPAFEndPoint(WiFiPAF, "exiting Send"); + 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() +{ + ChipLogDebugWiFiPAFEndPoint(WiFiPAF, "entered HandleHandshakeConfirmationReceived"); + + // Free capabilities request/response payload. + mSendQueue.FreeHead(); + ChipLogDebugWiFiPAFEndPoint(WiFiPAF, "exiting HandleHandshakeConfirmationReceived"); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR WiFiPAFEndPoint::HandleFragmentConfirmationReceived() +{ + CHIP_ERROR err = CHIP_NO_ERROR; + + ChipLogDebugWiFiPAFEndPoint(WiFiPAF, "entered HandleFragmentConfirmationReceived"); + + // 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 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() +{ + ChipLogDebugWiFiPAFEndPoint(WiFiPAF, "entered HandleSendConfirmationReceived"); + + // 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(); +} + +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, "entered DoSendStandAloneAck; 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() +{ + ChipLogDebugWiFiPAFEndPoint(WiFiPAF, "entered 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; + } + + // 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(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(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(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(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(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(newRemoteWindowBoundary - newestUnackedSentSeqNum); +} + +CHIP_ERROR WiFiPAFEndPoint::DebugPktAckSn(const PktDirect_t PktDirect, PacketBufferHandle && buf) +{ +#ifdef CHIP_WIFIPAF_END_POINT_DEBUG_LOGGING_ENABLED + BitFlags rx_flags; + Encoding::LittleEndian::Reader reader(buf->Start(), buf->DataLength()); + CHIP_ERROR err; + uint8_t * pHead = buf->Start(); + uint8_t * pAct = nullptr; + char AckBuff[4]; + uint8_t * pSn; + uint8_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), "%02hu", *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) +{ + ChipLogDebugWiFiPAFEndPoint(WiFiPAF, "+++++++++++++++++++++ entered receive"); + ChipLogDebugBufferWiFiPAFEndPoint(WiFiPAF, data); + DebugPktAckSn(PktDirect_t::kRx, std::move(data)); + + CHIP_ERROR err = CHIP_NO_ERROR; + SequenceNumber_t receivedAck = 0; + uint8_t closeFlags = kWiFiPAFCloseFlag_AbortTransmission; + bool didReceiveAck = false; + BitFlags rx_flags; + Encoding::LittleEndian::Reader reader(data->Start(), data->DataLength()); + + { // 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(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(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. + 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); + + ChipLogDebugWiFiPAFEndPoint(WiFiPAF, "==> %s():", __FUNCTION__); + ChipLogDebugBufferWiFiPAFEndPoint(WiFiPAF, buf); + DebugPktAckSn(PktDirect_t::kTx, std::move(buf)); + 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() +{ + ChipLogDebugWiFiPAFEndPoint(WiFiPAF, "entered 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; +} + +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::HandleConnectTimeout(chip::System::Layer * systemLayer, void * appState) +{ + WiFiPAFEndPoint * ep = static_cast(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(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(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); + } + } + } +} + +} /* namespace WiFiPAF */ +} /* namespace chip */ diff --git a/src/wifipaf/WiFiPAFEndPoint.h b/src/wifipaf/WiFiPAFEndPoint.h new file mode 100644 index 00000000000000..4247b5848cf493 --- /dev/null +++ b/src/wifipaf/WiFiPAFEndPoint.h @@ -0,0 +1,182 @@ +/* + * + * 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 defines a WiFiPAF connection endpoint abstraction. + * + */ + +#pragma once + +#include + +#include +#include +#include +#include +#include + +#include "WiFiPAFConfig.h" +#include "WiFiPAFRole.h" +#include "WiFiPAFTP.h" + +namespace chip { +namespace WiFiPAF { + +using ::chip::System::PacketBufferHandle; + +enum +{ + kWiFiPAFCloseFlag_SuppressCallback = 0x01, + kWiFiPAFCloseFlag_AbortTransmission = 0x02 +}; + +// Forward declarations +class WiFiPAFLayer; +class WiFiPAFEndPointPool; + +class DLL_EXPORT WiFiPAFEndPoint +{ + friend class WiFiPAFLayer; + friend class WiFiPAFEndPointPool; + +public: + typedef uint64_t AlignT; + + enum + { + kState_Ready = 0, + kState_Connecting = 1, + kState_Aborting = 2, + kState_Connected = 3, + kState_Closing = 4, + kState_Closed = 5 + } mState; // [READ-ONLY] End point connection state. Refers to state of CHIP over + // PAF transport protocol connection. + + typedef void (*OnSubscribeCompleteFunct)(void * appState); + typedef void (*OnSubscribeErrorFunct)(void * appState, CHIP_ERROR err); + OnSubscribeCompleteFunct mOnPafSubscribeComplete; + OnSubscribeErrorFunct mOnPafSubscribeError; + void * mAppState; + + typedef void (*OnMessageReceivedFunct)(WiFiPAFEndPoint * endPoint, PacketBufferHandle && msg); + OnMessageReceivedFunct OnMessageReceived; + + typedef void (*OnConnectionClosedFunct)(WiFiPAFEndPoint * endPoint, CHIP_ERROR err); + OnConnectionClosedFunct OnConnectionClosed; + + CHIP_ERROR Send(PacketBufferHandle && data); + CHIP_ERROR Receive(PacketBufferHandle && data); + CHIP_ERROR StartConnect(); + WiFiPAFEndPoint() = default; + ~WiFiPAFEndPoint() = default; + +private: + enum class PktDirect_t : uint8_t + { + kTx, + kRx + }; + CHIP_ERROR DebugPktAckSn(const PktDirect_t PktDirect, PacketBufferHandle && buf); + + WiFiPAFLayer * mWiFiPafLayer; ///< [READ-ONLY] Pointer to the WiFiPAFLayer object that owns this object. + WiFiPAFSession mSessionInfo; + + enum class ConnectionStateFlag : uint8_t + { + kCapabilitiesConfReceived = 0x02, // Ready for sent capabilities req/resp. + kCapabilitiesMsgReceived = 0x04, // Capabilities request or response message received. + kStandAloneAckInFlight = 0x10, // Stand-alone ack in flight. + kOperationInFlight = 0x20 // Operation in flight, + }; + + enum class TimerStateFlag : uint8_t + { + kConnectTimerRunning = 0x01, // PAFTP connect completion timer running. + kAckReceivedTimerRunning = 0x04, // Ack received timer running due to unacked sent fragment. + kSendAckTimerRunning = 0x08, // Send ack timer running; indicates pending ack to send. + }; + + // Queue of outgoing messages to send when current PAFTPEngine transmission completes. + // Re-used during connection setup to cache capabilities request and response payloads; payloads are freed when + // connection is established. + PacketBufferHandle mSendQueue; + + // Pending stand-alone PAFTP acknowledgement. Pre-empts regular send queue or fragmented message transmission in + // progress. + PacketBufferHandle mAckToSend; + + WiFiPAFTP mPafTP; + WiFiPafRole mRole; + + BitFlags mConnStateFlags; + BitFlags mTimerStateFlags; + SequenceNumber_t mLocalReceiveWindowSize; + SequenceNumber_t mRemoteReceiveWindowSize; + SequenceNumber_t mReceiveWindowMaxSize; + + CHIP_ERROR Init(WiFiPAFLayer * WiFiPafLayer, WiFiPAFSession & SessionInfo); + bool IsConnected(uint8_t state) const; + void DoClose(uint8_t flags, CHIP_ERROR err); + + // Transmit path: + CHIP_ERROR DriveSending(); + CHIP_ERROR DriveStandAloneAck(); + bool PrepareNextFragment(PacketBufferHandle && data, bool & sentAck); + CHIP_ERROR SendNextMessage(); + CHIP_ERROR ContinueMessageSend(); + CHIP_ERROR DoSendStandAloneAck(); + CHIP_ERROR SendCharacteristic(PacketBufferHandle && buf); + CHIP_ERROR SendWrite(PacketBufferHandle && buf); + + // Receive path: + CHIP_ERROR HandleConnectComplete(); + CHIP_ERROR HandleSendConfirmationReceived(); + CHIP_ERROR HandleHandshakeConfirmationReceived(); + CHIP_ERROR HandleFragmentConfirmationReceived(); + CHIP_ERROR HandleCapabilitiesRequestReceived(PacketBufferHandle && data); + CHIP_ERROR HandleCapabilitiesResponseReceived(PacketBufferHandle && data); + SequenceNumber_t AdjustRemoteReceiveWindow(SequenceNumber_t lastReceivedAck, SequenceNumber_t maxRemoteWindowSize, + SequenceNumber_t newestUnackedSentSeqNum); + + // Timer control functions: + CHIP_ERROR StartConnectTimer(); // Start connect timer. + CHIP_ERROR StartAckReceivedTimer(); // Start ack-received timer if it's not already running. + CHIP_ERROR RestartAckReceivedTimer(); // Restart ack-received timer. + CHIP_ERROR StartSendAckTimer(); // Start send-ack timer if it's not already running. + void StopConnectTimer(); // Stop connect timer. + void StopAckReceivedTimer(); // Stop ack-received timer. + void StopSendAckTimer(); // Stop send-ack timer. + + // Timer expired callbacks: + static void HandleConnectTimeout(chip::System::Layer * systemLayer, void * appState); + static void HandleAckReceivedTimeout(chip::System::Layer * systemLayer, void * appState); + static void HandleSendAckTimeout(chip::System::Layer * systemLayer, void * appState); + + // Close functions: + void DoCloseCallback(uint8_t state, uint8_t flags, CHIP_ERROR err); + void FinalizeClose(uint8_t state, uint8_t flags, CHIP_ERROR err); + void Free(); + void FreePAFtpEngine(); + + void QueueTx(PacketBufferHandle && data, PacketType_t type); +}; + +} /* namespace WiFiPAF */ +} /* namespace chip */ diff --git a/src/wifipaf/WiFiPAFError.h b/src/wifipaf/WiFiPAFError.h new file mode 100644 index 00000000000000..93a97303b47bdd --- /dev/null +++ b/src/wifipaf/WiFiPAFError.h @@ -0,0 +1,245 @@ +/* + * + * 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 defines constants for the CHIP Wi-Fi PAF subsystem. + * + * NOTE WELL: On some platforms, this header is included by C-language programs. + * + */ + +#pragma once + +#include +#include + +// Reuse the BLE error code space since there is no available space +#define CHIP_WIFIPAF_ERROR(e) CHIP_SDK_ERROR(::chip::ChipError::SdkPart::kBLE, (e)) + +// clang-format off + +/** + * @name Error Definitions + * + * @{ + */ + +/** + * @def WIFIPAF_ERROR_ADAPTER_UNAVAILABLE + * + * @brief + * Bluetooth LE adapter is (currently) unavailable. + * + */ +#define WIFIPAF_ERROR_ADAPTER_UNAVAILABLE CHIP_WIFIPAF_ERROR(0x01) + +// unused CHIP_WIFIPAF_ERROR(0x02) + +/** + * @def WIFIPAF_ERROR_NO_CONNECTION_RECEIVED_CALLBACK + * + * @brief + * No callback was registered to receive a Wi-Fi PAF Transport Protocol (PAFTP) + * connection. + * + */ +#define WIFIPAF_ERROR_NO_CONNECTION_RECEIVED_CALLBACK CHIP_WIFIPAF_ERROR(0x03) + +// unused CHIP_WIFIPAF_ERROR(0x04) +// unused CHIP_WIFIPAF_ERROR(0x05) +// unused CHIP_WIFIPAF_ERROR(0x06) +// unused CHIP_WIFIPAF_ERROR(0x07) +// unused CHIP_WIFIPAF_ERROR(0x08) +// unused CHIP_WIFIPAF_ERROR(0x09) +// unused CHIP_WIFIPAF_ERROR(0x0a) + + +/** + * @def WIFIPAF_ERROR_CHIPPAF_PROTOCOL_ABORT + * + * @brief + * A Wi-Fi PAF Transport Protocol (PAFTP) error was encountered. + * + */ +#define WIFIPAF_ERROR_CHIPPAF_PROTOCOL_ABORT CHIP_WIFIPAF_ERROR(0x0b) + +/** + * @def WIFIPAF_ERROR_REMOTE_DEVICE_DISCONNECTED + * + * @brief + * A remote WIFIPAF connection peer disconnected, either actively or due to the + * expiration of a WIFIPAF connection supervision timeout. + * + */ +#define WIFIPAF_ERROR_REMOTE_DEVICE_DISCONNECTED CHIP_WIFIPAF_ERROR(0x0c) + +/** + * @def WIFIPAF_ERROR_APP_CLOSED_CONNECTION + * + * @brief + * The local application closed a WIFIPAF connection, and has informed WIFIPAFLayer. + * + */ +#define WIFIPAF_ERROR_APP_CLOSED_CONNECTION CHIP_WIFIPAF_ERROR(0x0d) + +// unused CHIP_WIFIPAF_ERROR(0x0e) +// unused CHIP_WIFIPAF_ERROR(0x0f) + + +/** + * @def WIFIPAF_ERROR_INCOMPATIBLE_PROTOCOL_VERSIONS + * + * @brief + * A remote device does not offer a compatible version of the Wi-Fi PAF + * Transport Protocol (PAFTP). + * + */ +#define WIFIPAF_ERROR_INCOMPATIBLE_PROTOCOL_VERSIONS CHIP_WIFIPAF_ERROR(0x10) + +// unused CHIP_WIFIPAF_ERROR(0x11) +// unused CHIP_WIFIPAF_ERROR(0x12) + +/** + * @def WIFIPAF_ERROR_INVALID_FRAGMENT_SIZE + * + * @brief + * A remote device selected in invalid Wi-Fi PAF Transport Protocol (PAFTP) + * fragment size. + * + */ +#define WIFIPAF_ERROR_INVALID_FRAGMENT_SIZE CHIP_WIFIPAF_ERROR(0x13) + +/** + * @def WIFIPAF_ERROR_START_TIMER_FAILED + * + * @brief + * A timer failed to start within WIFIPAFLayer. + * + */ +#define WIFIPAF_ERROR_START_TIMER_FAILED CHIP_WIFIPAF_ERROR(0x14) + +/** + * @def WIFIPAF_ERROR_CONNECT_TIMED_OUT + * + * @brief + * A remote WIFIPAF peripheral device's Wi-Fi PAF Transport Protocol (PAFTP) + * connect handshake response timed out. + * + */ +#define WIFIPAF_ERROR_CONNECT_TIMED_OUT CHIP_WIFIPAF_ERROR(0x15) + +/** + * @def WIFIPAF_ERROR_RECEIVE_TIMED_OUT + * + * @brief + * A remote WIFIPAF central device's Wi-Fi PAF Transport Protocol (PAFTP) connect + * handshake timed out. + * + */ +#define WIFIPAF_ERROR_RECEIVE_TIMED_OUT CHIP_WIFIPAF_ERROR(0x16) + +/** + * @def WIFIPAF_ERROR_INVALID_MESSAGE + * + * @brief + * An invalid Wi-Fi PAF Transport Protocol (PAFTP) message was received. + * + */ +#define WIFIPAF_ERROR_INVALID_MESSAGE CHIP_WIFIPAF_ERROR(0x17) + +/** + * @def WIFIPAF_ERROR_FRAGMENT_ACK_TIMED_OUT + * + * @brief + * Receipt of an expected Wi-Fi PAF Transport Protocol (PAFTP) fragment + * acknowledgement timed out. + * + */ +#define WIFIPAF_ERROR_FRAGMENT_ACK_TIMED_OUT CHIP_WIFIPAF_ERROR(0x18) + +/** + * @def WIFIPAF_ERROR_KEEP_ALIVE_TIMED_OUT + * + * @brief + * Receipt of an expected Wi-Fi PAF Transport Protocol (PAFTP) keep-alive + * fragment timed out. + * + */ +#define WIFIPAF_ERROR_KEEP_ALIVE_TIMED_OUT CHIP_WIFIPAF_ERROR(0x19) + +/** + * @def WIFIPAF_ERROR_NO_CONNECT_COMPLETE_CALLBACK + * + * @brief + * No callback was registered to handle Wi-Fi PAF Transport Protocol (PAFTP) + * connect completion. + * + */ +#define WIFIPAF_ERROR_NO_CONNECT_COMPLETE_CALLBACK CHIP_WIFIPAF_ERROR(0x1a) + +/** + * @def WIFIPAF_ERROR_INVALID_ACK + * + * @brief + * A Bluetooth Transport Protcol (PAFTP) fragment acknowledgement was invalid. + * + */ +#define WIFIPAF_ERROR_INVALID_ACK CHIP_WIFIPAF_ERROR(0x1b) + +/** + * @def WIFIPAF_ERROR_REASSEMBLER_MISSING_DATA + * + * @brief + * A Wi-Fi PAF Transport Protocol (PAFTP) end-of-message fragment was + * received, but the total size of the received fragments is less than + * the indicated size of the original fragmented message. + * + */ +#define WIFIPAF_ERROR_REASSEMBLER_MISSING_DATA CHIP_WIFIPAF_ERROR(0x1c) + +/** + * @def WIFIPAF_ERROR_INVALID_PAFP_HEADER_FLAGS + * + * @brief + * A set of Wi-Fi PAF Transport Protocol (PAFTP) header flags is invalid. + * + */ +#define WIFIPAF_ERROR_INVALID_PAFTP_HEADER_FLAGS CHIP_WIFIPAF_ERROR(0x1d) + +/** + * @def WIFIPAF_ERROR_INVALID_PAFTP_SEQUENCE_NUMBER + * + * @brief + * A Wi-Fi PAF Transport Protocol (PAFTP) fragment sequence number is invalid. + * + */ +#define WIFIPAF_ERROR_INVALID_PAFTP_SEQUENCE_NUMBER CHIP_WIFIPAF_ERROR(0x1e) + +/** + * @def WIFIPAF_ERROR_REASSEMBLER_INCORRECT_STATE + * + * @brief + * The Wi-Fi PAF Transport Protocol (PAFTP) message reassembly engine + * encountered an unexpected state. + * + */ +#define WIFIPAF_ERROR_REASSEMBLER_INCORRECT_STATE CHIP_WIFIPAF_ERROR(0x1f) + +/** + * @} + */ diff --git a/src/wifipaf/WiFiPAFLayer.cpp b/src/wifipaf/WiFiPAFLayer.cpp index abfa44b8b6a258..1b50847ce3326b 100644 --- a/src/wifipaf/WiFiPAFLayer.cpp +++ b/src/wifipaf/WiFiPAFLayer.cpp @@ -1,6 +1,6 @@ /* * - * Copyright (c) 2024 Project CHIP Authors + * 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. @@ -22,15 +22,222 @@ * stack. * */ - #include "WiFiPAFLayer.h" +#include "WiFiPAFConfig.h" +#include "WiFiPAFEndPoint.h" +#include "WiFiPAFError.h" #include +#include + +// Magic values expected in first 2 bytes of valid PAF transport capabilities request or response: +// ref: 4.21.3, PAFTP Control Frames +#define CAPABILITIES_MSG_CHECK_BYTE_1 0b01100101 +#define CAPABILITIES_MSG_CHECK_BYTE_2 0b01101100 + namespace chip { namespace WiFiPAF { +class WiFiPAFEndPointPool +{ +public: + int Size() const { return WIFIPAF_LAYER_NUM_PAF_ENDPOINTS; } + + WiFiPAFEndPoint * Get(size_t i) const + { + static union + { + uint8_t Pool[sizeof(WiFiPAFEndPoint) * WIFIPAF_LAYER_NUM_PAF_ENDPOINTS]; + WiFiPAFEndPoint::AlignT ForceAlignment; + } sEndPointPool; + + if (i < WIFIPAF_LAYER_NUM_PAF_ENDPOINTS) + { + return reinterpret_cast(sEndPointPool.Pool + (sizeof(WiFiPAFEndPoint) * i)); + } + + return nullptr; + } + + WiFiPAFEndPoint * Find(WIFIPAF_CONNECTION_OBJECT c) const + { + if (c == WIFIPAF_CONNECTION_UNINITIALIZED) + { + return nullptr; + } + + for (size_t i = 0; i < WIFIPAF_LAYER_NUM_PAF_ENDPOINTS; i++) + { + WiFiPAFEndPoint * elem = Get(i); + WiFiPAFSession * pInInfo = reinterpret_cast(c); + if ((elem->mWiFiPafLayer != nullptr) && (elem->mSessionInfo.id == pInInfo->id) && + (elem->mSessionInfo.peer_id == pInInfo->peer_id) && + !memcmp(elem->mSessionInfo.peer_addr, pInInfo->peer_addr, sizeof(uint8_t) * 6)) + { + ChipLogProgress(WiFiPAF, "Find: Found WiFiPAFEndPoint[%lu]", i); + return elem; + } + if (memcmp(&elem->mSessionInfo, c, sizeof(WiFiPAFSession))) + { + WiFiPAFSession * pElmInfo = &elem->mSessionInfo; + WiFiPAFSession * pInInfo_1 = (WiFiPAFSession *) c; + ChipLogError(WiFiPAF, "EndPoint[%lu]", i); + ChipLogError(WiFiPAF, "Role: [%d, %d]", pElmInfo->role, pInInfo_1->role); + ChipLogError(WiFiPAF, "id: [%u, %u]", pElmInfo->id, pInInfo_1->id); + ChipLogError(WiFiPAF, "peer_id: [%d, %d]", pElmInfo->peer_id, pInInfo_1->peer_id); + ChipLogError(WiFiPAF, "ElmMac: [%02x:%02x:%02x:%02x:%02x:%02x]", pElmInfo->peer_addr[0], pElmInfo->peer_addr[1], + pElmInfo->peer_addr[2], pElmInfo->peer_addr[3], pElmInfo->peer_addr[4], pElmInfo->peer_addr[5]); + ChipLogError(WiFiPAF, "InMac: [%02x:%02x:%02x:%02x:%02x:%02x]", pInInfo_1->peer_addr[0], pInInfo_1->peer_addr[1], + pInInfo_1->peer_addr[2], pInInfo_1->peer_addr[3], pInInfo_1->peer_addr[4], pInInfo_1->peer_addr[5]); + ChipLogError(WiFiPAF, "nodeId: [%lu, %lu]", pElmInfo->nodeId, pInInfo_1->nodeId); + ChipLogError(WiFiPAF, "discriminator: [%d, %d]", pElmInfo->discriminator, pInInfo_1->discriminator); + } + } + + return nullptr; + } + + WiFiPAFEndPoint * GetFree() const + { + for (size_t i = 0; i < WIFIPAF_LAYER_NUM_PAF_ENDPOINTS; i++) + { + WiFiPAFEndPoint * elem = Get(i); + if (elem->mWiFiPafLayer == nullptr) + { + return elem; + } + } + return nullptr; + } +}; + +// EndPoint Pools +static WiFiPAFEndPointPool sWiFiPAFEndPointPool; + +/* + * PAFTransportCapabilitiesRequestMessage implementation: + * ref: 4.21.3.1, PAFTP Handshake Request + */ +void PAFTransportCapabilitiesRequestMessage::SetSupportedProtocolVersion(uint8_t index, uint8_t version) +{ + uint8_t mask; + + // If even-index, store version in lower 4 bits; else, higher 4 bits. + if (index % 2 == 0) + { + mask = 0x0F; + } + else + { + mask = 0xF0; + version = static_cast(version << 4); + } + + version &= mask; + + uint8_t & slot = mSupportedProtocolVersions[(index / 2)]; + slot = static_cast(slot & ~mask); // Clear version at index; leave other version in same byte alone + slot |= version; +} + +CHIP_ERROR PAFTransportCapabilitiesRequestMessage::Encode(const PacketBufferHandle & msgBuf) const +{ + uint8_t * p = msgBuf->Start(); + + // Verify we can write the fixed-length request without running into the end of the buffer. + VerifyOrReturnError(msgBuf->MaxDataLength() >= kCapabilitiesRequestLength, CHIP_ERROR_NO_MEMORY); + + chip::Encoding::Write8(p, CAPABILITIES_MSG_CHECK_BYTE_1); + chip::Encoding::Write8(p, CAPABILITIES_MSG_CHECK_BYTE_2); + + for (uint8_t version : mSupportedProtocolVersions) + { + chip::Encoding::Write8(p, version); + } + + chip::Encoding::LittleEndian::Write16(p, mMtu); + chip::Encoding::Write8(p, mWindowSize); + + msgBuf->SetDataLength(kCapabilitiesRequestLength); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR PAFTransportCapabilitiesRequestMessage::Decode(const PacketBufferHandle & msgBuf, + PAFTransportCapabilitiesRequestMessage & msg) +{ + const uint8_t * p = msgBuf->Start(); + + // Verify we can read the fixed-length request without running into the end of the buffer. + VerifyOrReturnError(msgBuf->DataLength() >= kCapabilitiesRequestLength, CHIP_ERROR_MESSAGE_INCOMPLETE); + + VerifyOrReturnError(CAPABILITIES_MSG_CHECK_BYTE_1 == chip::Encoding::Read8(p), WIFIPAF_ERROR_INVALID_MESSAGE); + VerifyOrReturnError(CAPABILITIES_MSG_CHECK_BYTE_2 == chip::Encoding::Read8(p), WIFIPAF_ERROR_INVALID_MESSAGE); + + static_assert(kCapabilitiesRequestSupportedVersionsLength == sizeof(msg.mSupportedProtocolVersions), + "Expected capability sizes and storage must match"); + for (unsigned char & version : msg.mSupportedProtocolVersions) + { + version = chip::Encoding::Read8(p); + } + + msg.mMtu = chip::Encoding::LittleEndian::Read16(p); + msg.mWindowSize = chip::Encoding::Read8(p); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR PAFTransportCapabilitiesResponseMessage::Encode(const PacketBufferHandle & msgBuf) const +{ + uint8_t * p = msgBuf->Start(); + + // Verify we can write the fixed-length request without running into the end of the buffer. + VerifyOrReturnError(msgBuf->MaxDataLength() >= kCapabilitiesResponseLength, CHIP_ERROR_NO_MEMORY); + + chip::Encoding::Write8(p, CAPABILITIES_MSG_CHECK_BYTE_1); + chip::Encoding::Write8(p, CAPABILITIES_MSG_CHECK_BYTE_2); + + chip::Encoding::Write8(p, mSelectedProtocolVersion); + chip::Encoding::LittleEndian::Write16(p, mFragmentSize); + chip::Encoding::Write8(p, mWindowSize); + + msgBuf->SetDataLength(kCapabilitiesResponseLength); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR PAFTransportCapabilitiesResponseMessage::Decode(const PacketBufferHandle & msgBuf, + PAFTransportCapabilitiesResponseMessage & msg) +{ + const uint8_t * p = msgBuf->Start(); + + // Verify we can read the fixed-length response without running into the end of the buffer. + VerifyOrReturnError(msgBuf->DataLength() >= kCapabilitiesResponseLength, CHIP_ERROR_MESSAGE_INCOMPLETE); + + VerifyOrReturnError(CAPABILITIES_MSG_CHECK_BYTE_1 == chip::Encoding::Read8(p), WIFIPAF_ERROR_INVALID_MESSAGE); + VerifyOrReturnError(CAPABILITIES_MSG_CHECK_BYTE_2 == chip::Encoding::Read8(p), WIFIPAF_ERROR_INVALID_MESSAGE); + + msg.mSelectedProtocolVersion = chip::Encoding::Read8(p); + msg.mFragmentSize = chip::Encoding::LittleEndian::Read16(p); + msg.mWindowSize = chip::Encoding::Read8(p); + + return CHIP_NO_ERROR; +} + +/* + * WiFiPAFLayer Implementation + */ + +CHIP_ERROR WiFiPAFLayer::Init(chip::System::Layer * systemLayer) +{ + mSystemLayer = systemLayer; + memset(&sWiFiPAFEndPointPool, 0, sizeof(sWiFiPAFEndPointPool)); + ChipLogProgress(WiFiPAF, "WiFiPAF: WiFiPAFLayer::Init()"); + return CHIP_NO_ERROR; +} + void WiFiPAFLayer::Shutdown(OnCancelDeviceHandle OnCancelDevice) { - ChipLogProgress(Inet, "WiFiPAF: Closing all WiFiPAF sessions (%lu)", PafInfoVect.size()); + ChipLogProgress(WiFiPAF, "WiFiPAF: Closing all WiFiPAF sessions (%lu)", PafInfoVect.size()); for (WiFiPAFSession & PafInfoElm : PafInfoVect) { if (PafInfoElm.id == UINT32_MAX) @@ -38,13 +245,25 @@ void WiFiPAFLayer::Shutdown(OnCancelDeviceHandle OnCancelDevice) // Unused session continue; } - ChipLogProgress(Inet, "WiFiPAF: Canceling id: %u", PafInfoElm.id); + ChipLogProgress(WiFiPAF, "WiFiPAF: Canceling id: %u", PafInfoElm.id); OnCancelDevice(PafInfoElm.id, PafInfoElm.role); } PafInfoVect.clear(); } -void WiFiPAFLayer::OnWiFiPAFMessageReceived(WiFiPAFSession & RxInfo, System::PacketBufferHandle && msg) +bool WiFiPAFLayer::OnWiFiPAFMessageReceived(WiFiPAFSession & RxInfo, System::PacketBufferHandle && msg) +{ + WiFiPAFEndPoint * endPoint = sWiFiPAFEndPointPool.Find(reinterpret_cast(&RxInfo)); + VerifyOrReturnError(endPoint != nullptr, false, ChipLogDetail(WiFiPAF, "No endpoint for received indication")); + + CHIP_ERROR err = endPoint->Receive(std::move(msg)); + VerifyOrReturnError(err == CHIP_NO_ERROR, false, + ChipLogError(WiFiPAF, "Receive failed, err = %" CHIP_ERROR_FORMAT, err.Format())); + + return true; +} + +void WiFiPAFLayer::OnWiFiPAFMsgRxComplete(WiFiPAFSession & RxInfo, System::PacketBufferHandle && msg) { if (mWiFiPAFTransport != nullptr) { @@ -57,12 +276,108 @@ void WiFiPAFLayer::SetWiFiPAFState(State state) mAppState = state; } +CHIP_ERROR WiFiPAFLayer::SendMessage(WiFiPAF::WiFiPAFSession & TxInfo, chip::System::PacketBufferHandle && msg) +{ + WiFiPAFEndPoint * endPoint = sWiFiPAFEndPointPool.Find(reinterpret_cast(&TxInfo)); + VerifyOrReturnError(endPoint != nullptr, CHIP_ERROR_INCORRECT_STATE, ChipLogDetail(WiFiPAF, "No endpoint to send packets")); + CHIP_ERROR err = endPoint->Send(std::move(msg)); + VerifyOrReturnError(err == CHIP_NO_ERROR, CHIP_ERROR_INCORRECT_STATE, + ChipLogError(WiFiPAF, "Send pakets failed, err = %" CHIP_ERROR_FORMAT, err.Format())); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR WiFiPAFLayer::HandleWriteConfirmed(WiFiPAF::WiFiPAFSession & TxInfo) +{ + WiFiPAFEndPoint * endPoint = sWiFiPAFEndPointPool.Find(reinterpret_cast(&TxInfo)); + VerifyOrReturnError(endPoint != nullptr, CHIP_ERROR_INCORRECT_STATE, ChipLogDetail(WiFiPAF, "No endpoint to send packets")); + CHIP_ERROR err = endPoint->HandleSendConfirmationReceived(); + VerifyOrReturnError(err == CHIP_NO_ERROR, CHIP_ERROR_INCORRECT_STATE, + ChipLogError(WiFiPAF, "Write_Confirm, Send pakets failed, err = %" CHIP_ERROR_FORMAT, err.Format())); + + return CHIP_NO_ERROR; +} + static WiFiPAFLayer sInstance; WiFiPAFLayer * WiFiPAFLayer::GetWiFiPAFLayer() { return &sInstance; } +CHIP_ERROR WiFiPAFLayer::NewEndPoint(WiFiPAFEndPoint ** retEndPoint, WiFiPAFSession & SessionInfo, WiFiPafRole role) +{ + *retEndPoint = nullptr; + + *retEndPoint = sWiFiPAFEndPointPool.GetFree(); + if (*retEndPoint == nullptr) + { + ChipLogError(WiFiPAF, "endpoint pool FULL"); + return CHIP_ERROR_ENDPOINT_POOL_FULL; + } + (*retEndPoint)->Init(this, SessionInfo); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR WiFiPAFLayer::HandleTransportConnectionInitiated(WiFiPAF::WiFiPAFSession & SessionInfo, + OnSubscribeCompleteFunct OnSubscribeDoneFunc, void * appState, + OnSubscribeErrorFunct OnSubscribeErrFunc) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + WiFiPAFEndPoint * newEndPoint = nullptr; + + ChipLogProgress(WiFiPAF, "Creating WiFiPAFEndPoint"); + err = NewEndPoint(&newEndPoint, SessionInfo, SessionInfo.role); + newEndPoint->mOnPafSubscribeComplete = OnSubscribeDoneFunc; + newEndPoint->mOnPafSubscribeError = OnSubscribeErrFunc; + newEndPoint->mAppState = appState; + if (SessionInfo.role == kWiFiPafRole_Subscriber) + { + err = newEndPoint->StartConnect(); + } + + return err; +} + +void WiFiPAFLayer::OnEndPointConnectComplete(WiFiPAFEndPoint * endPoint, CHIP_ERROR err) +{ + assert(endPoint != nullptr); + if (endPoint->mOnPafSubscribeComplete != nullptr) + { + endPoint->mOnPafSubscribeComplete(endPoint->mAppState); + endPoint->mOnPafSubscribeComplete = nullptr; + } + return; +} + +WiFiPAFTransportProtocolVersion +WiFiPAFLayer::GetHighestSupportedProtocolVersion(const PAFTransportCapabilitiesRequestMessage & reqMsg) +{ + WiFiPAFTransportProtocolVersion retVersion = kWiFiPAFTransportProtocolVersion_None; + + uint8_t shift_width = 4; + + for (int i = 0; i < NUM_PAFTP_SUPPORTED_PROTOCOL_VERSIONS; i++) + { + shift_width ^= 4; + + uint8_t version = reqMsg.mSupportedProtocolVersions[(i / 2)]; + version = static_cast((version >> shift_width) & 0x0F); // Grab just the nibble we want. + + if ((version >= CHIP_PAF_TRANSPORT_PROTOCOL_MIN_SUPPORTED_VERSION) && + (version <= CHIP_PAF_TRANSPORT_PROTOCOL_MAX_SUPPORTED_VERSION) && (version > retVersion)) + { + retVersion = static_cast(version); + } + else if (version == kWiFiPAFTransportProtocolVersion_None) // Signifies end of supported versions list + { + break; + } + } + + return retVersion; +} + void WiFiPAFLayer::AddPafSession(const NodeId nodeId, const uint16_t discriminator) { for (auto PafInfoElm : PafInfoVect) @@ -74,9 +389,8 @@ void WiFiPAFLayer::AddPafSession(const NodeId nodeId, const uint16_t discriminat return; } } - WiFiPAFSession PafInfo{ .id = UINT32_MAX, .peer_id = UINT32_MAX, .nodeId = nodeId, .discriminator = discriminator }; - PafInfoVect.push_back(PafInfo); - ChipLogProgress(Inet, "WiFiPAF: Add session with nodeId: %lu, disc: %x, total %lu sessions", nodeId, discriminator, + PafInfoVect.push_back({ .id = UINT32_MAX, .peer_id = UINT32_MAX, .nodeId = nodeId, .discriminator = discriminator }); + ChipLogProgress(WiFiPAF, "WiFiPAF: Add session with nodeId: %lu, disc: %x, total %lu sessions", nodeId, discriminator, PafInfoVect.size()); } @@ -90,9 +404,8 @@ void WiFiPAFLayer::AddPafSession(uint32_t id) return; } } - WiFiPAFSession PafInfo{ .id = id }; - PafInfoVect.push_back(PafInfo); - ChipLogProgress(Inet, "WiFiPAF: Add session with id: %u, total %lu sessions", id, PafInfoVect.size()); + PafInfoVect.push_back({ .id = id }); + ChipLogProgress(WiFiPAF, "WiFiPAF: Add session with id: %u, total %lu sessions", id, PafInfoVect.size()); } void WiFiPAFLayer::RmPafSession(uint32_t id) diff --git a/src/wifipaf/WiFiPAFLayer.h b/src/wifipaf/WiFiPAFLayer.h index f7ae9b2dc58881..9e3077c3912147 100644 --- a/src/wifipaf/WiFiPAFLayer.h +++ b/src/wifipaf/WiFiPAFLayer.h @@ -1,6 +1,6 @@ /* * - * Copyright (c) 2024 Project CHIP Authors + * 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. @@ -17,14 +17,129 @@ #pragma once +#include "WiFiPAFConfig.h" +#include "WiFiPAFEndPoint.h" #include "WiFiPAFLayerDelegate.h" +#include "WiFiPAFRole.h" #include #include +#include #include + #include namespace chip { namespace WiFiPAF { + +/** + * @def NUM_PAFTP_SUPPORTED_PROTOCOL_VERSIONS + * + * Number of unsigned 4-bit representations of supported transport protocol + * versions encapsulated in a BleTransportCapabilitiesRequest. Defined by CHIP + * over PAFTP protocol specification. + */ +#define NUM_PAFTP_SUPPORTED_PROTOCOL_VERSIONS 8 + +// Version(s) of the CHIP PAF Transport Protocol that this stack supports. +#define CHIP_PAF_TRANSPORT_PROTOCOL_MIN_SUPPORTED_VERSION kWiFiPAFTransportProtocolVersion_V1 +#define CHIP_PAF_TRANSPORT_PROTOCOL_MAX_SUPPORTED_VERSION kWiFiPAFTransportProtocolVersion_V1 + +/// Enum defining versions of CHIP over PAF transport protocol. +typedef enum +{ + kWiFiPAFTransportProtocolVersion_None = 0, + kWiFiPAFTransportProtocolVersion_V1 = 4 // PAFTP as defined by CHIP v1.5 +} WiFiPAFTransportProtocolVersion; + +inline constexpr size_t kCapabilitiesRequestMagicnumLength = 2; +inline constexpr size_t kCapabilitiesRequestL2capMtuLength = 2; +inline constexpr size_t kCapabilitiesRequestSupportedVersionsLength = 4; +inline constexpr size_t kCapabilitiesRequestWindowSizeLength = 1; +constexpr size_t kCapabilitiesRequestLength = (kCapabilitiesRequestMagicnumLength + kCapabilitiesRequestL2capMtuLength + + kCapabilitiesRequestSupportedVersionsLength + kCapabilitiesRequestWindowSizeLength); + +inline constexpr size_t kCapabilitiesResponseMagicnumLength = 2; +inline constexpr size_t kCapabilitiesResponseL2capMtuLength = 2; +inline constexpr size_t kCapabilitiesResponseSelectedProtocolVersionLength = 1; +inline constexpr size_t kCapabilitiesResponseWindowSizeLength = 1; +constexpr size_t kCapabilitiesResponseLength(kCapabilitiesResponseMagicnumLength + kCapabilitiesResponseL2capMtuLength + + kCapabilitiesResponseSelectedProtocolVersionLength + + kCapabilitiesResponseWindowSizeLength); + +class PAFTransportCapabilitiesRequestMessage +{ +public: + /** + * An array of size NUM_PAFTP_SUPPORTED_PROTOCOL_VERSIONS listing versions of the + * PAF transport protocol that this node supports. Each protocol version is + * specified as a 4-bit unsigned integer. A zero-value represents unused + * array elements. Counting up from the zero-index, the first zero-value + * specifies the end of the list of supported protocol versions. + */ + uint8_t mSupportedProtocolVersions[(NUM_PAFTP_SUPPORTED_PROTOCOL_VERSIONS / 2) + (NUM_PAFTP_SUPPORTED_PROTOCOL_VERSIONS % 2)]; + + /** + * The MTU that has been negotiated for this PAF connection. Specified in + * the PAFTransportCapabilitiesRequestMessage because the remote node may + * be unable to glean this info from its own PAF hardware/software stack + * + * A value of 0 means that the central could not determine the negotiated + * PAF connection MTU. + */ + uint16_t mMtu; + + /** + * The initial and maximum receive window size offered by the central + */ + uint8_t mWindowSize; + + /** + * Set supported version value at given index in + * SupportedProtocolVersions. uint8_t version argument is truncated to 4 + * least-significant bits. Index shall be 0 through number of + * SupportedProtocolVersions elements - 1. + */ + void SetSupportedProtocolVersion(uint8_t index, uint8_t version); + + /// Must be able to reserve 20 byte data length in msgBuf. + CHIP_ERROR Encode(const System::PacketBufferHandle & msgBuf) const; + + static CHIP_ERROR Decode(const System::PacketBufferHandle & msgBuf, PAFTransportCapabilitiesRequestMessage & msg); +}; + +class PAFTransportCapabilitiesResponseMessage +{ +public: + /** + * The lower 4 bits specify the PAF transport protocol version that the PAF + * peripheral has selected for this connection. + * + * A value of kWiFiPAFTransportProtocolVersion_None means that no supported + * protocol version was found in the central's capabilities request. The + * central should unsubscribe after such a response has been sent to free + * up the peripheral for connections from devices with supported protocol + * versions. + */ + uint8_t mSelectedProtocolVersion; + + /** + * PAF transport fragment size selected by peripheral in response to MTU + * value in PAFTransportCapabilitiesRequestMessage and its local + * observation of the PAF connection MTU. + */ + uint16_t mFragmentSize; + + /** + * The initial and maximum receive window size offered by the peripheral + */ + uint8_t mWindowSize; + + /// Must be able to reserve 20 byte data length in msgBuf. + CHIP_ERROR Encode(const System::PacketBufferHandle & msgBuf) const; + + static CHIP_ERROR Decode(const System::PacketBufferHandle & msgBuf, PAFTransportCapabilitiesResponseMessage & msg); +}; + /** * The State of the Wi-Fi-PAF connection * @@ -38,17 +153,34 @@ enum class State class DLL_EXPORT WiFiPAFLayer { + friend class WiFiPAFEndPoint; + public: State mAppState = State::kNotReady; WiFiPAFLayerDelegate * mWiFiPAFTransport = nullptr; WiFiPAFLayer() = default; static WiFiPAFLayer * GetWiFiPAFLayer(); - typedef void (*OnCancelDeviceHandle)(uint32_t id, WiFiPAFSession::PAFRole role); + CHIP_ERROR Init(chip::System::Layer * systemLayer); + + typedef void (*OnCancelDeviceHandle)(uint32_t id, WiFiPAF::WiFiPafRole role); void Shutdown(OnCancelDeviceHandle OnCancelDevice); - void OnWiFiPAFMessageReceived(WiFiPAFSession & RxInfo, System::PacketBufferHandle && msg); + bool OnWiFiPAFMessageReceived(WiFiPAFSession & RxInfo, System::PacketBufferHandle && msg); + void OnWiFiPAFMsgRxComplete(WiFiPAFSession & RxInfo, System::PacketBufferHandle && msg); State GetWiFiPAFState() { return mAppState; }; void SetWiFiPAFState(State state); + CHIP_ERROR SendMessage(WiFiPAF::WiFiPAFSession & TxInfo, chip::System::PacketBufferHandle && msg); + CHIP_ERROR HandleWriteConfirmed(WiFiPAF::WiFiPAFSession & TxInfo); + CHIP_ERROR NewEndPoint(WiFiPAFEndPoint ** retEndPoint, WiFiPAFSession & SessionInfo, WiFiPafRole role); + typedef void (*OnSubscribeCompleteFunct)(void * appState); + typedef void (*OnSubscribeErrorFunct)(void * appState, CHIP_ERROR err); + CHIP_ERROR HandleTransportConnectionInitiated(WiFiPAF::WiFiPAFSession & SessionInfo, + OnSubscribeCompleteFunct OnSubscribeDoneFunc = nullptr, void * appState = nullptr, + OnSubscribeErrorFunct OnSubscribeErrFunc = nullptr); + void OnEndPointConnectComplete(WiFiPAFEndPoint * endPoint, CHIP_ERROR err); + + static WiFiPAFTransportProtocolVersion + GetHighestSupportedProtocolVersion(const PAFTransportCapabilitiesRequestMessage & reqMsg); void AddPafSession(const NodeId nodeId, const uint16_t discriminator); void AddPafSession(uint32_t id); @@ -59,6 +191,7 @@ class DLL_EXPORT WiFiPAFLayer private: std::vector PafInfoVect; + chip::System::Layer * mSystemLayer; }; } /* namespace WiFiPAF */ diff --git a/src/wifipaf/WiFiPAFLayerDelegate.h b/src/wifipaf/WiFiPAFLayerDelegate.h index 4a81ffed3bedde..935998ff0defdd 100644 --- a/src/wifipaf/WiFiPAFLayerDelegate.h +++ b/src/wifipaf/WiFiPAFLayerDelegate.h @@ -1,6 +1,6 @@ /* * - * Copyright (c) 2024 Project CHIP Authors + * 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. @@ -23,32 +23,19 @@ #pragma once +#include "WiFiPAFRole.h" #include #include namespace chip { namespace WiFiPAF { -struct WiFiPAFSession -{ - enum PAFRole - { - publisher, - subscriber - }; - PAFRole role; - uint32_t id; - uint32_t peer_id; - uint8_t peer_addr[6]; - NodeId nodeId; - uint16_t discriminator; -}; - class WiFiPAFLayerDelegate { public: virtual ~WiFiPAFLayerDelegate() = default; virtual void OnWiFiPAFMessageReceived(WiFiPAFSession & RxInfo, System::PacketBufferHandle && msg) = 0; + virtual CHIP_ERROR WiFiPAFMessageSend(WiFiPAFSession & TxInfo, System::PacketBufferHandle && msg) = 0; }; } // namespace WiFiPAF diff --git a/src/wifipaf/WiFiPAFRole.h b/src/wifipaf/WiFiPAFRole.h new file mode 100644 index 00000000000000..06f111b7dd337c --- /dev/null +++ b/src/wifipaf/WiFiPAFRole.h @@ -0,0 +1,42 @@ +/* + * + * 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. + */ + +#pragma once +#include + +namespace chip { +namespace WiFiPAF { + +/// Role of end points' associated WiFiPAF connections. Determines means used by end points to send and receive data. +typedef enum +{ + kWiFiPafRole_Publisher = 0, + kWiFiPafRole_Subscriber = 1 +} WiFiPafRole; + +struct WiFiPAFSession +{ + WiFiPafRole role; + uint32_t id; + uint32_t peer_id; + uint8_t peer_addr[6]; + NodeId nodeId; + uint16_t discriminator; +}; + +} // namespace WiFiPAF +} // namespace chip diff --git a/src/wifipaf/WiFiPAFTP.cpp b/src/wifipaf/WiFiPAFTP.cpp new file mode 100644 index 00000000000000..48a6ede7e39cd2 --- /dev/null +++ b/src/wifipaf/WiFiPAFTP.cpp @@ -0,0 +1,606 @@ +/* + * + * 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 module implements encode, decode, fragmentation and reassembly of + * PAF Transport Layer (PAFTP) packet types for transport of + * CHIP-over-WiFiPAF (CHIPoPAF) links. + * + */ + +#define _CHIP_WIFI_PAFTP_H +#include "WiFiPAFTP.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "WiFiPAFConfig.h" +#include "WiFiPAFError.h" + +// Define below to enable extremely verbose PAFTP-specific debug logging. +#undef CHIP_PAF_PROTOCOL_ENGINE_DEBUG_LOGGING_ENABLED + +#ifdef CHIP_PAF_PROTOCOL_ENGINE_DEBUG_LOGGING_ENABLED +#define ChipLogDebugWiFiPAFTP(MOD, MSG, ...) ChipLogError(MOD, MSG, ##__VA_ARGS__) +#define ChipLogDebugBufferWiFiPAFTP(MOD, BUF) \ + ChipLogByteSpan(MOD, ByteSpan((BUF)->Start(), ((BUF)->DataLength() < 8 ? (BUF)->DataLength() : 8u))) +#else +#define ChipLogDebugWiFiPAFTP(MOD, MSG, ...) +#define ChipLogDebugBufferWiFiPAFTP(MOD, BUF) +#endif + +namespace chip { +namespace WiFiPAF { + +static inline void IncSeqNum(SequenceNumber_t & a_seq_num) +{ + a_seq_num = static_cast(0xff & ((a_seq_num) + 1)); +} + +static inline bool DidReceiveData(BitFlags rx_flags) +{ + return rx_flags.HasAny(WiFiPAFTP::HeaderFlags::kStartMessage, WiFiPAFTP::HeaderFlags::kContinueMessage, + WiFiPAFTP::HeaderFlags::kEndMessage); +} + +const uint16_t WiFiPAFTP::sDefaultFragmentSize = CHIP_PAF_DEFAULT_MTU; // minimum MTU - 3 bytes for operation header +const uint16_t WiFiPAFTP::sMaxFragmentSize = + CHIP_PAF_DEFAULT_MTU; // Maximum size of PAFTP segment. Ref: 4.21.3.1, "Supported Maximum Service Specific Info Length" + +CHIP_ERROR WiFiPAFTP::Init(void * an_app_state, bool expect_first_ack) +{ + mAppState = an_app_state; + mRxState = kState_Idle; + mRxBuf = nullptr; + mRxNewestUnackedSeqNum = 0; + mRxOldestUnackedSeqNum = 0; + mRxFragmentSize = sDefaultFragmentSize; + mTxState = kState_Idle; + mTxBuf = nullptr; + mTxFragmentSize = sDefaultFragmentSize; + mRxCharCount = 0; + mRxPacketCount = 0; + mTxCharCount = 0; + mTxPacketCount = 0; + mTxNewestUnackedSeqNum = 0; + mTxOldestUnackedSeqNum = 0; + + if (expect_first_ack) + { + mTxNextSeqNum = 1; + mExpectingAck = true; + mRxNextSeqNum = 0; + } + else + { + mTxNextSeqNum = 0; + mExpectingAck = false; + mRxNextSeqNum = 1; + } + + return CHIP_NO_ERROR; +} + +SequenceNumber_t WiFiPAFTP::GetAndIncrementNextTxSeqNum() +{ + SequenceNumber_t ret = mTxNextSeqNum; + + // If not already expecting ack... + if (!mExpectingAck) + { + mExpectingAck = true; + mTxOldestUnackedSeqNum = mTxNextSeqNum; + } + + // Update newest unacknowledged sequence number. + mTxNewestUnackedSeqNum = mTxNextSeqNum; + + // Increment mTxNextSeqNum. + IncSeqNum(mTxNextSeqNum); + + return ret; +} + +SequenceNumber_t WiFiPAFTP::GetAndRecordRxAckSeqNum() +{ + SequenceNumber_t ret = mRxNewestUnackedSeqNum; + + mRxNewestUnackedSeqNum = mRxNextSeqNum; + mRxOldestUnackedSeqNum = mRxNextSeqNum; + + return ret; +} + +bool WiFiPAFTP::HasUnackedData() const +{ + return (mRxOldestUnackedSeqNum != mRxNextSeqNum); +} + +bool WiFiPAFTP::IsValidAck(SequenceNumber_t ack_num) const +{ + ChipLogDebugWiFiPAFTP(WiFiPAF, "entered IsValidAck, ack = %u, oldest = %u, newest = %u", ack_num, mTxOldestUnackedSeqNum, + mTxNewestUnackedSeqNum); + + // Return false if not awaiting any ack. + if (!mExpectingAck) + { + ChipLogDebugWiFiPAFTP(WiFiPAF, "unexpected ack is invalid"); + return false; + } + + // Assumption: maximum valid sequence number equals maximum value of SequenceNumber_t. + if (mTxNewestUnackedSeqNum >= mTxOldestUnackedSeqNum) // If current unacked interval does NOT wrap... + { + return (ack_num <= mTxNewestUnackedSeqNum && ack_num >= mTxOldestUnackedSeqNum); + } + // Else, if current unacked interval DOES wrap... + return (ack_num <= mTxNewestUnackedSeqNum || ack_num >= mTxOldestUnackedSeqNum); +} + +CHIP_ERROR WiFiPAFTP::HandleAckReceived(SequenceNumber_t ack_num) +{ + ChipLogDebugWiFiPAFTP(WiFiPAF, "entered HandleAckReceived, ack_num = %u", ack_num); + + // Ensure ack_num falls within range of ack values we're expecting. + VerifyOrReturnError(IsValidAck(ack_num), WIFIPAF_ERROR_INVALID_ACK); + + if (mTxNewestUnackedSeqNum == ack_num) // If ack is for newest outstanding unacknowledged fragment... + { + mTxOldestUnackedSeqNum = ack_num; + + // All outstanding fragments have been acknowledged. + mExpectingAck = false; + } + else // If ack is valid, but not for newest outstanding unacknowledged fragment... + { + // Update newest unacknowledged fragment to one past that which was just acknowledged. + mTxOldestUnackedSeqNum = ack_num; + IncSeqNum(mTxOldestUnackedSeqNum); + } + + return CHIP_NO_ERROR; +} + +// Calling convention: +// EncodeStandAloneAck may only be called if data arg is committed for immediate, synchronous subsequent transmission. +CHIP_ERROR WiFiPAFTP::EncodeStandAloneAck(const PacketBufferHandle & data) +{ + // Ensure enough headroom exists for the lower BLE layers. + VerifyOrReturnError(data->EnsureReservedSize(CHIP_CONFIG_BLE_PKT_RESERVED_SIZE), CHIP_ERROR_NO_MEMORY); + + // Ensure enough space for standalone ack payload. + VerifyOrReturnError(data->MaxDataLength() >= kTransferProtocolStandaloneAckHeaderSize, CHIP_ERROR_NO_MEMORY); + uint8_t * characteristic = data->Start(); + + // Since there's no preexisting message payload, we can write BTP header without adjusting data start pointer. + characteristic[0] = static_cast(HeaderFlags::kFragmentAck); + + // Acknowledge most recently received sequence number. + characteristic[1] = GetAndRecordRxAckSeqNum(); + ChipLogDebugWiFiPAFTP(WiFiPAF, "===> encoded stand-alone ack = %u", characteristic[1]); + + // Include sequence number for stand-alone ack itself. + characteristic[2] = GetAndIncrementNextTxSeqNum(); + + // Set ack payload data length. + data->SetDataLength(kTransferProtocolStandaloneAckHeaderSize); + + return CHIP_NO_ERROR; +} + +// Calling convention: +// WiFiPAFTP does not retain ownership of reassembled messages, layer above needs to free when done. +// +// WiFiPAFTP does not reset itself on error. Upper layer should free outbound message and inbound reassembly buffers +// if there is a problem. + +// HandleCharacteristicReceived(): +// +// Non-NULL characteristic data arg is always either designated as or appended to the message reassembly buffer, +// or freed if it holds a stand-alone ack. In all cases, caller must clear its reference to data arg when this +// function returns. +// +// Upper layer must immediately clean up and reinitialize protocol engine if returned err != CHIP_NO_ERROR. +CHIP_ERROR WiFiPAFTP::HandleCharacteristicReceived(System::PacketBufferHandle && data, SequenceNumber_t & receivedAck, + bool & didReceiveAck) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + BitFlags rx_flags; + + VerifyOrExit(!data.IsNull(), err = CHIP_ERROR_INVALID_ARGUMENT); + + { // Scope for reader, so we can do the VerifyOrExit above. + // Data uses little-endian byte order. + Encoding::LittleEndian::Reader reader(data->Start(), data->DataLength()); + + mRxCharCount++; + + // Get header flags, always in first byte. + err = reader.Read8(rx_flags.RawStorage()).StatusCode(); + SuccessOrExit(err); + + didReceiveAck = rx_flags.Has(HeaderFlags::kFragmentAck); + + // Get ack number, if any. + if (didReceiveAck) + { + err = reader.Read8(&receivedAck).StatusCode(); + SuccessOrExit(err); + + err = HandleAckReceived(receivedAck); + // Multiple-ACK + if (err != CHIP_NO_ERROR) + { + ChipLogDebugWiFiPAFTP(WiFiPAF, "Drop the invalid ack, but update the seq_n and leave"); + err = reader.Read8(&mRxNewestUnackedSeqNum).StatusCode(); + SuccessOrExit(err); + ChipLogDebugWiFiPAFTP(WiFiPAF, "Update the seq_n: %u", mRxNewestUnackedSeqNum); + mRxNextSeqNum = mRxNewestUnackedSeqNum; + IncSeqNum(mRxNextSeqNum); + LogState(); + return CHIP_NO_ERROR; + } + } + + // Get sequence number. + err = reader.Read8(&mRxNewestUnackedSeqNum).StatusCode(); + SuccessOrExit(err); + ChipLogDebugWiFiPAFTP(WiFiPAF, "(Rx_Seq, mRxNextSeqNum)=(%u, %u), mRxState: %u", mRxNewestUnackedSeqNum, mRxNextSeqNum, + mRxState); + mRxSeqHist[mRxSeqHistId] = mRxNewestUnackedSeqNum; + mRxSeqHistId = (mRxSeqHistId + 1) % CHIP_PAFTP_RXHIST_SIZE; + if (mRxNewestUnackedSeqNum < mRxNextSeqNum) + { + // Drop the duplicated rx-pkt + ChipLogError(WiFiPAF, "Drop the duplicated rx pkt!"); + return CHIP_NO_ERROR; + } + + // Verify that received sequence number is the next one we'd expect. + VerifyOrExit(mRxNewestUnackedSeqNum == mRxNextSeqNum, err = WIFIPAF_ERROR_INVALID_PAFTP_SEQUENCE_NUMBER); + + // Increment next expected rx sequence number. + IncSeqNum(mRxNextSeqNum); + + // If fragment was stand-alone ack, we're done here; no payload for message reassembler. + if (!DidReceiveData(rx_flags)) + { + ExitNow(); + } + + // Truncate the incoming fragment length by the mRxFragmentSize as the negotiated + // mRxFragmentSize may be smaller than the characteristic size. Make sure + // we're not truncating to a data length smaller than what we have already consumed. + VerifyOrExit(reader.OctetsRead() <= mRxFragmentSize, err = WIFIPAF_ERROR_REASSEMBLER_INCORRECT_STATE); + data->SetDataLength(std::min(data->DataLength(), static_cast(mRxFragmentSize))); + + // Now mark the bytes we consumed as consumed. + data->ConsumeHead(static_cast(reader.OctetsRead())); + + ChipLogDebugWiFiPAFTP(WiFiPAF, ">>> PAFTP reassembler received data:"); + ChipLogDebugBufferWiFiPAFTP(WiFiPAF, data); + } + + if (mRxState == kState_Idle) + { + // We need a new reader, because the state of our outer reader no longer + // matches the state of the packetbuffer, both in terms of start + // position and available length. + Encoding::LittleEndian::Reader startReader(data->Start(), data->DataLength()); + + // Verify StartMessage header flag set. + VerifyOrExit(rx_flags.Has(HeaderFlags::kStartMessage), err = WIFIPAF_ERROR_INVALID_PAFTP_HEADER_FLAGS); + + err = startReader.Read16(&mRxLength).StatusCode(); + SuccessOrExit(err); + + mRxState = kState_InProgress; + + data->ConsumeHead(static_cast(startReader.OctetsRead())); + + // Create a new buffer for use as the Rx re-assembly area. + mRxBuf = System::PacketBufferHandle::New(System::PacketBuffer::kMaxSize); + + VerifyOrExit(!mRxBuf.IsNull(), err = CHIP_ERROR_NO_MEMORY); + + mRxBuf->AddToEnd(std::move(data)); + mRxBuf->CompactHead(); // will free 'data' and adjust rx buf's end/length + + // For now, limit WiFiPAFTP message size to max length of 1 pbuf, as we do for chip messages sent via IP. + // TODO add support for WiFiPAFTP messages longer than 1 pbuf + VerifyOrExit(!mRxBuf->HasChainedBuffer(), err = CHIP_ERROR_INBOUND_MESSAGE_TOO_BIG); + } + else if (mRxState == kState_InProgress) + { + // Verify StartMessage header flag NOT set, since we're in the middle of receiving a message. + VerifyOrExit(!rx_flags.Has(HeaderFlags::kStartMessage), err = WIFIPAF_ERROR_INVALID_PAFTP_HEADER_FLAGS); + + // Verify ContinueMessage or EndMessage header flag set. + VerifyOrExit(rx_flags.HasAny(HeaderFlags::kContinueMessage, HeaderFlags::kEndMessage), + err = WIFIPAF_ERROR_INVALID_PAFTP_HEADER_FLAGS); + + // Add received fragment to reassembled message buffer. + mRxBuf->AddToEnd(std::move(data)); + mRxBuf->CompactHead(); // will free 'data' and adjust rx buf's end/length + + // For now, limit WiFiPAFTP message size to max length of 1 pbuf, as we do for chip messages sent via IP. + // TODO add support for WiFiPAFTP messages longer than 1 pbuf + VerifyOrExit(!mRxBuf->HasChainedBuffer(), err = CHIP_ERROR_INBOUND_MESSAGE_TOO_BIG); + } + else + { + err = WIFIPAF_ERROR_REASSEMBLER_INCORRECT_STATE; + ExitNow(); + } + + if (rx_flags.Has(HeaderFlags::kEndMessage)) + { + // Trim remainder, if any, of the received packet buffer based on sender-specified length of reassembled message. + VerifyOrExit(CanCastTo(mRxBuf->DataLength()), err = CHIP_ERROR_MESSAGE_TOO_LONG); + int padding = static_cast(mRxBuf->DataLength()) - mRxLength; + + if (padding > 0) + { + mRxBuf->SetDataLength(static_cast(mRxLength)); + } + + // Ensure all received fragments add up to sender-specified total message size. + VerifyOrExit(mRxBuf->DataLength() == mRxLength, err = WIFIPAF_ERROR_REASSEMBLER_MISSING_DATA); + + // We've reassembled the entire message. + mRxState = kState_Complete; + mRxPacketCount++; + } + +exit: + if (err != CHIP_NO_ERROR) + { + mRxState = kState_Error; + // Dump protocol engine state, plus header flags and received data length. + ChipLogError(WiFiPAF, "HandleCharacteristicReceived failed, err = %" CHIP_ERROR_FORMAT ", rx_flags = %u", err.Format(), + rx_flags.Raw()); + if (didReceiveAck) + { + ChipLogError(WiFiPAF, "With rx'd ack = %u", receivedAck); + } + if (!mRxBuf.IsNull()) + { + ChipLogError(WiFiPAF, "With rx buf data length = %u", static_cast(mRxBuf->DataLength())); + } + LogState(); + + if (!data.IsNull()) // NOLINT(bugprone-use-after-move) + { + // Tack received data onto rx buffer, to be freed when end point resets protocol engine on close. + if (!mRxBuf.IsNull()) + { + mRxBuf->AddToEnd(std::move(data)); + } + else + { + mRxBuf = std::move(data); + } + } + } + + return err; +} + +PacketBufferHandle WiFiPAFTP::TakeRxPacket() +{ + if (mRxState == kState_Complete) + { + mRxState = kState_Idle; + } + return std::move(mRxBuf); +} + +// Calling convention: +// May only be called if data arg is committed for immediate, synchronous subsequent transmission. +// Returns false on error. Caller must free data arg on error. +bool WiFiPAFTP::HandleCharacteristicSend(System::PacketBufferHandle data, bool send_ack) +{ + uint8_t * characteristic; + mTxCharCount++; + + if (send_ack && !HasUnackedData()) + { + ChipLogError(Inet, "HandleCharacteristicSend: send_ack true, but nothing to acknowledge."); + return false; + } + + if (mTxState == kState_Idle) + { + if (data.IsNull()) + { + return false; + } + + mTxBuf = std::move(data); + mTxState = kState_InProgress; + VerifyOrReturnError(CanCastTo(mTxBuf->DataLength()), false); + mTxLength = static_cast(mTxBuf->DataLength()); + + ChipLogDebugWiFiPAFTP(WiFiPAF, ">>> CHIPoWiFiPAF preparing to send whole message:"); + ChipLogDebugBufferWiFiPAFTP(WiFiPAF, mTxBuf); + + // Determine fragment header size. + uint8_t header_size = + send_ack ? kTransferProtocolMaxHeaderSize : (kTransferProtocolMaxHeaderSize - kTransferProtocolAckSize); + + // Ensure enough headroom exists for the PAFTP header + if (!mTxBuf->EnsureReservedSize(header_size)) + { + // handle error + ChipLogError(Inet, "HandleCharacteristicSend: not enough headroom"); + mTxState = kState_Error; + mTxBuf = nullptr; // Avoid double-free after assignment above, as caller frees data on error. + + return false; + } + + // prepend header. + characteristic = mTxBuf->Start(); + characteristic -= header_size; + mTxBuf->SetStart(characteristic); + uint8_t cursor = 1; // first position past header flags byte + BitFlags headerFlags(HeaderFlags::kStartMessage); + + if (send_ack) + { + headerFlags.Set(HeaderFlags::kFragmentAck); + characteristic[cursor++] = GetAndRecordRxAckSeqNum(); + ChipLogDebugWiFiPAFTP(WiFiPAF, "===> encoded piggybacked ack, ack_num = %u", characteristic[cursor - 1]); + } + + characteristic[cursor++] = GetAndIncrementNextTxSeqNum(); + characteristic[cursor++] = static_cast(mTxLength & 0xff); + characteristic[cursor++] = static_cast(mTxLength >> 8); + + if ((mTxLength + cursor) <= mTxFragmentSize) + { + mTxBuf->SetDataLength(static_cast(mTxLength + cursor)); + mTxLength = 0; + headerFlags.Set(HeaderFlags::kEndMessage); + mTxState = kState_Complete; + mTxPacketCount++; + } + else + { + mTxBuf->SetDataLength(mTxFragmentSize); + mTxLength = static_cast((mTxLength + cursor) - mTxFragmentSize); + } + + characteristic[0] = headerFlags.Raw(); + ChipLogDebugWiFiPAFTP(WiFiPAF, ">>> CHIPoWiFiPAF preparing to send first fragment:"); + ChipLogDebugBufferWiFiPAFTP(WiFiPAF, mTxBuf); + } + else if (mTxState == kState_InProgress) + { + if (!data.IsNull()) + { + return false; + } + + // advance past the previous fragment + characteristic = mTxBuf->Start(); + characteristic += mTxFragmentSize; + + // prepend header + characteristic -= send_ack ? kTransferProtocolMidFragmentMaxHeaderSize + : (kTransferProtocolMidFragmentMaxHeaderSize - kTransferProtocolAckSize); + mTxBuf->SetStart(characteristic); + uint8_t cursor = 1; // first position past header flags byte + + BitFlags headerFlags(HeaderFlags::kContinueMessage); + + if (send_ack) + { + headerFlags.Set(HeaderFlags::kFragmentAck); + characteristic[cursor++] = GetAndRecordRxAckSeqNum(); + ChipLogDebugWiFiPAFTP(WiFiPAF, "===> encoded piggybacked ack, ack_num = %u", characteristic[cursor - 1]); + } + + characteristic[cursor++] = GetAndIncrementNextTxSeqNum(); + + if ((mTxLength + cursor) <= mTxFragmentSize) + { + mTxBuf->SetDataLength(static_cast(mTxLength + cursor)); + mTxLength = 0; + headerFlags.Set(HeaderFlags::kEndMessage); + mTxState = kState_Complete; + mTxPacketCount++; + } + else + { + mTxBuf->SetDataLength(mTxFragmentSize); + mTxLength = static_cast((mTxLength + cursor) - mTxFragmentSize); + } + + characteristic[0] = headerFlags.Raw(); + ChipLogDebugWiFiPAFTP(WiFiPAF, ">>> CHIPoWiFiPAF preparing to send additional fragment:"); + ChipLogDebugBufferWiFiPAFTP(WiFiPAF, mTxBuf); + } + else + { + // Invalid tx state. + ChipLogError(WiFiPAF, "Invalid tx state: %u", mTxState); + return false; + } + + return true; +} + +PacketBufferHandle WiFiPAFTP::TakeTxPacket() +{ + if (mTxState == kState_Complete) + { + mTxState = kState_Idle; + } + return std::move(mTxBuf); +} + +void WiFiPAFTP::LogState() const +{ + ChipLogError(WiFiPAF, "mAppState: %p", mAppState); + + ChipLogError(WiFiPAF, "mRxFragmentSize: %d", mRxFragmentSize); + ChipLogError(WiFiPAF, "mRxState: %d", mRxState); + ChipLogError(WiFiPAF, "mRxBuf: %d", !mRxBuf.IsNull()); + ChipLogError(WiFiPAF, "mRxNextSeqNum: %d", mRxNextSeqNum); + ChipLogError(WiFiPAF, "mRxNewestUnackedSeqNum: %d", mRxNewestUnackedSeqNum); + ChipLogError(WiFiPAF, "mRxOldestUnackedSeqNum: %d", mRxOldestUnackedSeqNum); + ChipLogError(WiFiPAF, "mRxCharCount: %d", mRxCharCount); + ChipLogError(WiFiPAF, "mRxPacketCount: %d", mRxPacketCount); + + char RxSeqHistMsg[64]; + memset(RxSeqHistMsg, 0, sizeof(RxSeqHistMsg)); + for (uint8_t idx = 0; idx < CHIP_PAFTP_RXHIST_SIZE; idx++) + { + char RxSeq[6]; + snprintf(RxSeq, sizeof(RxSeq), "%03u ", mRxSeqHist[(mRxSeqHistId + 1 + idx) % CHIP_PAFTP_RXHIST_SIZE]); + strcat(RxSeqHistMsg, RxSeq); + } + ChipLogError(WiFiPAF, "Rx_Seq_History: [%s]", RxSeqHistMsg); + + ChipLogError(WiFiPAF, "mTxFragmentSize: %d", mTxFragmentSize); + ChipLogError(WiFiPAF, "mTxState: %d", mTxState); + ChipLogError(WiFiPAF, "mTxBuf: %d", !mTxBuf.IsNull()); + ChipLogError(WiFiPAF, "mTxNextSeqNum: %d", mTxNextSeqNum); + ChipLogError(WiFiPAF, "mTxNewestUnackedSeqNum: %d", mTxNewestUnackedSeqNum); + ChipLogError(WiFiPAF, "mTxOldestUnackedSeqNum: %d", mTxOldestUnackedSeqNum); + ChipLogError(WiFiPAF, "mTxCharCount: %d", mTxCharCount); + ChipLogError(WiFiPAF, "mTxPacketCount: %d", mTxPacketCount); +} + +void WiFiPAFTP::LogStateDebug() const +{ +#ifdef CHIP_PAF_PROTOCOL_ENGINE_DEBUG_LOGGING_ENABLED + LogState(); +#endif +} + +} /* namespace WiFiPAF */ +} /* namespace chip */ diff --git a/src/wifipaf/WiFiPAFTP.h b/src/wifipaf/WiFiPAFTP.h new file mode 100644 index 00000000000000..3659333dcdc25d --- /dev/null +++ b/src/wifipaf/WiFiPAFTP.h @@ -0,0 +1,153 @@ +/* + * + * 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 defines types and an object for the chip over + * WiFiPAF (CHIPoPAF) links. + * + */ + +#pragma once + +#include +#include +#include +#include + +namespace chip { +namespace WiFiPAF { + +inline constexpr size_t kTransferProtocolHeaderFlagsSize = 1; // Size in bytes of encoded PAFTP fragment header flag bits +inline constexpr size_t kTransferProtocolMgmtOpSize = 1; // Size of bytes of encoded PAFTP management OpCode +inline constexpr size_t kTransferProtocolSequenceNumSize = 1; // Size in bytes of encoded PAFTP sequence number +inline constexpr size_t kTransferProtocolAckSize = 1; // Size in bytes of encoded PAFTP fragment acknowledgement number +inline constexpr size_t kTransferProtocolMsgLenSize = 2; // Size in byte of encoded PAFTP total fragmented message length + +constexpr size_t kTransferProtocolMaxHeaderSize = + kTransferProtocolHeaderFlagsSize + kTransferProtocolAckSize + kTransferProtocolSequenceNumSize + kTransferProtocolMsgLenSize; +constexpr size_t kTransferProtocolMidFragmentMaxHeaderSize = + kTransferProtocolHeaderFlagsSize + kTransferProtocolAckSize + kTransferProtocolSequenceNumSize; +constexpr size_t kTransferProtocolStandaloneAckHeaderSize = + kTransferProtocolHeaderFlagsSize + kTransferProtocolAckSize + kTransferProtocolSequenceNumSize; + +using ::chip::System::PacketBufferHandle; + +typedef uint8_t SequenceNumber_t; + +typedef enum +{ + kType_Data = 0, // Default 0 for data + kType_Control = 1, +} PacketType_t; // PAFTP packet types +#define CHIP_PAFTP_RXHIST_SIZE 8u + +class WiFiPAFTP // PAFTP Engine +{ +public: + typedef enum + { + kState_Idle = 0, + kState_InProgress = 1, + kState_Complete = 2, + kState_Error = 3 + } State_t; // [READ-ONLY] Current state + + // Masks for PAFTP fragment header flag bits. + enum class HeaderFlags : uint8_t + { + kStartMessage = 0x01, + kContinueMessage = 0x02, + kEndMessage = 0x04, + kFragmentAck = 0x08, + kManagementOpcode = 0x20, + kHankshake = 0x40, + }; + + static const uint16_t sDefaultFragmentSize; + static const uint16_t sMaxFragmentSize; + + // Public functions: + CHIP_ERROR Init(void * an_app_state, bool expect_first_ack); + + inline void SetTxFragmentSize(uint16_t size) { mTxFragmentSize = size; } + inline void SetRxFragmentSize(uint16_t size) { mRxFragmentSize = size; } + + uint16_t GetRxFragmentSize() const { return mRxFragmentSize; } + uint16_t GetTxFragmentSize() const { return mTxFragmentSize; } + + SequenceNumber_t GetAndIncrementNextTxSeqNum(); + SequenceNumber_t GetAndRecordRxAckSeqNum(); + + inline SequenceNumber_t GetLastReceivedSequenceNumber() const { return mRxNewestUnackedSeqNum; } + inline SequenceNumber_t GetNewestUnackedSentSequenceNumber() const { return mTxNewestUnackedSeqNum; } + + inline bool ExpectingAck() const { return mExpectingAck; } + + inline State_t RxState() { return mRxState; } + inline State_t TxState() { return mTxState; } + + bool HasUnackedData() const; + + CHIP_ERROR HandleCharacteristicReceived(System::PacketBufferHandle && data, SequenceNumber_t & receivedAck, + bool & didReceiveAck); + bool HandleCharacteristicSend(System::PacketBufferHandle data, bool send_ack); + CHIP_ERROR EncodeStandAloneAck(const PacketBufferHandle & data); + + PacketBufferHandle TakeRxPacket(); + PacketBufferHandle BorrowRxPacket() { return mRxBuf.Retain(); } + void ClearRxPacket() { (void) TakeRxPacket(); } + PacketBufferHandle TakeTxPacket(); + PacketBufferHandle BorrowTxPacket() { return mTxBuf.Retain(); } + void ClearTxPacket() { (void) TakeTxPacket(); } + + void LogState() const; + void LogStateDebug() const; + +private: + State_t mRxState; + uint16_t mRxLength; + void * mAppState; + System::PacketBufferHandle mRxBuf; + SequenceNumber_t mRxNextSeqNum; + SequenceNumber_t mRxNewestUnackedSeqNum; + SequenceNumber_t mRxOldestUnackedSeqNum; + uint16_t mRxFragmentSize; + SequenceNumber_t mRxSeqHist[CHIP_PAFTP_RXHIST_SIZE]; + uint8_t mRxSeqHistId; + + State_t mTxState; + uint16_t mTxLength; + System::PacketBufferHandle mTxBuf; + SequenceNumber_t mTxNextSeqNum; + SequenceNumber_t mTxNewestUnackedSeqNum; + SequenceNumber_t mTxOldestUnackedSeqNum; + bool mExpectingAck; + uint16_t mTxFragmentSize; + + uint16_t mRxCharCount; + uint16_t mRxPacketCount; + uint16_t mTxCharCount; + uint16_t mTxPacketCount; + + // Private functions: + bool IsValidAck(SequenceNumber_t ack_num) const; + CHIP_ERROR HandleAckReceived(SequenceNumber_t ack_num); +}; + +} /* namespace WiFiPAF */ +} /* namespace chip */