Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BDX transfer support for Python tests #34821

Open
wants to merge 42 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 37 commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
19436f1
Add the python-C++ translation.
harimau-qirex Jun 3, 2024
a0687c1
Add a BDX transfer server to handle unsolicited BDX init messages.
harimau-qirex Jun 3, 2024
f798bd8
Add the manager to implement the transfer pool.
harimau-qirex Jun 3, 2024
beb87c5
Add the initial implementation of a BDX transfer.
harimau-qirex Jun 4, 2024
0a73a52
Use BdxTransfer in the other classes.
harimau-qirex Jun 4, 2024
dd1b3c7
Update constructors to set the delegates etc. correctly.
harimau-qirex Jun 4, 2024
6d0fe4f
Implement the C++ side of the barrier. Move the data callback into th…
harimau-qirex Jun 4, 2024
9748281
Add a way to map the transfer to the python contexts.
harimau-qirex Jun 4, 2024
0cd39c3
Fix some of the minor TODOs.
harimau-qirex Jun 6, 2024
de7c762
Add init/shutdown to the transfer server.
harimau-qirex Jun 6, 2024
18449f5
Start on the implementation of the Python side.
harimau-qirex Jun 6, 2024
f51e192
Listen for all BDX protocol messages rather than just the init messages.
harimau-qirex Jun 7, 2024
b0603ae
Fix minor issues in the transfer server.
harimau-qirex Jun 7, 2024
79c7483
Implement a good chunk of the python side.
harimau-qirex Jun 11, 2024
17dcf11
Fix compile errors.
harimau-qirex Jul 23, 2024
c8201bb
Fix a number of issues preventing the BDX python code from running at…
harimau-qirex Jul 25, 2024
727ecbe
Return the results of the python-C methods.
harimau-qirex Jul 30, 2024
dc4c2aa
Fix the async-ness of the methods that prepare the system to receive …
harimau-qirex Jul 30, 2024
a7b40dd
Initialise the BDX transfer server.
harimau-qirex Aug 1, 2024
10973ff
Fixes necessary to await on the future from PrepareToReceive/SendBdxD…
harimau-qirex Aug 2, 2024
df7ea6c
Fix sending the accept message.
harimau-qirex Aug 2, 2024
7c5fe88
Acknowledge received blocks so the BDX transfer continues.
harimau-qirex Aug 2, 2024
84be6ab
Fix the parameters of the python callback methods.
harimau-qirex Aug 2, 2024
5ca363a
Add another async transaction class to handle the transfer completed …
harimau-qirex Aug 2, 2024
92cd2c8
Add comments to the C++ code.
harimau-qirex Aug 6, 2024
ff38946
Add a test for the BDX transfer that uses the diagnostic logs cluster.
harimau-qirex Aug 6, 2024
a03d1ad
Move the calls to release a transfer out of the manager so it works t…
harimau-qirex Aug 6, 2024
f921aab
Delay releasing the C++ BDX transfer object until after it's no longe…
harimau-qirex Aug 6, 2024
39218dd
Verify the diagnostic logs response is a success.
harimau-qirex Aug 6, 2024
7a2f298
Restyled by whitespace
restyled-commits Aug 6, 2024
68e10ed
Restyled by clang-format
restyled-commits Aug 6, 2024
36ea7ae
Restyled by gn
restyled-commits Aug 6, 2024
186efcd
Restyled by autopep8
restyled-commits Aug 6, 2024
5a28be5
Restyled by isort
restyled-commits Aug 6, 2024
c979d9d
Improve BdxTransferManager's comments.
harimau-qirex Aug 13, 2024
fff7994
Use a vector for the data to send over a BDX transfer rather than a r…
harimau-qirex Aug 13, 2024
0b4092f
Minor renames.
harimau-qirex Aug 13, 2024
420a913
Improve the error message when the BDX transfer pool is exhausted.
harimau-qirex Aug 20, 2024
59cc705
Minor fixes.
harimau-qirex Aug 21, 2024
bdab8b4
Pass the status report's status code up the stack.
harimau-qirex Aug 21, 2024
f4790d0
Merge the BDX transfer server into the manager.
harimau-qirex Aug 22, 2024
9d76978
Rename BdxTransferManager to TestBdxTransferServer.
harimau-qirex Aug 22, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions src/controller/python/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,14 @@ shared_library("ChipDeviceCtrl") {
"ChipDeviceController-StorageDelegate.cpp",
"ChipDeviceController-StorageDelegate.h",
"OpCredsBinding.cpp",
"chip/bdx/bdx-transfer-manager.cpp",
"chip/bdx/bdx-transfer-manager.h",
"chip/bdx/bdx-transfer-pool.h",
"chip/bdx/bdx-transfer-server.cpp",
"chip/bdx/bdx-transfer-server.h",
"chip/bdx/bdx-transfer.cpp",
"chip/bdx/bdx-transfer.h",
"chip/bdx/bdx.cpp",
"chip/clusters/attribute.cpp",
"chip/clusters/command.cpp",
"chip/commissioning/PlaceholderOperationalCredentialsIssuer.h",
Expand Down Expand Up @@ -166,6 +174,9 @@ chip_python_wheel_action("chip-core") {
"chip/ChipStack.py",
"chip/FabricAdmin.py",
"chip/__init__.py",
"chip/bdx/Bdx.py",
"chip/bdx/BdxTransfer.py",
"chip/bdx/__init__.py",
"chip/ble/__init__.py",
"chip/ble/commissioning/__init__.py",
"chip/ble/get_adapters.py",
Expand Down Expand Up @@ -233,6 +244,7 @@ chip_python_wheel_action("chip-core") {

py_packages = [
"chip",
"chip.bdx",
"chip.ble",
"chip.ble.commissioning",
"chip.configuration",
Expand Down
31 changes: 31 additions & 0 deletions src/controller/python/chip/ChipDeviceCtrl.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
from . import FabricAdmin
from . import clusters as Clusters
from . import discovery
from .bdx import Bdx
from .clusters import Attribute as ClusterAttribute
from .clusters import ClusterObjects as ClusterObjects
from .clusters import Command as ClusterCommand
Expand Down Expand Up @@ -1343,6 +1344,36 @@ def WriteGroupAttribute(
# An empty list is the expected return for sending group write attribute.
return []

def TestOnlyPrepareToReceiveBdxData(self) -> asyncio.Future:
'''
Sets up the system to expect a node to initiate a BDX transfer. The transfer will send data here.

Returns:
- a future that will yield a BdxTransfer with the init message from the transfer.
'''
self.CheckIsActive()

eventLoop = asyncio.get_running_loop()
future = eventLoop.create_future()

Bdx.PrepareToReceiveBdxData(future).raise_on_error()
return future

def TestOnlyPrepareToSendBdxData(self, data: bytes) -> asyncio.Future:
'''
Sets up the system to expect a node to initiate a BDX transfer. The transfer will send data to the node.

Returns:
- a future that will yield a BdxTransfer with the init message from the transfer.
'''
self.CheckIsActive()

eventLoop = asyncio.get_running_loop()
future = eventLoop.create_future()

Bdx.PrepareToSendBdxData(future, data).raise_on_error()
return future

def _parseAttributePathTuple(self, pathTuple: typing.Union[
None, # Empty tuple, all wildcard
typing.Tuple[int], # Endpoint
Expand Down
2 changes: 2 additions & 0 deletions src/controller/python/chip/ChipStack.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import chip.native
from chip.native import PyChipError

from .bdx import Bdx
from .clusters import Attribute as ClusterAttribute
from .clusters import Command as ClusterCommand
from .exceptions import ChipStackError, ChipStackException, DeviceError
Expand Down Expand Up @@ -174,6 +175,7 @@ def HandleChipThreadRun(callback):
im.InitIMDelegate()
ClusterAttribute.Init()
ClusterCommand.Init()
Bdx.Init()
cecille marked this conversation as resolved.
Show resolved Hide resolved

builtins.chipStack = self

Expand Down
180 changes: 180 additions & 0 deletions src/controller/python/chip/bdx/Bdx.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
#
# Copyright (c) 2024 Project CHIP Authors
# All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

import asyncio
import builtins
import ctypes
from asyncio.futures import Future
from ctypes import CFUNCTYPE, POINTER, c_char_p, c_size_t, c_uint8, c_uint16, c_uint64, c_void_p, py_object

import chip
from chip.native import PyChipError

from . import BdxTransfer

c_uint8_p = POINTER(c_uint8)


_OnTransferObtainedCallbackFunct = CFUNCTYPE(
None, py_object, PyChipError, c_void_p, c_uint8, c_uint16, c_uint64, c_uint64, c_uint8_p, c_uint16, c_uint8_p, c_size_t)
_OnDataReceivedCallbackFunct = CFUNCTYPE(None, py_object, c_uint8_p, c_size_t)
_OnTransferCompletedCallbackFunct = CFUNCTYPE(None, py_object, PyChipError)


class AsyncTransferObtainedTransaction:
def __init__(self, future, event_loop, data=None):
self._future = future
self._data = data
self._event_loop = event_loop

def _handleTransfer(self, bdxTransfer, initMessage: BdxTransfer.InitMessage):
transfer = BdxTransfer.BdxTransfer(bdx_transfer=bdxTransfer, init_message=initMessage, data=self._data)
self._future.set_result(transfer)

def handleTransfer(self, bdxTransfer, initMessage: BdxTransfer.InitMessage):
self._event_loop.call_soon_threadsafe(self._handleTransfer, bdxTransfer, initMessage)

def _handleError(self, result: PyChipError):
self._future.set_exception(result.to_exception())

def handleError(self, result: PyChipError):
self._event_loop.call_soon_threadsafe(self._handleError, result)


class AsyncTransferCompletedTransaction:
def __init__(self, future, event_loop):
self._future = future
self._event_loop = event_loop

def _handleResult(self, result: PyChipError):
if result.is_success:
self._future.set_result(result)
else:
self._future.set_exception(result.to_exception())

def handleResult(self, result: PyChipError):
self._event_loop.call_soon_threadsafe(self._handleResult, result)


@_OnTransferObtainedCallbackFunct
def _OnTransferObtainedCallback(transaction: AsyncTransferObtainedTransaction, result: PyChipError, bdxTransfer, transferControlFlags: int, maxBlockSize: int,
startOffset: int, length: int, fileDesignator, fileDesignatorLength: int, metadata,
metadataLength: int):
if result.is_success:
fileDesignatorData = ctypes.string_at(fileDesignator, fileDesignatorLength)
metadataData = ctypes.string_at(metadata, metadataLength)

initMessage = BdxTransfer.InitMessage(
transferControlFlags,
maxBlockSize,
startOffset,
length,
fileDesignatorData[:],
metadataData[:],
)

transaction.handleTransfer(bdxTransfer, initMessage)
else:
transaction.handleError(result)


@_OnDataReceivedCallbackFunct
def _OnDataReceivedCallback(context, dataBuffer, bufferLength: int):
data = ctypes.string_at(dataBuffer, bufferLength)
context(data[:])


@_OnTransferCompletedCallbackFunct
def _OnTransferCompletedCallback(transaction: AsyncTransferCompletedTransaction, result: PyChipError):
transaction.handleResult(result)


def PrepareToReceiveBdxData(future: Future) -> PyChipError:
handle = chip.native.GetLibraryHandle()
transaction = AsyncTransferObtainedTransaction(future=future, event_loop=asyncio.get_running_loop())

ctypes.pythonapi.Py_IncRef(ctypes.py_object(transaction))
res = builtins.chipStack.Call(
lambda: handle.pychip_Bdx_ExpectBdxTransfer(ctypes.py_object(transaction))
)
if not res.is_success:
ctypes.pythonapi.Py_DecRef(ctypes.py_object(transaction))
return res


def PrepareToSendBdxData(future: Future, data: bytes) -> PyChipError:
handle = chip.native.GetLibraryHandle()
transaction = AsyncTransferObtainedTransaction(future=future, event_loop=asyncio.get_running_loop(), data=data)

ctypes.pythonapi.Py_IncRef(ctypes.py_object(transaction))
res = builtins.chipStack.Call(
lambda: handle.pychip_Bdx_ExpectBdxTransfer(ctypes.py_object(transaction))
)
if not res.is_success:
ctypes.pythonapi.Py_DecRef(ctypes.py_object(transaction))
return res


def AcceptSendTransfer(transfer: c_void_p, dataReceivedClosure, transferComplete: Future):
handle = chip.native.GetLibraryHandle()
complete_transaction = AsyncTransferCompletedTransaction(future=transferComplete, event_loop=asyncio.get_running_loop())
ctypes.pythonapi.Py_IncRef(ctypes.py_object(dataReceivedClosure))
ctypes.pythonapi.Py_IncRef(ctypes.py_object(complete_transaction))
res = builtins.chipStack.Call(
lambda: handle.pychip_Bdx_AcceptSendTransfer(transfer, dataReceivedClosure, complete_transaction)
)
if not res.is_success:
ctypes.pythonapi.Py_DecRef(ctypes.py_object(dataReceivedClosure))
ctypes.pythonapi.Py_DecRef(ctypes.py_object(complete_transaction))
return res


def AcceptReceiveTransfer(transfer: c_void_p, data: bytes, transferComplete: Future):
handle = chip.native.GetLibraryHandle()
return builtins.chipStack.Call(
lambda: handle.pychip_Bdx_AcceptReceiveTransfer(transfer, ctypes.c_char_p(data), len(data), transferComplete)
)


async def RejectTransfer(transfer: c_void_p):
handle = chip.native.GetLibraryHandle()
return await builtins.chipStack.CallAsyncWithResult(
lambda: handle.pychip_Bdx_RejectTransfer(transfer)
)


def Init():
handle = chip.native.GetLibraryHandle()
# Uses one of the type decorators as an indicator for everything being initialized.
if not handle.pychip_Bdx_ExpectBdxTransfer.argtypes:
setter = chip.native.NativeLibraryHandleMethodArguments(handle)

setter.Set('pychip_Bdx_ExpectBdxTransfer',
PyChipError, [py_object])
setter.Set('pychip_Bdx_StopExpectingBdxTransfer',
PyChipError, [py_object])
setter.Set('pychip_Bdx_AcceptSendTransfer',
PyChipError, [c_void_p, py_object, py_object])
setter.Set('pychip_Bdx_AcceptReceiveTransfer',
PyChipError, [c_void_p, c_uint8_p, c_size_t])
setter.Set('pychip_Bdx_RejectTransfer',
PyChipError, [c_void_p])
setter.Set('pychip_Bdx_InitCallbacks', None, [
_OnTransferObtainedCallbackFunct, _OnDataReceivedCallbackFunct, _OnTransferCompletedCallbackFunct])

handle.pychip_Bdx_InitCallbacks(
_OnTransferObtainedCallback, _OnDataReceivedCallback, _OnTransferCompletedCallback)
60 changes: 60 additions & 0 deletions src/controller/python/chip/bdx/BdxTransfer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
#
# Copyright (c) 2024 Project CHIP Authors
# All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

import asyncio
import ctypes
from ctypes import c_void_p
from dataclasses import dataclass

from . import Bdx


@dataclass
class InitMessage:
TransferControlFlags: int
MaxBlockSize: int
StartOffset: int
Length: int
FileDesignator: bytes
Metadata: bytes


class BdxTransfer:
def __init__(self, bdx_transfer: c_void_p, init_message: InitMessage, data=None):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a type annotation for data? Is it expecting bytes? or string? or something else entirely

self.init_message = init_message
self._bdx_transfer = bdx_transfer
self._data = data

async def accept(self):
eventLoop = asyncio.get_running_loop()
future = eventLoop.create_future()

if self._data != None:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if self._data != None:
if self._data is not None:

I think Or not self._data if data can't be a thing that evaluates to false. Not sure on the data type or whether an empty data is viable.

res = Bdx.AcceptReceiveTransfer(self._bdx_transfer, self._data, future)
res.raise_on_error()
return await future
else:
self._data = []
res = Bdx.AcceptSendTransfer(self._bdx_transfer, lambda data: self._data.extend(data), future)
res.raise_on_error()
await future
return self._data

async def reject(self):
res = await Bdx.RejectTransfer(self._bdx_transfer)
res.raise_on_error()
return res
27 changes: 27 additions & 0 deletions src/controller/python/chip/bdx/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#
# Copyright (c) 2024 Project CHIP Authors
# All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

#
# @file
# Provides Python APIs for CHIP.
#

"""Provides Python APIs for CHIP."""

from . import Bdx, BdxTransfer

__all__ = [Bdx, BdxTransfer]
Loading
Loading