text: #!/usr/bin/env python
  
  
  #
  #    Copyright (c) 2016-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
  #       Implementes WeaveiSecurityPing class that tests Weave Security Ping(CASE, PASE) among Weave Nodes.
  #
  
  import os
  import sys
  import time
  
  from happy.ReturnMsg import ReturnMsg
  from happy.Utils import *
  from happy.HappyNode import HappyNode
  from happy.HappyNetwork import HappyNetwork
  
  from WeaveTest import WeaveTest
  import WeaveUtilities
  import plugins.plaid.Plaid as Plaid
  
  
  options = {}
  options["client"] = None
  options["server"] = None
  options["count"] = None
  options["udp"] = True
  options["wrmp"] = False
  options["tcp"] = False
  options["quiet"] = False
  options["CASE"] = True
  options["PASE"] = False
  options["group_key"] = False
  options["group_key_id"] = "0x00005536"
  options["tap"] = None
  options["client_faults"] = None
  options["server_faults"] = None
  options["iterations"] = None
  options["plaid"] = False
  options["test_tag"] = ""
  options["strace"] = False
  options["use_persistent_storage"] = True
  
  
  def option():
      return options.copy()
  
  
  class WeaveSecurityPing(HappyNode, HappyNetwork, WeaveTest):
      """
      weave-security-ping [-h --help] [-q --quiet] [-o --origin <NAME>] [-s --server <NAME>]
             [-c --count <NUMBER>] [-u --udp] [-t --tcp] [-C --CASE] [-P --PASE] [-G --group-key [--group-key-id <key-id>]]
             [-p --tap <TAP_INTERFACE>] [--client_faults <fault-injection configuration>]
             [--server_faults <fault-injection configuration>]
  
      commands:
           $ weave-security-ping -o node01 -s node02 -u -C
                 weave ping test between node01 and node02 via udp using CASE authenticate
  
           $ weave-security-ping -o node01 -s node02 -t -C
                 weave ping test between node01 and node02 via tcp using CASE authenticate
  
           $ weave-security-ping -o node01 -s node02 -t -P
                 weave ping test between node01 and node02 via tcp using PASE authenticate
  
           $ weave-security-ping -o node01 -s node02 -u --wrmp -G
                 weave ping test between node01 and node02 via wrmp over udp using group-key encryption,
                 using the default 0x00005536 key-id
  
           $ weave-security-ping -o node01 -s node02 -u --wrmp -G --group-key-id 0x00005536
                 weave ping test between node01 and node02 via wrmp over udp using group-key encryption
  
           $ weave-security-ping -o node01 -s node02 -t -C --server_faults Weave_CASEKeyConfirm_s0_f1
                 weave ping test between node01 and node02 via tcp using CASE authenticate;
                 the server will fail the CASE key confirmation step
  
      return:
          0-100   percentage of the lost packets
  
      """
      def __init__(self, opts = options):
          HappyNode.__init__(self)
          HappyNetwork.__init__(self)
          WeaveTest.__init__(self)
  
          self.quiet = opts["quiet"]
          self.tcp = opts["tcp"]
          self.udp = opts["udp"]
          self.wrmp = opts["wrmp"]
          self.case = opts["CASE"]
          self.pase = opts["PASE"]
          self.group_key = opts["group_key"]
          self.group_key_id = opts["group_key_id"]
          self.count = opts["count"]
          self.client = opts["client"]
          self.server = opts["server"]
          self.tap = opts["tap"]
          self.client_faults = opts["client_faults"]
          self.server_faults = opts["server_faults"]
          self.iterations = opts["iterations"]
          self.strace = opts["strace"]
          self.use_persistent_storage = opts["use_persistent_storage"]
  
          self.no_service = False
  
          self.server_process_tag = "WEAVE-SECURITY-PING-SERVER" + opts["test_tag"]
          self.client_process_tag = "WEAVE-SECURITY-PING-CLIENT" + opts["test_tag"]
          self.plaid_server_process_tag = "PLAID_SERVER" + opts["test_tag"]
  
          self.client_node_id = None
          self.server_node_id = None
  
          plaid_opts = Plaid.default_options()
          plaid_opts['quiet'] = self.quiet
          self.plaid_server_node_id = 'node03'
          plaid_opts['server_node_id'] = self.plaid_server_node_id
          plaid_opts['num_clients'] = 2
          plaid_opts['server_ip_address'] = self.getNodeWeaveIPAddress(self.plaid_server_node_id)
          plaid_opts['interface'] = 'wlan0'
          self.plaid = Plaid.Plaid(plaid_opts)
  
          self.use_plaid = opts["plaid"]
          if opts["plaid"] == "auto":
              if self.server == "service":
                  # can't use plaid when talking to an external service
                  self.use_plaid = False
              else:
                  self.use_plaid = self.plaid.isPlaidConfigured()
  
  
      def __pre_check(self):
          # Check if Weave Ping client node is given.
          if self.client == None:
              emsg = "Missing name or address of the Weave Ping client node."
              self.logger.error("[localhost] WeaveSecurityPing: %s" % (emsg))
              sys.exit(1)
  
          # Check if Weave Ping server node is given.
          if self.server == None:
              emsg = "Missing name or address of the Weave Ping server node."
              self.logger.error("[localhost] WeaveSecurityPing: %s" % (emsg))
              sys.exit(1)
  
          # Make sure that fabric was created
          if self.getFabricId() == None:
              emsg = "Weave Fabric has not been created yet."
              self.logger.error("[localhost] WeaveSecurityPing: %s" % (emsg))
              sys.exit(1)
  
          if self.count != None and self.count.isdigit():
              self.count = int(float(self.count))
          else:
              self.count = 1
  
          # Check if Weave Ping client node exists.
          if self._nodeExists(self.client):
              self.client_node_id = self.client
  
          # Check if Weave Ping server node exists.
          if self._nodeExists(self.server):
              self.server_node_id = self.server
  
          # Check if client is provided in a form of IP address
          if self.isIpAddress(self.client):
              self.client_node_id = self.getNodeIdFromAddress(self.client)
  
          # Check if server is provided in a form of IP address
          if self.isIpAddress(self.server):
              self.no_service = True
              self.server_ip = self.server
              self.server_weave_id = self.IPv6toWeaveId(self.server)
          else:
              # Check if server is a true clound service instance
              if self.getNodeType(self.server) == self.node_type_service:
                  self.no_service = True
  
          if self.client_node_id == None:
              emsg = "Unknown identity of the client node."
              self.logger.error("[localhost] WeaveSecurityPing: %s" % (emsg))
              sys.exit(1)
  
          if not self.no_service and self.server_node_id == None:
              emsg = "Unknown identity of the server node."
              self.logger.error("[localhost] WeaveSecurityPing: %s" % (emsg))
              sys.exit(1)
  
          if self.getNodeType(self.client_node_id) == "service":
              self.client_ip = self.getServiceWeaveIPAddress("Echo", self.client_node_id)
              self.client_weave_id = self.getServiceWeaveID("Echo", self.client_node_id)
          else:
              self.client_ip = self.getNodeWeaveIPAddress(self.client_node_id)
              self.client_weave_id = self.getWeaveNodeID(self.client_node_id)
  
          if self.getNodeType(self.server_node_id) == "service":
              self.server_ip = self.getServiceWeaveIPAddress("Echo", self.server_node_id)
              self.server_weave_id = self.getServiceWeaveID("Echo", self.server_node_id)
          else:
              if not self.no_service:
                  self.server_ip = self.getNodeWeaveIPAddress(self.server_node_id)
                  self.server_weave_id = self.getWeaveNodeID(self.server_node_id)
  
          # Check if all unknowns were found
  
          if self.client_ip == None:
              emsg = "Could not find IP address of the client node."
              self.logger.error("[localhost] WeaveSecurityPing: %s" % (emsg))
              sys.exit(1)
  
          if self.server_ip == None:
              emsg = "Could not find IP address of the server node."
              self.logger.error("[localhost] WeaveSecurityPing: %s" % (emsg))
              sys.exit(1)
  
          if self.client_weave_id == None:
              emsg = "Could not find Weave node ID of the client node."
              self.logger.error("[localhost] WeaveSecurityPing: %s" % (emsg))
              sys.exit(1)
  
          if not self.no_service and self.server_weave_id == None:
              emsg = "Could not find Weave node ID of the server node."
              self.logger.error("[localhost] WeaveSecurityPing: %s" % (emsg))
              sys.exit(1)
  
          num_security_modes = 0
          if self.case == True:
              num_security_modes += 1
          if self.pase == True:
              num_security_modes += 1
          if self.group_key == True:
              num_security_modes += 1
  
          if num_security_modes > 1:
              emsg = "Only one security mode can be enabled."
              self.logger.error("[localhost] WeaveSecurityPing: %s" % (emsg))
              sys.exit(1)
  
  
  
      def __process_results(self, output):
          # search for '%' e.g.  1/1(100.00%)
          loss_percentage = None
  
          # only consider the last iteration
          iterations = output.split("Iteration ")
  
          output = iterations[-1]
  
          for line in output.split("\n"):
              if not "%" in line:
                  continue
  
              for token in line.split():
                  if "%" in token:
                      loss_percentage = token.strip().split("(")[1]
                      loss_percentage = loss_percentage.translate(None, "()%")
  
          if loss_percentage == None:
              # hmmm, we haven't found our line
              loss_percentage = 100
          else:
              loss_percentage = float(loss_percentage)
              loss_percentage = 100 - loss_percentage
  
          if self.quiet == False:
              print "weave-security-ping from node %s (%s) to node %s (%s) : " % \
                  (self.client_node_id, self.client_ip,
                   self.server_node_id, self.server_ip),
  
              if loss_percentage == 0:
                  print hgreen("%d%% packet loss" % (loss_percentage))
              else:
                  print hred("%d%% packet loss" % (loss_percentage))
  
          return (loss_percentage, output) # indicate the loss for each client
  
      def __start_plaid_server(self):
  
          self.plaid.startPlaidServerProcess()
  
          emsg = "plaid-server should be running."
          self.logger.debug("[%s] WeaveSecurityPing: %s" % (self.plaid_server_node_id, emsg))
  
      def __start_server_side(self):
          if self.no_service:
              return
  
          cmd = self.getWeavePingPath()
          if not cmd:
              return
  
          cmd += " --debug-resource-usage --print-fault-counters --idle-session-timeout 15000 --session-establishment-timeout 5000"
  
          if self.tcp:
              cmd += " --tcp"
          else:
              # default is UDP
              cmd += " --udp"
              if self.wrmp:
                  cmd += " --wrmp"
  
          if self.pase:
              cmd += " --pase --pairing-code test"
          elif self.group_key:
              cmd += " --group-enc --group-enc-key-id " + self.group_key_id
  
          if self.tap:
              cmd += " --tap-device " + self.tap
  
          if self.server_faults:
              cmd += " --faults " + self.server_faults
  
          if self.server_faults or self.client_faults:
              cmd += " --extra-cleanup-time 10000"
  
          custom_env = {}
          if self.use_plaid:
              custom_env = self.plaid.getPlaidClientLibEnv(self.server_node_id)
  
          self.start_simple_weave_server(cmd, self.server_ip,
               self.server_node_id, self.server_process_tag, strace=self.strace, env=custom_env, use_persistent_storage=self.use_persistent_storage)
  
  
      def __start_client_side(self, pase_fail = False):
          cmd = self.getWeavePingPath()
          if not cmd:
              return
  
          cmd += " --debug-resource-usage --print-fault-counters --session-establishment-timeout 5000"
  
          if self.tcp:
              cmd += " --tcp"
          else:
              # default is UDP
              cmd += " --udp"
              if self.wrmp:
                  cmd += " --wrmp"
  
          if self.pase:
              if pase_fail:
                  cmd += " --pase --pairing-code fail"
              else:
                  cmd += " --pase --pairing-code test"
          elif self.case:
              cmd += " --case"
          elif self.group_key:
              cmd += " --group-enc --group-enc-key-id 0x00005536"
  
          cmd += " --count " + str(self.count)
  
          if self.tap:
              cmd += " --tap-device " + self.tap
  
          if self.client_faults:
              cmd += " --faults " + self.client_faults
  
          if self.iterations:
              cmd += " --iterations " + str(self.iterations)
  
          if self.server_faults or self.client_faults:
              cmd += " --extra-cleanup-time 10000"
  
          custom_env = {}
          if self.use_plaid:
              custom_env = self.plaid.getPlaidClientLibEnv(self.client_node_id)
  
          self.start_simple_weave_client(cmd, self.client_ip,
              self.server_ip, self.server_weave_id,
              self.client_node_id, self.client_process_tag, strace=self.strace, env=custom_env, use_persistent_storage=self.use_persistent_storage)
  
  
      def __wait_for_client(self):
          self.wait_for_test_to_end(self.client_node_id, self.client_process_tag)
  
  
      def __stop_plaid_server(self):
          self.plaid.stopPlaidServerProcess()
  
  
      def __stop_server_side(self):
          if self.no_service:
              return
  
          if (self.udp or self.wrmp) and (self.case):
              # Give enough time to the server to expire the session key at the end of the test.
              # the idle session timer is configured to 15 seconds; it has to fire twice to free the idle key.
              self.wait_for_test_time_secs(self.server_node_id, self.server_process_tag, secs=40)
  
          self.stop_weave_process(self.server_node_id, self.server_process_tag)
  
  
      def run(self):
          self.logger.debug("[localhost] WeaveSecurityPing: Run.")
  
          self.__pre_check()
  
          if self.use_plaid:
              self.__start_plaid_server()
  
          self.__start_server_side()
  
          emsg = "WeaveSecurityPing %s should be running." % (self.server_process_tag)
          self.logger.debug("[%s] WeaveSecurityPing: %s" % (self.server_node_id, emsg))
  
          self.__start_client_side(False)
          self.__wait_for_client()
  
          client_output_value, client_output_data = \
              self.get_test_output(self.client_node_id, self.client_process_tag, True)
          if self.strace:
              client_strace_value, client_strace_data = \
                  self.get_test_strace(self.client_node_id, self.client_process_tag, True)
  
  
          if self.no_service:
              server_output_data = ""
              server_strace_data = ""
          else:
              self.__stop_server_side()
              if self.use_plaid:
                  self.__stop_plaid_server()
              server_output_value, server_output_data = \
                  self.get_test_output(self.server_node_id, self.server_process_tag, True)
              if self.strace:
                  server_strace_value, server_strace_data = \
                      self.get_test_strace(self.server_node_id, self.server_process_tag, True)
  
          avg, results = self.__process_results(client_output_data)
          client_parser_error, client_leak_detected = WeaveUtilities.scan_for_leaks_and_parser_errors(client_output_data)
          server_parser_error, server_leak_detected = WeaveUtilities.scan_for_leaks_and_parser_errors(server_output_data)
  
          data = {}
          data["client_output"] = client_output_data
          data["server_output"] = server_output_data
          if self.strace:
              data["client_strace"] = client_strace_data
              data["server_strace"] = server_strace_data
          data["other_failure"] = client_parser_error or client_leak_detected or server_parser_error or server_leak_detected
  
          self.logger.debug("[localhost] WeaveSecurityPing: Done.")
  
          return ReturnMsg(avg, data)
  
  