| /* |
| * |
| * Copyright (c) 2013-2017 Nest Labs, Inc. |
| * All rights reserved. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| /** |
| * @file |
| * This file implements a command line tool, weave-heartbeat, for the |
| * Weave Heartbeat Profile. |
| * |
| */ |
| |
| #define __STDC_FORMAT_MACROS |
| #define __STDC_LIMIT_MACROS |
| |
| #include <inttypes.h> |
| #include <limits.h> |
| |
| #include "ToolCommon.h" |
| #include <Weave/WeaveVersion.h> |
| #include <Weave/Core/WeaveEncoding.h> |
| #include <Weave/Core/WeaveSecurityMgr.h> |
| #include <Weave/Core/WeaveBinding.h> |
| #include <Weave/Profiles/time/WeaveTime.h> |
| #include <Weave/Profiles/heartbeat/WeaveHeartbeat.h> |
| #include <Weave/Profiles/security/WeaveSecurity.h> |
| #include <Weave/Profiles/service-directory/ServiceDirectory.h> |
| |
| using nl::StatusReportStr; |
| using namespace nl::Weave::Profiles::Heartbeat; |
| using namespace nl::Weave::Profiles::Security; |
| |
| #define TOOL_NAME "weave-heartbeat" |
| |
| static bool HandleOption(const char *progName, OptionSet *optSet, int id, const char *name, const char *arg); |
| static bool HandleNonOptionArgs(const char *progName, int argc, char *argv[]); |
| static void HandleHeartbeatReceived(const WeaveMessageInfo *aMsgInfo, uint8_t nodeState, WEAVE_ERROR err); |
| static bool ParseDestAddress(const char *progName, const char *arg); |
| static void HeartbeatSenderEventHandler(void *appState, WeaveHeartbeatSender::EventType eventType, const WeaveHeartbeatSender::InEventParam& inParam, WeaveHeartbeatSender::OutEventParam& outParam); |
| static void BindingEventHandler(void *appState, Binding::EventType eventType, const Binding::InEventParam& inParam, Binding::OutEventParam& outParam); |
| |
| bool Listening = false; |
| uint32_t MaxHeartbeatCount = UINT32_MAX; |
| uint32_t HeartbeatInterval = 1000; // 1 second |
| uint32_t HeartbeatWindow = 0; |
| bool Debug = false; |
| uint64_t DestNodeId; |
| const char *DestAddr = NULL; |
| IPAddress DestIPAddr; |
| uint16_t DestPort; |
| InterfaceId DestIntf = INET_NULL_INTERFACEID; |
| uint32_t HeartbeatCount = 0; |
| WeaveHeartbeatSender HeartbeatSender; |
| WeaveHeartbeatReceiver HeartbeatReceiver; |
| bool RequestAck = false; |
| |
| static OptionDef gToolOptionDefs[] = |
| { |
| { "listen", kNoArgument, 'L' }, |
| { "dest-addr", kArgumentRequired, 'D' }, |
| { "count", kArgumentRequired, 'c' }, |
| { "interval", kArgumentRequired, 'i' }, |
| { "window", kArgumentRequired, 'W' }, |
| { "request-ack", kNoArgument, 'r' }, |
| { } |
| }; |
| |
| static const char *const gToolOptionHelp = |
| " -D, --dest-addr <host>[:<port>][%<interface>]\n" |
| " Send Heartbeats to a specific address rather than one\n" |
| " derived from the destination node id. <host> can be a hostname,\n" |
| " an IPv4 address or an IPv6 address. If <port> is specified, Heartbeat\n" |
| " requests will be sent to the specified port. If <interface> is\n" |
| " specified, Heartbeats will be sent over the specified local\n" |
| " interface.\n" |
| "\n" |
| " NOTE: When specifying a port with an IPv6 address, the IPv6 address\n" |
| " must be enclosed in brackets, e.g. [fd00:0:1:1::1]:11095.\n" |
| "\n" |
| " -c, --count <num>\n" |
| " Send the specified number of Heartbeats and exit.\n" |
| "\n" |
| " -i, --interval <ms>\n" |
| " Send Heartbeats at the specified interval in milliseconds.\n" |
| "\n" |
| " -W, --window <ms>\n" |
| " Randomize the sending of Heartbeats over the specified interval in milliseconds.\n" |
| "\n" |
| " -L, --listen\n" |
| " Listen and respond to Heartbeats sent from another node.\n" |
| "\n" |
| " -r, --request-ack\n" |
| " Use Weave Reliable Messaging when sending heartbeats over UDP.\n" |
| "\n"; |
| |
| static OptionSet gToolOptions = |
| { |
| HandleOption, |
| gToolOptionDefs, |
| "GENERAL OPTIONS", |
| gToolOptionHelp |
| }; |
| |
| static HelpOptions gHelpOptions( |
| TOOL_NAME, |
| "Usage: " TOOL_NAME " [<options...>] <dest-node-id>[@<dest-host>[:<dest-port>][%%<interface>]]\n" |
| " " TOOL_NAME " [<options...>] --listen\n", |
| WEAVE_VERSION_STRING "\n" WEAVE_TOOL_COPYRIGHT |
| ); |
| |
| static OptionSet *gToolOptionSets[] = |
| { |
| &gToolOptions, |
| &gNetworkOptions, |
| &gWeaveNodeOptions, |
| &gWRMPOptions, |
| &gFaultInjectionOptions, |
| &gHelpOptions, |
| NULL |
| }; |
| |
| int main(int argc, char *argv[]) |
| { |
| WEAVE_ERROR err; |
| |
| InitToolCommon(); |
| |
| SetSIGUSR1Handler(); |
| |
| // Use a smaller default WRMP retransmission interval and count so that the total retry time |
| // does not exceed the default heartbeat interval of 1 second. |
| gWRMPOptions.RetransInterval = 200; |
| gWRMPOptions.RetransCount = 2; |
| |
| if (argc == 1) |
| { |
| gHelpOptions.PrintBriefUsage(stderr); |
| exit(EXIT_FAILURE); |
| } |
| |
| if (!ParseArgsFromEnvVar(TOOL_NAME, TOOL_OPTIONS_ENV_VAR_NAME, gToolOptionSets, NULL, true) || |
| !ParseArgs(TOOL_NAME, argc, argv, gToolOptionSets, HandleNonOptionArgs) || |
| !ResolveWeaveNetworkOptions(TOOL_NAME, gWeaveNodeOptions, gNetworkOptions)) |
| { |
| exit(EXIT_FAILURE); |
| } |
| |
| InitSystemLayer(); |
| |
| InitNetwork(); |
| |
| InitWeaveStack(true, true); |
| |
| if (!Listening) // heartbeat sender |
| { |
| // Create a binding for the HeartbeatSender. |
| Binding *binding = ExchangeMgr.NewBinding(BindingEventHandler, NULL); |
| |
| // Initialize the HeartbeatSender object. |
| err = HeartbeatSender.Init(&ExchangeMgr, binding, HeartbeatSenderEventHandler, NULL); |
| if (err != WEAVE_NO_ERROR) |
| { |
| printf("WeaveHeartbeatSender.Init failed: %s\n", ErrorStr(err)); |
| exit(EXIT_FAILURE); |
| } |
| |
| // Release the binding (the HeartbeatSender will retain its own reference). |
| binding->Release(); |
| } |
| else // heartbeat receiver |
| { |
| // Initialize the HeartbeatReceiver application. |
| err = HeartbeatReceiver.Init(&ExchangeMgr); |
| if (err) |
| { |
| printf("WeaveHeartbeatReceiver.Init failed: %s\n", ErrorStr(err)); |
| exit(EXIT_FAILURE); |
| } |
| |
| // Arrange to get a callback whenever a Heartbeat is received. |
| HeartbeatReceiver.OnHeartbeatReceived = HandleHeartbeatReceived; |
| } |
| |
| PrintNodeConfig(); |
| |
| if (!Listening) // heartbeat sender |
| { |
| printf("Sending"); |
| if (MaxHeartbeatCount != UINT32_MAX) |
| printf(" %u", MaxHeartbeatCount); |
| printf(" Heartbeats via %s to node %" PRIX64, (RequestAck) ? "UDP with WRMP" : "UDP", DestNodeId); |
| if (DestAddr != NULL) |
| printf(" (%s)", DestAddr); |
| printf(" every %" PRId32 " ms", HeartbeatInterval); |
| if (HeartbeatWindow > 0) |
| printf(", with a randomization window of %" PRId32 " ms", HeartbeatWindow); |
| printf("\n"); |
| |
| HeartbeatSender.SetConfiguration(HeartbeatInterval, 0, HeartbeatWindow); |
| HeartbeatSender.SetRequestAck(RequestAck); |
| HeartbeatSender.SetSubscriptionState(0x01); |
| |
| err = HeartbeatSender.StartHeartbeat(); |
| if (err != WEAVE_NO_ERROR) |
| { |
| printf("HeartbeatSender.StartHeartbeat failed: %s\n", ErrorStr(err)); |
| exit(EXIT_FAILURE); |
| } |
| } |
| else // heartbeat receiver |
| { |
| printf("Listening for Heartbeats...\n"); |
| } |
| |
| while (!Done) |
| { |
| struct timeval sleepTime; |
| sleepTime.tv_sec = 0; |
| sleepTime.tv_usec = 100000; |
| |
| ServiceNetwork(sleepTime); |
| |
| if (MaxHeartbeatCount != UINT32_MAX && HeartbeatCount >= MaxHeartbeatCount) |
| Done = true; |
| } |
| |
| HeartbeatSender.Shutdown(); |
| HeartbeatReceiver.Shutdown(); |
| |
| ShutdownWeaveStack(); |
| ShutdownNetwork(); |
| ShutdownSystemLayer(); |
| |
| return EXIT_SUCCESS; |
| } |
| |
| bool HandleOption(const char *progName, OptionSet *optSet, int id, const char *name, const char *arg) |
| { |
| switch (id) |
| { |
| case 'L': |
| Listening = true; |
| break; |
| case 'c': |
| if (!ParseInt(arg, MaxHeartbeatCount)) |
| { |
| PrintArgError("%s: Invalid value specified for send count: %s\n", progName, arg); |
| return false; |
| } |
| break; |
| case 'i': |
| if (!ParseInt(arg, HeartbeatInterval)) |
| { |
| PrintArgError("%s: Invalid value specified for heartbeat interval: %s\n", progName, arg); |
| return false; |
| } |
| break; |
| case 'W': |
| if (!ParseInt(arg, HeartbeatWindow)) |
| { |
| PrintArgError("%s: Invalid value specified for heartbeat randomization window: %s\n", progName, arg); |
| return false; |
| } |
| break; |
| case 'D': |
| return ParseDestAddress(progName, arg); |
| break; |
| case 'r': |
| #if WEAVE_CONFIG_ENABLE_RELIABLE_MESSAGING |
| RequestAck = true; |
| break; |
| #else |
| PrintArgError("%s: WRMP not supported: %s\n", progName, name); |
| return false; |
| #endif |
| default: |
| PrintArgError("%s: INTERNAL ERROR: Unhandled option: %s\n", progName, name); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool HandleNonOptionArgs(const char *progName, int argc, char *argv[]) |
| { |
| const char *destAddr = NULL; |
| |
| if (argc > 0) |
| { |
| if (argc > 1) |
| { |
| PrintArgError("%s: Unexpected argument: %s\n", progName, argv[1]); |
| return false; |
| } |
| |
| if (Listening) |
| { |
| PrintArgError("%s: Please specify either a node id or --listen\n", progName); |
| return false; |
| } |
| |
| const char *nodeId = argv[0]; |
| char *p = (char *)strchr(nodeId, '@'); |
| if (p != NULL) |
| { |
| *p = 0; |
| destAddr = p+1; |
| } |
| |
| if (!ParseNodeId(nodeId, DestNodeId)) |
| { |
| PrintArgError("%s: Invalid value specified for destination node-id: %s\n", progName, nodeId); |
| return false; |
| } |
| |
| if (destAddr != NULL && !ParseDestAddress(progName, destAddr)) |
| return false; |
| } |
| |
| else |
| { |
| if (!Listening) |
| { |
| PrintArgError("%s: Please specify either a node id or --listen\n", progName); |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| void HandleHeartbeatSent(uint64_t destId, IPAddress destAddr, uint8_t state, WEAVE_ERROR err) |
| { |
| char ipAddrStr[64]; |
| destAddr.ToString(ipAddrStr, sizeof(ipAddrStr)); |
| |
| printf("Heartbeat sent to node %" PRIX64 " (%s): state=%u, err=%s\n", destId, ipAddrStr, state, ErrorStr(err)); |
| |
| HeartbeatCount++; |
| } |
| |
| void HandleHeartbeatReceived(const WeaveMessageInfo *aMsgInfo, uint8_t nodeState, WEAVE_ERROR err) |
| { |
| char ipAddrStr[64]; |
| aMsgInfo->InPacketInfo->SrcAddress.ToString(ipAddrStr, sizeof(ipAddrStr)); |
| |
| printf("Heartbeat from node %" PRIX64 " (%s): state=%u, err=%s\n", aMsgInfo->SourceNodeId, ipAddrStr, nodeState, ErrorStr(err)); |
| |
| HeartbeatCount++; |
| } |
| |
| bool ParseDestAddress(const char *progName, const char *arg) |
| { |
| WEAVE_ERROR err; |
| const char *addr; |
| uint16_t addrLen; |
| const char *intfName; |
| uint16_t intfNameLen; |
| |
| err = ParseHostPortAndInterface(arg, strlen(arg), addr, addrLen, DestPort, intfName, intfNameLen); |
| if (err != INET_NO_ERROR) |
| { |
| PrintArgError("%s: Invalid destination address: %s\n", progName, arg); |
| return false; |
| } |
| |
| if (!IPAddress::FromString(addr, DestIPAddr)) |
| { |
| PrintArgError("%s: Invalid destination address: %s\n", progName, arg); |
| return false; |
| } |
| |
| if (intfName != NULL) |
| { |
| err = InterfaceNameToId(intfName, DestIntf); |
| if (err != INET_NO_ERROR) |
| { |
| PrintArgError("%s: Invalid interface name: %s\n", progName, intfName); |
| return false; |
| } |
| } |
| |
| DestAddr = arg; |
| |
| return true; |
| } |
| |
| void HeartbeatSenderEventHandler(void *appState, WeaveHeartbeatSender::EventType eventType, const WeaveHeartbeatSender::InEventParam& inParam, WeaveHeartbeatSender::OutEventParam& outParam) |
| { |
| WeaveHeartbeatSender *sender = inParam.Source; |
| Binding *binding = sender->GetBinding(); |
| |
| switch (eventType) |
| { |
| case WeaveHeartbeatSender::kEvent_HeartbeatSent: |
| printf("Heartbeat sent to node %" PRIX64 ": state=%u\n", |
| binding->GetPeerNodeId(), sender->GetSubscriptionState()); |
| HeartbeatCount++; |
| break; |
| case WeaveHeartbeatSender::kEvent_HeartbeatFailed: |
| printf("Heartbeat FAILED to node %" PRIX64 ": state=%u, err=%s\n", |
| binding->GetPeerNodeId(), sender->GetSubscriptionState(), |
| ErrorStr(inParam.HeartbeatFailed.Reason)); |
| HeartbeatCount++; |
| break; |
| default: |
| WeaveHeartbeatSender::DefaultEventHandler(appState, eventType, inParam, outParam); |
| break; |
| } |
| } |
| |
| void BindingEventHandler(void *appState, Binding::EventType eventType, const Binding::InEventParam& inParam, Binding::OutEventParam& outParam) |
| { |
| switch (eventType) |
| { |
| case Binding::kEvent_PrepareRequested: |
| { |
| Binding::Configuration bindingConfig = inParam.Source->BeginConfiguration() |
| .Target_NodeId(DestNodeId) |
| .Transport_UDP() |
| .Transport_DefaultWRMPConfig(gWRMPOptions.GetWRMPConfig()) |
| .Security_None(); |
| if (DestAddr != NULL) |
| { |
| bindingConfig.TargetAddress_IP(DestIPAddr, DestPort, DestIntf); |
| } |
| outParam.PrepareRequested.PrepareError = bindingConfig.PrepareBinding(); |
| break; |
| } |
| default: |
| Binding::DefaultEventHandler(appState, eventType, inParam, outParam); |
| break; |
| } |
| } |