text: # Copyright 2017 British Broadcasting Corporation
  #
  # 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.
  
  from __future__ import absolute_import
  
  import os
  import json
  import copy
  from .abstractDevice import AbstractDevice
  from .constants import SCHEMA_LOCAL
  
  __location__ = os.path.realpath(
      os.path.join(os.getcwd(), os.path.dirname(__file__)))
  
  __tp__ = 'transport_params'
  
  
  class RtpSender(AbstractDevice):
  
      def __init__(self, logger, legs=1):
          """All IP and Port parameters should be tuples containing one
          entry for each leg. In single leg mode the second entry of each
          tuple may be set to 'None'"""
          super(RtpSender, self).__init__(logger)
  
          if int(legs) < 1 or int(legs) > 2:
              raise ValueError("Reciever may only support 1 or 2 legs")
  
          self.schemaPath = SCHEMA_LOCAL
  
          self.sourceSelector = self.defaultSourceSelector
          self.destinationSelector = self.defaultDestinationSelector
  
          self.legs = legs
          self.staged[__tp__] = list(range(legs))
          self.staged[__tp__][0] = {}
          if legs == 2:
              self.staged[__tp__][1] = {}
  
          # Set collections of parameters
          self.generalParams = [
              'source_ip', 'destination_ip', 'destination_port', 'source_port', 'rtp_enabled'
          ]
          self.fecParams = [
              'fec_enabled', 'fec_destination_ip', 'fec_mode', 'fec_type',
              'fec_block_width', 'fec_block_height', 'fec1D_destination_port',
              'fec1D_source_port', 'fec2D_destination_port', 'fec2D_source_port'
          ]
          self.rtcpParams = [
              'rtcp_enabled', 'rtcp_destination_ip', 'rtcp_destination_port', 'rtcp_source_port'
          ]
  
          # Set up default values as per spec.
          for leg in range(0, legs):
              self.staged[__tp__][leg]['source_ip'] = "auto"
              self.staged[__tp__][leg]['destination_ip'] = "auto"
              self.staged[__tp__][leg]['destination_port'] = 5004
              self.staged[__tp__][leg]['source_port'] = "auto"
              self.staged[__tp__][leg]['fec_enabled'] = False
              self.staged[__tp__][leg]['fec_destination_ip'] = "auto"
              self.staged[__tp__][leg]['fec_mode'] = "1D"
              self.staged[__tp__][leg]['fec_type'] = "XOR"
              self.staged[__tp__][leg]['fec_block_width'] = 4
              self.staged[__tp__][leg]['fec_block_height'] = 4
              self.staged[__tp__][leg]['fec1D_destination_port'] = "auto"
              self.staged[__tp__][leg]['fec2D_destination_port'] = "auto"
              self.staged[__tp__][leg]['fec1D_source_port'] = "auto"
              self.staged[__tp__][leg]['fec2D_source_port'] = "auto"
              self.staged[__tp__][leg]['rtcp_enabled'] = False
              self.staged[__tp__][leg]['rtcp_destination_ip'] = "auto"
              self.staged[__tp__][leg]['rtcp_destination_port'] = "auto"
              self.staged[__tp__][leg]['rtcp_source_port'] = "auto"
              self.staged[__tp__][leg]['rtp_enabled'] = True
          self.staged['receiver_id'] = None
          self.staged['master_enable'] = False
          self.transportFile = ""
  
          self._enableRtcp = True
          self._enableFec = True
          self._initConstraints()
          self.activateStaged()
  
      def supportRtcp(self, support=True):
          self._enableRtcp = support
  
      def supportFec(self, support=True):
          self._enableFec = support
  
      def _initConstraints(self):
          self.constraints = []
          for leg in range(0, self.legs):
              self.constraints.append({})
              for param in self.generalParams:
                  self.constraints[leg][param] = {}
              for param in self.fecParams:
                  self.constraints[leg][param] = {}
              for param in self.rtcpParams:
                  self.constraints[leg][param] = {}
          self._initSourceConstraints()
  
      def _initSourceConstraints(self):
          for leg in range(0, self.legs):
              self.constraints[leg]['source_ip']['enum'] = ["auto"]
  
      def addInterface(self, addr, leg=0):
          """Used to add allowed source IPs"""
          # Check supplied IP is valid
          if self._checkIsIpv4(addr) or self._checkIsIpv6(addr):
              self.constraints[leg]['source_ip']['enum'].append(addr)
          else:
              self.logger.writeWarning("Driver tried to provide an invalid source IP: {}".format(addr))
              raise ValueError("Invalid source IP added by driver")
  
      def setDestinationSelector(self, selector):
          """Set the method used to select the destination IP
          address. The method must accept two parameters, which are
          the staged parameter set and the 0 based leg number."""
          self.destinationSelector = selector
  
      def setSourceSelector(self, selector):
          """Set the method used to select the source IP address.
          The method must accept two parameters, which are
          the staged parameter set and the 0 based leg number."""
          self.sourceSelector = selector
  
      def defaultDestinationSelector(self, parameterSet, leg):
          """This method provides a 'dummy' loopback destination
          address in the absense of a method being supplied by the driver.
          It should only ever be used for testing purposes, drivers should
          always provide their own method. As such this method will always log
          a warning when it runs"""
          self.logger.writeWarning("No destination selector has been provided by \
          the driver to the RTP Sender. The loopback will be used. This is a bug \
          in anything other than a test or example system")
          return "127.0.0.1"
  
      def defaultSourceSelector(self, parameterSet, leg):
          """In the absense of the driver having supplied an source
          resolution method this method just returns the first source IP
          on the list"""
          try:
              return self.constraints[leg]['source_ip']['enum'][1]
          except IndexError:
              self.logger.writeError("Driver has not supplied a source for the receiver, cannot resolve source ip")
              return None
  
      def resolveParameters(self, parameterSet):
          """For all parameters that may be set to auto run through and resolve
          their actual values"""
          toReturn = copy.deepcopy(parameterSet)
          resolveLookup = {
              "source_ip": self.sourceSelector,
              "destination_ip": self.destinationSelector,
              "source_port": self._resolveSourcePort,
              "destination_port": self._resolveDestinationPort,
              "fec_destination_ip": self._resolveFecIp,
              "fec1D_destination_port": self._resolveFec1DDestPort,
              "fec2D_destination_port": self._resolveFec2DDestPort,
              "fec1D_source_port": self._resolveFec1DSrcPort,
              "fec2D_source_port": self._resolveFec2DSrcPort,
              "rtcp_destination_ip": self._resolveRtcpDestIp,
              "rtcp_source_port": self._resolveRtcpSrcPort,
              "rtcp_destination_port": self._resolveRtcpDestPort
          }
          order = ["source_ip", "destination_ip", "source_port",
                   "destination_port", "fec_destination_ip",
                   "fec1D_destination_port", "fec2D_destination_port",
                   "fec1D_source_port", "fec2D_source_port", "rtcp_source_port",
                   "rtcp_destination_ip", "rtcp_destination_port"]
          for leg in range(0, len(toReturn[__tp__])):
              for key in order:
                  if parameterSet[__tp__][leg][key] == "auto":
                      toReturn[__tp__][leg][key] = resolveLookup[key](toReturn[__tp__], leg)
          return toReturn
  
      def _resolveRtcpDestPort(self, parameterSet, leg):
          return parameterSet[leg]['destination_port'] + 1
  
      def _resolveRtcpSrcPort(self, parameterSet, leg):
          return parameterSet[leg]['source_port'] + 1
  
      def _resolveRtcpDestIp(self, parameterSet, leg):
          return parameterSet[leg]['destination_ip']
  
      def _resolveFec1DDestPort(self, parameterSet, leg):
          return parameterSet[leg]['destination_port'] + 2
  
      def _resolveFec2DDestPort(self, parmaeterSet, leg):
          return parmaeterSet[leg]['destination_port'] + 4
  
      def _resolveFec1DSrcPort(self, parameterSet, leg):
          return parameterSet[leg]['source_port'] + 2
  
      def _resolveFec2DSrcPort(self, parmaeterSet, leg):
          return parmaeterSet[leg]['source_port'] + 4
  
      def _resolveFecIp(self, parameterSet, leg):
          return parameterSet[leg]['destination_ip']
  
      def _resolveSourcePort(self, parameterSet, keg):
          return 5004
  
      def _resolveDestinationPort(self, parameterSet, leg):
          return 5004
  
      def _assembleJsonDescription(self, params):
          """Assemble a dictionary only of parameters required currently"""
          toReturn = copy.deepcopy(params)
          for leg in range(0, self.legs):
              if not self._enableFec:
                  for param in self.fecParams:
                      toReturn[__tp__][leg].pop(param)
              if not self._enableRtcp:
                  for param in self.rtcpParams:
                      toReturn[__tp__][leg].pop(param)
          toReturn.pop('sender_id')
          return toReturn
  
      def getParamsSchema(self, leg=0):
          """Get the schema of the transport params"""
          schema = self.schemaPath + 'v1.0_sender_transport_params_rtp.json'
          try:
              schemaPath = os.path.join(__location__, schema)
              with open(schemaPath) as json_data:
                  obj = json.loads(json_data.read())
          except EnvironmentError:
              raise IOError('failed to load schema file')
          params = obj['items']['properties']
          if not self._enableFec:
              for key in self.fecParams:
                  if key in params:
                      params.pop(key)
          if not self._enableRtcp:
              for key in self.rtcpParams:
                  if key in params:
                      params.pop(key)
          # Merge in extra requirements required by constraints
          for key, entry in params.items():
              if key in self.constraints[leg]:
                  entry.update(self.constraints[leg][key])
          obj['items']['properties'] = params
          return obj
  
      def getConstraints(self):
          toReturn = copy.deepcopy(self.constraints)
          for leg in range(0, self.legs):
              for key, value in self.constraints[leg].items():
                  if not self._enableFec:
                      if key in self.fecParams:
                          toReturn[leg].pop(key)
                  if not self._enableRtcp:
                      if key in self.rtcpParams:
                          toReturn[leg].pop(key)
                  if value == "auto":
                      toReturn[leg].pop(key)
              toReturn[leg]['source_ip']['enum'].remove("auto")
          return toReturn
  
      def getActiveTransportFileURL(self):
          return self.transportFile
  
      def getActiveReceiverID(self):
          return self.active['receiver_id']
  
      def getTransportType(self):
          return "rtp"
  
      def _setTp(self, value, field, leg):
          q = {__tp__: [{}, {}]}
          q[__tp__][leg][field] = value
          return self.patch(q)
  