| /* |
| * Copyright (c) 2020, 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. |
| */ |
| |
| #define OTBR_LOG_TAG "WEB" |
| |
| #include "web/web-service/ot_client.hpp" |
| |
| #include <openthread/platform/toolchain.h> |
| |
| #include <errno.h> |
| #include <inttypes.h> |
| #include <signal.h> |
| #include <stdarg.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/select.h> |
| #include <sys/socket.h> |
| #include <sys/un.h> |
| #include <unistd.h> |
| |
| #include "common/code_utils.hpp" |
| #include "common/logging.hpp" |
| |
| // Temporary solution before posix platform header files are cleaned up. |
| #ifndef OPENTHREAD_POSIX_DAEMON_SOCKET_NAME |
| #ifdef __linux__ |
| #define OPENTHREAD_POSIX_CONFIG_DAEMON_SOCKET_BASENAME "/run/openthread-%s" |
| #else |
| #define OPENTHREAD_POSIX_CONFIG_DAEMON_SOCKET_BASENAME "/tmp/openthread-%s" |
| #endif |
| #define OPENTHREAD_POSIX_DAEMON_SOCKET_NAME OPENTHREAD_POSIX_CONFIG_DAEMON_SOCKET_BASENAME ".sock" |
| #endif |
| |
| namespace otbr { |
| namespace Web { |
| |
| OpenThreadClient::OpenThreadClient(const char *aNetifName) |
| |
| : mNetifName(aNetifName) |
| , mTimeout(kDefaultTimeout) |
| , mSocket(-1) |
| { |
| } |
| |
| OpenThreadClient::~OpenThreadClient(void) |
| { |
| Disconnect(); |
| } |
| |
| void OpenThreadClient::Disconnect(void) |
| { |
| if (mSocket != -1) |
| { |
| close(mSocket); |
| mSocket = -1; |
| } |
| } |
| |
| bool OpenThreadClient::Connect(void) |
| { |
| struct sockaddr_un sockname; |
| int ret; |
| |
| mSocket = socket(AF_UNIX, SOCK_STREAM, 0); |
| VerifyOrExit(mSocket != -1, perror("socket"); ret = EXIT_FAILURE); |
| |
| memset(&sockname, 0, sizeof(struct sockaddr_un)); |
| sockname.sun_family = AF_UNIX; |
| ret = snprintf(sockname.sun_path, sizeof(sockname.sun_path), OPENTHREAD_POSIX_DAEMON_SOCKET_NAME, mNetifName); |
| |
| VerifyOrExit(ret >= 0 && static_cast<size_t>(ret) < sizeof(sockname.sun_path), { |
| errno = EINVAL; |
| ret = -1; |
| }); |
| |
| ret = connect(mSocket, reinterpret_cast<const struct sockaddr *>(&sockname), sizeof(struct sockaddr_un)); |
| |
| if (ret == -1) |
| { |
| otbrLogErr("OpenThread daemon is not running."); |
| } |
| |
| exit: |
| return ret == 0; |
| } |
| |
| void OpenThreadClient::DiscardRead(void) |
| { |
| fd_set readFdSet; |
| timeval timeout = {0, 0}; |
| ssize_t count; |
| int ret; |
| |
| for (;;) |
| { |
| FD_ZERO(&readFdSet); |
| FD_SET(mSocket, &readFdSet); |
| |
| ret = select(mSocket + 1, &readFdSet, nullptr, nullptr, &timeout); |
| if (ret <= 0) |
| { |
| break; |
| } |
| |
| count = read(mSocket, mBuffer, sizeof(mBuffer)); |
| if (count <= 0) |
| { |
| break; |
| } |
| } |
| } |
| |
| char *OpenThreadClient::Execute(const char *aFormat, ...) |
| { |
| va_list args; |
| int ret; |
| char *rval = nullptr; |
| ssize_t count; |
| size_t rxLength = 0; |
| |
| DiscardRead(); |
| |
| va_start(args, aFormat); |
| ret = vsnprintf(&mBuffer[1], sizeof(mBuffer) - 2, aFormat, args); |
| va_end(args); |
| |
| if (ret < 0) |
| { |
| otbrLogErr("Failed to generate command: %s", strerror(errno)); |
| ExitNow(); |
| } |
| if (static_cast<size_t>(ret) >= sizeof(mBuffer) - 2) |
| { |
| otbrLogErr("Command exceeds maximum limit: %d", kBufferSize); |
| ExitNow(); |
| } |
| |
| mBuffer[0] = '\n'; |
| mBuffer[ret + 1] = '\n'; |
| ret += 2; |
| |
| count = write(mSocket, mBuffer, ret); |
| |
| if (count != ret) |
| { |
| mBuffer[ret] = '\0'; |
| otbrLogErr("Failed to send command: %s", mBuffer); |
| ExitNow(); |
| } |
| |
| for (int i = 0; i < mTimeout; ++i) |
| { |
| fd_set readFdSet; |
| timeval timeout = {0, 1000}; |
| char *done; |
| |
| FD_ZERO(&readFdSet); |
| FD_SET(mSocket, &readFdSet); |
| |
| ret = select(mSocket + 1, &readFdSet, nullptr, nullptr, &timeout); |
| VerifyOrExit(ret != -1 || errno == EINTR); |
| if (ret <= 0) |
| { |
| continue; |
| } |
| |
| count = read(mSocket, &mBuffer[rxLength], sizeof(mBuffer) - rxLength); |
| VerifyOrExit(count > 0); |
| rxLength += count; |
| |
| mBuffer[rxLength] = '\0'; |
| done = strstr(mBuffer, "Done\r\n> "); |
| |
| if (done != nullptr) |
| { |
| // remove trailing \r\n |
| if (done - mBuffer > 2) |
| { |
| done[-2] = '\0'; |
| } |
| |
| rval = mBuffer; |
| break; |
| } |
| } |
| |
| exit: |
| return rval; |
| } |
| |
| char *OpenThreadClient::Read(const char *aResponse, int aTimeout) |
| { |
| ssize_t count = 0; |
| size_t rxLength = 0; |
| char *found; |
| char *rval = nullptr; |
| |
| for (int i = 0; i < aTimeout; ++i) |
| { |
| count = read(mSocket, &mBuffer[rxLength], sizeof(mBuffer) - rxLength); |
| VerifyOrExit(count > 0); |
| rxLength += count; |
| |
| mBuffer[rxLength] = '\0'; |
| found = strstr(mBuffer, aResponse); |
| |
| if (found != nullptr) |
| { |
| rval = mBuffer; |
| break; |
| } |
| } |
| |
| exit: |
| return rval; |
| } |
| |
| int OpenThreadClient::Scan(WpanNetworkInfo *aNetworks, int aLength) |
| { |
| char *result; |
| int rval = 0; |
| |
| mTimeout = 5000; |
| result = Execute("scan"); |
| VerifyOrExit(result != nullptr); |
| |
| for (result = strtok(result, "\r\n"); result != nullptr && rval < aLength; result = strtok(nullptr, "\r\n")) |
| { |
| static const char kCliPrompt[] = "> "; |
| char *cliPrompt; |
| int matched; |
| int lqi; |
| |
| // remove prompt |
| if ((cliPrompt = strstr(result, kCliPrompt)) != nullptr) |
| { |
| if (cliPrompt == result) |
| { |
| result += sizeof(kCliPrompt) - 1; |
| } |
| else |
| { |
| memmove(cliPrompt, cliPrompt + sizeof(kCliPrompt) - 1, strlen(cliPrompt) - sizeof(kCliPrompt) - 1); |
| } |
| } |
| |
| matched = sscanf(result, "| %hx | %02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx | %hu | %hhd | %d |", |
| &aNetworks[rval].mPanId, &aNetworks[rval].mHardwareAddress[0], |
| &aNetworks[rval].mHardwareAddress[1], &aNetworks[rval].mHardwareAddress[2], |
| &aNetworks[rval].mHardwareAddress[3], &aNetworks[rval].mHardwareAddress[4], |
| &aNetworks[rval].mHardwareAddress[5], &aNetworks[rval].mHardwareAddress[6], |
| &aNetworks[rval].mHardwareAddress[7], &aNetworks[rval].mChannel, &aNetworks[rval].mRssi, &lqi); |
| |
| // 15 is the number of output arguments of the last sscanf() |
| if (matched != 12) |
| { |
| continue; |
| } |
| |
| ++rval; |
| } |
| |
| mTimeout = kDefaultTimeout; |
| |
| exit: |
| return rval; |
| } |
| |
| bool OpenThreadClient::FactoryReset(void) |
| { |
| const char *result; |
| bool rval = false; |
| #if __APPLE__ |
| typedef sig_t sighandler_t; |
| #endif |
| sighandler_t handler; |
| |
| // Ignore the expected SIGPIPE signal during daemon reset. |
| handler = signal(SIGPIPE, SIG_IGN); |
| Execute("factoryreset"); |
| signal(SIGPIPE, handler); |
| Disconnect(); |
| sleep(4); |
| VerifyOrExit(rval = Connect()); |
| |
| result = Execute("version"); |
| VerifyOrExit(result != nullptr); |
| |
| rval = strstr(result, "OPENTHREAD") != nullptr; |
| |
| exit: |
| return rval; |
| } |
| |
| } // namespace Web |
| } // namespace otbr |