diff --git a/patches/openwrt/hack/0001-ubus-Hackilly-add-Stepans-python-bindings.patch b/patches/openwrt/hack/0001-ubus-Hackilly-add-Stepans-python-bindings.patch index a23508d2b2f71fe4a975a5d1fd9ce288f62efc49..1a3fc94e6e08b7f48ca6f2038833adeeb81a6358 100644 --- a/patches/openwrt/hack/0001-ubus-Hackilly-add-Stepans-python-bindings.patch +++ b/patches/openwrt/hack/0001-ubus-Hackilly-add-Stepans-python-bindings.patch @@ -70,6 +70,2454 @@ index 523c362..d39b918 100644 $(eval $(call BuildPackage,ubus)) $(eval $(call BuildPackage,ubusd)) +$(eval $(call BuildPackage,python-ubus)) +diff --git a/package/system/ubus/patches/001-python.patch b/package/system/ubus/patches/001-python.patch +new file mode 100644 +index 0000000..7338b2f +--- /dev/null ++++ b/package/system/ubus/patches/001-python.patch +@@ -0,0 +1,2442 @@ ++diff --git a/CMakeLists.txt b/CMakeLists.txt ++index 471b38e..cc6376c 100644 ++--- a/CMakeLists.txt +++++ b/CMakeLists.txt ++@@ -4,6 +4,7 @@ PROJECT(ubus C) ++ ADD_DEFINITIONS(-Os -Wall --std=gnu99 -g3 -Wmissing-declarations) ++ ++ OPTION(BUILD_LUA "build Lua plugin" ON) +++OPTION(BUILD_PYTHON "build python plugin" OFF) ++ OPTION(BUILD_EXAMPLES "build examples" ON) ++ ++ SET(CMAKE_SHARED_LIBRARY_LINK_C_FLAGS "") ++@@ -37,6 +38,9 @@ SET_TARGET_PROPERTIES(cli PROPERTIES OUTPUT_NAME ubus) ++ TARGET_LINK_LIBRARIES(cli ubus ${ubox_library} ${blob_library} ${json}) ++ ++ ADD_SUBDIRECTORY(lua) +++if (BUILD_PYTHON) +++ ADD_SUBDIRECTORY(python) +++endif () ++ ADD_SUBDIRECTORY(examples) ++ ++ INSTALL(TARGETS ubus cli ++diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt ++new file mode 100644 ++index 0000000..2e66f2a ++--- /dev/null +++++ b/python/CMakeLists.txt ++@@ -0,0 +1,58 @@ +++cmake_minimum_required(VERSION 3.0) +++ +++SET(CURRENT_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}") +++SET(SETUP_PY_IN "${CMAKE_CURRENT_SOURCE_DIR}/setup.py.in") +++SET(SETUP_PY "${CMAKE_CURRENT_BINARY_DIR}/setup.py") +++ +++IF(NOT PYTHON_CFLAGS) +++ FIND_PROGRAM(PKG_CONFIG pkg-config) +++ IF(PKG_CONFIG) +++ EXECUTE_PROCESS( +++ COMMAND pkg-config --silence-errors --cflags python2 +++ OUTPUT_VARIABLE PYTHON_CFLAGS +++ OUTPUT_STRIP_TRAILING_WHITESPACE +++ ) +++ ENDIF() +++ENDIF() +++ +++IF(NOT PYTHON) +++ FIND_PROGRAM(PYTHON python) +++ENDIF() +++ +++SET(PYTHON_CFLAGS "-Os -Wall --std=gnu99 -g3 -I.. ${PYTHON_CFLAGS}") +++INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/..) +++LINK_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/..) +++ +++IF(APPLE) +++ SET(CMAKE_SHARED_MODULE_CREATE_C_FLAGS "${CMAKE_SHARED_MODULE_CREATE_C_FLAGS} -undefined dynamic_lookup") +++ENDIF(APPLE) +++ +++IF(NOT PYTHONPATH) +++ EXECUTE_PROCESS( +++ COMMAND ${PYTHON} -c "import os; print os.path.dirname(os.__file__)" +++ OUTPUT_VARIABLE PYTHONPATH +++ RESULT_VARIABLE PYTHON_CHECK_RES +++ OUTPUT_STRIP_TRAILING_WHITESPACE +++ ) +++ +++ IF(NOT ${PYTHON_CHECK_RES} EQUAL 0 OR "${PYTHONPATH}" EQUAL "") +++ MESSAGE(SEND_ERROR "Python was not found on your system") +++ ENDIF() +++ENDIF() +++ +++CONFIGURE_FILE(${SETUP_PY_IN} ${SETUP_PY}) +++ +++ +++SET(PYTHON_CFLAGS "${PYTHON_CFLAGS} -DUBUS_UNIX_SOCKET=\\\\\\\"${UBUS_UNIX_SOCKET}\\\\\\\"") +++SET_PROPERTY(DIRECTORY APPEND PROPERTY ADDITIONAL_MAKE_CLEAN_FILES "build") +++ +++SET(LDSHARED "${CMAKE_C_COMPILER} ${CMAKE_C_COMPILER_ARG1} -shared") +++ +++ADD_CUSTOM_TARGET(python ALL +++ COMMAND ${CMAKE_COMMAND} -E env "CC=${CMAKE_C_COMPILER} ${CMAKE_C_COMPILER_ARG1}" "LDSHARED=${LDSHARED}" "CFLAGS=${PYTHON_CFLAGS}" ${PYTHON} ${SETUP_PY} build +++ DEPENDS ubus "${CURRENT_SOURCE_DIR}/ubus_python.c" +++) +++ +++INSTALL( +++ CODE "execute_process(COMMAND ${CMAKE_COMMAND} -E env \"CC=${CMAKE_C_COMPILER} ${CMAKE_C_COMPILER_ARG1}\" \"LDSHARED=${LDSHARED}\" \"CFLAGS=${PYTHON_CFLAGS}\" ${PYTHON} ${SETUP_PY} install --prefix=${CMAKE_INSTALL_PREFIX})" +++) ++diff --git a/python/README.rst b/python/README.rst ++new file mode 100644 ++index 0000000..8fcd7f4 ++--- /dev/null +++++ b/python/README.rst ++@@ -0,0 +1,97 @@ +++Python bindings for ubus +++======================== +++Code in this directory enables a subset of libubus functions to be used directly from python. +++ +++Examples +++######## +++ +++connect and disconnect +++---------------------- +++To connect you need to:: +++ +++ import ubus +++ ubus.connect("/var/run/ubus.sock") +++ +++To disconnect you can simply:: +++ +++ ubus.disconnect() +++ +++Note that calling connect()/disconnect() on opened/closed connection will throw an exception. +++ +++add +++--- +++To add an object to ubus you can (you need to become root first):: +++ +++ def callback(handler, data): +++ handler.reply(data) # this should return exactly the same data to the caller +++ +++ ubus.add( +++ "my_object", { +++ "my_method": {"method": callback, "signature": { +++ "first": ubus.BLOBMSG_TYPE_STRING, +++ "second": ubus.BLOBMSG_TYPE_BOOL, +++ "third": ubus.BLOBMSG_TYPE_INT32, +++ }}, +++ }, +++ ) +++ +++You need to enter the loop to serve the object methods afterwards:: +++ +++ ubus.loop() +++ +++ +++Note that it might not be a good idea to call the callback function recursively. +++ +++ +++objects +++------- +++To list the objects which are currently connected to ubus you can call:: +++ +++ ubus.objects() +++ +++ -> +++ +++ {u'my_object': {u'my_method': {u'first': 3, u'second': 7, u'third': 5}}} +++ +++ +++ +++call +++---- +++To call an actual method on an object you can use:: +++ +++ ubus.call("my_object", "my_method", {"first": "my_string", "second": True, "third": 42}) +++ +++ -> +++ +++ [{"first": "my_string", "second": True, "third": 42}] +++ +++ +++listen +++------ +++To listen for an event you can:: +++ +++ def callback(event, data): +++ print(event, data) # just print event name and data to stdout +++ +++ ubus.listen(("my_event", callback)) +++ +++And you need to enter the loop to start to listen:: +++ +++ ubus.loop() +++ +++Note that it might not be a good idea to call the callback function recursively. +++ +++send +++---- +++This will send an event to ubus:: +++ +++ ubus.send("my_event", {"some": "data"}) +++ +++ +++Notes +++##### +++ +++There are some tests present ('tests/' directory). So feel free to check it for some more complex examples. +++To run the tests you need to have ubus installed and become root:: +++ +++ sudo python setup.py test ++diff --git a/python/setup.cfg b/python/setup.cfg ++new file mode 100644 ++index 0000000..d1844e1 ++--- /dev/null +++++ b/python/setup.cfg ++@@ -0,0 +1,6 @@ +++[aliases] +++test=pytest +++ +++[tool:pytest] +++addopts = --verbose -s +++python_files = tests/*.py ++diff --git a/python/setup.py.in b/python/setup.py.in ++new file mode 100644 ++index 0000000..b030d87 ++--- /dev/null +++++ b/python/setup.py.in ++@@ -0,0 +1,18 @@ +++from setuptools import setup, Extension +++ +++extension = Extension( +++ 'ubus', +++ ['@CURRENT_SOURCE_DIR@/ubus_python.c'], +++ libraries=['ubus', 'blobmsg_json', 'ubox'], +++ include_dirs=['@PROJECT_SOURCE_DIR@'], +++ library_dirs=['@PROJECT_BINARY_DIR@'], +++) +++ +++setup( +++ name='ubus', +++ version='@PYTHON_PACKAGE_VERSION@', +++ description="Python bindings for libubus", +++ ext_modules=[extension], +++ provides=['ubus'], +++ tests_require=['pytest'], +++) ++diff --git a/python/tests/__init__.py b/python/tests/__init__.py ++new file mode 100644 ++index 0000000..e69de29 ++diff --git a/python/tests/fixtures.py b/python/tests/fixtures.py ++new file mode 100644 ++index 0000000..e9d28b4 ++--- /dev/null +++++ b/python/tests/fixtures.py ++@@ -0,0 +1,226 @@ +++import os +++from multiprocessing import Process, Value +++import pytest +++import subprocess +++import time +++ +++ +++UBUSD_TEST_SOCKET_PATH = "/tmp/ubus-test-socket" +++ +++ +++class Guard(object): +++ +++ def __init__(self): +++ self.counter = Value('i', 0) +++ +++ def __enter__(self): +++ return self +++ +++ def __exit__(self, *args): +++ return True +++ +++ def touch(self): +++ self.counter.value += 1 +++ +++ def wait(self): +++ while not self.counter.value > 0: +++ time.sleep(0.05) +++ +++ +++@pytest.fixture(scope="session") +++def ubusd_test(): +++ ubusd_instance = subprocess.Popen(["ubusd", "-s", UBUSD_TEST_SOCKET_PATH]) +++ while not os.path.exists(UBUSD_TEST_SOCKET_PATH): +++ time.sleep(0.2) +++ yield ubusd_instance +++ ubusd_instance.kill() +++ os.unlink(UBUSD_TEST_SOCKET_PATH) +++ +++ +++@pytest.fixture(scope="function") +++def event_sender(): +++ +++ with Guard() as guard: +++ +++ def process_function(): +++ import ubus +++ ubus.connect(UBUSD_TEST_SOCKET_PATH) +++ while True: +++ ubus.send("event_sender", dict(a="b", c=3, d=False)) +++ guard.touch() +++ time.sleep(0.1) +++ +++ p = Process(target=process_function, name="event_sender") +++ p.start() +++ guard.wait() +++ setattr(p, 'counter', guard.counter) +++ +++ yield p +++ +++ p.terminate() +++ p.join() +++ +++ +++@pytest.fixture(scope="function") +++def registered_objects(): +++ with Guard() as guard: +++ +++ def process_function(): +++ +++ def handler1(*arg): +++ pass +++ +++ import ubus +++ ubus.connect(UBUSD_TEST_SOCKET_PATH) +++ ubus.add( +++ "registered_object1", +++ { +++ "method1": {"method": handler1, "signature": {}}, +++ "method2": {"method": handler1, "signature": { +++ "first": ubus.BLOBMSG_TYPE_STRING, +++ "second": ubus.BLOBMSG_TYPE_BOOL, +++ "third": ubus.BLOBMSG_TYPE_INT32, +++ }}, +++ }, +++ ) +++ ubus.add( +++ "registered_object2", {}, +++ ) +++ ubus.add( +++ "registered_object3", +++ { +++ "method1": {"method": handler1, "signature": {}}, +++ } +++ ) +++ guard.touch() +++ ubus.loop() +++ +++ p = Process(target=process_function) +++ p.start() +++ guard.wait() +++ +++ yield p +++ +++ p.terminate() +++ p.join() +++ +++ +++@pytest.fixture(scope="function") +++def responsive_object(): +++ with Guard() as guard: +++ +++ def process_function(): +++ +++ def handler1(handler, data): +++ data["passed"] = True +++ handler.reply(data) +++ +++ def handler2(handler, data): +++ data["passed1"] = True +++ handler.reply(data) +++ data["passed2"] = True +++ handler.reply(data) +++ data["passed3"] = True +++ handler.reply(data) +++ +++ def handler_fail(handler, data): +++ raise Exception("Handler Fails") +++ +++ import ubus +++ ubus.connect(UBUSD_TEST_SOCKET_PATH) +++ ubus.add( +++ "responsive_object", +++ { +++ "respond": {"method": handler1, "signature": { +++ "first": ubus.BLOBMSG_TYPE_STRING, +++ "second": ubus.BLOBMSG_TYPE_BOOL, +++ "third": ubus.BLOBMSG_TYPE_INT32, +++ }}, +++ "fail": {"method": handler_fail, "signature": {}}, +++ "multi_respond": {"method": handler2, "signature": {}}, +++ "number": {"method": handler1, "signature": { +++ "number": ubus.BLOBMSG_TYPE_INT32, +++ }}, +++ }, +++ ) +++ guard.touch() +++ ubus.loop() +++ +++ p = Process(target=process_function) +++ p.start() +++ guard.wait() +++ +++ yield p +++ +++ p.terminate() +++ p.join() +++ +++ +++@pytest.fixture(scope="function") +++def call_for_object(): +++ with Guard() as guard: +++ +++ def process_function(): +++ +++ import ubus +++ ubus.connect(UBUSD_TEST_SOCKET_PATH) +++ args = [ +++ ('callee_object', 'method1', {'first': 1}), +++ ('callee_object', 'method2', {'second': 2}), +++ ('callee_object', 'method3', {}), +++ ] +++ while True: +++ for arg in args: +++ try: +++ ubus.call(*arg) +++ except RuntimeError as e: +++ pass +++ guard.touch() +++ time.sleep(0.05) +++ +++ p = Process(target=process_function) +++ p.start() +++ guard.wait() +++ +++ yield p +++ +++ p.terminate() +++ p.join() +++ +++ +++@pytest.fixture(scope="function") +++def calls_extensive(): +++ with Guard() as guard: +++ +++ def process_function(): +++ +++ import ubus +++ ubus.connect(UBUSD_TEST_SOCKET_PATH) +++ while True: +++ for i in range(20): +++ try: +++ ubus.call('extensive_object_%d' % i, 'method', {}) +++ time.sleep(0.1) +++ except RuntimeError as e: +++ pass +++ guard.touch() +++ +++ p = Process(target=process_function) +++ p.start() +++ guard.wait() +++ setattr(p, 'counter', guard.counter) +++ +++ yield p +++ +++ p.terminate() +++ p.join() +++ +++ +++@pytest.fixture(scope="function") +++def disconnect_after(): +++ yield None +++ import ubus +++ try: +++ ubus.disconnect() +++ except: +++ pass ++diff --git a/python/tests/test_connection.py b/python/tests/test_connection.py ++new file mode 100644 ++index 0000000..d5ee835 ++--- /dev/null +++++ b/python/tests/test_connection.py ++@@ -0,0 +1,646 @@ +++import time +++import pytest +++import ubus +++import sys +++ +++from fixtures import ( +++ event_sender, +++ call_for_object, +++ calls_extensive, +++ disconnect_after, +++ ubusd_test, +++ registered_objects, +++ responsive_object, +++ UBUSD_TEST_SOCKET_PATH, +++) +++ +++stored_reference_counts = None +++ +++ +++class CheckRefCount(object): +++ +++ def __init__(self, *objects): +++ self.objects = objects +++ +++ def __enter__(self): +++ self.stored_reference_counts = [sys.getrefcount(e) for e in self.objects] +++ +++ def __exit__(self, exc_type, *args): +++ +++ if exc_type: +++ # don't count references when an exception occured +++ return +++ +++ objects = self.objects +++ current = [sys.getrefcount(e) for e in objects] +++ stored = self.stored_reference_counts +++ for (obj, old, new) in zip(objects, stored, current): +++ assert old == new, "Reference count for '%s' mismatch (%d!=%d)" % (obj, old, new) +++ +++ +++def test_socket_missing(ubusd_test): +++ path = "/non/existing/path" +++ +++ with CheckRefCount(path): +++ with pytest.raises(IOError): +++ ubus.connect(socket_path="/non/existing/path") +++ +++ +++def test_connect_and_disconnect(ubusd_test, disconnect_after): +++ path = UBUSD_TEST_SOCKET_PATH +++ +++ with CheckRefCount(path): +++ +++ assert ubus.get_connected() is False +++ assert ubus.get_socket_path() is None +++ +++ ubus.connect(socket_path=path) +++ assert ubus.get_socket_path() == path +++ assert ubus.get_connected() is True +++ +++ assert ubus.disconnect() is None +++ with pytest.raises(RuntimeError): +++ ubus.disconnect() +++ assert ubus.get_connected() is False +++ assert ubus.get_socket_path() is None +++ +++ ubus.connect(socket_path=path) +++ with pytest.raises(RuntimeError): +++ ubus.connect(socket_path=path) +++ assert ubus.get_socket_path() == path +++ assert ubus.get_connected() is True +++ +++ assert ubus.disconnect() is None +++ +++ assert ubus.get_connected() is False +++ assert ubus.get_socket_path() is None +++ +++ +++def test_send_failed(ubusd_test, disconnect_after): +++ path = UBUSD_TEST_SOCKET_PATH +++ +++ with CheckRefCount(path): +++ +++ with pytest.raises(RuntimeError): +++ ubus.send("disconnected", {}) +++ +++ ubus.connect(socket_path=path) +++ with pytest.raises(TypeError): +++ ubus.send() +++ +++ with pytest.raises(TypeError): +++ ubus.send({}, {}) +++ +++ with pytest.raises(TypeError): +++ ubus.send("", "") +++ +++ with pytest.raises(TypeError): +++ ubus.send("", {}, {}) +++ +++ class NonSerializable(object): +++ pass +++ +++ with pytest.raises(TypeError): +++ ubus.send("", NonSerializable()) +++ +++ ubus.disconnect() +++ +++ +++def test_send_succeeded(ubusd_test, disconnect_after): +++ path = UBUSD_TEST_SOCKET_PATH +++ test_dict = dict(a=5, b="True", c=False) +++ +++ with CheckRefCount(path, test_dict): +++ +++ ubus.connect(socket_path=path) +++ +++ assert ubus.send("normal", test_dict) +++ assert ubus.send("*", test_dict) +++ assert ubus.send("", test_dict) +++ +++ ubus.disconnect() +++ +++ +++def test_loop(ubusd_test, disconnect_after): +++ path = UBUSD_TEST_SOCKET_PATH +++ +++ time1 = 200 +++ time2 = 1 +++ time3 = 50 +++ time4 = 2 ** 65 +++ time5 = None +++ time6 = "5" +++ time7 = 0 +++ +++ with CheckRefCount(path, time1, time2, time3, time4, time6, time7): +++ +++ with pytest.raises(RuntimeError): +++ ubus.loop(time1) +++ +++ ubus.connect(socket_path=path) +++ assert ubus.loop(time1) is None +++ assert ubus.loop(time2) is None +++ assert ubus.loop(time3) is None +++ +++ with pytest.raises(OverflowError): +++ ubus.loop(time4) +++ +++ with pytest.raises(TypeError): +++ ubus.loop(time5) +++ +++ with pytest.raises(TypeError): +++ ubus.loop(time6) +++ +++ assert ubus.loop(time7) is None +++ +++ ubus.disconnect() +++ +++ +++def test_listen_failed(ubusd_test, disconnect_after): +++ path = UBUSD_TEST_SOCKET_PATH +++ +++ obj1 = {} +++ obj2 = ("a", lambda x, y: (x, y)) +++ obj3 = ("b", None) +++ obj4 = ({}, lambda x, y: (x, y)) +++ obj5 = ("b", lambda x, y: (x, y)) +++ +++ with CheckRefCount(path, obj1, obj2, obj3, obj4, obj5): +++ +++ with pytest.raises(RuntimeError): +++ ubus.listen(obj1, obj1) +++ +++ ubus.connect(socket_path=path) +++ +++ with pytest.raises(TypeError): +++ ubus.listen(obj1, obj1) +++ +++ with pytest.raises(TypeError): +++ ubus.send(obj2, obj3) +++ +++ with pytest.raises(TypeError): +++ ubus.send(obj4, obj5) +++ +++ with pytest.raises(TypeError): +++ ubus.listen() +++ +++ ubus.disconnect() +++ +++ +++def test_listen(ubusd_test, event_sender, disconnect_after): +++ listen_test = {"passed": False, "passed2": False} +++ +++ def set_result(event, data): +++ assert event == "event_sender" +++ assert data == dict(a="b", c=3, d=False) +++ listen_test["passed"] = True +++ +++ def test1(event, data): +++ assert event == "event_sender" +++ assert data == dict(a="b", c=3, d=False) +++ listen_test["passed2"] = True +++ +++ path = UBUSD_TEST_SOCKET_PATH +++ timeout = 300 +++ event_name = "event_sender" +++ +++ with CheckRefCount(path, time, event_name, test1): +++ +++ ubus.connect(socket_path=path) +++ ubus.listen((event_name, test1), (event_name, set_result)) +++ ubus.listen((event_name, test1)) +++ +++ del set_result +++ +++ ubus.loop(timeout) +++ assert listen_test["passed"] +++ assert listen_test["passed2"] +++ +++ listen_test = {"passed": False, "passed2": False} +++ ubus.loop(timeout) +++ assert listen_test["passed"] +++ assert listen_test["passed2"] +++ +++ ubus.disconnect() +++ +++ +++def test_add_object_failed(ubusd_test, registered_objects, disconnect_after): +++ path = UBUSD_TEST_SOCKET_PATH +++ +++ def fake(*args): +++ pass +++ +++ type_errors = [ +++ [], +++ ("name", ), +++ ("name", {}, False), +++ ("name", {"test": []}), +++ ("name", {"test": 5}), +++ ("name", {"test": {"method": 5, "signature": {}}}), +++ ("name", {"test": {"method": fake}}), +++ ("name", {"test": {"method": fake, "signature": {}, "another": 5}}), +++ ] +++ +++ runtime_errors = [ +++ ("registered_object1", {"test": {"method": fake, "signature": {}}}), +++ ("registered_object2", {}), +++ ] +++ +++ with CheckRefCount(path, fake, *(type_errors + runtime_errors)): +++ +++ with pytest.raises(RuntimeError): +++ ubus.add(*type_errors[0]) +++ +++ ubus.connect(socket_path=path) +++ +++ for wrong_args in type_errors: +++ with pytest.raises(TypeError): +++ ubus.add(*wrong_args) +++ +++ for wrong_args in runtime_errors: +++ with pytest.raises(RuntimeError): +++ ubus.add(*wrong_args) +++ +++ ubus.disconnect() +++ del wrong_args +++ +++ +++def test_add_object(ubusd_test, disconnect_after): +++ path = UBUSD_TEST_SOCKET_PATH +++ +++ def fake(*args): +++ pass +++ +++ arguments1 = ("new_object1", {}) +++ arguments2 = ("new_object2", {"test": {"method": fake, "signature": {}}}) +++ arguments3 = ("new_object3", { +++ "test1": {"method": fake, "signature": dict(arg1=3)}, +++ "test2": {"method": fake, "signature": dict(arg2=2)}, +++ }) +++ +++ with CheckRefCount(path, fake, *(arguments1 + arguments2 + arguments3)): +++ +++ ubus.connect(socket_path=path) +++ +++ assert ubus.add(*arguments1) is None +++ assert ubus.add(*arguments2) is None +++ assert ubus.add(*arguments3) is None +++ +++ with pytest.raises(RuntimeError): +++ ubus.add(*arguments1) +++ +++ ubus.disconnect() +++ +++ +++def test_list_objects_failed(ubusd_test, registered_objects, disconnect_after): +++ path = UBUSD_TEST_SOCKET_PATH +++ +++ type_errors = [ +++ ("more", "than", "one"), +++ ({}, ), +++ (True, ), +++ (3, ), +++ (None, ), +++ ] +++ +++ with CheckRefCount(path, *(type_errors)): +++ +++ with pytest.raises(RuntimeError): +++ ubus.objects() +++ +++ ubus.connect(socket_path=path) +++ +++ for wrong_args in type_errors: +++ with pytest.raises(TypeError): +++ ubus.objects(*wrong_args) +++ +++ ubus.disconnect() +++ del wrong_args +++ +++ +++def test_list_objects(ubusd_test, registered_objects, disconnect_after): +++ path = UBUSD_TEST_SOCKET_PATH +++ +++ def fake(*args): +++ pass +++ +++ new_object = ("new_object", {"new_method": {"method": fake, "signature": {}}}) +++ +++ expected1 = { +++ u"registered_object1": { +++ u"method1": {}, +++ u"method2": { +++ u"first": ubus.BLOBMSG_TYPE_STRING, +++ u"second": ubus.BLOBMSG_TYPE_BOOL, +++ u"third": ubus.BLOBMSG_TYPE_INT32, +++ }, +++ }, +++ u"registered_object2": {}, +++ u"registered_object3": { +++ u"method1": {}, +++ }, +++ } +++ +++ expected2 = { +++ u"registered_object2": {}, +++ } +++ +++ expected3 = { +++ u"registered_object1": { +++ u"method1": {}, +++ u"method2": { +++ u"first": ubus.BLOBMSG_TYPE_STRING, +++ u"second": ubus.BLOBMSG_TYPE_BOOL, +++ u"third": ubus.BLOBMSG_TYPE_INT32, +++ }, +++ }, +++ u"registered_object2": {}, +++ u"registered_object3": { +++ u"method1": {}, +++ }, +++ u"new_object": { +++ u"new_method": {}, +++ }, +++ } +++ +++ expected4 = { +++ u"new_object": {"new_method": {}}, +++ } +++ +++ with CheckRefCount(path, fake, expected1, expected2, expected3, expected4, *new_object): +++ +++ ubus.connect(socket_path=path) +++ +++ # All objects +++ res1 = ubus.objects() +++ assert res1 == expected1 +++ +++ # Filtered objects +++ res2 = ubus.objects("registered_object2") +++ assert res2 == expected2 +++ +++ # Append an object +++ ubus.add(*new_object) +++ +++ # All objects + new +++ res3 = ubus.objects() +++ assert res3 == expected3 +++ +++ # New object +++ res4 = ubus.objects("new_object") +++ assert res4 == expected4 +++ +++ ubus.disconnect() +++ del res1 +++ del res2 +++ del res3 +++ del res4 +++ +++ +++def test_list_objects_empty(ubusd_test, disconnect_after): +++ path = UBUSD_TEST_SOCKET_PATH +++ +++ with CheckRefCount(path): +++ ubus.connect(path) +++ assert ubus.objects() == {} +++ +++ ubus.disconnect() +++ +++ +++def test_reply_out_of_handler(): +++ data = {"this": "should fail"} +++ +++ with CheckRefCount(data): +++ +++ # should be called only within call +++ handler = ubus.__ResponseHandler() +++ with pytest.raises(RuntimeError): +++ handler.reply(data) +++ +++ del handler +++ +++ +++def test_reply_failed(ubusd_test, call_for_object, disconnect_after): +++ path = UBUSD_TEST_SOCKET_PATH +++ +++ results = {e: {'data': None, 'exits': False} for e in range(1, 4)} +++ +++ def handler_fail1(handler, data): +++ results[1]['data'] = data +++ handler.reply() +++ results[1]['exits'] = True +++ +++ def handler_fail2(handler, data): +++ results[2]['data'] = data +++ handler.reply(6) +++ results[2]['exits'] = True +++ +++ def handler_fail3(handler, data): +++ results[3]['data'] = data +++ handler.reply({'data': 6}, {'fail': 'here'}) +++ results[3]['exits'] = True +++ +++ with CheckRefCount(path, results, handler_fail1, handler_fail2, handler_fail3): +++ +++ ubus.connect(path) +++ ubus.add( +++ "callee_object", +++ { +++ "method1": {"method": handler_fail1, "signature": { +++ "first": ubus.BLOBMSG_TYPE_INT32, +++ }}, +++ "method2": {"method": handler_fail2, "signature": { +++ "second": ubus.BLOBMSG_TYPE_INT32, +++ }}, +++ "method3": {"method": handler_fail3, "signature": {}}, +++ }, +++ ) +++ ubus.loop(500) +++ +++ assert results == { +++ 1: {'data': {'first': 1}, 'exits': False}, +++ 2: {'data': {'second': 2}, 'exits': False}, +++ 3: {'data': {}, 'exits': False}, +++ } +++ +++ ubus.disconnect() +++ +++ +++def test_reply(ubusd_test, call_for_object, disconnect_after): +++ path = UBUSD_TEST_SOCKET_PATH +++ +++ results = {e: {'data': None, 'exits': False} for e in range(1, 4)} +++ +++ def handler1(handler, data): +++ results[1]['data'] = data +++ handler.reply(data) +++ results[1]['exits'] = True +++ +++ def handler2(handler, data): +++ results[2]['data'] = data +++ handler.reply(data) +++ results[2]['exits'] = True +++ +++ def handler3(handler, data): +++ results[3]['data'] = data +++ handler.reply(data) +++ results[3]['exits'] = True +++ +++ with CheckRefCount(path, results, handler1, handler2, handler3): +++ +++ ubus.connect(path) +++ ubus.add( +++ "callee_object", +++ { +++ "method1": {"method": handler1, "signature": { +++ "first": ubus.BLOBMSG_TYPE_INT32, +++ }}, +++ "method2": {"method": handler2, "signature": { +++ "second": ubus.BLOBMSG_TYPE_INT32, +++ }}, +++ "method3": {"method": handler3, "signature": {}}, +++ }, +++ ) +++ ubus.loop(500) +++ +++ assert results == { +++ 1: {'data': {'first': 1}, 'exits': True}, +++ 2: {'data': {'second': 2}, 'exits': True}, +++ 3: {'data': {}, 'exits': True}, +++ } +++ +++ ubus.disconnect() +++ +++ +++def test_call_failed(ubusd_test, responsive_object, disconnect_after): +++ path = UBUSD_TEST_SOCKET_PATH +++ +++ args1 = ( +++ [], +++ ("responsive_object", ), +++ ("responsive_object", "respond", ), +++ ("responsive_object", "respond", 4, ), +++ ("responsive_object", "respond", {"first": "test", "second": True, "third": 56}, "x"), +++ ("responsive_object", "respond", {"first": "test", "second": True, "third": 56}, -1), +++ ) +++ args2 = ( +++ ("responsive_object", "respond", {"first": 6, "second": True, "third": 56}, ), +++ ("responsive_object", "respond", {"first": "test", "third": 56}, ), +++ ("responsive_object", "respond", {"first": "test", "second": True, "third": 56, "x": 1}), +++ ("responsive_object", "fail", {"first": "test", "second": True, "third": 56, "x": 1}), +++ ("responsive_object", "fail", {}), +++ ) +++ args3 = ( +++ ("responsive_object", "respond", {"first": "test", "second": True, "third": 56}, 2 ** 64), +++ ) +++ with CheckRefCount(path, *(args1 + args2 + args3)): +++ +++ with pytest.raises(RuntimeError): +++ ubus.objects(*args1[0]) +++ +++ ubus.connect(path) +++ for arg in args1: +++ with pytest.raises(TypeError): +++ ubus.call(*arg) +++ +++ for arg in args2: +++ with pytest.raises(RuntimeError): +++ ubus.call(*arg) +++ +++ for arg in args3: +++ with pytest.raises(OverflowError): +++ ubus.call(*arg) +++ +++ ubus.disconnect() +++ del arg +++ +++ +++def test_call(ubusd_test, responsive_object, disconnect_after): +++ path = UBUSD_TEST_SOCKET_PATH +++ +++ ubus_object = "responsive_object" +++ method1 = "respond" +++ method2 = "multi_respond" +++ data = {"first": "1", "second": False, "third": 22} +++ +++ with CheckRefCount(path, ubus_object, method1, method2): +++ +++ ubus.connect(socket_path=path) +++ res = ubus.call(ubus_object, method1, data) +++ assert len(res) == 1 +++ assert res[0] == {"first": "1", "second": False, "third": 22, "passed": True} +++ +++ res = ubus.call(ubus_object, method1, data, timeout=200) +++ assert len(res) == 1 +++ assert res[0] == {"first": "1", "second": False, "third": 22, "passed": True} +++ +++ res = ubus.call(ubus_object, method2, {}) +++ assert len(res) == 3 +++ assert res[0] == {"passed1": True} +++ assert res[1] == {"passed1": True, "passed2": True} +++ assert res[2] == {"passed1": True, "passed2": True, "passed3": True} +++ +++ del res +++ ubus.disconnect() +++ +++ +++def test_call_max_min_number(ubusd_test, responsive_object, disconnect_after): +++ path = UBUSD_TEST_SOCKET_PATH +++ data1 = {"number": 2 ** 32} +++ data2 = {"number": -(2 ** 32)} +++ +++ with CheckRefCount(path, data1, data2): +++ +++ ubus.connect(socket_path=path) +++ res = ubus.call("responsive_object", "number", data1) +++ assert res[0] == {"number": 2 ** 31 - 1, "passed": True} +++ res = ubus.call("responsive_object", "number", data2) +++ assert res[0] == {"number": -(2 ** 31), "passed": True} +++ +++ del res +++ ubus.disconnect() +++ +++ +++def test_multi_objects_listeners(ubusd_test, event_sender, calls_extensive, disconnect_after): +++ counts = 20 +++ listen_test = {"pass%d" % e: False for e in range(counts)} +++ object_test = {"pass%d" % e: False for e in range(counts)} +++ event_name = "event_sender" +++ timeout = 200 +++ +++ path = UBUSD_TEST_SOCKET_PATH +++ +++ def passed_listen_gen(index): +++ def passed(*args): +++ listen_test["pass%d" % index] = True +++ return passed +++ +++ def passed_object_gen(index): +++ def passed(*args): +++ object_test["pass%d" % index] = True +++ return passed +++ +++ with CheckRefCount(path, time): +++ +++ for _ in range(5): +++ ubus.connect(socket_path=path) +++ +++ for i in range(counts): +++ ubus.listen((event_name, passed_listen_gen(i))) +++ ubus.add( +++ "extensive_object_%d" % i, +++ {"method": {"method": passed_object_gen(i), "signature": {}}} +++ ) +++ +++ stored_counter = calls_extensive.counter.value +++ while calls_extensive.counter.value - stored_counter < counts: +++ ubus.loop(timeout) +++ ubus.disconnect() +++ +++ for i in range(counts): +++ current = "pass%d" % i +++ assert listen_test[current] +++ assert object_test[current] +++ +++ listen_test = {"pass%d" % e: False for e in range(counts)} +++ object_test = {"pass%d" % e: False for e in range(counts)} ++diff --git a/python/ubus_python.c b/python/ubus_python.c ++new file mode 100644 ++index 0000000..d51ce43 ++--- /dev/null +++++ b/python/ubus_python.c ++@@ -0,0 +1,1324 @@ +++ +++#include <Python.h> +++#include <dlfcn.h> +++#include <libubox/blobmsg_json.h> +++#include <libubus.h> +++#include <stdio.h> +++ +++#ifndef UBUS_UNIX_SOCKET +++#define UBUS_UNIX_SOCKET "/var/run/ubus.sock" +++#endif +++ +++#define DEFAULT_SOCKET UBUS_UNIX_SOCKET +++#define RESPONSE_HANDLER_OBJECT_NAME "ubus.__ResponseHandler" +++ +++#define MSG_ALLOCATION_FAILS "Failed to allocate memory!" +++#define MSG_LISTEN_TUPLE_EXPECTED "Expected (event, callback) tuple" +++#define MSG_ADD_SIGNATURE_INVALID \ +++"Incorrect method arguments!\n" \ +++"Expected:\n" \ +++" (<obj_name>, { " \ +++ "<method_name>: {'signature': <method_signature>, 'method': <callable>}" \ +++", ...})" +++#define MSG_JSON_TO_UBUS_FAILED "Failed to create json for ubus." +++#define MSG_JSON_FROM_UBUS_FAILED "Failed to create json from ubus." +++#define MSG_NOT_CONNECTED "You are not connected to ubus." +++#define MSG_ALREADY_CONNECTED "You are already connected to ubus." +++ +++typedef struct { +++ struct ubus_object object; +++ PyObject *methods; +++} ubus_Object; +++ +++typedef struct { +++ struct ubus_event_handler handler; +++ PyObject *callback; +++}ubus_Listener ; +++ +++ +++PyObject *prepare_bool(bool yes) +++{ +++ if (yes) { +++ Py_INCREF(Py_True); +++ return Py_True; +++ } else { +++ Py_INCREF(Py_False); +++ return Py_False; +++ } +++} +++ +++/* ubus module objects */ +++static PyMethodDef ubus_methods[]; +++PyObject *python_alloc_list = NULL; +++char *socket_path = NULL; +++ubus_Listener **listeners = NULL; +++size_t listerners_size = 0; +++ubus_Object **objects = NULL; +++size_t objects_size = 0; +++struct blob_buf python_buf; +++struct ubus_context *ctx = NULL; +++ +++#define CONNECTED (ctx != NULL) +++ +++ +++/* json module handlers */ +++PyObject *json_module = NULL; +++ +++enum json_function { +++ LOADS, +++ DUMPS, +++}; +++ +++const char *json_function_names[2] = { +++ [LOADS] = "loads", +++ [DUMPS] = "dumps", +++}; +++ +++ +++PyObject *perform_json_function(enum json_function json_function, PyObject *input) +++{ +++ PyObject *function = PyObject_GetAttrString(json_module, json_function_names[json_function]); +++ if (!function) { +++ return NULL; +++ } +++ PyObject *arglist = Py_BuildValue("(O)", input); +++ if (!arglist) { +++ Py_DECREF(function); +++ return NULL; +++ } +++ PyObject *data_object = PyObject_CallObject(function, arglist); +++ Py_DECREF(function); +++ Py_DECREF(arglist); +++ +++ return data_object; // New reference - should be decreased by the caller +++} +++ +++/* ResponseHandler */ +++ +++typedef struct { +++ PyObject_HEAD +++ struct ubus_context *ctx; +++ struct ubus_request_data *req; +++ struct blob_buf buf; +++} ubus_ResponseHandler; +++ +++static void ubus_ResponseHandler_dealloc(ubus_ResponseHandler* self) +++{ +++ blob_buf_free(&self->buf); +++ Py_TYPE(self)->tp_free((PyObject*)self); +++} +++ +++PyDoc_STRVAR( +++ ResponseHandler_reply_doc, +++ "reply(data)\n" +++ "\n" +++ ":param data: JSON to be send as a response to a ubus call.\n" +++ ":type data: dict\n" +++); +++ +++static PyObject *ubus_ResponseHandler_reply(ubus_ResponseHandler *self, PyObject *args, PyObject *kwargs) +++{ +++ if (!CONNECTED) { +++ PyErr_Format(PyExc_RuntimeError, MSG_NOT_CONNECTED); +++ return NULL; +++ } +++ +++ PyObject *data = NULL; +++ static char *kwlist[] = {"data", NULL}; +++ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O", kwlist, &data)) { +++ return NULL; +++ } +++ +++ // Call python function json.dumps +++ PyObject *json_str = perform_json_function(DUMPS, data); +++ if (!json_str) { +++ return NULL; +++ } +++ +++ // put json string into buffer +++ blob_buf_init(&self->buf, 0); +++ bool res = blobmsg_add_json_from_string(&self->buf, PyString_AsString(json_str)); +++ Py_DECREF(json_str); +++ if (!res) { +++ PyErr_Format(PyExc_TypeError, MSG_JSON_TO_UBUS_FAILED); +++ return NULL; +++ } +++ +++ // handler is not linked to a call response +++ if (!self->req || !self->ctx) { +++ PyErr_Format(PyExc_RuntimeError, "Handler is not linked to a call response."); +++ return NULL; +++ } +++ +++ int retval = ubus_send_reply(self->ctx, self->req, self->buf.head); +++ return prepare_bool(!retval); +++} +++ +++PyDoc_STRVAR( +++ ResponseHandler_doc, +++ "__ResponseHandler\n" +++ "\n" +++ "Object which is used to handle responses to ubus calls.\n" +++); +++ +++static PyMethodDef ubus_ResponseHandler_methods[] = { +++ {"reply", (PyCFunction)ubus_ResponseHandler_reply, METH_VARARGS|METH_KEYWORDS, ResponseHandler_reply_doc}, +++ {NULL}, +++}; +++ +++static int ubus_ResponseHandler_init(ubus_ResponseHandler *self, PyObject *args, PyObject *kwargs) +++{ +++ static char *kwlist[] = {NULL}; +++ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "", kwlist)){ +++ return -1; +++ } +++ memset(&self->buf, 0, sizeof(self->buf)); +++ self->ctx = NULL; +++ self->req = NULL; +++ return 0; +++} +++ +++static PyObject *ubus_ResponseHandler_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) +++{ +++ ubus_ResponseHandler *self = (ubus_ResponseHandler *)type->tp_alloc(type, 0); +++ return (PyObject *)self; +++} +++ +++static PyTypeObject ubus_ResponseHandlerType = { +++ PyVarObject_HEAD_INIT(NULL, 0) +++ RESPONSE_HANDLER_OBJECT_NAME, /* tp_name */ +++ sizeof(ubus_ResponseHandler), /* tp_basicsize */ +++ 0, /* tp_itemsize */ +++ (destructor)ubus_ResponseHandler_dealloc, /* tp_dealloc */ +++ 0, /* tp_print */ +++ 0, /* tp_getattr */ +++ 0, /* tp_setattr */ +++ 0, /* tp_compare */ +++ 0, /* tp_repr */ +++ 0, /* tp_as_number */ +++ 0, /* tp_as_sequence */ +++ 0, /* tp_as_mapping */ +++ 0, /* tp_hash */ +++ 0, /* tp_call */ +++ 0, /* tp_str */ +++ 0, /* tp_getattro */ +++ 0, /* tp_setattro */ +++ 0, /* tp_as_buffer */ +++ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */ +++ ResponseHandler_doc, /* tp_doc */ +++ 0, /* tp_traverse */ +++ 0, /* tp_clear */ +++ 0, /* tp_richcompare */ +++ 0, /* tp_weaklistoffset */ +++ 0, /* tp_iter */ +++ 0, /* tp_iternext */ +++ ubus_ResponseHandler_methods, /* tp_methods */ +++ 0, /* tp_members */ +++ 0, /* tp_getset */ +++ 0, /* tp_base */ +++ 0, /* tp_dict */ +++ 0, /* tp_descr_get */ +++ 0, /* tp_descr_set */ +++ 0, /* tp_dictoffset */ +++ (initproc)ubus_ResponseHandler_init, /* tp_init */ +++ 0, /* tp_alloc */ +++ ubus_ResponseHandler_new, /* tp_new */ +++}; +++ +++typedef struct { +++ PyObject_HEAD +++ PyObject *socket_path; +++ ubus_Listener **listeners; +++ size_t listerners_size; +++ ubus_Object **objects; +++ size_t objects_size; +++ PyObject *alloc_list; // Used for easy deallocation +++ struct blob_buf buf; +++ struct ubus_context *ctx; +++} ubus_Connection; +++ +++void free_ubus_object(ubus_Object *obj) +++{ +++ if (obj->object.methods) { +++ for (int i = 0; i < obj->object.n_methods; i++) { +++ if (&obj->object.methods[i] && obj->object.methods[i].policy) { +++ free((struct blobmsg_policy *)obj->object.methods[i].policy); +++ } +++ } +++ free((struct ubus_method *)obj->object.methods); +++ } +++ +++ if (obj->object.type) { +++ free(obj->object.type); +++ } +++ free(obj); +++} +++ +++PyObject *ubus_python_module_init(void) +++{ +++ PyObject *module = Py_InitModule3("ubus", ubus_methods, "Ubus bindings"); +++ return module; +++} +++ +++PyDoc_STRVAR( +++ disconnect_doc, +++ "disconnect(deregister=True)\n" +++ "\n" +++ ":param deregister: Deregisters object and handlers from ubus as well.\n" +++ ":type deregister: bool\n" +++ "Disconnects from ubus and disposes all connection structures.\n" +++); +++ +++void dispose_connection(bool deregister) +++{ +++ if (ctx != NULL) { +++ if (deregister) { +++ // remove objects +++ for (int i = 0; i < objects_size; i++) { +++ ubus_remove_object(ctx, &objects[i]->object); +++ } +++ +++ // remove listeners +++ for (int i = 0; i < listerners_size; i++) { +++ ubus_unregister_event_handler(ctx, &listeners[i]->handler); +++ } +++ } +++ +++ ubus_free(ctx); +++ ctx = NULL; +++ } +++ uloop_done(); +++ blob_buf_free(&python_buf); +++ if (python_alloc_list) { +++ Py_DECREF(python_alloc_list); +++ python_alloc_list = NULL; +++ } +++ // clear event listeners +++ if (listeners) { +++ for (int i = 0; i < listerners_size; i++) { +++ free(listeners[i]); +++ } +++ free(listeners); +++ listerners_size = 0; +++ listeners = NULL; +++ } +++ // clear objects +++ if (objects) { +++ for (int i = 0; i < objects_size; i++) { +++ free_ubus_object(objects[i]); +++ } +++ free(objects); +++ objects_size = 0; +++ objects = NULL; +++ } +++ +++ if (socket_path) { +++ free(socket_path); +++ socket_path = NULL; +++ } +++} +++ +++static PyObject *ubus_python_disconnect(PyObject *module, PyObject *args, PyObject *kwargs) +++{ +++ if (!CONNECTED) { +++ PyErr_Format(PyExc_RuntimeError, MSG_NOT_CONNECTED); +++ return NULL; +++ } +++ +++ PyObject* deregister = Py_True; +++ static char *kwlist[] = {"deregister", NULL}; +++ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|O!", kwlist, &PyBool_Type, &deregister)){ +++ return NULL; +++ } +++ +++ dispose_connection(PyObject_IsTrue(deregister)); +++ +++ Py_INCREF(Py_None); +++ return Py_None; +++} +++ +++PyDoc_STRVAR( +++ connect_doc, +++ "connect(socket_path='" DEFAULT_SOCKET "')\n" +++ "\n" +++ "Establishes a connection to ubus.\n" +++); +++ +++static PyObject *ubus_python_connect(PyObject *module, PyObject *args, PyObject *kwargs) +++{ +++ if (CONNECTED) { +++ PyErr_Format(PyExc_RuntimeError, MSG_ALREADY_CONNECTED); +++ return NULL; +++ } +++ +++ static char *kwlist[] = {"socket_path", NULL}; +++ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|s", kwlist, &socket_path)){ +++ return NULL; +++ } +++ +++ // Init object list +++ python_alloc_list = PyList_New(0); +++ if (!python_alloc_list) { +++ return NULL; +++ } +++ +++ // socket path +++ if (!socket_path) { +++ socket_path = strdup(DEFAULT_SOCKET); +++ if (!socket_path) { +++ PyErr_Format(PyExc_MemoryError, MSG_ALLOCATION_FAILS); +++ return NULL; +++ } +++ } else { +++ char *tmp = strdup(socket_path); +++ if (!tmp) { +++ PyErr_Format(PyExc_MemoryError, MSG_ALLOCATION_FAILS); +++ return NULL; +++ } +++ socket_path = tmp; +++ } +++ +++ // Init event listner array +++ listeners = NULL; +++ listerners_size = 0; +++ +++ // Init objects array +++ objects = NULL; +++ objects_size = 0; +++ +++ // Connect to ubus +++ ctx = ubus_connect(socket_path); +++ if (!ctx) { +++ PyErr_Format( +++ PyExc_IOError, +++ "Failed to connect to the ubus socket '%s'\n", socket_path +++ ); +++ dispose_connection(true); +++ return NULL; +++ } +++ ubus_add_uloop(ctx); +++ memset(&python_buf, 0, sizeof(python_buf)); +++ +++ return prepare_bool(true); +++} +++ +++PyDoc_STRVAR( +++ get_connected_doc, +++ "get_connected()\n" +++ "\n" +++ "Determines whether we are connected to ubus.\n" +++ ":return: True if connected, False otherwise.\n" +++ ":rtype: bool \n" +++); +++ +++static PyObject *ubus_python_get_connected(PyObject *module, PyObject *args, PyObject *kwargs) +++{ +++ return prepare_bool(CONNECTED); +++} +++ +++PyDoc_STRVAR( +++ get_socket_path_doc, +++ "get_socket_path()\n" +++ "\n" +++ "Gets socket path for the current connection.\n" +++ ":return: path to socket if connected, None otherwise.\n" +++ ":rtype: bool or str \n" +++); +++ +++static PyObject *ubus_python_get_socket_path(PyObject *module, PyObject *args, PyObject *kwargs) +++{ +++ if (socket_path) { +++ return PyString_FromString(socket_path); +++ } else { +++ Py_INCREF(Py_None); +++ return Py_None; +++ } +++} +++ +++PyDoc_STRVAR( +++ connect_send_doc, +++ "send(event, data)\n" +++ "\n" +++ "Send an event via ubus.\n" +++ "\n" +++ ":param event: ubus event which will be used \n" +++ ":type event: str\n" +++ ":param data: python object which can be serialized to json \n" +++ ":type data: dict or list \n" +++ ":return: True on success, False otherwise \n" +++ ":rtype: bool \n" +++); +++ +++static PyObject *ubus_python_send(PyObject *module, PyObject *args, PyObject *kwargs) +++{ +++ if (!CONNECTED) { +++ PyErr_Format(PyExc_RuntimeError, MSG_NOT_CONNECTED); +++ return NULL; +++ } +++ +++ char *event = NULL; +++ PyObject *data = NULL; +++ static char *kwlist[] = {"event", "data", NULL}; +++ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "sO", kwlist, &event, &data)){ +++ return NULL; +++ } +++ +++ // Call python function json.dumps +++ PyObject *json_str = perform_json_function(DUMPS, data); +++ if (!json_str) { +++ return NULL; +++ } +++ +++ // put json string into buffer +++ blob_buf_init(&python_buf, 0); +++ bool res = blobmsg_add_json_from_string(&python_buf, PyString_AsString(json_str)); +++ Py_DECREF(json_str); +++ if (!res) { +++ PyErr_Format(PyExc_TypeError, MSG_JSON_TO_UBUS_FAILED); +++ return NULL; +++ } +++ +++ int retval = ubus_send_event(ctx, event, python_buf.head); +++ return prepare_bool(!retval); +++} +++ +++static void ubus_python_event_handler(struct ubus_context *ctx, struct ubus_event_handler *ev, +++ const char *type, struct blob_attr *msg) +++{ +++ PyGILState_STATE gstate = PyGILState_Ensure(); +++ +++ // Prepare event +++ PyObject *event = PyString_FromString(type); +++ if (!event) { +++ goto event_handler_cleanup0; +++ } +++ +++ // Prepare json data +++ char *str = blobmsg_format_json(msg, true); +++ if (!str) { +++ goto event_handler_cleanup1; +++ } +++ PyObject *data = PyString_FromString(str); +++ free(str); +++ if (!data) { +++ goto event_handler_cleanup1; +++ } +++ +++ // Call python function json.loads +++ PyObject *data_object = perform_json_function(LOADS, data); +++ if (!data_object) { +++ goto event_handler_cleanup2; +++ } +++ +++ // Get PyObject callback +++ ubus_Listener *listener = container_of(ev, ubus_Listener, handler); +++ +++ // Trigger callback +++ PyObject *callback_arglist = Py_BuildValue("(O, O)", event, data_object); +++ if (!callback_arglist) { +++ goto event_handler_cleanup3; +++ } +++ +++ PyObject *result = PyObject_CallObject(listener->callback, callback_arglist); +++ if (result) { +++ Py_DECREF(result); // result of the callback is quite useless +++ } else { +++ PyErr_Print(); +++ } +++ Py_DECREF(callback_arglist); +++ +++event_handler_cleanup3: +++ Py_DECREF(data_object); +++event_handler_cleanup2: +++ Py_DECREF(data); +++event_handler_cleanup1: +++ Py_DECREF(event); +++ +++event_handler_cleanup0: +++ // Clear python exceptions +++ PyErr_Clear(); +++ +++ PyGILState_Release(gstate); +++} +++ +++PyDoc_STRVAR( +++ connect_listen_doc, +++ "listen(event, ...)\n" +++ "\n" +++ "Adds a listener on ubus events.\n" +++ "\n" +++ ":param event: tuple contaning event string and a callback (str, callable) \n" +++ ":type event: tuple\n" +++); +++ +++static PyObject *ubus_python_listen(PyObject *module, PyObject *args, PyObject *kwargs) +++{ +++ if (!CONNECTED) { +++ PyErr_Format(PyExc_RuntimeError, MSG_NOT_CONNECTED); +++ return NULL; +++ } +++ +++ args = PySequence_Fast(args, "expected a sequence"); +++ int len = PySequence_Size(args); +++ if (!len) { +++ PyErr_Format(PyExc_TypeError, "You need to set at least one event."); +++ goto listen_error1; +++ } +++ +++ if (!PyTuple_Check(args)) { +++ PyErr_Format(PyExc_TypeError, "Tuple of (event, callback) expected."); +++ goto listen_error1; +++ } +++ +++ // Test whether the arguments are valid +++ for (int i = 0; i < len; i++) { +++ PyObject *item = PySequence_Fast_GET_ITEM(args, i); +++ // Test tuple +++ if (!PyTuple_Check(item)) { +++ PyErr_Format(PyExc_TypeError, MSG_LISTEN_TUPLE_EXPECTED); +++ goto listen_error1; +++ } +++ PyObject *item_tuple = PySequence_Fast(item, MSG_LISTEN_TUPLE_EXPECTED); +++ if (!item_tuple) { +++ PyErr_Format(PyExc_MemoryError, "Failed to obtain tuple item"); +++ goto listen_error1; +++ } +++ if (!PyTuple_Check(item_tuple) || PySequence_Size(item_tuple) != 2) { +++ PyErr_Format(PyExc_TypeError, MSG_LISTEN_TUPLE_EXPECTED); +++ Py_DECREF(item_tuple); +++ Py_DECREF(args); +++ goto listen_error1; +++ } +++ +++ // Test types +++ if (!PyString_Check(PyTuple_GET_ITEM(item_tuple, 0)) +++ || !PyCallable_Check(PyTuple_GET_ITEM(item_tuple, 1))) { +++ PyErr_Format(PyExc_TypeError, MSG_LISTEN_TUPLE_EXPECTED); +++ Py_DECREF(item_tuple); +++ goto listen_error1; +++ } +++ Py_DECREF(item_tuple); +++ } +++ +++ // add callbacks +++ for (int i = 0; i < len; i++) { +++ PyObject *item = PySequence_Fast_GET_ITEM(args, i); +++ PyObject *item_tuple = PySequence_Fast(item, MSG_LISTEN_TUPLE_EXPECTED); +++ if (!item_tuple) { +++ PyErr_Format(PyExc_MemoryError, "Failed to obtain tuple item"); +++ goto listen_error1; +++ } +++ PyObject *event = PyTuple_GET_ITEM(item_tuple, 0); +++ PyObject *callback = PyTuple_GET_ITEM(item_tuple, 1); +++ // Keep event and callback references +++ if (PyList_Append(python_alloc_list, event) || PyList_Append(python_alloc_list, callback)) { +++ Py_DECREF(item_tuple); +++ goto listen_error1; +++ } +++ Py_DECREF(item_tuple); +++ +++ // prepare event listener +++ ubus_Listener *listener = calloc(1, sizeof(ubus_Listener)); +++ if (!listener) { +++ PyErr_Format(PyExc_MemoryError, MSG_ALLOCATION_FAILS); +++ Py_DECREF(item_tuple); +++ goto listen_error1; +++ } +++ +++ listener->handler.cb = ubus_python_event_handler; +++ listener->callback = callback; +++ +++ ubus_Listener **new_listeners = realloc(listeners, +++ (listerners_size + 1) * sizeof(*listeners)); +++ if (!new_listeners) { +++ free(listener); +++ goto listen_error1; +++ } +++ listeners = new_listeners; +++ listeners[listerners_size++] = listener; +++ +++ // register event handler +++ int retval = ubus_register_event_handler(ctx, &listener->handler, PyString_AsString(event)); +++ if (retval != UBUS_STATUS_OK) { +++ listerners_size--; +++ free(listener); +++ } +++ } +++ +++ Py_DECREF(args); +++ +++ Py_INCREF(Py_None); +++ return Py_None; +++ +++listen_error1: +++ Py_DECREF(args); +++ return NULL; +++} +++ +++static void ubus_python_timeout_handler(struct uloop_timeout *timeout) { +++ uloop_end(); +++} +++ +++PyDoc_STRVAR( +++ connect_loop_doc, +++ "loop(timeout=-1)\n" +++ "\n" +++ "Enters a loop and processes events.\n" +++ "\n" +++ ":param timeout: loop timeout in ms (if lower than zero then it will run forever) \n" +++ ":type timeout: int\n" +++); +++ +++static PyObject *ubus_python_loop(PyObject *module, PyObject *args, PyObject *kwargs) +++{ +++ if (!CONNECTED) { +++ PyErr_Format(PyExc_RuntimeError, MSG_NOT_CONNECTED); +++ return NULL; +++ } +++ +++ int timeout = -1; +++ static char *kwlist[] = {"timeout", NULL}; +++ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|i", kwlist, &timeout)){ +++ return NULL; +++ } +++ +++ Py_BEGIN_ALLOW_THREADS +++ if (timeout == 0) { +++ // process events directly without uloop +++ ubus_handle_event(ctx); +++ } else { +++ uloop_init(); +++ struct uloop_timeout u_timeout; +++ if (timeout > 0) { +++ // prepare for timeout +++ memset(&u_timeout, 0, sizeof(u_timeout)); +++ u_timeout.cb = ubus_python_timeout_handler; +++ uloop_timeout_set(&u_timeout, timeout); // Timeout to seconds +++ } +++ uloop_run(); +++ if (timeout > 0) { +++ uloop_timeout_cancel(&u_timeout); +++ } +++ } +++ Py_END_ALLOW_THREADS +++ +++ Py_INCREF(Py_None); +++ return Py_None; +++} +++ +++bool test_policies(const struct blobmsg_policy *policies, int n_policies, struct blob_attr *args) +++{ +++ struct blob_attr *cur; +++ int idx = 0, passed_count = 0; +++ +++ blob_for_each_attr(cur, args, idx) { +++ const char *name = blobmsg_name(cur); +++ int type = blobmsg_type(cur); +++ int pol_idx; +++ +++ // Iterate through policies +++ for (pol_idx = 0; pol_idx < n_policies; pol_idx++) { +++ +++ if (!strcmp(name, policies[pol_idx].name)) { +++ passed_count += 1; +++ int pol_type = policies[pol_idx].type; +++ if (pol_type != BLOBMSG_TYPE_UNSPEC && pol_type != type) { +++ return false; +++ } +++ break; +++ } +++ } +++ +++ // Policy was not found +++ if (pol_idx >= n_policies) { +++ return false; +++ } +++ } +++ +++ // All attributes are present and checked +++ return passed_count == n_policies; +++} +++ +++static int ubus_python_method_handler(struct ubus_context *ctx, struct ubus_object *obj, +++ struct ubus_request_data *req, const char *method, +++ struct blob_attr *msg) +++{ +++ // Check whether method signature matches +++ int method_idx; +++ for (method_idx = 0; method_idx < obj->n_methods; ++method_idx) { +++ if (!strcmp(obj->methods[method_idx].name, method)) { +++ break; +++ } +++ } +++ if (method_idx >= obj->n_methods) { +++ // Can't find method +++ return UBUS_STATUS_UNKNOWN_ERROR; +++ } +++ if (!test_policies(obj->methods[method_idx].policy, obj->methods[method_idx].n_policy, msg)) { +++ return UBUS_STATUS_INVALID_ARGUMENT; +++ } +++ +++ PyGILState_STATE gstate = PyGILState_Ensure(); +++ +++ int retval = UBUS_STATUS_OK; +++ // Get python method +++ PyObject *methods = container_of(obj, ubus_Object, object)->methods; +++ PyObject *python_method = PyDict_GetItemString(methods, method); +++ if (!python_method) { +++ retval = UBUS_STATUS_METHOD_NOT_FOUND; +++ goto method_handler_exit; +++ } +++ +++ // prepare json data +++ char *str = blobmsg_format_json(msg, true); +++ if (!str) { +++ retval = UBUS_STATUS_UNKNOWN_ERROR; +++ goto method_handler_exit; +++ } +++ PyObject *data = PyString_FromString(str); +++ free(str); +++ if (!data) { +++ retval = UBUS_STATUS_UNKNOWN_ERROR; +++ goto method_handler_exit; +++ } +++ +++ // Call python function json.loads +++ PyObject *data_object = perform_json_function(LOADS, data); +++ if (!data_object) { +++ retval = UBUS_STATUS_UNKNOWN_ERROR; +++ goto method_handler_cleanup1; +++ } +++ +++ PyObject *handler = PyObject_CallObject((PyObject *)&ubus_ResponseHandlerType, NULL); +++ if (!handler) { +++ PyErr_Print(); +++ goto method_handler_cleanup2; +++ } +++ ((ubus_ResponseHandler *)handler)->req = req; +++ ((ubus_ResponseHandler *)handler)->ctx = ctx; +++ +++ // Trigger method +++ PyObject *callback_arglist = Py_BuildValue("(O, O)", handler, data_object); +++ if (!callback_arglist) { +++ retval = UBUS_STATUS_UNKNOWN_ERROR; +++ goto method_handler_cleanup3; +++ } +++ PyObject *callable = PyDict_GetItemString(python_method, "method"); +++ PyObject *result = PyObject_CallObject(callable, callback_arglist); +++ Py_DECREF(callback_arglist); +++ if (!result) { +++ PyErr_Print(); +++ retval = UBUS_STATUS_UNKNOWN_ERROR; +++ } else { +++ Py_DECREF(result); // we don't care about the result +++ } +++ +++method_handler_cleanup3: +++ // NULLify the structures so that using this structure will we useless if a reference +++ // is left outside the callback code +++ ((ubus_ResponseHandler *)handler)->req = NULL; +++ ((ubus_ResponseHandler *)handler)->ctx = NULL; +++ Py_DECREF(handler); +++method_handler_cleanup2: +++ Py_DECREF(data_object); +++method_handler_cleanup1: +++ Py_DECREF(data); +++method_handler_exit: +++ +++ // Clear python exceptions +++ PyErr_Clear(); +++ +++ PyGILState_Release(gstate); +++ +++ return retval; +++} +++ +++static bool test_methods_argument(PyObject *methods) +++{ +++ if (!methods) { +++ return false; +++ } +++ +++ PyObject *method_name = NULL, *value = NULL; +++ Py_ssize_t pos = 0; +++ // Iterate through methods +++ while(PyDict_Next(methods, &pos, &method_name, &value)) { +++ // Test name +++ if (!PyString_Check(method_name)) { +++ return false; +++ } +++ if (!PyDict_Check(value)) { +++ return false; +++ } +++ +++ // Dict should contain only two elemnts - 'signature' and 'method' +++ if (PyDict_Size(value) != 2) { +++ return false; +++ } +++ +++ // Test signature +++ PyObject *signature = PyDict_GetItemString(value, "signature"); +++ if (!signature || !PyDict_Check(signature)) { +++ return false; +++ } +++ Py_ssize_t sig_pos = 0; +++ PyObject *signature_name = NULL, *signature_type = NULL; +++ while (PyDict_Next(signature, &sig_pos, &signature_name, &signature_type)) { +++ if (!PyString_Check(signature_name)) { +++ return false; +++ } +++ if (!PyInt_Check(signature_type)) { +++ return false; +++ } +++ int type = PyInt_AsLong(signature_type); +++ if (type < 0 || type > BLOBMSG_TYPE_LAST) { // indexed from 0 +++ return false; +++ } +++ } +++ +++ // Test callable +++ PyObject *method = PyDict_GetItemString(value, "method"); +++ if (!method || !PyCallable_Check(method)) { +++ return false; +++ } +++ } +++ +++ return true; +++} +++ +++PyDoc_STRVAR( +++ connect_add_doc, +++ "add(object_name, methods)\n" +++ "\n" +++ "Adds an object to ubus.\n" +++ "methods should look like this: \n" +++ "{ \n" +++ " <method_name>: {'signature': <method_signature>, 'method': <callable>} \n" +++ "} \n" +++ "\n" +++ "{ \n" +++ " test: {'signature': {'argument1': BLOBMSG_TYPE_STRING}, 'method': my_callback} \n" +++ "} \n" +++ "\n" +++ ":param object_name: the name of the object which will be present on ubus \n" +++ ":type object_name: str\n" +++ ":param methods: {<method_name>: callable} where callable signature is (request, msg) \n" +++ ":type methods: dict\n" +++); +++ +++static PyObject *ubus_python_add(PyObject *module, PyObject *args, PyObject *kwargs) +++{ +++ if (!CONNECTED) { +++ PyErr_Format(PyExc_RuntimeError, MSG_NOT_CONNECTED); +++ return NULL; +++ } +++ +++ // arguments +++ PyObject *object_name = NULL; +++ PyObject *methods= NULL; +++ static char *kwlist[] = {"object_name", "methods", NULL}; +++ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "OO", kwlist, &object_name, &methods)){ +++ return NULL; +++ } +++ +++ // test arguments +++ if (!PyString_Check(object_name)) { +++ PyErr_Format(PyExc_TypeError, MSG_ADD_SIGNATURE_INVALID); +++ return NULL; +++ } +++ +++ if (!test_methods_argument(methods)) { +++ PyErr_Format(PyExc_TypeError, MSG_ADD_SIGNATURE_INVALID); +++ return NULL; +++ } +++ +++ // allocate the object +++ ubus_Object *object = calloc(1, sizeof(ubus_Object)); +++ if (!object) { +++ PyErr_Format(PyExc_MemoryError, MSG_ALLOCATION_FAILS); +++ return NULL; +++ } +++ object->methods = methods; +++ +++ // set the object +++ object->object.name = PyString_AsString(object_name); +++ object->object.n_methods = PyDict_Size(methods); +++ +++ if (object->object.n_methods > 0) { +++ struct ubus_method *ubus_methods = calloc(object->object.n_methods, sizeof(struct ubus_method)); +++ if (!ubus_methods) { +++ free(object); +++ PyErr_Format(PyExc_MemoryError, MSG_ALLOCATION_FAILS); +++ return NULL; +++ } +++ +++ PyObject *method_name = NULL, *value = NULL; +++ Py_ssize_t pos = 0; +++ // Iterate through methods +++ for (int i = 0; PyDict_Next(methods, &pos, &method_name, &value); i++) { +++ ubus_methods[i].name = PyString_AsString(method_name); +++ ubus_methods[i].handler = ubus_python_method_handler; +++ +++ // alocate and set policy objects +++ PyObject *signature = PyDict_GetItemString(value, "signature"); +++ Py_ssize_t signature_size = PyDict_Size(signature); +++ struct blobmsg_policy *policy = calloc(signature_size, sizeof(struct blobmsg_policy)); +++ if (!policy) { +++ // dealloc allocated data +++ free_ubus_object(object); +++ PyErr_Format(PyExc_MemoryError, MSG_ALLOCATION_FAILS); +++ return NULL; +++ } +++ Py_ssize_t sig_pos = 0; +++ PyObject *signature_name = NULL, *signature_type = NULL; +++ for (int j = 0; PyDict_Next(signature, &sig_pos, &signature_name, &signature_type); j++) { +++ policy[j].name = PyString_AsString(signature_name); +++ policy[j].type = PyInt_AsLong(signature_type); +++ } +++ ubus_methods[i].policy = policy; +++ ubus_methods[i].n_policy = signature_size; +++ } +++ +++ // assign methods +++ object->object.methods = ubus_methods; +++ } +++ +++ object->object.type = calloc(1, sizeof(struct ubus_object_type)); +++ if (!object->object.type) { +++ free_ubus_object(object); +++ PyErr_Format(PyExc_MemoryError, MSG_ALLOCATION_FAILS); +++ return NULL; +++ } +++ object->object.type->name = PyString_AsString(object_name); +++ object->object.type->methods = object->object.methods; +++ object->object.type->n_methods = object->object.n_methods; +++ +++ // add object to object array to be deallocated later +++ ubus_Object **new_objects = realloc(objects, +++ (objects_size + 1) * sizeof(*objects)); +++ if (!new_objects) { +++ // dealloc the object +++ free_ubus_object(object); +++ PyErr_Format(PyExc_MemoryError, MSG_ALLOCATION_FAILS); +++ return NULL; +++ } +++ objects = new_objects; +++ objects[objects_size++] = object; +++ +++ int ret = ubus_add_object(ctx, &object->object); +++ if (ret) { +++ // deallocate object on failure +++ objects_size--; // no need to realloc the whole array +++ free_ubus_object(object); +++ +++ PyErr_Format( +++ PyExc_RuntimeError, +++ "ubus error occured: %s", ubus_strerror(ret) +++ ); +++ return NULL; +++ } +++ +++ // put arguments into alloc list (used for reference counting) +++ if (PyList_Append(python_alloc_list, object_name)) { +++ ubus_remove_object(ctx, &object->object); +++ free_ubus_object(object); +++ return NULL; +++ } +++ +++ if (PyList_Append(python_alloc_list, methods)) { +++ ubus_remove_object(ctx, &object->object); +++ free_ubus_object(object); +++ PyEval_CallMethod(python_alloc_list, "pop", ""); +++ return NULL; +++ } +++ +++ Py_INCREF(Py_None); +++ return Py_None; +++} +++ +++static void ubus_python_objects_handler(struct ubus_context *c, struct ubus_object_data *o, void *p) +++{ +++ // should be a single instance for all the objects +++ PyObject *objects = (PyObject *)p; +++ +++ PyObject *str_signatures = PyString_FromString("{"); +++ if (!str_signatures) { +++ return; +++ } +++ +++ bool first = true; +++ if (o->signature) { +++ struct blob_attr *cur; +++ int rem = 0; +++ blob_for_each_attr(cur, o->signature, rem) { +++ char *s = blobmsg_format_json(cur, false); +++ if (!s) { +++ goto object_handler_cleanup; +++ } +++ PyObject *str_signature = NULL; +++ if (first) { +++ first = false; +++ str_signature = PyString_FromString(s); +++ } else { +++ str_signature = PyString_FromFormat(" ,%s", s); +++ } +++ free(s); +++ if (!str_signature) { +++ goto object_handler_cleanup; +++ } +++ PyString_Concat(&str_signatures, str_signature); +++ Py_DECREF(str_signature); +++ if (!str_signatures) { +++ return; //str_signatures already discarded in PyString_Concat +++ } +++ } +++ } +++ +++ PyObject *closing_bracket = PyString_FromString("}"); +++ PyString_Concat(&str_signatures, closing_bracket); +++ Py_DECREF(closing_bracket); +++ if (!str_signatures) { +++ return; +++ } +++ +++ // convert json string to json +++ PyObject *json_signatures = perform_json_function(LOADS, str_signatures); +++ if (!json_signatures) { +++ goto object_handler_cleanup; +++ } +++ +++ // Add it to dict object +++ PyObject *path = PyUnicode_FromString(o->path); +++ if (!path) { +++ goto object_handler_cleanup; +++ } +++ PyDict_SetItem(objects, path, json_signatures); // we don't care about retval here +++ Py_DECREF(path); +++ +++object_handler_cleanup: +++ Py_DECREF(str_signatures); +++ +++ // Clear python exceptions +++ PyErr_Clear(); +++} +++ +++PyDoc_STRVAR( +++ connect_objects_doc, +++ "objects(path='*')\n" +++ "\n" +++ "Prints all objects present on ubus\n" +++ "\n" +++ ":param path: only object which match the given path \n" +++ ":type path: str\n" +++ ":return: {<object_path>: {{<function_name>: <function_signature>}, ...}, ...} \n" +++ ":rtype: dict\n" +++); +++ +++static PyObject *ubus_python_objects(PyObject *module, PyObject *args, PyObject *kwargs) +++{ +++ if (!CONNECTED) { +++ PyErr_Format(PyExc_RuntimeError, MSG_NOT_CONNECTED); +++ return NULL; +++ } +++ +++ char *ubus_path = NULL; +++ static char *kwlist[] = {"path", NULL}; +++ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|s", kwlist, &ubus_path)){ +++ return NULL; +++ } +++ +++ ubus_path = ubus_path ? ubus_path : "*"; +++ +++ PyObject *res = PyDict_New(); +++ if (!res) { +++ return NULL; +++ } +++ +++ int retval = ubus_lookup(ctx, ubus_path, ubus_python_objects_handler, res); +++ switch (retval) { +++ case UBUS_STATUS_OK: +++ case UBUS_STATUS_NOT_FOUND: +++ break; +++ default: +++ Py_DECREF(res); +++ PyErr_Format( +++ PyExc_RuntimeError, +++ "ubus error occured: %s", ubus_strerror(retval) +++ ); +++ return NULL; +++ } +++ +++ return res; +++} +++ +++static void ubus_python_call_handler(struct ubus_request *req, int type, struct blob_attr *msg) +++{ +++ assert(type == UBUS_MSG_DATA); +++ +++ PyObject **results = (PyObject **)req->priv; +++ if (!*results) { +++ // error has occured in some previous call -> exit +++ return; +++ } +++ +++ if (!msg) { +++ PyErr_Format(PyExc_RuntimeError, "No data in call hander"); +++ goto call_handler_cleanup; +++ } +++ +++ // convert message do python json object +++ char *str = blobmsg_format_json(msg, true); +++ if (!str) { +++ PyErr_Format(PyExc_RuntimeError, MSG_JSON_FROM_UBUS_FAILED); +++ goto call_handler_cleanup; +++ } +++ PyObject *data = PyString_FromString(str); +++ free(str); +++ if (!data) { +++ goto call_handler_cleanup; +++ } +++ PyObject *data_object = perform_json_function(LOADS, data); +++ Py_DECREF(data); +++ if (!data_object) { +++ goto call_handler_cleanup; +++ } +++ +++ // append to results +++ int failed = PyList_Append(*results, data_object); +++ Py_DECREF(data_object); +++ if (failed) { +++ goto call_handler_cleanup; +++ } +++ +++ return; +++ +++ call_handler_cleanup: +++ +++ // clear the result +++ Py_DECREF(*results); +++ results = NULL; +++} +++ +++PyDoc_STRVAR( +++ connect_call_doc, +++ "call(object, method, arguments, timeout=0)\n" +++ "\n" +++ "Calls object's method on ubus.\n" +++ "\n" +++ ":param object: name of the object\n" +++ ":type object: str\n" +++ ":param method: name of the method\n" +++ ":type method: str\n" +++ ":param arguments: arguments of the method (should be JSON serialisable).\n" +++ ":type argument: dict\n" +++ ":param timeout: timeout in ms (0 = wait forever)\n" +++ ":type timeout: int\n" +++); +++ +++static PyObject *ubus_python_call(PyObject *module, PyObject *args, PyObject *kwargs) +++{ +++ if (!CONNECTED) { +++ PyErr_Format(PyExc_RuntimeError, MSG_NOT_CONNECTED); +++ return NULL; +++ } +++ +++ char *object = NULL, *method = NULL; +++ int timeout = 0; +++ PyObject *arguments = NULL; +++ static char *kwlist[] = {"object", "method", "arguments", "timeout", NULL}; +++ if (!PyArg_ParseTupleAndKeywords( +++ args, kwargs, "ssO|i", kwlist, &object, &method, &arguments, &timeout)){ +++ return NULL; +++ } +++ if (timeout < 0) { +++ PyErr_Format(PyExc_TypeError, "timeout can't be lower than 0"); +++ return NULL; +++ } +++ +++ uint32_t id = 0; +++ int retval = ubus_lookup_id(ctx, object, &id); +++ if (retval != UBUS_STATUS_OK) { +++ PyErr_Format(PyExc_RuntimeError, "Object '%s' was not found.", object); +++ return NULL; +++ } +++ +++ // Call python function json.dumps +++ PyObject *json_arguments = perform_json_function(DUMPS, arguments); +++ if (!json_arguments) { +++ return NULL; +++ } +++ +++ // put data into buffer +++ blob_buf_init(&python_buf, 0); +++ bool res = blobmsg_add_json_from_string(&python_buf, PyString_AsString(json_arguments)); +++ Py_DECREF(json_arguments); +++ if (!res) { +++ PyErr_Format(PyExc_TypeError, MSG_JSON_TO_UBUS_FAILED); +++ return NULL; +++ } +++ +++ PyObject *results = PyList_New(0); +++ if (!results) { +++ return NULL; +++ } +++ +++ retval = ubus_invoke( +++ ctx, id, method, python_buf.head, ubus_python_call_handler, &results, timeout); +++ +++ if (retval != UBUS_STATUS_OK) { +++ Py_XDECREF(results); +++ PyErr_Format( +++ PyExc_RuntimeError, +++ "ubus error occured: %s", ubus_strerror(retval) +++ ); +++ return NULL; +++ } +++ +++ // Note that results might be NULL indicating that something went wrong in the handler +++ return results; +++} +++ +++static PyMethodDef ubus_methods[] = { +++ {"disconnect", (PyCFunction)ubus_python_disconnect, METH_VARARGS|METH_KEYWORDS, disconnect_doc}, +++ {"connect", (PyCFunction)ubus_python_connect, METH_VARARGS|METH_KEYWORDS, connect_doc}, +++ {"get_connected", (PyCFunction)ubus_python_get_connected, METH_NOARGS, get_connected_doc}, +++ {"get_socket_path", (PyCFunction)ubus_python_get_socket_path, METH_NOARGS, get_socket_path_doc}, +++ {"send", (PyCFunction)ubus_python_send, METH_VARARGS|METH_KEYWORDS, connect_send_doc}, +++ {"listen", (PyCFunction)ubus_python_listen, METH_VARARGS, connect_listen_doc}, +++ {"loop", (PyCFunction)ubus_python_loop, METH_VARARGS|METH_KEYWORDS, connect_loop_doc}, +++ {"add", (PyCFunction)ubus_python_add, METH_VARARGS|METH_KEYWORDS, connect_add_doc}, +++ {"objects", (PyCFunction)ubus_python_objects, METH_VARARGS|METH_KEYWORDS, connect_objects_doc}, +++ {"call", (PyCFunction)ubus_python_call, METH_VARARGS|METH_KEYWORDS, connect_call_doc}, +++ {NULL} +++}; +++ +++PyMODINIT_FUNC initubus(void) { +++ if (PyType_Ready(&ubus_ResponseHandlerType)) { +++ return; +++ } +++ +++ json_module = PyImport_ImportModule("json"); +++ if (!json_module) { +++ return; +++ } +++ +++ PyObject *module = ubus_python_module_init(); +++ if (!module) { +++ return; +++ } +++ +++ Py_INCREF(&ubus_ResponseHandlerType); +++ PyModule_AddObject(module, "__ResponseHandler", (PyObject *)&ubus_ResponseHandlerType); +++ +++ /* export ubus json types */ +++ PyModule_AddIntMacro(module, BLOBMSG_TYPE_UNSPEC); +++ PyModule_AddIntMacro(module, BLOBMSG_TYPE_ARRAY); +++ PyModule_AddIntMacro(module, BLOBMSG_TYPE_TABLE); +++ PyModule_AddIntMacro(module, BLOBMSG_TYPE_STRING); +++ PyModule_AddIntMacro(module, BLOBMSG_TYPE_INT64); +++ PyModule_AddIntMacro(module, BLOBMSG_TYPE_INT32); +++ PyModule_AddIntMacro(module, BLOBMSG_TYPE_INT16); +++ PyModule_AddIntMacro(module, BLOBMSG_TYPE_INT8); +++ PyModule_AddIntMacro(module, BLOBMSG_TYPE_DOUBLE); +++ PyModule_AddIntMacro(module, BLOBMSG_TYPE_BOOL); +++} -- 2.18.0