blob: 1d466df06258f06ee3cba6dc9a1cd844813e246b [file] [log] [blame]
/*
*
* Copyright (c) 2024 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.
*/
#include <vector>
#include <protocols/bdx/BdxMessages.h>
#include <controller/CHIPDeviceControllerFactory.h>
#include <controller/CHIPDeviceControllerSystemState.h>
#include <controller/python/chip/bdx/bdx-transfer.h>
#include <controller/python/chip/bdx/test-bdx-transfer-server.h>
#include <controller/python/chip/native/PyChipError.h>
// The BDX transfer system is split into:
// * BdxTransfer: A transfer object that contains the information about a transfer and is an ExchangeDelegate.
// It owns the data for a transfer, either copying what was sent from Python or requiring the Python side to
// copy it during a callback.
// * TransferMap: A map that associates the BdxTransfer object with its Python context using TransferInfo objects.
// It owns the TransferInfo objects but doesn't own the BdxTransfer objects or the Python context objects.
// * TransferDelegate: A delegate that calls back into Python when certain events happen in C++. It uses the
// TransferMap but doesn't own it.
// TestBdxTransferServer: A server that listens for incoming BDX messages, creates BdxTransfer objects, and
// informs the delegate when certain events happen. It owns the BdxTransfer objects but not the delegate. A
// BdxTransfer object is created when a BDX message is received and destroyed when the transfer completes or
// fails.
// The TransferMap, TransferDelegate, and TestBdxTransferServer instances are all owned by this file.
using PyObject = void *;
namespace chip {
namespace python {
// The Python callbacks to call when certain events happen.
using OnTransferObtainedCallback = void (*)(PyObject context, void * bdxTransfer, bdx::TransferControlFlags transferControlFlags,
uint16_t maxBlockSize, uint64_t startOffset, uint64_t length,
const uint8_t * fileDesignator, uint16_t fileDesignatorLength, const uint8_t * metadata,
size_t metadataLength);
using OnFailedToObtainTransferCallback = void (*)(PyObject context, PyChipError result);
using OnDataReceivedCallback = void (*)(PyObject context, const uint8_t * dataBuffer, size_t bufferLength);
using OnTransferCompletedCallback = void (*)(PyObject context, PyChipError result);
// The callback methods provided by python.
OnTransferObtainedCallback gOnTransferObtainedCallback = nullptr;
OnFailedToObtainTransferCallback gOnFailedToObtainTransferCallback = nullptr;
OnDataReceivedCallback gOnDataReceivedCallback = nullptr;
OnTransferCompletedCallback gOnTransferCompletedCallback = nullptr;
// The information for a single transfer.
struct TransferInfo
{
// The transfer object. Owned by the transfer server.
bdx::BdxTransfer * Transfer = nullptr;
// The contexts for different python callbacks. Owned by the python side.
PyObject OnTransferObtainedContext = nullptr;
PyObject OnDataReceivedContext = nullptr;
PyObject OnTransferCompletedContext = nullptr;
bool operator==(const TransferInfo & other) const { return Transfer == other.Transfer; }
};
// The set of transfers.
class TransferMap
{
public:
// Returns the transfer data associated with the given transfer.
TransferInfo * TransferInfoForTransfer(bdx::BdxTransfer * transfer)
{
std::vector<TransferInfo>::iterator result = std::find_if(
mTransfers.begin(), mTransfers.end(), [transfer](const TransferInfo & data) { return data.Transfer == transfer; });
VerifyOrReturnValue(result != mTransfers.end(), nullptr);
return &*result;
}
// Returns the transfer data that has the given context when a transfer is obtained.
TransferInfo * TransferInfoForTransferObtainedContext(PyObject transferObtainedContext)
{
std::vector<TransferInfo>::iterator result =
std::find_if(mTransfers.begin(), mTransfers.end(), [transferObtainedContext](const TransferInfo & data) {
return data.OnTransferObtainedContext == transferObtainedContext;
});
VerifyOrReturnValue(result != mTransfers.end(), nullptr);
return &*result;
}
// This returns the next transfer data that has no associated BdxTransfer.
TransferInfo * NextUnassociatedTransferInfo()
{
std::vector<TransferInfo>::iterator result =
std::find_if(mTransfers.begin(), mTransfers.end(), [](const TransferInfo & data) { return data.Transfer == nullptr; });
VerifyOrReturnValue(result != mTransfers.end(), nullptr);
return &*result;
}
// Creates a new transfer data.
TransferInfo * CreateUnassociatedTransferInfo() { return &mTransfers.emplace_back(); }
void RemoveTransferInfo(TransferInfo * transferInfo)
{
std::vector<TransferInfo>::iterator result = std::find(mTransfers.begin(), mTransfers.end(), *transferInfo);
VerifyOrReturn(result != mTransfers.end());
mTransfers.erase(result);
}
private:
std::vector<TransferInfo> mTransfers;
};
// A method to release a transfer.
void ReleaseTransfer(System::Layer * systemLayer, bdx::BdxTransfer * transfer);
// A delegate to forward events from a transfer to the appropriate Python callback and context.
class TransferDelegate : public bdx::BdxTransfer::Delegate
{
public:
TransferDelegate(TransferMap * transfers) : mTransfers(transfers) {}
~TransferDelegate() override = default;
void Init(System::Layer * systemLayer) { mSystemLayer = systemLayer; }
void InitMessageReceived(bdx::BdxTransfer * transfer, bdx::TransferSession::TransferInitData init_data) override
{
TransferInfo * transferInfo = mTransfers->NextUnassociatedTransferInfo();
if (gOnTransferObtainedCallback && transferInfo)
{
transferInfo->Transfer = transfer;
gOnTransferObtainedCallback(transferInfo->OnTransferObtainedContext, transfer, init_data.TransferCtlFlags,
init_data.MaxBlockSize, init_data.StartOffset, init_data.Length, init_data.FileDesignator,
init_data.FileDesLength, init_data.Metadata, init_data.MetadataLength);
}
}
void DataReceived(bdx::BdxTransfer * transfer, const ByteSpan & block) override
{
TransferInfo * transferInfo = mTransfers->TransferInfoForTransfer(transfer);
if (gOnDataReceivedCallback && transferInfo)
{
gOnDataReceivedCallback(transferInfo->OnDataReceivedContext, block.data(), block.size());
}
}
void TransferCompleted(bdx::BdxTransfer * transfer, CHIP_ERROR result) override
{
TransferInfo * transferInfo = mTransfers->TransferInfoForTransfer(transfer);
if (!transferInfo && result != CHIP_NO_ERROR)
{
// The transfer failed during initialisation.
transferInfo = mTransfers->NextUnassociatedTransferInfo();
if (gOnFailedToObtainTransferCallback && transferInfo)
{
gOnFailedToObtainTransferCallback(transferInfo->OnTransferObtainedContext, ToPyChipError(result));
}
}
else if (gOnTransferCompletedCallback && transferInfo)
{
gOnTransferCompletedCallback(transferInfo->OnTransferCompletedContext, ToPyChipError(result));
mTransfers->RemoveTransferInfo(transferInfo);
}
ReleaseTransfer(mSystemLayer, transfer);
}
private:
TransferMap * mTransfers = nullptr;
System::Layer * mSystemLayer = nullptr;
};
TransferMap gTransfers;
TransferDelegate gBdxTransferDelegate(&gTransfers);
bdx::TestBdxTransferServer gBdxTransferServer(&gBdxTransferDelegate);
void ReleaseTransfer(System::Layer * systemLayer, bdx::BdxTransfer * transfer)
{
systemLayer->ScheduleWork(
[](auto * theSystemLayer, auto * appState) -> void {
auto * theTransfer = static_cast<bdx::BdxTransfer *>(appState);
gBdxTransferServer.Release(theTransfer);
},
transfer);
}
} // namespace python
} // namespace chip
using namespace chip::python;
// These methods are expected to be called from Python.
extern "C" {
// Initialises the BDX system.
void pychip_Bdx_InitCallbacks(OnTransferObtainedCallback onTransferObtainedCallback,
OnFailedToObtainTransferCallback onFailedToObtainTransferCallback,
OnDataReceivedCallback onDataReceivedCallback,
OnTransferCompletedCallback onTransferCompletedCallback)
{
gOnTransferObtainedCallback = onTransferObtainedCallback;
gOnFailedToObtainTransferCallback = onFailedToObtainTransferCallback;
gOnDataReceivedCallback = onDataReceivedCallback;
gOnTransferCompletedCallback = onTransferCompletedCallback;
chip::Controller::DeviceControllerFactory & factory = chip::Controller::DeviceControllerFactory::GetInstance();
chip::System::Layer * systemLayer = factory.GetSystemState()->SystemLayer();
gBdxTransferDelegate.Init(systemLayer);
gBdxTransferServer.Init(systemLayer, factory.GetSystemState()->ExchangeMgr());
}
// Prepares the BDX system to expect a new transfer.
PyChipError pychip_Bdx_ExpectBdxTransfer(PyObject transferObtainedContext)
{
TransferInfo * transferInfo = gTransfers.CreateUnassociatedTransferInfo();
VerifyOrReturnValue(transferInfo != nullptr, ToPyChipError(CHIP_ERROR_NO_MEMORY));
transferInfo->OnTransferObtainedContext = transferObtainedContext;
gBdxTransferServer.ExpectATransfer();
return ToPyChipError(CHIP_NO_ERROR);
}
// Stops expecting a transfer.
PyChipError pychip_Bdx_StopExpectingBdxTransfer(PyObject transferObtainedContext)
{
TransferInfo * transferInfo = gTransfers.TransferInfoForTransferObtainedContext(transferObtainedContext);
VerifyOrReturnValue(transferInfo != nullptr, ToPyChipError(CHIP_ERROR_NOT_FOUND));
gBdxTransferServer.StopExpectingATransfer();
gTransfers.RemoveTransferInfo(transferInfo);
return ToPyChipError(CHIP_NO_ERROR);
}
// Accepts a transfer with the intent to receive data from the other device.
PyChipError pychip_Bdx_AcceptTransferAndReceiveData(chip::bdx::BdxTransfer * transfer, PyObject dataReceivedContext,
PyObject transferCompletedContext)
{
TransferInfo * transferInfo = gTransfers.TransferInfoForTransfer(transfer);
transferInfo->OnDataReceivedContext = dataReceivedContext;
transferInfo->OnTransferCompletedContext = transferCompletedContext;
return ToPyChipError(transfer->AcceptAndReceiveData());
}
// Accepts a transfer with the intent to send data to the other device.
PyChipError pychip_Bdx_AcceptTransferAndSendData(chip::bdx::BdxTransfer * transfer, const uint8_t * dataBuffer, size_t dataLength,
PyObject transferCompletedContext)
{
TransferInfo * transferInfo = gTransfers.TransferInfoForTransfer(transfer);
transferInfo->OnTransferCompletedContext = transferCompletedContext;
chip::ByteSpan data(dataBuffer, dataLength);
return ToPyChipError(transfer->AcceptAndSendData(data));
}
// Rejects a transfer.
PyChipError pychip_Bdx_RejectTransfer(chip::bdx::BdxTransfer * transfer)
{
return ToPyChipError(transfer->Reject());
}
}