From 8459caf02ee26a9cc8e147ae00bb5b9661ac14b7 Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Wed, 31 May 2023 09:36:54 +0200 Subject: [PATCH] silabs-multiprotocol: Update to upstream REST API (#3067) * silabs-multiprotocol: Update to upstream REST API Update the local patches to use the upstream REST API variant. This is a breaking change and requires an Home Assistant Core update. * Update major version since this is an API break * Correct Core requirements --- ...set-add-otDatasetUpdateTlvs-API-8871.patch | 89 +++ ...isten-on-IPv6-addresses-as-well-1659.patch | 6 +- ...002-rest-add-active-dataset-API-1658.patch | 10 +- ...3-rest-use-map-for-HTTP-headers-1665.patch | 10 +- ...atest-Simple-Web-Server-version-1667.patch | 10 +- ...-to-specify-REST-listen-address-1670.patch | 10 +- ...rfluous-space-in-HTTP-status-line-17.patch | 33 + ...set-Connection-header-to-close-1774.patch} | 24 +- .../0008-rest-support-state-change.patch | 129 ---- ...st-add-Operational-Dataset-API-1682.patch} | 623 ++++++++++-------- ...uperfluous-space-in-HTTP-status-line.patch | 28 - ...low-to-specify-REST-listen-port-1862.patch | 173 +++++ ...est-support-Thread-state-change-1866.patch | 511 ++++++++++++++ silabs-multiprotocol/CHANGELOG.md | 4 + silabs-multiprotocol/Dockerfile | 28 +- silabs-multiprotocol/config.yaml | 6 +- 16 files changed, 1231 insertions(+), 463 deletions(-) create mode 100644 silabs-multiprotocol/0001-dataset-add-otDatasetUpdateTlvs-API-8871.patch create mode 100644 silabs-multiprotocol/0007-rest-remove-superfluous-space-in-HTTP-status-line-17.patch rename silabs-multiprotocol/{0010-rest-explicitly-set-Connection-header-to-close.patch => 0008-rest-explicitly-set-Connection-header-to-close-1774.patch} (67%) delete mode 100644 silabs-multiprotocol/0008-rest-support-state-change.patch rename silabs-multiprotocol/{0007-rest-implement-REST-API-to-get-dataset.patch => 0009-rest-add-Operational-Dataset-API-1682.patch} (76%) delete mode 100644 silabs-multiprotocol/0009-rest-remove-superfluous-space-in-HTTP-status-line.patch create mode 100644 silabs-multiprotocol/0010-rest-allow-to-specify-REST-listen-port-1862.patch create mode 100644 silabs-multiprotocol/0011-rest-support-Thread-state-change-1866.patch diff --git a/silabs-multiprotocol/0001-dataset-add-otDatasetUpdateTlvs-API-8871.patch b/silabs-multiprotocol/0001-dataset-add-otDatasetUpdateTlvs-API-8871.patch new file mode 100644 index 00000000000..48b687d6e92 --- /dev/null +++ b/silabs-multiprotocol/0001-dataset-add-otDatasetUpdateTlvs-API-8871.patch @@ -0,0 +1,89 @@ +From b60d9aaaee4f72b403c55684bb8464e27b12b3c1 Mon Sep 17 00:00:00 2001 +Message-Id: +From: Jonathan Hui +Date: Wed, 15 Mar 2023 18:36:39 -0700 +Subject: [PATCH] [dataset] add `otDatasetUpdateTlvs` API (#8871) + +--- + include/openthread/dataset.h | 27 +++++++++++++++++++++++++++ + src/core/api/dataset_api.cpp | 29 +++++++++++++++++++++++++++++ + 2 files changed, 56 insertions(+) + +diff --git a/include/openthread/dataset.h b/include/openthread/dataset.h +index 3edf952ba..04bb874bc 100644 +--- a/include/openthread/dataset.h ++++ b/include/openthread/dataset.h +@@ -594,6 +594,33 @@ otError otNetworkNameFromString(otNetworkName *aNetworkName, const char *aNameSt + */ + otError otDatasetParseTlvs(const otOperationalDatasetTlvs *aDatasetTlvs, otOperationalDataset *aDataset); + ++/** ++ * Converts a given Operational Dataset to `otOperationalDatasetTlvs`. ++ * ++ * @param[in] aDataset An Operational dataset to convert to TLVs. ++ * @param[out] aDatasetTlvs A pointer to dataset TLVs to return the result. ++ * ++ * @retval OT_ERROR_NONE Successfully converted @p aDataset and updated @p aDatasetTlvs. ++ * @retval OT_ERROR_INVALID_ARGS @p aDataset is invalid, does not contain active or pending timestamps. ++ * ++ */ ++otError otDatasetConvertToTlvs(const otOperationalDataset *aDataset, otOperationalDatasetTlvs *aDatasetTlvs); ++ ++/** ++ * Updates a given Operational Dataset. ++ * ++ * @p aDataset contains the fields to be updated and their new value. ++ * ++ * @param[in] aDataset Specifies the set of types and values to update. ++ * @param[in,out] aDatasetTlvs A pointer to dataset TLVs to update. ++ * ++ * @retval OT_ERROR_NONE Successfully updated @p aDatasetTlvs. ++ * @retval OT_ERROR_INVALID_ARGS @p aDataset contains invalid values. ++ * @retval OT_ERROR_NO_BUFS Not enough space space in @p aDatasetTlvs to apply the update. ++ * ++ */ ++otError otDatasetUpdateTlvs(const otOperationalDataset *aDataset, otOperationalDatasetTlvs *aDatasetTlvs); ++ + /** + * @} + * +diff --git a/src/core/api/dataset_api.cpp b/src/core/api/dataset_api.cpp +index f8d14f608..075ae3abd 100644 +--- a/src/core/api/dataset_api.cpp ++++ b/src/core/api/dataset_api.cpp +@@ -168,3 +168,32 @@ otError otDatasetParseTlvs(const otOperationalDatasetTlvs *aDatasetTlvs, otOpera + exit: + return error; + } ++ ++otError otDatasetConvertToTlvs(const otOperationalDataset *aDataset, otOperationalDatasetTlvs *aDatasetTlvs) ++{ ++ Error error = kErrorNone; ++ MeshCoP::Dataset dataset; ++ ++ AssertPointerIsNotNull(aDatasetTlvs); ++ ++ SuccessOrExit(error = dataset.SetFrom(AsCoreType(aDataset))); ++ dataset.ConvertTo(*aDatasetTlvs); ++ ++exit: ++ return error; ++} ++ ++otError otDatasetUpdateTlvs(const otOperationalDataset *aDataset, otOperationalDatasetTlvs *aDatasetTlvs) ++{ ++ Error error = kErrorNone; ++ MeshCoP::Dataset dataset; ++ ++ AssertPointerIsNotNull(aDatasetTlvs); ++ ++ dataset.SetFrom(*aDatasetTlvs); ++ SuccessOrExit(error = dataset.SetFrom(AsCoreType(aDataset))); ++ dataset.ConvertTo(*aDatasetTlvs); ++ ++exit: ++ return error; ++} +-- +2.40.1 + diff --git a/silabs-multiprotocol/0001-web-rest-listen-on-IPv6-addresses-as-well-1659.patch b/silabs-multiprotocol/0001-web-rest-listen-on-IPv6-addresses-as-well-1659.patch index d4232cad7a9..e0c4b131c45 100644 --- a/silabs-multiprotocol/0001-web-rest-listen-on-IPv6-addresses-as-well-1659.patch +++ b/silabs-multiprotocol/0001-web-rest-listen-on-IPv6-addresses-as-well-1659.patch @@ -1,8 +1,8 @@ From c2dc2a9940d1f9809403d4dfb28e1f942ef90bab Mon Sep 17 00:00:00 2001 -Message-Id: +Message-Id: From: Stefan Agner Date: Fri, 16 Dec 2022 06:20:03 +0100 -Subject: [PATCH 01/10] [web/rest] listen on IPv6 addresses as well (#1659) +Subject: [PATCH] [web/rest] listen on IPv6 addresses as well (#1659) Make the Web and REST API listen on IPv4 and IPv6 by default. @@ -78,5 +78,5 @@ index e5f47f52c2..c49047dc89 100644 std::unique_ptr sServer(nullptr); -- -2.39.0 +2.40.1 diff --git a/silabs-multiprotocol/0002-rest-add-active-dataset-API-1658.patch b/silabs-multiprotocol/0002-rest-add-active-dataset-API-1658.patch index 68ef5634109..b62a9ed0137 100644 --- a/silabs-multiprotocol/0002-rest-add-active-dataset-API-1658.patch +++ b/silabs-multiprotocol/0002-rest-add-active-dataset-API-1658.patch @@ -1,10 +1,10 @@ From 9a624b0ce0f5565a94663693f7690367c95c9d9b Mon Sep 17 00:00:00 2001 -Message-Id: <9a624b0ce0f5565a94663693f7690367c95c9d9b.1673530580.git.stefan@agner.ch> -In-Reply-To: -References: +Message-Id: <9a624b0ce0f5565a94663693f7690367c95c9d9b.1684999160.git.stefan@agner.ch> +In-Reply-To: +References: From: Stefan Agner Date: Fri, 16 Dec 2022 07:34:07 +0100 -Subject: [PATCH 02/10] [rest] add active dataset API (#1658) +Subject: [PATCH] [rest] add active dataset API (#1658) Support setting active dataset using HTTP PUT method. The body needs to be formatted as a hex string representing the operational @@ -210,5 +210,5 @@ index c20d9c1699..addc7d2cbb 100644 kStatusMethodNotAllowed = 405, kStatusRequestTimeout = 408, -- -2.39.0 +2.40.1 diff --git a/silabs-multiprotocol/0003-rest-use-map-for-HTTP-headers-1665.patch b/silabs-multiprotocol/0003-rest-use-map-for-HTTP-headers-1665.patch index c7d7ef37b6b..d1dd2c8f930 100644 --- a/silabs-multiprotocol/0003-rest-use-map-for-HTTP-headers-1665.patch +++ b/silabs-multiprotocol/0003-rest-use-map-for-HTTP-headers-1665.patch @@ -1,10 +1,10 @@ From 1fd0572e7ceeb0f7dff59f7a9bdbe8519fb332ec Mon Sep 17 00:00:00 2001 -Message-Id: <1fd0572e7ceeb0f7dff59f7a9bdbe8519fb332ec.1673530580.git.stefan@agner.ch> -In-Reply-To: -References: +Message-Id: <1fd0572e7ceeb0f7dff59f7a9bdbe8519fb332ec.1684999160.git.stefan@agner.ch> +In-Reply-To: +References: From: Stefan Agner Date: Tue, 20 Dec 2022 07:22:01 +0100 -Subject: [PATCH 03/10] [rest] use map for HTTP headers (#1665) +Subject: [PATCH] [rest] use map for HTTP headers (#1665) (cherry picked from commit 2ff52570fdb1a2daa7c93e0aae7e6cb551322252) --- @@ -91,5 +91,5 @@ index ecfe98f4de..8ae24ffe30 100644 } // namespace rest -- -2.39.0 +2.40.1 diff --git a/silabs-multiprotocol/0005-web-bump-to-latest-Simple-Web-Server-version-1667.patch b/silabs-multiprotocol/0005-web-bump-to-latest-Simple-Web-Server-version-1667.patch index 3150922fbad..7064849ddc4 100644 --- a/silabs-multiprotocol/0005-web-bump-to-latest-Simple-Web-Server-version-1667.patch +++ b/silabs-multiprotocol/0005-web-bump-to-latest-Simple-Web-Server-version-1667.patch @@ -1,10 +1,10 @@ From 2e2f6018e1ae48432f5b9eec97f7ca737a3cd871 Mon Sep 17 00:00:00 2001 -Message-Id: <2e2f6018e1ae48432f5b9eec97f7ca737a3cd871.1673530580.git.stefan@agner.ch> -In-Reply-To: -References: +Message-Id: <2e2f6018e1ae48432f5b9eec97f7ca737a3cd871.1684999160.git.stefan@agner.ch> +In-Reply-To: +References: From: Stefan Agner Date: Thu, 22 Dec 2022 16:34:59 +0100 -Subject: [PATCH 05/10] [web] bump to latest Simple-Web-Server version (#1667) +Subject: [PATCH] [web] bump to latest Simple-Web-Server version (#1667) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit @@ -10280,5 +10280,5 @@ index 0000000000..cac7dfa442 + +#endif // SIMPLE_WEB_UTILITY_HPP -- -2.39.0 +2.40.1 diff --git a/silabs-multiprotocol/0006-rest-allow-to-specify-REST-listen-address-1670.patch b/silabs-multiprotocol/0006-rest-allow-to-specify-REST-listen-address-1670.patch index 218f6539043..e022f274c63 100644 --- a/silabs-multiprotocol/0006-rest-allow-to-specify-REST-listen-address-1670.patch +++ b/silabs-multiprotocol/0006-rest-allow-to-specify-REST-listen-address-1670.patch @@ -1,10 +1,10 @@ From 8959300846adcd0d8dc43c553d54cbc753a7c942 Mon Sep 17 00:00:00 2001 -Message-Id: <8959300846adcd0d8dc43c553d54cbc753a7c942.1673530580.git.stefan@agner.ch> -In-Reply-To: -References: +Message-Id: <8959300846adcd0d8dc43c553d54cbc753a7c942.1684999160.git.stefan@agner.ch> +In-Reply-To: +References: From: Stefan Agner Date: Fri, 23 Dec 2022 04:00:41 +0100 -Subject: [PATCH 06/10] [rest] allow to specify REST listen address (#1670) +Subject: [PATCH] [rest] allow to specify REST listen address (#1670) Allow to bind to a specific address to listen to for REST requests. This allows to limit access to the REST interface e.g. from localhost only. @@ -202,5 +202,5 @@ index 9a5a49ca4a..97e36b37e0 100644 bool SetFdNonblocking(int32_t fd); -- -2.39.0 +2.40.1 diff --git a/silabs-multiprotocol/0007-rest-remove-superfluous-space-in-HTTP-status-line-17.patch b/silabs-multiprotocol/0007-rest-remove-superfluous-space-in-HTTP-status-line-17.patch new file mode 100644 index 00000000000..7f83be06aea --- /dev/null +++ b/silabs-multiprotocol/0007-rest-remove-superfluous-space-in-HTTP-status-line-17.patch @@ -0,0 +1,33 @@ +From edcd1f6a875cbbde5803662c595a7485da14d391 Mon Sep 17 00:00:00 2001 +Message-Id: +In-Reply-To: +References: +From: Stefan Agner +Date: Tue, 17 Jan 2023 19:31:59 +0100 +Subject: [PATCH] [rest] remove superfluous space in HTTP status line (#1713) + +A space is already added in Response::Serialize(). According to the HTTP +specification there should only be a single space between the protcol +version and the status code. + +(cherry picked from commit 10a181ee914eb9641eaf416e3747a0a360a41ede) +--- + src/rest/response.cpp | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/rest/response.cpp b/src/rest/response.cpp +index 2cdfc80ea5..bcf63a6c31 100644 +--- a/src/rest/response.cpp ++++ b/src/rest/response.cpp +@@ -45,7 +45,7 @@ Response::Response(void) + , mComplete(false) + { + // HTTP protocol +- mProtocol = "HTTP/1.1 "; ++ mProtocol = "HTTP/1.1"; + + // Pre-defined headers + mHeaders["Content-Type"] = OT_REST_RESPONSE_CONTENT_TYPE_JSON; +-- +2.40.1 + diff --git a/silabs-multiprotocol/0010-rest-explicitly-set-Connection-header-to-close.patch b/silabs-multiprotocol/0008-rest-explicitly-set-Connection-header-to-close-1774.patch similarity index 67% rename from silabs-multiprotocol/0010-rest-explicitly-set-Connection-header-to-close.patch rename to silabs-multiprotocol/0008-rest-explicitly-set-Connection-header-to-close-1774.patch index 7f550cd32d6..da9ada0b46f 100644 --- a/silabs-multiprotocol/0010-rest-explicitly-set-Connection-header-to-close.patch +++ b/silabs-multiprotocol/0008-rest-explicitly-set-Connection-header-to-close-1774.patch @@ -1,10 +1,10 @@ -From 65a87b8e2beabccf22fe5fc27cacb98dd9ba05dc Mon Sep 17 00:00:00 2001 -Message-Id: <65a87b8e2beabccf22fe5fc27cacb98dd9ba05dc.1673530580.git.stefan@agner.ch> -In-Reply-To: -References: +From fcfe69e94666fb8a21fcb0c474f7d350a5790cbf Mon Sep 17 00:00:00 2001 +Message-Id: +In-Reply-To: +References: From: Stefan Agner -Date: Thu, 12 Jan 2023 14:35:04 +0100 -Subject: [PATCH 10/10] [rest] explicitly set Connection header to close +Date: Thu, 2 Mar 2023 03:12:46 +0100 +Subject: [PATCH] [rest] explicitly set Connection header to close (#1774) By default, HTTP 1.1 connections should stay open after a transaction. However, that is not how the current REST server implementation behaves: @@ -13,23 +13,25 @@ server. Set the Connection header to "close" to tell the client about this behavior. + +(cherry picked from commit 0e8af359120ee051314c1afb2bec2107c5bdf38d) --- src/rest/response.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/rest/response.cpp b/src/rest/response.cpp -index 540a0840e0..5c08fd77b4 100644 +index bcf63a6c31..2fd536c546 100644 --- a/src/rest/response.cpp +++ b/src/rest/response.cpp -@@ -35,6 +35,7 @@ +@@ -36,6 +36,7 @@ "Access-Control-Allow-Headers, Origin,Accept, X-Requested-With, Content-Type, Access-Control-Request-Method, " \ "Access-Control-Request-Headers" - #define OT_REST_RESPONSE_ACCESS_CONTROL_ALLOW_METHOD "GET, OPTIONS, POST, PUT" + #define OT_REST_RESPONSE_ACCESS_CONTROL_ALLOW_METHOD "GET" +#define OT_REST_RESPONSE_CONNECTION "close" namespace otbr { namespace rest { -@@ -51,6 +52,7 @@ Response::Response(void) +@@ -52,6 +53,7 @@ Response::Response(void) mHeaders["Access-Control-Allow-Origin"] = OT_REST_RESPONSE_ACCESS_CONTROL_ALLOW_ORIGIN; mHeaders["Access-Control-Allow-Methods"] = OT_REST_RESPONSE_ACCESS_CONTROL_ALLOW_METHOD; mHeaders["Access-Control-Allow-Headers"] = OT_REST_RESPONSE_ACCESS_CONTROL_ALLOW_HEADERS; @@ -38,5 +40,5 @@ index 540a0840e0..5c08fd77b4 100644 void Response::SetComplete() -- -2.39.0 +2.40.1 diff --git a/silabs-multiprotocol/0008-rest-support-state-change.patch b/silabs-multiprotocol/0008-rest-support-state-change.patch deleted file mode 100644 index ffff4ce43b3..00000000000 --- a/silabs-multiprotocol/0008-rest-support-state-change.patch +++ /dev/null @@ -1,129 +0,0 @@ -From 5f38366d5e4c904d2ff2becbfae5a9fbe0ecaaf9 Mon Sep 17 00:00:00 2001 -Message-Id: <5f38366d5e4c904d2ff2becbfae5a9fbe0ecaaf9.1673530580.git.stefan@agner.ch> -In-Reply-To: -References: -From: Stefan Agner -Date: Wed, 28 Dec 2022 11:46:25 +0100 -Subject: [PATCH 08/10] [rest] support state change - ---- - src/rest/openapi.yaml | 16 +++++++++++++ - src/rest/resource.cpp | 54 ++++++++++++++++++++++++++++++++++++++++--- - src/rest/resource.hpp | 1 + - 3 files changed, 68 insertions(+), 3 deletions(-) - -diff --git a/src/rest/openapi.yaml b/src/rest/openapi.yaml -index e4b23ca297..36c30245fd 100644 ---- a/src/rest/openapi.yaml -+++ b/src/rest/openapi.yaml -@@ -107,6 +107,22 @@ paths: - type: number - description: Current state - example: 4 -+ post: -+ tags: -+ - node -+ summary: Set current Thread state. -+ description: Allow to enable the network interface and Thread -+ responses: -+ "200": -+ description: Successful operation. -+ requestBody: -+ description: New Thread state -+ content: -+ application/json: -+ schema: -+ type: string -+ description: Can be "enable" or "disable". -+ example: "enable" - /node/network-name: - get: - tags: -diff --git a/src/rest/resource.cpp b/src/rest/resource.cpp -index b9a50691e7..4688462f62 100644 ---- a/src/rest/resource.cpp -+++ b/src/rest/resource.cpp -@@ -312,17 +312,65 @@ void Resource::GetDataState(Response &aResponse) const - aResponse.SetResponsCode(errorCode); - } - --void Resource::State(const Request &aRequest, Response &aResponse) const -+void Resource::SetDataState(const Request &aRequest, Response &aResponse) const - { -+ otbrError error = OTBR_ERROR_NONE; - std::string errorCode; -+ std::string body = aRequest.GetBody(); - -- if (aRequest.GetMethod() == HttpMethod::kGet) -+ if (body == "\"enable\"") - { -- GetDataState(aResponse); -+ if (!otIp6IsEnabled(mInstance)) -+ SuccessOrExit(otIp6SetEnabled(mInstance, true)); -+ VerifyOrExit(otThreadSetEnabled(mInstance, true) == OT_ERROR_NONE, error = OTBR_ERROR_INVALID_STATE); -+ } -+ else if (body == "\"disable\"") -+ { -+ VerifyOrExit(otThreadSetEnabled(mInstance, false) == OT_ERROR_NONE, error = OTBR_ERROR_INVALID_STATE); - } - else - { -+ ExitNow(error = OTBR_ERROR_INVALID_ARGS); -+ } -+ -+ errorCode = GetHttpStatus(HttpStatusCode::kStatusOk); -+ aResponse.SetResponsCode(errorCode); -+ -+exit: -+ if (error == OTBR_ERROR_INVALID_STATE) -+ { -+ ErrorHandler(aResponse, HttpStatusCode::kStatusConflict); -+ } -+ if (error == OTBR_ERROR_INVALID_ARGS) -+ { -+ ErrorHandler(aResponse, HttpStatusCode::kStatusBadRequest); -+ } -+ else if (error != OTBR_ERROR_NONE) -+ { -+ ErrorHandler(aResponse, HttpStatusCode::kStatusInternalServerError); -+ } -+} -+ -+void Resource::State(const Request &aRequest, Response &aResponse) const -+{ -+ std::string errorCode; -+ -+ switch (aRequest.GetMethod()) -+ { -+ case HttpMethod::kGet: -+ GetDataState(aResponse); -+ break; -+ case HttpMethod::kPost: -+ SetDataState(aRequest, aResponse); -+ break; -+ case HttpMethod::kOptions: -+ errorCode = GetHttpStatus(HttpStatusCode::kStatusOk); -+ aResponse.SetResponsCode(errorCode); -+ aResponse.SetComplete(); -+ break; -+ default: - ErrorHandler(aResponse, HttpStatusCode::kStatusMethodNotAllowed); -+ break; - } - } - -diff --git a/src/rest/resource.hpp b/src/rest/resource.hpp -index fa45b4fbd9..7ff5f94895 100644 ---- a/src/rest/resource.hpp -+++ b/src/rest/resource.hpp -@@ -134,6 +134,7 @@ private: - void GetNodeInfo(Response &aResponse) const; - void GetDataExtendedAddr(Response &aResponse) const; - void GetDataState(Response &aResponse) const; -+ void SetDataState(const Request &aRequest, Response &aResponse) const; - void GetDataNetworkName(Response &aResponse) const; - void GetDataLeaderData(Response &aResponse) const; - void GetDataNumOfRoute(Response &aResponse) const; --- -2.39.0 - diff --git a/silabs-multiprotocol/0007-rest-implement-REST-API-to-get-dataset.patch b/silabs-multiprotocol/0009-rest-add-Operational-Dataset-API-1682.patch similarity index 76% rename from silabs-multiprotocol/0007-rest-implement-REST-API-to-get-dataset.patch rename to silabs-multiprotocol/0009-rest-add-Operational-Dataset-API-1682.patch index 037c3b4f3ea..52e8d3f7e5f 100644 --- a/silabs-multiprotocol/0007-rest-implement-REST-API-to-get-dataset.patch +++ b/silabs-multiprotocol/0009-rest-add-Operational-Dataset-API-1682.patch @@ -1,30 +1,88 @@ -From daad0f74723bd5dfb63302ad07bd9e0aefcf9c1f Mon Sep 17 00:00:00 2001 -Message-Id: -In-Reply-To: -References: +From ced6eebdb5c73617f81dfcf3369e9a62c3fafaf1 Mon Sep 17 00:00:00 2001 +Message-Id: +In-Reply-To: +References: From: Stefan Agner -Date: Fri, 23 Dec 2022 10:39:34 +0100 -Subject: [PATCH 07/10] [rest] implement REST API to get dataset +Date: Wed, 10 May 2023 03:22:31 +0200 +Subject: [PATCH] [rest] add Operational Dataset API (#1682) +This commit extends the REST API to support reading and writing Active +and Pending Operational Datasets. + +It adds the following endpoints and methods: + +* /node/dataset/active [`GET`, `OPTIONS`, `POST`, `PUT`] +* /node/dataset/pending [`GET`, `OPTIONS`, `POST`, `PUT`] + +The `GET` method returns current datasets, `POST` allows to create a +new dataset (with defaults as initialized by the OpenThread +implementation just like `ot-ctl dataset init new`). Any fields +provided with `POST` will overwrite the defaults, hence a call like + +``` +curl -X POST http://localhost:8081/node/dataset/pending -d '{ "Delay": 30000, "ActiveTimestamp": 10 }' +``` + +Is equivalent to: + +``` +ot-ctl dataset init new +ot-ctl dataset delay 30000 +ot-ctl dataset activetimestamp 10 +ot-ctl dataset commit pending +``` + +`PUT` requests do not initialize a new dataset (which makes the call +idempotent as defined in HTTP). `PUT` simply loads the current dataset +and updates it with the new values provided by the JSON body. When +using the TLV format PUT is a simple write to the active/pending +dataset. + +Datasets can be read and written using a straight forward JSON format +or alternatively using the Dataset TLV format. This is implemented in +a RESTful manor by using the same endpoints but `Content-Type` to +distinguish the two data formats. The `application/json` type is used +for JSON and `plain/text` for Dataset TLV format. + +The API is documented by a OpenAPI Specification (Swagger). The YAML +description file is stored at `src/rest/openapi.yaml`. Swagger UI +allows to test all the variants described above as well. To start +Swagger UI locally the following command can be used: + +``` +docker run -p 8082:8080 -e SWAGGER_JSON=/openapi.yaml -v $(pwd)/src/rest/openapi.yaml:/openapi.yaml swaggerapi/swagger-ui +``` + +The following things are open/tbd at this point: + +- No method to change state of the OTBR (probably best done in a separate PR) +- Automatic/implicit `ActiveTimestamp` support (or should this be left to the client) +- Generate Swagger UI API in GitHub actions/deploy GitHub pages? +- Additional testing/unit tests? + +The commit also removes the `/node/active-dataset-tlvs` endpoint as it +is obsolete with this much more capable endpoints. + +(cherry picked from commit 21c5b675d883ba5e18415ba3d5dbb7ffc539829b) --- - src/rest/json.cpp | 355 +++++++++++++++++++++++++++++++ - src/rest/json.hpp | 24 +++ - src/rest/openapi.yaml | 470 ++++++++++++++++++++++++++++++++++++++++++ + src/rest/json.cpp | 427 ++++++++++++++++++++++++++++++++++++++++++ + src/rest/json.hpp | 47 +++++ + src/rest/openapi.yaml | 423 +++++++++++++++++++++++++++++++++++++++++ src/rest/parser.cpp | 28 ++- src/rest/request.cpp | 18 ++ src/rest/request.hpp | 40 +++- - src/rest/resource.cpp | 176 +++++++++++++--- + src/rest/resource.cpp | 178 +++++++++++++++--- src/rest/resource.hpp | 20 +- src/rest/response.cpp | 10 +- src/rest/response.hpp | 8 + - src/rest/types.hpp | 8 + - src/utils/hex.cpp | 38 ++-- + src/rest/types.hpp | 10 +- + src/utils/hex.cpp | 40 ++-- src/utils/hex.hpp | 40 ++++ - 13 files changed, 1182 insertions(+), 53 deletions(-) + 13 files changed, 1230 insertions(+), 59 deletions(-) create mode 100644 src/rest/openapi.yaml diff --git a/src/rest/json.cpp b/src/rest/json.cpp -index 3a6c9aa08f..e938b3db59 100644 +index 3a6c9aa08f..61c0be2046 100644 --- a/src/rest/json.cpp +++ b/src/rest/json.cpp @@ -27,6 +27,7 @@ @@ -167,62 +225,85 @@ index 3a6c9aa08f..e938b3db59 100644 static cJSON *ChildTableEntry2Json(const otNetworkDiagChildEntry &aChildEntry) { cJSON *childEntry = cJSON_CreateObject(); -@@ -482,6 +608,235 @@ std::string Error2JsonString(HttpStatusCode aErrorCode, std::string aErrorMessag +@@ -482,6 +608,307 @@ std::string Error2JsonString(HttpStatusCode aErrorCode, std::string aErrorMessag return ret; } -+std::string Dataset2JsonString(const otOperationalDataset &aDataset) ++cJSON *ActiveDataset2Json(const otOperationalDataset &aActiveDataset) +{ -+ cJSON *node = cJSON_CreateObject(); -+ std::string ret; ++ cJSON *node = cJSON_CreateObject(); + -+ if (aDataset.mComponents.mIsActiveTimestampPresent) ++ if (aActiveDataset.mComponents.mIsActiveTimestampPresent) + { -+ cJSON_AddItemToObject(node, "ActiveTimestamp", Timestamp2Json(aDataset.mActiveTimestamp)); ++ cJSON_AddItemToObject(node, "ActiveTimestamp", Timestamp2Json(aActiveDataset.mActiveTimestamp)); + } -+ if (aDataset.mComponents.mIsPendingTimestampPresent) ++ if (aActiveDataset.mComponents.mIsNetworkKeyPresent) + { -+ cJSON_AddItemToObject(node, "PendingTimestamp", Timestamp2Json(aDataset.mPendingTimestamp)); ++ cJSON_AddItemToObject(node, "NetworkKey", Bytes2HexJson(aActiveDataset.mNetworkKey.m8, OT_NETWORK_KEY_SIZE)); + } -+ if (aDataset.mComponents.mIsNetworkKeyPresent) ++ if (aActiveDataset.mComponents.mIsNetworkNamePresent) + { -+ cJSON_AddItemToObject(node, "NetworkKey", Bytes2HexJson(aDataset.mNetworkKey.m8, OT_NETWORK_KEY_SIZE)); ++ cJSON_AddItemToObject(node, "NetworkName", cJSON_CreateString(aActiveDataset.mNetworkName.m8)); + } -+ if (aDataset.mComponents.mIsNetworkNamePresent) ++ if (aActiveDataset.mComponents.mIsExtendedPanIdPresent) + { -+ cJSON_AddItemToObject(node, "NetworkName", cJSON_CreateString(aDataset.mNetworkName.m8)); ++ cJSON_AddItemToObject(node, "ExtPanId", Bytes2HexJson(aActiveDataset.mExtendedPanId.m8, OT_EXT_PAN_ID_SIZE)); + } -+ if (aDataset.mComponents.mIsExtendedPanIdPresent) ++ if (aActiveDataset.mComponents.mIsMeshLocalPrefixPresent) + { -+ cJSON_AddItemToObject(node, "ExtPanId", Bytes2HexJson(aDataset.mExtendedPanId.m8, OT_EXT_PAN_ID_SIZE)); ++ cJSON_AddItemToObject(node, "MeshLocalPrefix", IpPrefix2Json(aActiveDataset.mMeshLocalPrefix)); + } -+ if (aDataset.mComponents.mIsMeshLocalPrefixPresent) ++ if (aActiveDataset.mComponents.mIsPanIdPresent) + { -+ cJSON_AddItemToObject(node, "MeshLocalPrefix", IpPrefix2Json(aDataset.mMeshLocalPrefix)); ++ cJSON_AddItemToObject(node, "PanId", cJSON_CreateNumber(aActiveDataset.mPanId)); + } -+ if (aDataset.mComponents.mIsDelayPresent) ++ if (aActiveDataset.mComponents.mIsChannelPresent) + { -+ cJSON_AddItemToObject(node, "Delay", cJSON_CreateNumber(aDataset.mDelay)); ++ cJSON_AddItemToObject(node, "Channel", cJSON_CreateNumber(aActiveDataset.mChannel)); + } -+ if (aDataset.mComponents.mIsPanIdPresent) ++ if (aActiveDataset.mComponents.mIsPskcPresent) + { -+ cJSON_AddItemToObject(node, "PanId", cJSON_CreateNumber(aDataset.mPanId)); ++ cJSON_AddItemToObject(node, "PSKc", Bytes2HexJson(aActiveDataset.mPskc.m8, OT_PSKC_MAX_SIZE)); + } -+ if (aDataset.mComponents.mIsChannelPresent) ++ if (aActiveDataset.mComponents.mIsSecurityPolicyPresent) + { -+ cJSON_AddItemToObject(node, "Channel", cJSON_CreateNumber(aDataset.mChannel)); ++ cJSON_AddItemToObject(node, "SecurityPolicy", SecurityPolicy2Json(aActiveDataset.mSecurityPolicy)); + } -+ if (aDataset.mComponents.mIsPskcPresent) ++ if (aActiveDataset.mComponents.mIsChannelMaskPresent) + { -+ cJSON_AddItemToObject(node, "PSKc", Bytes2HexJson(aDataset.mPskc.m8, OT_PSKC_MAX_SIZE)); ++ cJSON_AddItemToObject(node, "ChannelMask", cJSON_CreateNumber(aActiveDataset.mChannelMask)); + } -+ if (aDataset.mComponents.mIsSecurityPolicyPresent) ++ ++ return node; ++} ++ ++std::string ActiveDataset2JsonString(const otOperationalDataset &aActiveDataset) ++{ ++ cJSON *node; ++ std::string ret; ++ ++ node = ActiveDataset2Json(aActiveDataset); ++ ret = Json2String(node); ++ cJSON_Delete(node); ++ ++ return ret; ++} ++ ++std::string PendingDataset2JsonString(const otOperationalDataset &aPendingDataset) ++{ ++ cJSON *nodeActiveDataset; ++ cJSON *node = cJSON_CreateObject(); ++ std::string ret; ++ ++ nodeActiveDataset = ActiveDataset2Json(aPendingDataset); ++ cJSON_AddItemToObject(node, "ActiveDataset", nodeActiveDataset); ++ if (aPendingDataset.mComponents.mIsPendingTimestampPresent) + { -+ cJSON_AddItemToObject(node, "SecurityPolicy", SecurityPolicy2Json(aDataset.mSecurityPolicy)); ++ cJSON_AddItemToObject(node, "PendingTimestamp", Timestamp2Json(aPendingDataset.mPendingTimestamp)); + } -+ if (aDataset.mComponents.mIsChannelMaskPresent) ++ if (aPendingDataset.mComponents.mIsDelayPresent) + { -+ cJSON_AddItemToObject(node, "ChannelMask", cJSON_CreateNumber(aDataset.mChannelMask)); ++ cJSON_AddItemToObject(node, "Delay", cJSON_CreateNumber(aPendingDataset.mDelay)); + } + + ret = Json2String(node); @@ -231,16 +312,13 @@ index 3a6c9aa08f..e938b3db59 100644 + return ret; +} + -+bool JsonString2Dataset(const std::string &aJsonDataset, otOperationalDataset &aDataset) ++bool JsonActiveDataset2Dataset(const cJSON *jsonActiveDataset, otOperationalDataset &aDataset) +{ + cJSON *value; -+ cJSON *jsonDataset; + otTimestamp timestamp; + bool ret = true; + -+ VerifyOrExit((jsonDataset = cJSON_Parse(aJsonDataset.c_str())) != nullptr, ret = false); -+ -+ value = cJSON_GetObjectItemCaseSensitive(jsonDataset, "ActiveTimestamp"); ++ value = cJSON_GetObjectItemCaseSensitive(jsonActiveDataset, "ActiveTimestamp"); + if (cJSON_IsObject(value)) + { + VerifyOrExit(Json2Timestamp(value, timestamp), ret = false); @@ -256,23 +334,7 @@ index 3a6c9aa08f..e938b3db59 100644 + ExitNow(ret = false); + } + -+ value = cJSON_GetObjectItemCaseSensitive(jsonDataset, "PendingTimestamp"); -+ if (cJSON_IsObject(value)) -+ { -+ VerifyOrExit(Json2Timestamp(value, timestamp), ret = false); -+ aDataset.mPendingTimestamp = timestamp; -+ aDataset.mComponents.mIsPendingTimestampPresent = true; -+ } -+ else if (cJSON_IsNull(value)) -+ { -+ aDataset.mComponents.mIsPendingTimestampPresent = false; -+ } -+ else if (value != nullptr) -+ { -+ ExitNow(ret = false); -+ } -+ -+ value = cJSON_GetObjectItemCaseSensitive(jsonDataset, "NetworkKey"); ++ value = cJSON_GetObjectItemCaseSensitive(jsonActiveDataset, "NetworkKey"); + if (cJSON_IsString(value)) + { + VerifyOrExit(value->valuestring != nullptr, ret = false); @@ -286,7 +348,7 @@ index 3a6c9aa08f..e938b3db59 100644 + aDataset.mComponents.mIsNetworkKeyPresent = false; + } + -+ value = cJSON_GetObjectItemCaseSensitive(jsonDataset, "NetworkName"); ++ value = cJSON_GetObjectItemCaseSensitive(jsonActiveDataset, "NetworkName"); + if (cJSON_IsString(value)) + { + VerifyOrExit(value->valuestring != nullptr, ret = false); @@ -299,7 +361,7 @@ index 3a6c9aa08f..e938b3db59 100644 + aDataset.mComponents.mIsNetworkNamePresent = false; + } + -+ value = cJSON_GetObjectItemCaseSensitive(jsonDataset, "ExtPanId"); ++ value = cJSON_GetObjectItemCaseSensitive(jsonActiveDataset, "ExtPanId"); + if (cJSON_IsString(value)) + { + VerifyOrExit(value->valuestring != nullptr, ret = false); @@ -313,7 +375,7 @@ index 3a6c9aa08f..e938b3db59 100644 + aDataset.mComponents.mIsExtendedPanIdPresent = false; + } + -+ value = cJSON_GetObjectItemCaseSensitive(jsonDataset, "MeshLocalPrefix"); ++ value = cJSON_GetObjectItemCaseSensitive(jsonActiveDataset, "MeshLocalPrefix"); + if (cJSON_IsString(value)) + { + VerifyOrExit(value->valuestring != nullptr, ret = false); @@ -325,18 +387,7 @@ index 3a6c9aa08f..e938b3db59 100644 + aDataset.mComponents.mIsMeshLocalPrefixPresent = false; + } + -+ value = cJSON_GetObjectItemCaseSensitive(jsonDataset, "Delay"); -+ if (cJSON_IsNumber(value)) -+ { -+ aDataset.mDelay = value->valueint; -+ aDataset.mComponents.mIsDelayPresent = true; -+ } -+ else if (cJSON_IsNull(value)) -+ { -+ aDataset.mComponents.mIsDelayPresent = false; -+ } -+ -+ value = cJSON_GetObjectItemCaseSensitive(jsonDataset, "PanId"); ++ value = cJSON_GetObjectItemCaseSensitive(jsonActiveDataset, "PanId"); + if (cJSON_IsNumber(value)) + { + aDataset.mPanId = static_cast(value->valueint); @@ -347,7 +398,7 @@ index 3a6c9aa08f..e938b3db59 100644 + aDataset.mComponents.mIsPanIdPresent = false; + } + -+ value = cJSON_GetObjectItemCaseSensitive(jsonDataset, "Channel"); ++ value = cJSON_GetObjectItemCaseSensitive(jsonActiveDataset, "Channel"); + if (cJSON_IsNumber(value)) + { + aDataset.mChannel = static_cast(value->valueint); @@ -358,7 +409,7 @@ index 3a6c9aa08f..e938b3db59 100644 + aDataset.mComponents.mIsChannelPresent = false; + } + -+ value = cJSON_GetObjectItemCaseSensitive(jsonDataset, "PSKc"); ++ value = cJSON_GetObjectItemCaseSensitive(jsonActiveDataset, "PSKc"); + if (cJSON_IsString(value)) + { + VerifyOrExit(value->valuestring != nullptr, ret = false); @@ -372,7 +423,7 @@ index 3a6c9aa08f..e938b3db59 100644 + aDataset.mComponents.mIsPskcPresent = false; + } + -+ value = cJSON_GetObjectItemCaseSensitive(jsonDataset, "SecurityPolicy"); ++ value = cJSON_GetObjectItemCaseSensitive(jsonActiveDataset, "SecurityPolicy"); + if (cJSON_IsObject(value)) + { + VerifyOrExit(Json2SecurityPolicy(value, aDataset.mSecurityPolicy), ret = false); @@ -383,7 +434,7 @@ index 3a6c9aa08f..e938b3db59 100644 + aDataset.mComponents.mIsSecurityPolicyPresent = false; + } + -+ value = cJSON_GetObjectItemCaseSensitive(jsonDataset, "ChannelMask"); ++ value = cJSON_GetObjectItemCaseSensitive(jsonActiveDataset, "ChannelMask"); + if (cJSON_IsNumber(value)) + { + aDataset.mChannelMask = value->valueint; @@ -395,6 +446,85 @@ index 3a6c9aa08f..e938b3db59 100644 + } + +exit: ++ return ret; ++} ++ ++bool JsonActiveDatasetString2Dataset(const std::string &aJsonActiveDataset, otOperationalDataset &aDataset) ++{ ++ cJSON *jsonActiveDataset; ++ bool ret = true; ++ ++ VerifyOrExit((jsonActiveDataset = cJSON_Parse(aJsonActiveDataset.c_str())) != nullptr, ret = false); ++ VerifyOrExit(cJSON_IsObject(jsonActiveDataset), ret = false); ++ ++ ret = JsonActiveDataset2Dataset(jsonActiveDataset, aDataset); ++ ++exit: ++ cJSON_Delete(jsonActiveDataset); ++ ++ return ret; ++} ++ ++bool JsonPendingDatasetString2Dataset(const std::string &aJsonPendingDataset, otOperationalDataset &aDataset) ++{ ++ cJSON *value; ++ cJSON *jsonDataset; ++ otTimestamp timestamp; ++ bool ret = true; ++ ++ VerifyOrExit((jsonDataset = cJSON_Parse(aJsonPendingDataset.c_str())) != nullptr, ret = false); ++ VerifyOrExit(cJSON_IsObject(jsonDataset), ret = false); ++ ++ value = cJSON_GetObjectItemCaseSensitive(jsonDataset, "ActiveDataset"); ++ if (cJSON_IsObject(value)) ++ { ++ VerifyOrExit(JsonActiveDataset2Dataset(value, aDataset), ret = false); ++ } ++ else if (cJSON_IsString(value)) ++ { ++ otOperationalDatasetTlvs datasetTlvs; ++ int len; ++ ++ len = ++ Hex2BytesJsonString(std::string(value->valuestring), datasetTlvs.mTlvs, OT_OPERATIONAL_DATASET_MAX_LENGTH); ++ VerifyOrExit(len > 0, ret = false); ++ datasetTlvs.mLength = len; ++ ++ VerifyOrExit(otDatasetParseTlvs(&datasetTlvs, &aDataset) == OT_ERROR_NONE, ret = false); ++ } ++ else ++ { ++ ExitNow(ret = false); ++ } ++ ++ value = cJSON_GetObjectItemCaseSensitive(jsonDataset, "PendingTimestamp"); ++ if (cJSON_IsObject(value)) ++ { ++ VerifyOrExit(Json2Timestamp(value, timestamp), ret = false); ++ aDataset.mPendingTimestamp = timestamp; ++ aDataset.mComponents.mIsPendingTimestampPresent = true; ++ } ++ else if (cJSON_IsNull(value)) ++ { ++ aDataset.mComponents.mIsPendingTimestampPresent = false; ++ } ++ else if (value != nullptr) ++ { ++ ExitNow(ret = false); ++ } ++ ++ value = cJSON_GetObjectItemCaseSensitive(jsonDataset, "Delay"); ++ if (cJSON_IsNumber(value)) ++ { ++ aDataset.mDelay = value->valueint; ++ aDataset.mComponents.mIsDelayPresent = true; ++ } ++ else if (cJSON_IsNull(value)) ++ { ++ aDataset.mComponents.mIsDelayPresent = false; ++ } ++ ++exit: + cJSON_Delete(jsonDataset); + + return ret; @@ -404,7 +534,7 @@ index 3a6c9aa08f..e938b3db59 100644 } // namespace rest } // namespace otbr diff --git a/src/rest/json.hpp b/src/rest/json.hpp -index e9fd178201..86d92c42ec 100644 +index e9fd178201..29153d5149 100644 --- a/src/rest/json.hpp +++ b/src/rest/json.hpp @@ -34,6 +34,7 @@ @@ -415,42 +545,65 @@ index e9fd178201..86d92c42ec 100644 #include "openthread/link.h" #include "openthread/thread_ftd.h" -@@ -213,6 +214,29 @@ std::string ChildTableEntry2JsonString(const otNetworkDiagChildEntry &aChildEntr +@@ -213,6 +214,52 @@ std::string ChildTableEntry2JsonString(const otNetworkDiagChildEntry &aChildEntr */ std::string Error2JsonString(HttpStatusCode aErrorCode, std::string aErrorMessage); +/** -+ * This method formats a Json object from a dataset. ++ * This method formats a Json object from an active dataset. + * + * @param[in] aDataset A dataset struct. + * + * @returns A string of serialized Json object. + * + */ -+std::string Dataset2JsonString(const otOperationalDataset &aDataset); ++std::string ActiveDataset2JsonString(const otOperationalDataset &aDataset); ++ ++/** ++ * This method formats a Json object from a pending dataset. ++ * ++ * @param[in] aDataset A dataset struct. ++ * ++ * @returns A string of serialized Json object. ++ * ++ */ ++std::string PendingDataset2JsonString(const otOperationalDataset &aPendingDataset); + +/** + * This method parses a Json string and fills the provided dataset. Fields + * set to null are cleared (set to not present). Non-present fields are left + * as is. + * -+ * @param[in] aJsonDataset The Json string to be parsed. -+ * @param[in] aDataset The dataset struct to be filled. ++ * @param[in] aJsonActiveDataset The Json string to be parsed. ++ * @param[in] aDataset The dataset struct to be filled. + * + * @returns If the Json string has been successfully parsed. + * + */ -+bool JsonString2Dataset(const std::string &aJsonDataset, otOperationalDataset &aDataset); ++bool JsonActiveDatasetString2Dataset(const std::string &aJsonActiveDataset, otOperationalDataset &aDataset); ++ ++/** ++ * This method parses a Json string and fills the provided dataset. Fields ++ * set to null are cleared (set to not present). Non-present fields are left ++ * as is. ++ * ++ * @param[in] aJsonActiveDataset The Json string to be parsed. ++ * @param[in] aDataset The dataset struct to be filled. ++ * ++ * @returns If the Json string has been successfully parsed. ++ * ++ */ ++bool JsonPendingDatasetString2Dataset(const std::string &aJsonPendingDataset, otOperationalDataset &aDataset); + }; // namespace Json } // namespace rest diff --git a/src/rest/openapi.yaml b/src/rest/openapi.yaml new file mode 100644 -index 0000000000..e4b23ca297 +index 0000000000..d2bb2f0026 --- /dev/null +++ b/src/rest/openapi.yaml -@@ -0,0 +1,470 @@ +@@ -0,0 +1,423 @@ +openapi: 3.0.3 +info: + title: Swagger OpenThread REST API @@ -631,56 +784,33 @@ index 0000000000..e4b23ca297 + $ref: "#/components/schemas/DatasetTlv" + "204": + description: No active operational dataset -+ post: -+ tags: -+ - node -+ summary: Create new active operational dataset -+ description: |- -+ Creates new active operational dataset on the current node. Only allowed if the Thread node is inactive. Create a -+ pending dataset if you would like to change the current active operational dataset if the Thread node is active. -+ -+ Default parameters are chosen for parameters not set in the request body. -+ requestBody: -+ description: |- -+ Operational dataset object that will be stored as active operational dataset. JSON keys which are not set will be initialized with -+ default or random values. -+ content: -+ application/json: -+ schema: -+ $ref: "#/components/schemas/Dataset" -+ responses: -+ "202": -+ description: Successfully created the active operational dataset. -+ "400": -+ description: Invalid request body. -+ "409": -+ description: Writing active operational dataset rejected because Thread network is active. + put: + tags: + - node -+ summary: Update current active operational dataset ++ summary: Creates or updates the active operational dataset + description: |- -+ Updates the current active operational dataset on the current node. Only allowed if the Thread node is inactive. Create a -+ pending dataset if you would like to change the current active operational dataset if the Thread node is active. ++ Creates or updates the the active operational dataset on the current node. Only allowed if the Thread node ++ is inactive. If the Thread node is active, a pending dataset should be used to update the current active ++ operational dataset. + requestBody: + description: |- -+ Operational dataset object that will be stored as active operational dataset. Supports request body Content-Type `text/plain` -+ (dataset in TLV format as hex string) or `application/json` (dataset in JSON format, JSON keys which are not set will be -+ initialized with default or random values). ++ Operational dataset that will be stored as active operational dataset. Supports request body Content-Type ++ `text/plain` (dataset in TLV format as hex string) or `application/json` (dataset in JSON format). In both ++ cases keys which are not set will be initialized with defaults. + content: + application/json: + schema: -+ $ref: "#/components/schemas/Dataset" ++ $ref: "#/components/schemas/ActiveDataset" + plain/text: + schema: + $ref: "#/components/schemas/DatasetTlv" + responses: -+ "202": ++ "200": + description: Successfully updated the active operational dataset. ++ "201": ++ description: Successfully created the active operational dataset. + "400": + description: Invalid request body. -+ "404": -+ description: No active operational dataset to update. + "409": + description: Writing active operational dataset rejected because Thread network is active. + /node/dataset/pending: @@ -694,62 +824,38 @@ index 0000000000..e4b23ca297 + content: + application/json: + schema: -+ $ref: "#/components/schemas/Dataset" ++ $ref: "#/components/schemas/PendingDataset" + text/plain: + schema: + $ref: "#/components/schemas/DatasetTlv" + "204": + description: No pending operational dataset -+ post: -+ tags: -+ - node -+ summary: Create new pending operational dataset -+ description: |- -+ Creates new pending operational dataset on the current node. If the delay timer is specified, the new pending operational dataset -+ will become active after the time elapsed. The pending dataset will become empty. -+ -+ Default parameters are chosen for parameters not set in the request body. By default, the delay timer is *not* set. -+ -+ Note: To be considered as a valid active operational dataset the ActiveTimestamp needs to be newer than the current active operational -+ dataset. -+ requestBody: -+ description: |- -+ Operational dataset object that will be stored as pending operational dataset. JSON keys which are not set will be initialized with -+ default or random values. -+ content: -+ application/json: -+ schema: -+ $ref: "#/components/schemas/Dataset" -+ responses: -+ "202": -+ description: Successfully created the active operational dataset. -+ "400": -+ description: Invalid request body. + put: + tags: + - node -+ summary: Update current pending operational dataset ++ summary: Creates or updates the pending operational dataset + description: |- -+ Updates the current pending operational dataset on the current node. ++ Creates or updates the the pending operational dataset on the current node. Delay needs to be set to let ++ the pending dataset apply as active dataset in the near future. + requestBody: + description: |- -+ Operational dataset object that will be stored as pending operational dataset. Supports request body Content-Type `text/plain` -+ (dataset in TLV format as hex string) or `application/json` (dataset in JSON format, JSON keys which are not set will be -+ initialized with default or random values). ++ Operational dataset that will be stored as pending operational dataset. Supports request body Content-Type ++ `text/plain` (dataset in TLV format as hex string) or `application/json` (dataset in JSON format). In both ++ cases keys which are not set will be initialized with defaults. + content: + application/json: + schema: -+ $ref: "#/components/schemas/Dataset" -+ plain/text: ++ $ref: "#/components/schemas/PendingDataset" ++ text/plain: + schema: + $ref: "#/components/schemas/DatasetTlv" + responses: -+ "202": ++ "200": + description: Successfully updated the pending operational dataset. ++ "201": ++ description: Successfully created the pending operational dataset. + "400": + description: Invalid request body. -+ "404": -+ description: No pending operational dataset to update. +components: + schemas: + LeaderData: @@ -779,13 +885,11 @@ index 0000000000..e4b23ca297 + format: uint8 + description: Leader Router ID + example: 4 -+ Dataset: ++ ActiveDataset: + type: object + properties: + ActiveTimestamp: + $ref: "#/components/schemas/Timestamp" -+ PendingTimestamp: -+ $ref: "#/components/schemas/Timestamp" + NetworkKey: + type: string + description: Network key, 16 bytes long, formatted as a hexadecimal string @@ -806,12 +910,6 @@ index 0000000000..e4b23ca297 + description: Mesh local IPv6 prefix + example: fd33:d3b9:89e3:72e4::/64 + default: random -+ Delay: -+ type: integer -+ description: Delay timer in milliseconds -+ format: uint32 -+ example: 30000 -+ default: not set + PanId: + type: integer + description: IEEE 802.15.4 PAN ID of the Thread network @@ -837,6 +935,21 @@ index 0000000000..e4b23ca297 + format: uint32 + example: 134215680 + default: 134215680 ++ PendingDataset: ++ type: object ++ properties: ++ ActiveDataset: ++ oneOf: ++ - $ref: "#/components/schemas/ActiveDataset" ++ - $ref: "#/components/schemas/DatasetTlv" ++ PendingTimestamp: ++ $ref: "#/components/schemas/Timestamp" ++ Delay: ++ type: integer ++ description: Delay timer in milliseconds ++ format: uint32 ++ example: 30000 ++ default: not set + SecurityPolicy: + type: object + properties: @@ -914,13 +1027,6 @@ index 0000000000..e4b23ca297 + type: string + description: Operational dataset as hex-encoded TLVs. + example: 0E080000000000010000000300000F35060004001FFFE0020811111111222222220708FDAD70BFE5AA15DD051000112233445566778899AABBCCDDEEFF030E4F70656E54687265616444656D6F010212340410445F2B5CA6F2A93A55CE570A70EFEECB0C0402A0F7F8 -+ requestBodies: -+ Dataset: -+ description: Operational dataset object that will be stored -+ content: -+ application/json: -+ schema: -+ $ref: "#/components/schemas/Dataset" diff --git a/src/rest/parser.cpp b/src/rest/parser.cpp index 566f88cbb2..9401e4d0d5 100644 --- a/src/rest/parser.cpp @@ -1084,10 +1190,10 @@ index 63380f0080..5b73b06736 100644 } // namespace rest diff --git a/src/rest/resource.cpp b/src/rest/resource.cpp -index fdd1171339..b9a50691e7 100644 +index fdd1171339..a3265825f9 100644 --- a/src/rest/resource.cpp +++ b/src/rest/resource.cpp -@@ -45,7 +45,8 @@ +@@ -45,18 +45,21 @@ #define OT_REST_RESOURCE_PATH_NODE_LEADERDATA "/node/leader-data" #define OT_REST_RESOURCE_PATH_NODE_NUMOFROUTER "/node/num-of-router" #define OT_REST_RESOURCE_PATH_NODE_EXTPANID "/node/ext-panid" @@ -1097,10 +1203,11 @@ index fdd1171339..b9a50691e7 100644 #define OT_REST_RESOURCE_PATH_NETWORK "/networks" #define OT_REST_RESOURCE_PATH_NETWORK_CURRENT "/networks/current" #define OT_REST_RESOURCE_PATH_NETWORK_CURRENT_COMMISSION "/networks/commission" -@@ -53,10 +54,12 @@ + #define OT_REST_RESOURCE_PATH_NETWORK_CURRENT_PREFIX "/networks/current/prefix" #define OT_REST_HTTP_STATUS_200 "200 OK" - #define OT_REST_HTTP_STATUS_202 "202 Accepted" +-#define OT_REST_HTTP_STATUS_202 "202 Accepted" ++#define OT_REST_HTTP_STATUS_201 "201 Created" +#define OT_REST_HTTP_STATUS_204 "204 No Content" #define OT_REST_HTTP_STATUS_400 "400 Bad Request" #define OT_REST_HTTP_STATUS_404 "404 Not Found" @@ -1110,16 +1217,20 @@ index fdd1171339..b9a50691e7 100644 #define OT_REST_HTTP_STATUS_500 "500 Internal Server Error" using std::chrono::duration_cast; -@@ -93,6 +96,9 @@ static std::string GetHttpStatus(HttpStatusCode aErrorCode) - case HttpStatusCode::kStatusAccepted: - httpStatus = OT_REST_HTTP_STATUS_202; +@@ -90,8 +93,11 @@ static std::string GetHttpStatus(HttpStatusCode aErrorCode) + case HttpStatusCode::kStatusOk: + httpStatus = OT_REST_HTTP_STATUS_200; break; +- case HttpStatusCode::kStatusAccepted: +- httpStatus = OT_REST_HTTP_STATUS_202; ++ case HttpStatusCode::kStatusCreated: ++ httpStatus = OT_REST_HTTP_STATUS_201; ++ break; + case HttpStatusCode::kStatusNoContent: + httpStatus = OT_REST_HTTP_STATUS_204; -+ break; + break; case HttpStatusCode::kStatusBadRequest: httpStatus = OT_REST_HTTP_STATUS_400; - break; @@ -105,6 +111,9 @@ static std::string GetHttpStatus(HttpStatusCode aErrorCode) case HttpStatusCode::kStatusRequestTimeout: httpStatus = OT_REST_HTTP_STATUS_408; @@ -1141,7 +1252,7 @@ index fdd1171339..b9a50691e7 100644 // Resource callback handler mResourceCallbackMap.emplace(OT_REST_RESOURCE_PATH_DIAGNOETIC, &Resource::HandleDiagnosticCallback); -@@ -499,68 +509,170 @@ void Resource::Rloc(const Request &aRequest, Response &aResponse) const +@@ -499,68 +509,164 @@ void Resource::Rloc(const Request &aRequest, Response &aResponse) const } } @@ -1180,12 +1291,13 @@ index fdd1171339..b9a50691e7 100644 + if (aDatasetType == DatasetType::kActive) + { + VerifyOrExit(otDatasetGetActive(mInstance, &dataset) == OT_ERROR_NONE, error = OTBR_ERROR_NOT_FOUND); ++ body = Json::ActiveDataset2JsonString(dataset); + } + else if (aDatasetType == DatasetType::kPending) + { + VerifyOrExit(otDatasetGetPending(mInstance, &dataset) == OT_ERROR_NONE, error = OTBR_ERROR_NOT_FOUND); ++ body = Json::PendingDataset2JsonString(dataset); + } -+ body = Json::Dataset2JsonString(dataset); + } aResponse.SetBody(body); @@ -1212,21 +1324,21 @@ index fdd1171339..b9a50691e7 100644 } -void Resource::SetActiveDatasetTlvs(const Request &aRequest, Response &aResponse) const -+void Resource::SetDataset(DatasetType aDatasetType, const Request &aRequest, Response &aResponse, bool create) const ++void Resource::SetDataset(DatasetType aDatasetType, const Request &aRequest, Response &aResponse) const { - int ret; -- otOperationalDatasetTlvs datasetTlvs; -- otError error = OT_ERROR_NONE; -+ otbrError error = OTBR_ERROR_NONE; ++ otError errorOt = OT_ERROR_NONE; ++ otbrError error = OTBR_ERROR_NONE; + struct NodeInfo node; + std::string body; - std::string errorCode; ++ std::string errorCode = GetHttpStatus(HttpStatusCode::kStatusOk); + otOperationalDataset dataset; -+ otOperationalDatasetTlvs datasetTlvs; + otOperationalDatasetTlvs datasetTlvs; +- otError error = OT_ERROR_NONE; +- std::string errorCode; ++ otOperationalDatasetTlvs datasetUpdateTlvs; + int ret; -+ bool isText; -+ -+ isText = aRequest.GetHeaderValue(OT_REST_CONTENT_TYPE_HEADER) == OT_REST_CONTENT_TYPE_PLAIN; ++ bool isTlv; - ret = Json::Hex2BytesJsonString(aRequest.GetBody(), datasetTlvs.mTlvs, OT_OPERATIONAL_DATASET_MAX_LENGTH); - if (ret < 0) @@ -1235,60 +1347,61 @@ index fdd1171339..b9a50691e7 100644 - errorCode = GetHttpStatus(HttpStatusCode::kStatusBadRequest); - aResponse.SetResponsCode(errorCode); - ExitNow(); -+ otbrLogWarning("STate is %d", otThreadGetDeviceRole(mInstance)); + VerifyOrExit(otThreadGetDeviceRole(mInstance) == OT_DEVICE_ROLE_DISABLED, error = OTBR_ERROR_INVALID_STATE); ++ errorOt = otDatasetGetActiveTlvs(mInstance, &datasetTlvs); ++ } ++ else if (aDatasetType == DatasetType::kPending) ++ { ++ errorOt = otDatasetGetPendingTlvs(mInstance, &datasetTlvs); } - datasetTlvs.mLength = ret; - SuccessOrExit(error = otDatasetSetActiveTlvs(mInstance, &datasetTlvs)); -+ if (isText) ++ // Create a new operational dataset if it doesn't exist. ++ if (errorOt == OT_ERROR_NOT_FOUND) + { -+ // Only PUT allowed for text/plain -+ VerifyOrExit(!create, error = OTBR_ERROR_INVALID_ARGS); -+ ret = Json::Hex2BytesJsonString(aRequest.GetBody(), datasetTlvs.mTlvs, OT_OPERATIONAL_DATASET_MAX_LENGTH); ++ VerifyOrExit(otDatasetCreateNewNetwork(mInstance, &dataset) == OT_ERROR_NONE, error = OTBR_ERROR_REST); ++ VerifyOrExit(otDatasetConvertToTlvs(&dataset, &datasetTlvs) == OT_ERROR_NONE, error = OTBR_ERROR_REST); ++ errorCode = GetHttpStatus(HttpStatusCode::kStatusCreated); ++ } ++ ++ isTlv = aRequest.GetHeaderValue(OT_REST_CONTENT_TYPE_HEADER) == OT_REST_CONTENT_TYPE_PLAIN; ++ ++ if (isTlv) ++ { ++ ret = Json::Hex2BytesJsonString(aRequest.GetBody(), datasetUpdateTlvs.mTlvs, OT_OPERATIONAL_DATASET_MAX_LENGTH); + VerifyOrExit(ret >= 0, error = OTBR_ERROR_INVALID_ARGS); -+ datasetTlvs.mLength = ret; ++ datasetUpdateTlvs.mLength = ret; + -+ if (aDatasetType == DatasetType::kActive) -+ { -+ VerifyOrExit(otDatasetSetActiveTlvs(mInstance, &datasetTlvs) == OT_ERROR_NONE, error = OTBR_ERROR_REST); -+ } -+ else if (aDatasetType == DatasetType::kPending) -+ { -+ VerifyOrExit(otDatasetSetPendingTlvs(mInstance, &datasetTlvs) == OT_ERROR_NONE, error = OTBR_ERROR_REST); -+ } ++ VerifyOrExit(otDatasetParseTlvs(&datasetUpdateTlvs, &dataset) == OT_ERROR_NONE, error = OTBR_ERROR_REST); ++ VerifyOrExit(otDatasetUpdateTlvs(&dataset, &datasetTlvs) == OT_ERROR_NONE, error = OTBR_ERROR_REST); + } + else + { -+ if (create) -+ { -+ VerifyOrExit(otDatasetCreateNewNetwork(mInstance, &dataset) == OT_ERROR_NONE, error = OTBR_ERROR_REST); -+ } -+ else -+ { -+ if (aDatasetType == DatasetType::kActive) -+ { -+ VerifyOrExit(otDatasetGetActive(mInstance, &dataset) == OT_ERROR_NONE, error = OTBR_ERROR_NOT_FOUND); -+ } -+ else if (aDatasetType == DatasetType::kPending) -+ { -+ VerifyOrExit(otDatasetGetPending(mInstance, &dataset) == OT_ERROR_NONE, error = OTBR_ERROR_NOT_FOUND); -+ } -+ } -+ -+ VerifyOrExit(Json::JsonString2Dataset(aRequest.GetBody(), dataset), error = OTBR_ERROR_INVALID_ARGS); -+ + if (aDatasetType == DatasetType::kActive) + { -+ VerifyOrExit(otDatasetSetActive(mInstance, &dataset) == OT_ERROR_NONE, error = OTBR_ERROR_REST); ++ VerifyOrExit(Json::JsonActiveDatasetString2Dataset(aRequest.GetBody(), dataset), ++ error = OTBR_ERROR_INVALID_ARGS); + } + else if (aDatasetType == DatasetType::kPending) + { -+ VerifyOrExit(otDatasetSetPending(mInstance, &dataset) == OT_ERROR_NONE, error = OTBR_ERROR_REST); ++ VerifyOrExit(Json::JsonPendingDatasetString2Dataset(aRequest.GetBody(), dataset), ++ error = OTBR_ERROR_INVALID_ARGS); ++ VerifyOrExit(dataset.mComponents.mIsDelayPresent, error = OTBR_ERROR_INVALID_ARGS); + } ++ VerifyOrExit(otDatasetUpdateTlvs(&dataset, &datasetTlvs) == OT_ERROR_NONE, error = OTBR_ERROR_REST); ++ } ++ ++ if (aDatasetType == DatasetType::kActive) ++ { ++ VerifyOrExit(otDatasetSetActiveTlvs(mInstance, &datasetTlvs) == OT_ERROR_NONE, error = OTBR_ERROR_REST); ++ } ++ else if (aDatasetType == DatasetType::kPending) ++ { ++ VerifyOrExit(otDatasetSetPendingTlvs(mInstance, &datasetTlvs) == OT_ERROR_NONE, error = OTBR_ERROR_REST); + } - errorCode = GetHttpStatus(HttpStatusCode::kStatusAccepted); +- errorCode = GetHttpStatus(HttpStatusCode::kStatusAccepted); aResponse.SetResponsCode(errorCode); + exit: @@ -1297,10 +1410,6 @@ index fdd1171339..b9a50691e7 100644 + { + ErrorHandler(aResponse, HttpStatusCode::kStatusBadRequest); + } -+ else if (error == OTBR_ERROR_NOT_FOUND) -+ { -+ ErrorHandler(aResponse, HttpStatusCode::kStatusResourceNotFound); -+ } + else if (error == OTBR_ERROR_INVALID_STATE) + { + ErrorHandler(aResponse, HttpStatusCode::kStatusConflict); @@ -1322,13 +1431,10 @@ index fdd1171339..b9a50691e7 100644 case HttpMethod::kGet: - GetActiveDatasetTlvs(aResponse); + GetDataset(aDatasetType, aRequest, aResponse); -+ break; -+ case HttpMethod::kPost: -+ SetDataset(aDatasetType, aRequest, aResponse, true); break; case HttpMethod::kPut: - SetActiveDatasetTlvs(aRequest, aResponse); -+ SetDataset(aDatasetType, aRequest, aResponse, false); ++ SetDataset(aDatasetType, aRequest, aResponse); + break; + case HttpMethod::kOptions: + errorCode = GetHttpStatus(HttpStatusCode::kStatusOk); @@ -1337,7 +1443,7 @@ index fdd1171339..b9a50691e7 100644 break; default: ErrorHandler(aResponse, HttpStatusCode::kStatusMethodNotAllowed); -@@ -568,6 +680,16 @@ void Resource::ActiveDatasetTlvs(const Request &aRequest, Response &aResponse) c +@@ -568,6 +674,16 @@ void Resource::ActiveDatasetTlvs(const Request &aRequest, Response &aResponse) c } } @@ -1355,7 +1461,7 @@ index fdd1171339..b9a50691e7 100644 { auto eraseIt = mDiagSet.begin(); diff --git a/src/rest/resource.hpp b/src/rest/resource.hpp -index 7c0d4c2e7d..fa45b4fbd9 100644 +index 7c0d4c2e7d..2d25b7bc58 100644 --- a/src/rest/resource.hpp +++ b/src/rest/resource.hpp @@ -39,6 +39,8 @@ @@ -1402,12 +1508,12 @@ index 7c0d4c2e7d..fa45b4fbd9 100644 - void GetActiveDatasetTlvs(Response &aResponse) const; - void SetActiveDatasetTlvs(const Request &aRequest, Response &aResponse) const; + void GetDataset(DatasetType aDatasetType, const Request &aRequest, Response &aResponse) const; -+ void SetDataset(DatasetType aDatasetType, const Request &aRequest, Response &aResponse, bool create) const; ++ void SetDataset(DatasetType aDatasetType, const Request &aRequest, Response &aResponse) const; void DeleteOutDatedDiagnostic(void); void UpdateDiag(std::string aKey, std::vector &aDiag); diff --git a/src/rest/response.cpp b/src/rest/response.cpp -index 2cdfc80ea5..17ae97e384 100644 +index 2fd536c546..93cbe0b6df 100644 --- a/src/rest/response.cpp +++ b/src/rest/response.cpp @@ -30,12 +30,11 @@ @@ -1420,12 +1526,12 @@ index 2cdfc80ea5..17ae97e384 100644 "Access-Control-Allow-Headers, Origin,Accept, X-Requested-With, Content-Type, Access-Control-Request-Method, " \ "Access-Control-Request-Headers" -#define OT_REST_RESPONSE_ACCESS_CONTROL_ALLOW_METHOD "GET" -+#define OT_REST_RESPONSE_ACCESS_CONTROL_ALLOW_METHOD "GET, OPTIONS, POST, PUT" ++#define OT_REST_RESPONSE_ACCESS_CONTROL_ALLOW_METHOD "GET, OPTIONS, PUT" + #define OT_REST_RESPONSE_CONNECTION "close" namespace otbr { - namespace rest { -@@ -48,7 +47,7 @@ Response::Response(void) - mProtocol = "HTTP/1.1 "; +@@ -49,7 +48,7 @@ Response::Response(void) + mProtocol = "HTTP/1.1"; // Pre-defined headers - mHeaders["Content-Type"] = OT_REST_RESPONSE_CONTENT_TYPE_JSON; @@ -1433,7 +1539,7 @@ index 2cdfc80ea5..17ae97e384 100644 mHeaders["Access-Control-Allow-Origin"] = OT_REST_RESPONSE_ACCESS_CONTROL_ALLOW_ORIGIN; mHeaders["Access-Control-Allow-Methods"] = OT_REST_RESPONSE_ACCESS_CONTROL_ALLOW_METHOD; mHeaders["Access-Control-Allow-Headers"] = OT_REST_RESPONSE_ACCESS_CONTROL_ALLOW_HEADERS; -@@ -79,6 +78,11 @@ void Response::SetResponsCode(std::string &aCode) +@@ -81,6 +80,11 @@ void Response::SetResponsCode(std::string &aCode) mCode = aCode; } @@ -1465,7 +1571,7 @@ index 8ae24ffe30..a7cb73e372 100644 * This method labels the response as need callback. * diff --git a/src/rest/types.hpp b/src/rest/types.hpp -index addc7d2cbb..e1e048237d 100644 +index addc7d2cbb..8216a978d7 100644 --- a/src/rest/types.hpp +++ b/src/rest/types.hpp @@ -40,6 +40,12 @@ @@ -1481,10 +1587,12 @@ index addc7d2cbb..e1e048237d 100644 using std::chrono::steady_clock; namespace otbr { -@@ -60,10 +66,12 @@ enum class HttpStatusCode : std::uint16_t +@@ -59,11 +65,13 @@ enum class HttpMethod : std::uint8_t + enum class HttpStatusCode : std::uint16_t { kStatusOk = 200, - kStatusAccepted = 202, +- kStatusAccepted = 202, ++ kStatusCreated = 201, + kStatusNoContent = 204, kStatusBadRequest = 400, kStatusResourceNotFound = 404, @@ -1495,7 +1603,7 @@ index addc7d2cbb..e1e048237d 100644 }; diff --git a/src/utils/hex.cpp b/src/utils/hex.cpp -index ad4eba9266..4205cd4412 100644 +index ad4eba9266..45da74e96f 100644 --- a/src/utils/hex.cpp +++ b/src/utils/hex.cpp @@ -96,34 +96,48 @@ size_t Bytes2Hex(const uint8_t *aBytes, const uint16_t aBytesLength, char *aHex) @@ -1514,7 +1622,7 @@ index ad4eba9266..4205cd4412 100644 { - sprintf(byteHex, "%02X", cur[i]); - hexString += byteHex; -+ sprintf(byteHex, "%02X", aBytes[i]); ++ snprintf(byteHex, sizeof(byteHex), "%02X", aBytes[i]); + strcat(aHex, byteHex); } - strcpy(aHex, hexString.c_str()); @@ -1549,8 +1657,9 @@ index ad4eba9266..4205cd4412 100644 for (uint8_t i = 0; i < sizeof(uint64_t); i++) { uint8_t byte = longValue & 0xff; - sprintf(byteHex, "%02X", byte); +- sprintf(byteHex, "%02X", byte); - hexString += byteHex; ++ snprintf(byteHex, sizeof(byteHex), "%02X", byte); + strcat(aHex, byteHex); longValue = longValue >> 8; } @@ -1622,5 +1731,5 @@ index 0aa4b23946..49ba37d561 100644 } // namespace Utils -- -2.39.0 +2.40.1 diff --git a/silabs-multiprotocol/0009-rest-remove-superfluous-space-in-HTTP-status-line.patch b/silabs-multiprotocol/0009-rest-remove-superfluous-space-in-HTTP-status-line.patch deleted file mode 100644 index 91eaed77102..00000000000 --- a/silabs-multiprotocol/0009-rest-remove-superfluous-space-in-HTTP-status-line.patch +++ /dev/null @@ -1,28 +0,0 @@ -From 40440fd30df9dcfc8090c11b2e0a6c7cbd49d1c8 Mon Sep 17 00:00:00 2001 -Message-Id: <40440fd30df9dcfc8090c11b2e0a6c7cbd49d1c8.1673530580.git.stefan@agner.ch> -In-Reply-To: -References: -From: Stefan Agner -Date: Thu, 12 Jan 2023 12:10:47 +0100 -Subject: [PATCH 09/10] [rest] remove superfluous space in HTTP status line - ---- - src/rest/response.cpp | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/src/rest/response.cpp b/src/rest/response.cpp -index 17ae97e384..540a0840e0 100644 ---- a/src/rest/response.cpp -+++ b/src/rest/response.cpp -@@ -44,7 +44,7 @@ Response::Response(void) - , mComplete(false) - { - // HTTP protocol -- mProtocol = "HTTP/1.1 "; -+ mProtocol = "HTTP/1.1"; - - // Pre-defined headers - mHeaders[OT_REST_CONTENT_TYPE_HEADER] = OT_REST_CONTENT_TYPE_JSON; --- -2.39.0 - diff --git a/silabs-multiprotocol/0010-rest-allow-to-specify-REST-listen-port-1862.patch b/silabs-multiprotocol/0010-rest-allow-to-specify-REST-listen-port-1862.patch new file mode 100644 index 00000000000..8e28c910e88 --- /dev/null +++ b/silabs-multiprotocol/0010-rest-allow-to-specify-REST-listen-port-1862.patch @@ -0,0 +1,173 @@ +From 472bb79b071c770758690f66789a6f5a20df2ddb Mon Sep 17 00:00:00 2001 +Message-Id: <472bb79b071c770758690f66789a6f5a20df2ddb.1684999160.git.stefan@agner.ch> +In-Reply-To: +References: +From: Stefan Agner +Date: Fri, 12 May 2023 16:52:34 +0200 +Subject: [PATCH] [rest] allow to specify REST listen port (#1862) + +Allow to bind to a specific port to listen to for REST requests. Note +that the built-in web interface always assumes the REST to be available +on port 8081! + +(cherry picked from commit 7af2ca4aef636e5f6d71f5ac5cd038a11d6686a9) +--- + src/agent/application.cpp | 6 ++++-- + src/agent/application.hpp | 5 ++++- + src/agent/main.cpp | 14 +++++++++++++- + src/rest/rest_web_server.cpp | 6 ++---- + src/rest/rest_web_server.hpp | 2 +- + 5 files changed, 24 insertions(+), 9 deletions(-) + +diff --git a/src/agent/application.cpp b/src/agent/application.cpp +index 42cc307c09..ee5c947749 100644 +--- a/src/agent/application.cpp ++++ b/src/agent/application.cpp +@@ -51,7 +51,8 @@ Application::Application(const std::string & aInterfaceName, + const std::vector &aBackboneInterfaceNames, + const std::vector &aRadioUrls, + bool aEnableAutoAttach, +- const std::string &aRestListenAddress) ++ const std::string &aRestListenAddress, ++ int aRestListenPort) + : mInterfaceName(aInterfaceName) + #if __linux__ + , mInfraLinkSelector(aBackboneInterfaceNames) +@@ -70,7 +71,7 @@ Application::Application(const std::string & aInterfaceName, + , mUbusAgent(mNcp) + #endif + #if OTBR_ENABLE_REST_SERVER +- , mRestWebServer(mNcp, aRestListenAddress) ++ , mRestWebServer(mNcp, aRestListenAddress, aRestListenPort) + #endif + #if OTBR_ENABLE_DBUS_SERVER && OTBR_ENABLE_BORDER_AGENT + , mDBusAgent(mNcp, mBorderAgent.GetPublisher()) +@@ -80,6 +81,7 @@ Application::Application(const std::string & aInterfaceName, + #endif + { + OTBR_UNUSED_VARIABLE(aRestListenAddress); ++ OTBR_UNUSED_VARIABLE(aRestListenPort); + } + + void Application::Init(void) +diff --git a/src/agent/application.hpp b/src/agent/application.hpp +index 829916772c..8c909b629f 100644 +--- a/src/agent/application.hpp ++++ b/src/agent/application.hpp +@@ -85,13 +85,16 @@ public: + * @param[in] aBackboneInterfaceName Name of the backbone network interface. + * @param[in] aRadioUrls The radio URLs (can be IEEE802.15.4 or TREL radio). + * @param[in] aEnableAutoAttach Whether or not to automatically attach to the saved network. ++ * @param[in] aRestListenAddress Network address to listen on. ++ * @param[in] aRestListenPort Network port to listen on. + * + */ + explicit Application(const std::string & aInterfaceName, + const std::vector &aBackboneInterfaceNames, + const std::vector &aRadioUrls, + bool aEnableAutoAttach, +- const std::string &aRestListenAddress); ++ const std::string &aRestListenAddress, ++ int aRestListenPort); + + /** + * This method initializes the Application instance. +diff --git a/src/agent/main.cpp b/src/agent/main.cpp +index 883c685671..f942cc916e 100644 +--- a/src/agent/main.cpp ++++ b/src/agent/main.cpp +@@ -61,6 +61,9 @@ + static const char kSyslogIdent[] = "otbr-agent"; + static const char kDefaultInterfaceName[] = "wpan0"; + ++// Port number used by Rest server. ++static const uint32_t kPortNumber = 8081; ++ + enum + { + OTBR_OPT_BACKBONE_INTERFACE_NAME = 'B', +@@ -73,6 +76,7 @@ enum + OTBR_OPT_RADIO_VERSION, + OTBR_OPT_AUTO_ATTACH, + OTBR_OPT_REST_LISTEN_ADDR, ++ OTBR_OPT_REST_LISTEN_PORT, + }; + + static jmp_buf sResetJump; +@@ -89,6 +93,7 @@ static const struct option kOptions[] = { + {"radio-version", no_argument, nullptr, OTBR_OPT_RADIO_VERSION}, + {"auto-attach", optional_argument, nullptr, OTBR_OPT_AUTO_ATTACH}, + {"rest-listen-address", required_argument, nullptr, OTBR_OPT_REST_LISTEN_ADDR}, ++ {"rest-listen-port", required_argument, nullptr, OTBR_OPT_REST_LISTEN_PORT}, + {0, 0, 0, 0}}; + + static bool ParseInteger(const char *aStr, long &aOutResult) +@@ -192,6 +197,7 @@ static int realmain(int argc, char *argv[]) + bool printRadioVersion = false; + bool enableAutoAttach = true; + const char *restListenAddress = ""; ++ int restListenPort = kPortNumber; + std::vector radioUrls; + std::vector backboneInterfaceNames; + long parseResult; +@@ -250,6 +256,11 @@ static int realmain(int argc, char *argv[]) + restListenAddress = optarg; + break; + ++ case OTBR_OPT_REST_LISTEN_PORT: ++ VerifyOrExit(ParseInteger(optarg, parseResult), ret = EXIT_FAILURE); ++ restListenPort = parseResult; ++ break; ++ + default: + PrintHelp(argv[0]); + ExitNow(ret = EXIT_FAILURE); +@@ -280,7 +291,8 @@ static int realmain(int argc, char *argv[]) + } + + { +- otbr::Application app(interfaceName, backboneInterfaceNames, radioUrls, enableAutoAttach, restListenAddress); ++ otbr::Application app(interfaceName, backboneInterfaceNames, radioUrls, enableAutoAttach, restListenAddress, ++ restListenPort); + + gApp = &app; + app.Init(); +diff --git a/src/rest/rest_web_server.cpp b/src/rest/rest_web_server.cpp +index cfb3dd4435..0327bb44ea 100644 +--- a/src/rest/rest_web_server.cpp ++++ b/src/rest/rest_web_server.cpp +@@ -46,16 +46,14 @@ namespace rest { + + // Maximum number of connection a server support at the same time. + static const uint32_t kMaxServeNum = 500; +-// Port number used by Rest server. +-static const uint32_t kPortNumber = 8081; + +-RestWebServer::RestWebServer(ControllerOpenThread &aNcp, const std::string &aRestListenAddress) ++RestWebServer::RestWebServer(ControllerOpenThread &aNcp, const std::string &aRestListenAddress, int aRestListenPort) + : mResource(Resource(&aNcp)) + , mListenFd(-1) + { + mAddress.sin6_family = AF_INET6; + mAddress.sin6_addr = in6addr_any; +- mAddress.sin6_port = htons(kPortNumber); ++ mAddress.sin6_port = htons(aRestListenPort); + + if (!aRestListenAddress.empty()) + { +diff --git a/src/rest/rest_web_server.hpp b/src/rest/rest_web_server.hpp +index 97e36b37e0..6e7956ba89 100644 +--- a/src/rest/rest_web_server.hpp ++++ b/src/rest/rest_web_server.hpp +@@ -60,7 +60,7 @@ public: + * @param[in] aNcp A reference to the NCP controller. + * + */ +- RestWebServer(ControllerOpenThread &aNcp, const std::string &aRestListenAddress); ++ RestWebServer(ControllerOpenThread &aNcp, const std::string &aRestListenAddress, int aRestListenPort); + + /** + * The destructor destroys the server instance. +-- +2.40.1 + diff --git a/silabs-multiprotocol/0011-rest-support-Thread-state-change-1866.patch b/silabs-multiprotocol/0011-rest-support-Thread-state-change-1866.patch new file mode 100644 index 00000000000..bd463c1bec5 --- /dev/null +++ b/silabs-multiprotocol/0011-rest-support-Thread-state-change-1866.patch @@ -0,0 +1,511 @@ +From afdb90adbfa23bfeee36abef529e4ad639fd8877 Mon Sep 17 00:00:00 2001 +Message-Id: +In-Reply-To: +References: +From: Stefan Agner +Date: Thu, 18 May 2023 00:09:33 +0200 +Subject: [PATCH] [rest] support Thread state change (#1866) + +Allow to enable and disable the Thread protocol operation. + +(cherry picked from commit 9a87cea0d57a475f0df1696752a9feab4062eaf2) +--- + src/common/CMakeLists.txt | 1 + + src/common/api_strings.cpp | 55 +++++++++++++++++++ + src/common/api_strings.hpp | 50 +++++++++++++++++ + src/dbus/client/thread_api_dbus.cpp | 1 + + src/dbus/common/constants.hpp | 6 --- + src/dbus/server/dbus_thread_object.cpp | 27 +--------- + src/rest/json.cpp | 18 ++++++- + src/rest/json.hpp | 10 ++++ + src/rest/openapi.yaml | 32 ++++++++--- + src/rest/resource.cpp | 74 ++++++++++++++++++++------ + src/rest/resource.hpp | 2 + + src/rest/types.hpp | 2 +- + tests/rest/test_rest.py | 4 +- + 13 files changed, 224 insertions(+), 58 deletions(-) + create mode 100644 src/common/api_strings.cpp + create mode 100644 src/common/api_strings.hpp + +diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt +index 1f3aec70e7..d5bc1d31c5 100644 +--- a/src/common/CMakeLists.txt ++++ b/src/common/CMakeLists.txt +@@ -27,6 +27,7 @@ + # + + add_library(otbr-common ++ api_strings.cpp + byteswap.hpp + code_utils.hpp + dns_utils.cpp +diff --git a/src/common/api_strings.cpp b/src/common/api_strings.cpp +new file mode 100644 +index 0000000000..fcb429f75b +--- /dev/null ++++ b/src/common/api_strings.cpp +@@ -0,0 +1,55 @@ ++/* ++ * Copyright (c) 2023, The OpenThread Authors. ++ * All rights reserved. ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions are met: ++ * 1. Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * 2. Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * 3. Neither the name of the copyright holder nor the ++ * names of its contributors may be used to endorse or promote products ++ * derived from this software without specific prior written permission. ++ * ++ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" ++ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ++ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ++ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE ++ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ++ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ++ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ++ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ++ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ++ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE ++ * POSSIBILITY OF SUCH DAMAGE. ++ */ ++ ++#include "common/api_strings.hpp" ++ ++std::string GetDeviceRoleName(otDeviceRole aRole) ++{ ++ std::string roleName; ++ ++ switch (aRole) ++ { ++ case OT_DEVICE_ROLE_DISABLED: ++ roleName = OTBR_ROLE_NAME_DISABLED; ++ break; ++ case OT_DEVICE_ROLE_DETACHED: ++ roleName = OTBR_ROLE_NAME_DETACHED; ++ break; ++ case OT_DEVICE_ROLE_CHILD: ++ roleName = OTBR_ROLE_NAME_CHILD; ++ break; ++ case OT_DEVICE_ROLE_ROUTER: ++ roleName = OTBR_ROLE_NAME_ROUTER; ++ break; ++ case OT_DEVICE_ROLE_LEADER: ++ roleName = OTBR_ROLE_NAME_LEADER; ++ break; ++ } ++ ++ return roleName; ++} +diff --git a/src/common/api_strings.hpp b/src/common/api_strings.hpp +new file mode 100644 +index 0000000000..72f3314b10 +--- /dev/null ++++ b/src/common/api_strings.hpp +@@ -0,0 +1,50 @@ ++/* ++ * Copyright (c) 2023, The OpenThread Authors. ++ * All rights reserved. ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions are met: ++ * 1. Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * 2. Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * 3. Neither the name of the copyright holder nor the ++ * names of its contributors may be used to endorse or promote products ++ * derived from this software without specific prior written permission. ++ * ++ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" ++ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ++ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ++ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE ++ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ++ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ++ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ++ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ++ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ++ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE ++ * POSSIBILITY OF SUCH DAMAGE. ++ */ ++ ++/** ++ * @file ++ * This file has helper functions to convert internal state representations ++ * to string (useful for APIs). ++ * ++ */ ++#ifndef OTBR_COMMON_API_STRINGS_HPP_ ++#define OTBR_COMMON_API_STRINGS_HPP_ ++ ++#include ++ ++#include ++ ++#define OTBR_ROLE_NAME_DISABLED "disabled" ++#define OTBR_ROLE_NAME_DETACHED "detached" ++#define OTBR_ROLE_NAME_CHILD "child" ++#define OTBR_ROLE_NAME_ROUTER "router" ++#define OTBR_ROLE_NAME_LEADER "leader" ++ ++std::string GetDeviceRoleName(otDeviceRole aRole); ++ ++#endif // OTBR_COMMON_API_STRINGS_HPP_ +diff --git a/src/dbus/client/thread_api_dbus.cpp b/src/dbus/client/thread_api_dbus.cpp +index 0d379ef9c8..cf93c6a754 100644 +--- a/src/dbus/client/thread_api_dbus.cpp ++++ b/src/dbus/client/thread_api_dbus.cpp +@@ -29,6 +29,7 @@ + #include + #include + ++#include "common/api_strings.hpp" + #include "common/code_utils.hpp" + #include "dbus/client/client_error.hpp" + #include "dbus/client/thread_api_dbus.hpp" +diff --git a/src/dbus/common/constants.hpp b/src/dbus/common/constants.hpp +index a90fccf234..bd55992658 100644 +--- a/src/dbus/common/constants.hpp ++++ b/src/dbus/common/constants.hpp +@@ -106,12 +106,6 @@ + #define OTBR_DBUS_PROPERTY_UPTIME "Uptime" + #define OTBR_DBUS_PROPERTY_RADIO_COEX_METRICS "RadioCoexMetrics" + +-#define OTBR_ROLE_NAME_DISABLED "disabled" +-#define OTBR_ROLE_NAME_DETACHED "detached" +-#define OTBR_ROLE_NAME_CHILD "child" +-#define OTBR_ROLE_NAME_ROUTER "router" +-#define OTBR_ROLE_NAME_LEADER "leader" +- + #define OTBR_DBUS_SIGNAL_READY "Ready" + + #endif // OTBR_DBUS_CONSTANTS_HPP_ +diff --git a/src/dbus/server/dbus_thread_object.cpp b/src/dbus/server/dbus_thread_object.cpp +index c908b51003..3035fc6534 100644 +--- a/src/dbus/server/dbus_thread_object.cpp ++++ b/src/dbus/server/dbus_thread_object.cpp +@@ -41,6 +41,7 @@ + #include + #include + ++#include "common/api_strings.hpp" + #include "common/byteswap.hpp" + #include "dbus/common/constants.hpp" + #include "dbus/server/dbus_agent.hpp" +@@ -56,32 +57,6 @@ + using std::placeholders::_1; + using std::placeholders::_2; + +-static std::string GetDeviceRoleName(otDeviceRole aRole) +-{ +- std::string roleName; +- +- switch (aRole) +- { +- case OT_DEVICE_ROLE_DISABLED: +- roleName = OTBR_ROLE_NAME_DISABLED; +- break; +- case OT_DEVICE_ROLE_DETACHED: +- roleName = OTBR_ROLE_NAME_DETACHED; +- break; +- case OT_DEVICE_ROLE_CHILD: +- roleName = OTBR_ROLE_NAME_CHILD; +- break; +- case OT_DEVICE_ROLE_ROUTER: +- roleName = OTBR_ROLE_NAME_ROUTER; +- break; +- case OT_DEVICE_ROLE_LEADER: +- roleName = OTBR_ROLE_NAME_LEADER; +- break; +- } +- +- return roleName; +-} +- + static uint64_t ConvertOpenThreadUint64(const uint8_t *aValue) + { + uint64_t val = 0; +diff --git a/src/rest/json.cpp b/src/rest/json.cpp +index 61c0be2046..a034eb643b 100644 +--- a/src/rest/json.cpp ++++ b/src/rest/json.cpp +@@ -73,6 +73,22 @@ exit: + return ret; + } + ++bool JsonString2String(const std::string &aJsonString, std::string &aString) ++{ ++ cJSON *jsonString; ++ bool ret = true; ++ ++ VerifyOrExit((jsonString = cJSON_Parse(aJsonString.c_str())) != nullptr, ret = false); ++ VerifyOrExit(cJSON_IsString(jsonString), ret = false); ++ ++ aString = std::string(jsonString->valuestring); ++ ++exit: ++ cJSON_Delete(jsonString); ++ ++ return ret; ++} ++ + std::string Json2String(const cJSON *aJson) + { + std::string ret; +@@ -346,7 +362,7 @@ std::string Node2JsonString(const NodeInfo &aNode) + cJSON * node = cJSON_CreateObject(); + std::string ret; + +- cJSON_AddItemToObject(node, "State", cJSON_CreateNumber(aNode.mRole)); ++ cJSON_AddItemToObject(node, "State", cJSON_CreateString(aNode.mRole.c_str())); + cJSON_AddItemToObject(node, "NumOfRouter", cJSON_CreateNumber(aNode.mNumOfRouter)); + cJSON_AddItemToObject(node, "RlocAddress", IpAddr2Json(aNode.mRlocAddress)); + cJSON_AddItemToObject(node, "ExtAddress", Bytes2HexJson(aNode.mExtAddress, OT_EXT_ADDRESS_SIZE)); +diff --git a/src/rest/json.hpp b/src/rest/json.hpp +index 29153d5149..a4f6bcf438 100644 +--- a/src/rest/json.hpp ++++ b/src/rest/json.hpp +@@ -103,6 +103,16 @@ std::string CString2JsonString(const char *aCString); + */ + std::string String2JsonString(const std::string &aString); + ++/** ++ * This method parses a Json string and checks its datatype and returns a string if it is a string. ++ * ++ * @param[in] aJsonString A Json string. ++ * @param[out] aString The string. ++ * ++ * @returns A boolean indicating whether the Json string was indeed a string. ++ */ ++bool JsonString2String(const std::string &aJsonString, std::string &aString); ++ + /** + * This method formats a Node object to a Json object and serialize it to a string. + * +diff --git a/src/rest/openapi.yaml b/src/rest/openapi.yaml +index d2bb2f0026..18d894008c 100644 +--- a/src/rest/openapi.yaml ++++ b/src/rest/openapi.yaml +@@ -93,20 +93,38 @@ paths: + summary: Get current Thread state. + description: |- + State describing the current Thread role of this Thread node. +- - 0: disabled +- - 1: detached +- - 2: child +- - 3: router +- - 4: leader ++ - disabled: The Thread stack is disabled. ++ - detached: Not currently participating in a Thread network/partition. ++ - child: The Thread Child role. ++ - router: The Thread Router role. ++ - leader: The Thread Leader role. + responses: + "200": + description: Successful operation + content: + application/json: + schema: +- type: number ++ type: string + description: Current state +- example: 4 ++ example: "leader" ++ put: ++ tags: ++ - node ++ summary: Set current Thread state. ++ description: |- ++ Enable and disable the Thread protocol operation. If network interface ++ hasn't been started yet, it will get started automatically. ++ responses: ++ "200": ++ description: Successful operation. ++ requestBody: ++ description: New Thread state ++ content: ++ application/json: ++ schema: ++ type: string ++ description: Can be "enable" or "disable". ++ example: "enable" + /node/network-name: + get: + tags: +diff --git a/src/rest/resource.cpp b/src/rest/resource.cpp +index a3265825f9..25818ae72e 100644 +--- a/src/rest/resource.cpp ++++ b/src/rest/resource.cpp +@@ -30,8 +30,6 @@ + + #include "rest/resource.hpp" + +-#include "string.h" +- + #define OT_PSKC_MAX_LENGTH 16 + #define OT_EXTENDED_PANID_LENGTH 8 + +@@ -234,7 +232,7 @@ void Resource::GetNodeInfo(Response &aResponse) const + ++node.mNumOfRouter; + } + +- node.mRole = otThreadGetDeviceRole(mInstance); ++ node.mRole = GetDeviceRoleName(otThreadGetDeviceRole(mInstance)); + node.mExtAddress = reinterpret_cast(otLinkGetExtendedAddress(mInstance)); + node.mNetworkName = otThreadGetNetworkName(mInstance); + node.mRloc16 = otThreadGetRloc16(mInstance); +@@ -296,33 +294,79 @@ void Resource::ExtendedAddr(const Request &aRequest, Response &aResponse) const + + void Resource::GetDataState(Response &aResponse) const + { +- std::string state; +- std::string errorCode; +- uint8_t role; +- // 0 : disabled +- // 1 : detached +- // 2 : child +- // 3 : router +- // 4 : leader ++ std::string state; ++ std::string errorCode; ++ otDeviceRole role; + + role = otThreadGetDeviceRole(mInstance); +- state = Json::Number2JsonString(role); ++ state = Json::String2JsonString(GetDeviceRoleName(role)); + aResponse.SetBody(state); + errorCode = GetHttpStatus(HttpStatusCode::kStatusOk); + aResponse.SetResponsCode(errorCode); + } + +-void Resource::State(const Request &aRequest, Response &aResponse) const ++void Resource::SetDataState(const Request &aRequest, Response &aResponse) const + { ++ otbrError error = OTBR_ERROR_NONE; + std::string errorCode; ++ std::string body; + +- if (aRequest.GetMethod() == HttpMethod::kGet) ++ VerifyOrExit(Json::JsonString2String(aRequest.GetBody(), body), error = OTBR_ERROR_INVALID_ARGS); ++ if (body == "enable") + { +- GetDataState(aResponse); ++ if (!otIp6IsEnabled(mInstance)) ++ { ++ SuccessOrExit(otIp6SetEnabled(mInstance, true)); ++ } ++ VerifyOrExit(otThreadSetEnabled(mInstance, true) == OT_ERROR_NONE, error = OTBR_ERROR_INVALID_STATE); ++ } ++ else if (body == "disable") ++ { ++ VerifyOrExit(otThreadSetEnabled(mInstance, false) == OT_ERROR_NONE, error = OTBR_ERROR_INVALID_STATE); + } + else + { ++ ExitNow(error = OTBR_ERROR_INVALID_ARGS); ++ } ++ ++ errorCode = GetHttpStatus(HttpStatusCode::kStatusOk); ++ aResponse.SetResponsCode(errorCode); ++ ++exit: ++ if (error == OTBR_ERROR_INVALID_STATE) ++ { ++ ErrorHandler(aResponse, HttpStatusCode::kStatusConflict); ++ } ++ if (error == OTBR_ERROR_INVALID_ARGS) ++ { ++ ErrorHandler(aResponse, HttpStatusCode::kStatusBadRequest); ++ } ++ else if (error != OTBR_ERROR_NONE) ++ { ++ ErrorHandler(aResponse, HttpStatusCode::kStatusInternalServerError); ++ } ++} ++ ++void Resource::State(const Request &aRequest, Response &aResponse) const ++{ ++ std::string errorCode; ++ ++ switch (aRequest.GetMethod()) ++ { ++ case HttpMethod::kGet: ++ GetDataState(aResponse); ++ break; ++ case HttpMethod::kPut: ++ SetDataState(aRequest, aResponse); ++ break; ++ case HttpMethod::kOptions: ++ errorCode = GetHttpStatus(HttpStatusCode::kStatusOk); ++ aResponse.SetResponsCode(errorCode); ++ aResponse.SetComplete(); ++ break; ++ default: + ErrorHandler(aResponse, HttpStatusCode::kStatusMethodNotAllowed); ++ break; + } + } + +diff --git a/src/rest/resource.hpp b/src/rest/resource.hpp +index 2d25b7bc58..f9f6e692a3 100644 +--- a/src/rest/resource.hpp ++++ b/src/rest/resource.hpp +@@ -38,6 +38,7 @@ + + #include + ++#include "common/api_strings.hpp" + #include "ncp/ncp_openthread.hpp" + #include "openthread/dataset.h" + #include "openthread/dataset_ftd.h" +@@ -134,6 +135,7 @@ private: + void GetNodeInfo(Response &aResponse) const; + void GetDataExtendedAddr(Response &aResponse) const; + void GetDataState(Response &aResponse) const; ++ void SetDataState(const Request &aRequest, Response &aResponse) const; + void GetDataNetworkName(Response &aResponse) const; + void GetDataLeaderData(Response &aResponse) const; + void GetDataNumOfRoute(Response &aResponse) const; +diff --git a/src/rest/types.hpp b/src/rest/types.hpp +index 8216a978d7..861c0af26e 100644 +--- a/src/rest/types.hpp ++++ b/src/rest/types.hpp +@@ -96,7 +96,7 @@ enum class ConnectionState : std::uint8_t + }; + struct NodeInfo + { +- uint32_t mRole; ++ std::string mRole; + uint32_t mNumOfRouter; + uint16_t mRloc16; + const uint8_t *mExtPanId; +diff --git a/tests/rest/test_rest.py b/tests/rest/test_rest.py +index 6a846489f3..b419c2d655 100644 +--- a/tests/rest/test_rest.py ++++ b/tests/rest/test_rest.py +@@ -194,7 +194,7 @@ def node_check(data): + "Rloc16", "LeaderData", "ExtPanId" + ] + expected_value_type = [ +- int, int, str, str, str, int, dict, str ++ str, int, str, str, str, int, dict, str + ] + expected_check_dict = dict(zip(expected_keys, expected_value_type)) + +@@ -250,7 +250,7 @@ def node_ext_address_check(data): + def node_state_check(data): + assert data is not None + +- assert (type(data) == int) ++ assert (type(data) == str) + + return True + +-- +2.40.1 + diff --git a/silabs-multiprotocol/CHANGELOG.md b/silabs-multiprotocol/CHANGELOG.md index 56c80a6c655..38de94f96cb 100644 --- a/silabs-multiprotocol/CHANGELOG.md +++ b/silabs-multiprotocol/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 2.0.0 + +- Update OpenThread REST API to latest upstreamed API variant + ## 1.1.3 - Use native zigbeed on x86-64/amd64 architecture diff --git a/silabs-multiprotocol/Dockerfile b/silabs-multiprotocol/Dockerfile index a533a2e6e79..5729edaf485 100644 --- a/silabs-multiprotocol/Dockerfile +++ b/silabs-multiprotocol/Dockerfile @@ -167,10 +167,12 @@ COPY 0002-rest-add-active-dataset-API-1658.patch /usr/src COPY 0003-rest-use-map-for-HTTP-headers-1665.patch /usr/src COPY 0005-web-bump-to-latest-Simple-Web-Server-version-1667.patch /usr/src COPY 0006-rest-allow-to-specify-REST-listen-address-1670.patch /usr/src -COPY 0007-rest-implement-REST-API-to-get-dataset.patch /usr/src -COPY 0008-rest-support-state-change.patch /usr/src -COPY 0009-rest-remove-superfluous-space-in-HTTP-status-line.patch /usr/src -COPY 0010-rest-explicitly-set-Connection-header-to-close.patch /usr/src +COPY 0007-rest-remove-superfluous-space-in-HTTP-status-line-17.patch /usr/src +COPY 0008-rest-explicitly-set-Connection-header-to-close-1774.patch /usr/src +COPY 0009-rest-add-Operational-Dataset-API-1682.patch /usr/src +COPY 0010-rest-allow-to-specify-REST-listen-port-1862.patch /usr/src +COPY 0011-rest-support-Thread-state-change-1866.patch /usr/src +COPY 0001-dataset-add-otDatasetUpdateTlvs-API-8871.patch /usr/src # Required and installed during build (script/bootstrap), could be removed ENV OTBR_BUILD_DEPS build-essential ninja-build cmake wget ca-certificates \ @@ -199,20 +201,24 @@ RUN \ netcat \ sudo \ && cd ot-br-posix \ + && patch -p1 < /usr/src/0001-Avoid-writing-to-system-console.patch \ && patch -p1 < /usr/src/0001-web-rest-listen-on-IPv6-addresses-as-well-1659.patch \ && patch -p1 < /usr/src/0002-rest-add-active-dataset-API-1658.patch \ && patch -p1 < /usr/src/0003-rest-use-map-for-HTTP-headers-1665.patch \ && patch -p1 < /usr/src/0005-web-bump-to-latest-Simple-Web-Server-version-1667.patch \ && patch -p1 < /usr/src/0006-rest-allow-to-specify-REST-listen-address-1670.patch \ - && patch -p1 < /usr/src/0007-rest-implement-REST-API-to-get-dataset.patch \ - && patch -p1 < /usr/src/0008-rest-support-state-change.patch \ - && patch -p1 < /usr/src/0009-rest-remove-superfluous-space-in-HTTP-status-line.patch \ - && patch -p1 < /usr/src/0010-rest-explicitly-set-Connection-header-to-close.patch \ - && chmod +x ./script/bootstrap \ - && ./script/bootstrap \ + && patch -p1 < /usr/src/0007-rest-remove-superfluous-space-in-HTTP-status-line-17.patch \ + && patch -p1 < /usr/src/0008-rest-explicitly-set-Connection-header-to-close-1774.patch \ + && patch -p1 < /usr/src/0009-rest-add-Operational-Dataset-API-1682.patch \ + && patch -p1 < /usr/src/0010-rest-allow-to-specify-REST-listen-port-1862.patch \ + && patch -p1 < /usr/src/0011-rest-support-Thread-state-change-1866.patch \ && ln -s ../../../openthread/ third_party/openthread/repo \ + && ( \ + cd third_party/openthread/repo \ + && patch -p1 < /usr/src/0001-dataset-add-otDatasetUpdateTlvs-API-8871.patch \ + ) \ && chmod +x ./script/* \ - && patch -p1 < /usr/src/0001-Avoid-writing-to-system-console.patch \ + && ./script/bootstrap \ # Mimic rt_tables_install \ && echo "88 openthread" >> /etc/iproute2/rt_tables \ # Mimic otbr_install \ diff --git a/silabs-multiprotocol/config.yaml b/silabs-multiprotocol/config.yaml index 1f7e336ff30..3b6b9324ce4 100644 --- a/silabs-multiprotocol/config.yaml +++ b/silabs-multiprotocol/config.yaml @@ -1,5 +1,5 @@ --- -version: 1.1.3 +version: 2.0.0 slug: silabs_multiprotocol name: Silicon Labs Multiprotocol description: Zigbee and OpenThread multiprotocol add-on @@ -9,6 +9,7 @@ arch: - armv7 - aarch64 - amd64 +homeassistant: 2023.6.0.dev20230531 gpio: true hassio_api: true discovery: @@ -51,6 +52,3 @@ schema: otbr_firewall: bool stage: experimental startup: services -panel_title: OpenThread -panel_icon: mdi:view-grid-plus-outline -panel_admin: true