blob: ae934ff95b5e1cd97477599607db14547d98774b [file]
/*
* Copyright (c) 2017, 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 implements the wpan controller service
*/
#define OTBR_LOG_TAG "WEB"
#include "web/web-service/wpan_service.hpp"
#include <sstream>
#include <inttypes.h>
#include <stdio.h>
#include <unistd.h>
#include "common/byteswap.hpp"
#include "common/code_utils.hpp"
namespace otbr {
namespace Web {
#define WPAN_RESPONSE_SUCCESS "successful"
#define WPAN_RESPONSE_FAILURE "failed"
#define CREDENTIAL_TYPE_NETWORK_KEY "networkKeyType"
#define CREDENTIAL_TYPE_PSKD "pskdType"
std::string WpanService::HandleGetQRCodeRequest()
{
Json::Value root, networkInfo;
Json::FastWriter jsonWriter;
std::string response;
int ret = kWpanStatus_Ok;
otbr::Web::OpenThreadClient client(mIfName);
char *rval;
VerifyOrExit(client.Connect(), ret = kWpanStatus_SetFailed);
// eui64 is the only required information to generate the QR code.
VerifyOrExit((rval = client.Execute("eui64")) != nullptr, ret = kWpanStatus_GetPropertyFailed);
exit:
root.clear();
root["result"] = WPAN_RESPONSE_SUCCESS;
if (ret == kWpanStatus_Ok)
{
root["eui64"] = rval;
}
else
{
root["result"] = WPAN_RESPONSE_FAILURE;
otbrLogErr("Wpan service error: %d", ret);
}
response = jsonWriter.write(root);
return response;
}
std::string WpanService::HandleJoinNetworkRequest(const std::string &aJoinRequest)
{
Json::Value root;
Json::Reader reader;
Json::FastWriter jsonWriter;
std::string response;
int index;
std::string credentialType;
std::string networkKey;
std::string pskd;
std::string prefix;
bool defaultRoute;
int ret = kWpanStatus_Ok;
otbr::Web::OpenThreadClient client(mIfName);
char *rval;
VerifyOrExit(client.Connect(), ret = kWpanStatus_SetFailed);
VerifyOrExit(reader.parse(aJoinRequest.c_str(), root) == true, ret = kWpanStatus_ParseRequestFailed);
index = root["index"].asUInt();
credentialType = root["credentialType"].asString();
networkKey = root["networkKey"].asString();
pskd = root["pskd"].asString();
prefix = root["prefix"].asString();
defaultRoute = root["defaultRoute"].asBool();
if (prefix.find('/') == std::string::npos)
{
prefix += "/64";
}
VerifyOrExit(client.FactoryReset(), ret = kWpanStatus_LeaveFailed);
if (credentialType == CREDENTIAL_TYPE_NETWORK_KEY)
{
VerifyOrExit((ret = joinActiveDataset(client, networkKey, mNetworks[index].mChannel,
mNetworks[index].mPanId)) == kWpanStatus_Ok);
VerifyOrExit(client.Execute("ifconfig up") != nullptr, ret = kWpanStatus_JoinFailed);
}
else if (credentialType == CREDENTIAL_TYPE_PSKD)
{
VerifyOrExit(client.Execute("ifconfig up") != nullptr, ret = kWpanStatus_JoinFailed);
VerifyOrExit(client.Execute("joiner start %s", pskd.c_str()) != nullptr, ret = kWpanStatus_JoinFailed);
VerifyOrExit((rval = client.Read("Join ", 5000)) != nullptr, ret = kWpanStatus_JoinFailed);
if (strstr(rval, "Join success"))
{
ExitNow();
}
else if (strstr(rval, "Join failed [NotFound]"))
{
ExitNow(ret = kWpanStatus_JoinFailed_NotFound);
}
else if (strstr(rval, "Join failed [Security]"))
{
ExitNow(ret = kWpanStatus_JoinFailed_Security);
}
else
{
ExitNow(ret = kWpanStatus_JoinFailed);
}
}
else
{
ExitNow(ret = kWpanStatus_JoinFailed);
}
VerifyOrExit(client.Execute("thread start") != nullptr, ret = kWpanStatus_JoinFailed);
VerifyOrExit(client.Execute("prefix add %s paso%s", prefix.c_str(), (defaultRoute ? "r" : "")) != nullptr,
ret = kWpanStatus_SetFailed);
exit:
root.clear();
root["result"] = WPAN_RESPONSE_SUCCESS;
root["error"] = ret;
if (ret != kWpanStatus_Ok)
{
otbrLogErr("Wpan service error: %d", ret);
root["result"] = WPAN_RESPONSE_FAILURE;
}
root["message"] = "";
if (ret == kWpanStatus_JoinFailed_NotFound)
{
root["message"] = "Please make sure this joiner has been added by an active commissioner.";
}
else if (ret == kWpanStatus_JoinFailed_Security)
{
root["message"] = "Please make sure the provided PSKd matches the one given to the commissioner.";
}
response = jsonWriter.write(root);
return response;
}
std::string WpanService::HandleFormNetworkRequest(const std::string &aFormRequest)
{
Json::Value root;
Json::FastWriter jsonWriter;
Json::Reader reader;
std::string response;
otbr::Psk::Pskc psk;
char pskcStr[OT_PSKC_MAX_LENGTH * 2 + 1];
uint8_t extPanIdBytes[OT_EXTENDED_PANID_LENGTH];
std::string networkKey;
std::string prefix;
uint16_t channel;
std::string networkName;
std::string passphrase;
uint16_t panId;
uint64_t extPanId;
bool defaultRoute;
int ret = kWpanStatus_Ok;
otbr::Web::OpenThreadClient client(mIfName);
VerifyOrExit(client.Connect(), ret = kWpanStatus_SetFailed);
pskcStr[OT_PSKC_MAX_LENGTH * 2] = '\0'; // for manipulating with strlen
VerifyOrExit(reader.parse(aFormRequest.c_str(), root) == true, ret = kWpanStatus_ParseRequestFailed);
networkKey = root["networkKey"].asString();
prefix = root["prefix"].asString();
channel = root["channel"].asUInt();
networkName = root["networkName"].asString();
passphrase = root["passphrase"].asString();
VerifyOrExit(sscanf(root["panId"].asString().c_str(), "%hx", &panId) == 1, ret = kWpanStatus_ParseRequestFailed);
VerifyOrExit(sscanf(root["extPanId"].asString().c_str(), "%" PRIx64, &extPanId) == 1,
ret = kWpanStatus_ParseRequestFailed);
defaultRoute = root["defaultRoute"].asBool();
otbr::Utils::Hex2Bytes(root["extPanId"].asString().c_str(), extPanIdBytes, OT_EXTENDED_PANID_LENGTH);
otbr::Utils::Bytes2Hex(psk.ComputePskc(extPanIdBytes, networkName.c_str(), passphrase.c_str()), OT_PSKC_MAX_LENGTH,
pskcStr);
if (prefix.find('/') == std::string::npos)
{
prefix += "/64";
}
VerifyOrExit(client.FactoryReset(), ret = kWpanStatus_LeaveFailed);
VerifyOrExit((ret = formActiveDataset(client, networkKey, networkName, pskcStr, channel, extPanId, panId)) ==
kWpanStatus_Ok);
VerifyOrExit(client.Execute("ifconfig up") != nullptr, ret = kWpanStatus_FormFailed);
VerifyOrExit(client.Execute("thread start") != nullptr, ret = kWpanStatus_FormFailed);
VerifyOrExit(client.Execute("prefix add %s paso%s", prefix.c_str(), (defaultRoute ? "r" : "")) != nullptr,
ret = kWpanStatus_SetFailed);
exit:
root.clear();
root["result"] = WPAN_RESPONSE_SUCCESS;
root["error"] = ret;
if (ret != kWpanStatus_Ok)
{
otbrLogErr("Wpan service error: %d", ret);
root["result"] = WPAN_RESPONSE_FAILURE;
}
response = jsonWriter.write(root);
return response;
}
std::string WpanService::HandleAddPrefixRequest(const std::string &aAddPrefixRequest)
{
Json::Value root;
Json::FastWriter jsonWriter;
Json::Reader reader;
std::string response;
std::string prefix;
bool defaultRoute;
int ret = kWpanStatus_Ok;
otbr::Web::OpenThreadClient client(mIfName);
VerifyOrExit(client.Connect(), ret = kWpanStatus_SetFailed);
VerifyOrExit(reader.parse(aAddPrefixRequest.c_str(), root) == true, ret = kWpanStatus_ParseRequestFailed);
prefix = root["prefix"].asString();
defaultRoute = root["defaultRoute"].asBool();
if (prefix.find('/') == std::string::npos)
{
prefix += "/64";
}
VerifyOrExit(client.Execute("prefix add %s paso%s", prefix.c_str(), (defaultRoute ? "r" : "")) != nullptr,
ret = kWpanStatus_SetGatewayFailed);
VerifyOrExit(client.Execute("netdata register") != nullptr, ret = kWpanStatus_SetGatewayFailed);
exit:
root.clear();
root["result"] = WPAN_RESPONSE_SUCCESS;
root["error"] = ret;
if (ret != kWpanStatus_Ok)
{
otbrLogErr("Wpan service error: %d", ret);
root["result"] = WPAN_RESPONSE_FAILURE;
}
response = jsonWriter.write(root);
return response;
}
std::string WpanService::HandleDeletePrefixRequest(const std::string &aDeleteRequest)
{
Json::Value root;
Json::FastWriter jsonWriter;
Json::Reader reader;
std::string response;
std::string prefix;
int ret = kWpanStatus_Ok;
otbr::Web::OpenThreadClient client(mIfName);
VerifyOrExit(client.Connect(), ret = kWpanStatus_SetFailed);
VerifyOrExit(reader.parse(aDeleteRequest.c_str(), root) == true, ret = kWpanStatus_ParseRequestFailed);
prefix = root["prefix"].asString();
if (prefix.find('/') == std::string::npos)
{
prefix += "/64";
}
VerifyOrExit(client.Execute("prefix remove %s", prefix.c_str()) != nullptr, ret = kWpanStatus_SetGatewayFailed);
VerifyOrExit(client.Execute("netdata register") != nullptr, ret = kWpanStatus_SetGatewayFailed);
exit:
root.clear();
root["result"] = WPAN_RESPONSE_SUCCESS;
root["error"] = ret;
if (ret != kWpanStatus_Ok)
{
otbrLogErr("Wpan service error: %d", ret);
root["result"] = WPAN_RESPONSE_FAILURE;
}
response = jsonWriter.write(root);
return response;
}
std::string WpanService::HandleStatusRequest()
{
Json::Value root, networkInfo;
Json::FastWriter jsonWriter;
std::string response, networkName, extPanId, propertyValue;
int ret = kWpanStatus_Ok;
otbr::Web::OpenThreadClient client(mIfName);
char *rval;
networkInfo["WPAN service"] = "uninitialized";
VerifyOrExit(client.Connect(), ret = kWpanStatus_SetFailed);
VerifyOrExit((rval = client.Execute("state")) != nullptr, ret = kWpanStatus_GetPropertyFailed);
networkInfo["RCP:State"] = rval;
if (!strcmp(rval, "disabled"))
{
networkInfo["WPAN service"] = "offline";
ExitNow();
}
else if (!strcmp(rval, "detached"))
{
networkInfo["WPAN service"] = "associating";
ExitNow();
}
else
{
networkInfo["WPAN service"] = "associated";
}
VerifyOrExit((rval = client.Execute("version")) != nullptr, ret = kWpanStatus_GetPropertyFailed);
networkInfo["OpenThread:Version"] = rval;
VerifyOrExit((rval = client.Execute("version api")) != nullptr, ret = kWpanStatus_GetPropertyFailed);
networkInfo["OpenThread:Version API"] = rval;
VerifyOrExit((rval = client.Execute("rcp version")) != nullptr, ret = kWpanStatus_GetPropertyFailed);
networkInfo["RCP:Version"] = rval;
VerifyOrExit((rval = client.Execute("eui64")) != nullptr, ret = kWpanStatus_GetPropertyFailed);
networkInfo["RCP:EUI64"] = rval;
VerifyOrExit((rval = client.Execute("channel")) != nullptr, ret = kWpanStatus_GetPropertyFailed);
networkInfo["RCP:Channel"] = rval;
VerifyOrExit((rval = client.Execute("txpower")) != nullptr, ret = kWpanStatus_GetPropertyFailed);
networkInfo["RCP:TxPower"] = rval;
VerifyOrExit((rval = client.Execute("networkname")) != nullptr, ret = kWpanStatus_GetPropertyFailed);
networkInfo["Network:Name"] = rval;
VerifyOrExit((rval = client.Execute("extpanid")) != nullptr, ret = kWpanStatus_GetPropertyFailed);
networkInfo["Network:XPANID"] = rval;
VerifyOrExit((rval = client.Execute("panid")) != nullptr, ret = kWpanStatus_GetPropertyFailed);
networkInfo["Network:PANID"] = rval;
VerifyOrExit((rval = client.Execute("partitionid")) != nullptr, ret = kWpanStatus_GetPropertyFailed);
networkInfo["Network:PartitionID"] = rval;
{
static const char kMeshLocalPrefixLocator[] = "Mesh Local Prefix: ";
static const char kMeshLocalAddressTokenLocator[] = "0:ff:fe00:";
static const char localAddressToken[] = "fd";
static const char linkLocalAddressToken[] = "fe80";
std::string meshLocalPrefix = "";
VerifyOrExit((rval = client.Execute("dataset active")) != nullptr, ret = kWpanStatus_GetPropertyFailed);
rval = strstr(rval, kMeshLocalPrefixLocator);
if (rval != nullptr)
{
rval += sizeof(kMeshLocalPrefixLocator) - 1;
*strstr(rval, "\r\n") = '\0';
networkInfo["IPv6:MeshLocalPrefix"] = rval;
meshLocalPrefix = rval;
meshLocalPrefix.resize(meshLocalPrefix.find(":/"));
}
VerifyOrExit((rval = client.Execute("ipaddr")) != nullptr, ret = kWpanStatus_GetPropertyFailed);
for (rval = strtok(rval, "\r\n"); rval != nullptr; rval = strtok(nullptr, "\r\n"))
{
char *meshLocalAddressToken = nullptr;
if (strstr(rval, "> ") != nullptr)
{
rval += 2;
}
if (strstr(rval, linkLocalAddressToken) == rval)
{
networkInfo["IPv6:LinkLocalAddress"] = rval;
continue;
}
meshLocalAddressToken = strstr(rval, kMeshLocalAddressTokenLocator);
if (meshLocalAddressToken == nullptr)
{
if ((meshLocalPrefix.size() > 0) && (strstr(rval, meshLocalPrefix.c_str()) == rval))
{
networkInfo["IPv6:MeshLocalAddress"] = rval;
continue;
}
if (strstr(rval, localAddressToken) != rval)
{
networkInfo["IPv6:GlobalAddress"] = rval;
}
else
{
networkInfo["IPv6:LocalAddress"] = rval;
}
}
else
{
*meshLocalAddressToken = '\0';
meshLocalPrefix = rval;
networkInfo["IPv6:MeshLocalPrefix"] = rval;
std::string la = networkInfo.get("IPv6:LocalAddress", "unknown").asString();
if (strstr(rval, la.c_str()) != nullptr)
{
networkInfo["IPv6:MeshLocalAddress"] = networkInfo.get("IPv6:LocalAddress", "notfound").asString();
networkInfo.removeMember("IPv6:LocalAddress");
}
}
}
}
exit:
root["result"] = networkInfo;
if (ret != kWpanStatus_Ok)
{
root["result"] = WPAN_RESPONSE_FAILURE;
otbrLogErr("Wpan service error: %d", ret);
}
root["error"] = ret;
response = jsonWriter.write(root);
return response;
}
std::string WpanService::HandleAvailableNetworkRequest()
{
Json::Value root, networks, networkInfo;
Json::FastWriter jsonWriter;
std::string response;
int ret = kWpanStatus_Ok;
otbr::Web::OpenThreadClient client(mIfName);
VerifyOrExit(client.Connect(), ret = kWpanStatus_ScanFailed);
VerifyOrExit((mNetworksCount = client.Scan(mNetworks, sizeof(mNetworks) / sizeof(mNetworks[0]))) > 0,
ret = kWpanStatus_NetworkNotFound);
for (int i = 0; i < mNetworksCount; i++)
{
char panId[OT_PANID_LENGTH * 2 + 3], hardwareAddress[OT_HARDWARE_ADDRESS_LENGTH * 2 + 1];
otbr::Utils::Bytes2Hex(mNetworks[i].mHardwareAddress, OT_HARDWARE_ADDRESS_LENGTH, hardwareAddress);
snprintf(panId, sizeof(panId), "0x%X", mNetworks[i].mPanId);
networkInfo[i]["pi"] = panId;
networkInfo[i]["ch"] = mNetworks[i].mChannel;
networkInfo[i]["ha"] = hardwareAddress;
}
root["result"] = networkInfo;
exit:
if (ret != kWpanStatus_Ok)
{
root["result"] = WPAN_RESPONSE_FAILURE;
otbrLogErr("Error is %d", ret);
}
root["error"] = ret;
response = jsonWriter.write(root);
return response;
}
int WpanService::GetWpanServiceStatus(std::string &aNetworkName, std::string &aExtPanId) const
{
int status = kWpanStatus_Ok;
otbr::Web::OpenThreadClient client(mIfName);
const char *rval;
VerifyOrExit(client.Connect(), status = kWpanStatus_Uninitialized);
rval = client.Execute("state");
VerifyOrExit(rval != nullptr, status = kWpanStatus_Down);
if (!strcmp(rval, "disabled"))
{
status = kWpanStatus_Offline;
}
else if (!strcmp(rval, "detached"))
{
status = kWpanStatus_Associating;
}
else
{
rval = client.Execute("networkname");
VerifyOrExit(rval != nullptr, status = kWpanStatus_Down);
aNetworkName = rval;
rval = client.Execute("extpanid");
VerifyOrExit(rval != nullptr, status = kWpanStatus_Down);
aExtPanId = rval;
}
exit:
return status;
}
std::string WpanService::HandleCommission(const std::string &aCommissionRequest)
{
Json::Value root;
Json::Reader reader;
Json::FastWriter jsonWriter;
int ret = kWpanStatus_Ok;
std::string pskd;
std::string response;
const char *rval;
VerifyOrExit(reader.parse(aCommissionRequest.c_str(), root) == true, ret = kWpanStatus_ParseRequestFailed);
pskd = root["pskd"].asString();
{
otbr::Web::OpenThreadClient client(mIfName);
VerifyOrExit(client.Connect(), ret = kWpanStatus_Uninitialized);
for (int i = 0; i < 5; i++)
{
VerifyOrExit((rval = client.Execute("commissioner state")) != nullptr, ret = kWpanStatus_Down);
if (strcmp(rval, "disabled") == 0)
{
VerifyOrExit((rval = client.Execute("commissioner start")) != nullptr, ret = kWpanStatus_Down);
}
else if (strcmp(rval, "active") == 0)
{
VerifyOrExit(client.Execute("commissioner joiner add * %s", pskd.c_str()) != nullptr,
ret = kWpanStatus_Down);
root["error"] = ret;
ExitNow();
}
sleep(1);
}
client.Execute("commissioner stop");
}
ret = kWpanStatus_SetFailed;
exit:
root.clear();
root["result"] = WPAN_RESPONSE_SUCCESS;
root["error"] = ret;
if (ret != kWpanStatus_Ok)
{
root["result"] = WPAN_RESPONSE_FAILURE;
otbrLogErr("error: %d", ret);
}
response = jsonWriter.write(root);
return response;
}
int WpanService::joinActiveDataset(otbr::Web::OpenThreadClient &aClient,
const std::string &aNetworkKey,
uint16_t aChannel,
uint16_t aPanId)
{
int ret = kWpanStatus_Ok;
VerifyOrExit(aClient.Execute("dataset clear") != nullptr, ret = kWpanStatus_SetFailed);
VerifyOrExit(aClient.Execute("dataset networkkey %s", aNetworkKey.c_str()) != nullptr, ret = kWpanStatus_SetFailed);
VerifyOrExit(aClient.Execute("dataset channel %u", aChannel) != nullptr, ret = kWpanStatus_SetFailed);
VerifyOrExit(aClient.Execute("dataset panid %u", aPanId) != nullptr, ret = kWpanStatus_SetFailed);
VerifyOrExit(aClient.Execute("dataset commit active") != nullptr, ret = kWpanStatus_SetFailed);
exit:
return ret;
}
int WpanService::formActiveDataset(otbr::Web::OpenThreadClient &aClient,
const std::string &aNetworkKey,
const std::string &aNetworkName,
const std::string &aPskc,
uint16_t aChannel,
uint64_t aExtPanId,
uint16_t aPanId)
{
int ret = kWpanStatus_Ok;
VerifyOrExit(aClient.Execute("dataset init new") != nullptr, ret = kWpanStatus_SetFailed);
VerifyOrExit(aClient.Execute("dataset networkkey %s", aNetworkKey.c_str()) != nullptr, ret = kWpanStatus_SetFailed);
VerifyOrExit(aClient.Execute("dataset networkname %s", escapeOtCliEscapable(aNetworkName).c_str()) != nullptr,
ret = kWpanStatus_SetFailed);
VerifyOrExit(aClient.Execute("dataset pskc %s", aPskc.c_str()) != nullptr, ret = kWpanStatus_SetFailed);
VerifyOrExit(aClient.Execute("dataset channel %u", aChannel) != nullptr, ret = kWpanStatus_SetFailed);
VerifyOrExit(aClient.Execute("dataset extpanid %016" PRIx64, aExtPanId) != nullptr, ret = kWpanStatus_SetFailed);
VerifyOrExit(aClient.Execute("dataset panid %u", aPanId) != nullptr, ret = kWpanStatus_SetFailed);
VerifyOrExit(aClient.Execute("dataset commit active") != nullptr, ret = kWpanStatus_SetFailed);
exit:
return ret;
}
std::string WpanService::escapeOtCliEscapable(const std::string &aArg)
{
std::stringbuf strbuf;
for (char c : aArg)
{
switch (c)
{
case ' ':
case '\t':
case '\r':
case '\n':
case '\\':
strbuf.sputc('\\');
// Fallthrough
default:
strbuf.sputc(c);
}
}
return strbuf.str();
}
} // namespace Web
} // namespace otbr