#!/usr/bin/env python2
# -*- coding: utf-8 -*-

# TRX Toolkit
# BTS <-> BB burst forwarding
#
# (C) 2017-2018 by Vadim Yanitskiy <axilirator@gmail.com>
#
# All Rights Reserved
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

import logging as log
import random

from data_msg import *

class BurstForwarder:
	""" Performs burst forwarding and preprocessing between MS and BTS.

	== Pass-filtering parameters

	BurstForwarder may drop or pass an UL/DL burst depending
	on the following parameters:

	  - bts_freq / bb_freq - the current BTS / MS frequency
	    that was set using RXTUNE control command. By default,
	    both freq. values are set to None, so nothing is being
	    forwarded (i.e. bursts are getting dropped).

	    FIXME: currently, we don't care about TXTUNE command
	    and transmit frequencies. It would be great to distinguish
	    between RX and TX frequencies for both BTS and MS.

	  - ts_pass_list - the list of active (i.e. configured)
	    timeslot numbers for the MS. A timeslot can be activated
	    or deactivated using SETSLOT control command from the MS.

	    FIXME: there is no such list for the BTS side.

	== Preprocessing and measurement simulation

	Since this is a virtual environment, we can simulate different
	parameters of a virtual RF interface:

	  - ToA (Timing of Arrival) - measured difference between expected
	    and actual time of burst arrival in units of 1/256 of GSM symbol
	    periods. A pair of both base and threshold values defines a range
	    of ToA value randomization:

	      DL: from (toa256_dl_base - toa256_dl_threshold)
	            to (toa256_dl_base + toa256_dl_threshold),
	      UL: from (toa256_ul_base - toa256_ul_threshold)
	            to (toa256_ul_base + toa256_ul_threshold).

	  - RSSI (Received Signal Strength Indication) - measured "power" of
	    the signal (per burst) in dBm. A pair of both base and threshold
	    values defines a range of RSSI value randomization:

	      DL: from (rssi_dl_base - rssi_dl_threshold)
	            to (rssi_dl_base + rssi_dl_threshold),
	      UL: from (rssi_ul_base - rssi_ul_threshold)
	            to (rssi_ul_base + rssi_ul_threshold).

	Please note that the randomization of both RSSI and ToA
	is optional, and can be enabled from the control interface.

	=== Timing Advance handling

	The BTS is using ToA measurements for UL bursts in order to calculate
	Timing Advance value, that is then indicated to a MS, which in its turn
	shall apply this value to the transmitted signal in order to compensate
	the delay. Basically, every burst is transmitted in advance defined by
	the indicated Timing Advance value. The valid range is 0..63, where
	each unit means one GSM symbol advance. The actual Timing Advance value
	is set using SETTA control command from MS. By default, it's set to 0.

	=== Path loss simulation - burst dropping

	In some cases, e.g. due to a weak signal or high interference, a burst
	can be lost, i.e. not detected by the receiver. This can also be
	simulated using FAKE_DROP command on both control interfaces:

	  - burst_{dl|ul}_drop_amount - the amount of DL/UL bursts
	      to be dropped (i.e. not forwarded towards the MS/BTS),

	  - burst_{dl|ul}_drop_period - drop every X DL/UL burst, e.g.
	    1 - drop every consequent burst, 2 - drop every second burst, etc.

	"""

	def __init__(self, bts_link, bb_link):
		self.bts_link = bts_link
		self.bb_link = bb_link

		# Init default parameters
		self.reset_dl()
		self.reset_ul()

	# Initialize (or reset to) default parameters for Downlink
	def reset_dl(self):
		# Unset current DL freq.
		self.bts_freq = None

		# Indicated RSSI / ToA values
		self.toa256_dl_base = 0
		self.rssi_dl_base = -60

		# RSSI / ToA randomization threshold
		self.toa256_dl_threshold = 0
		self.rssi_dl_threshold = 0

		# Path loss simulation (burst dropping)
		self.burst_dl_drop_amount = 0
		self.burst_dl_drop_period = 1

	# Initialize (or reset to) default parameters for Uplink
	def reset_ul(self):
		# Unset current DL freq.
		self.bb_freq = None

		# Indicated RSSI / ToA values
		self.rssi_ul_base = -70
		self.toa256_ul_base = 0

		# RSSI / ToA randomization threshold
		self.toa256_ul_threshold = 0
		self.rssi_ul_threshold = 0

		# Path loss simulation (burst dropping)
		self.burst_ul_drop_amount = 0
		self.burst_ul_drop_period = 1

		# Init timeslot filter (drop everything by default)
		self.ts_pass_list = []

		# Reset Timing Advance value
		self.ta = 0

	# Converts TA value from symbols to
	# units of 1/256 of GSM symbol periods
	def calc_ta256(self):
		return self.ta * 256

	# Calculates a random ToA value for Downlink bursts
	def calc_dl_toa256(self):
		# Check if randomization is required
		if self.toa256_dl_threshold is 0:
			return self.toa256_dl_base

		# Calculate a range for randomization
		toa256_min = self.toa256_dl_base - self.toa256_dl_threshold
		toa256_max = self.toa256_dl_base + self.toa256_dl_threshold

		# Generate a random ToA value
		toa256 = random.randint(toa256_min, toa256_max)

		return toa256

	# Calculates a random ToA value for Uplink bursts
	def calc_ul_toa256(self):
		# Check if randomization is required
		if self.toa256_ul_threshold is 0:
			return self.toa256_ul_base

		# Calculate a range for randomization
		toa256_min = self.toa256_ul_base - self.toa256_ul_threshold
		toa256_max = self.toa256_ul_base + self.toa256_ul_threshold

		# Generate a random ToA value
		toa256 = random.randint(toa256_min, toa256_max)

		return toa256

	# Calculates a random RSSI value for Downlink bursts
	def calc_dl_rssi(self):
		# Check if randomization is required
		if self.rssi_dl_threshold is 0:
			return self.rssi_dl_base

		# Calculate a range for randomization
		rssi_min = self.rssi_dl_base - self.rssi_dl_threshold
		rssi_max = self.rssi_dl_base + self.rssi_dl_threshold

		# Generate a random RSSI value
		return random.randint(rssi_min, rssi_max)

	# Calculates a random RSSI value for Uplink bursts
	def calc_ul_rssi(self):
		# Check if randomization is required
		if self.rssi_ul_threshold is 0:
			return self.rssi_ul_base

		# Calculate a range for randomization
		rssi_min = self.rssi_ul_base - self.rssi_ul_threshold
		rssi_max = self.rssi_ul_base + self.rssi_ul_threshold

		# Generate a random RSSI value
		return random.randint(rssi_min, rssi_max)

	# DL path loss simulation
	def path_loss_sim_dl(self, msg):
		# Burst dropping
		if self.burst_dl_drop_amount > 0:
			if msg.fn % self.burst_dl_drop_period == 0:
				log.info("Simulation: dropping DL burst (fn=%u %% %u == 0)"
					% (msg.fn, self.burst_dl_drop_period))
				self.burst_dl_drop_amount -= 1
				return None

		return msg

	# UL path loss simulation
	def path_loss_sim_ul(self, msg):
		# Burst dropping
		if self.burst_ul_drop_amount > 0:
			if msg.fn % self.burst_ul_drop_period == 0:
				log.info("Simulation: dropping UL burst (fn=%u %% %u == 0)"
					% (msg.fn, self.burst_ul_drop_period))
				self.burst_ul_drop_amount -= 1
				return None

		return msg

	# DL burst preprocessing
	def preprocess_dl_burst(self, msg):
		# Calculate both RSSI and ToA values
		msg.toa256 = self.calc_dl_toa256()
		msg.rssi = self.calc_dl_rssi()

	# UL burst preprocessing
	def preprocess_ul_burst(self, msg):
		# Calculate both RSSI and ToA values,
		# also apply Timing Advance
		msg.toa256 = self.calc_ul_toa256()
		msg.toa256 -= self.calc_ta256()
		msg.rssi = self.calc_ul_rssi()

	# Converts a L12TRX message to TRX2L1 message
	def transform_msg(self, msg_raw):
		# Attempt to parse a message
		try:
			msg_l12trx = DATAMSG_L12TRX()
			msg_l12trx.parse_msg(bytearray(msg_raw))
		except:
			log.error("Dropping unhandled DL message...")
			return None

		# Compose a new message for L1
		return msg_l12trx.gen_trx2l1()

	# Downlink handler: BTS -> BB
	def bts2bb(self):
		# Read data from socket
		data, addr = self.bts_link.sock.recvfrom(512)

		# BB is not connected / tuned
		if self.bb_freq is None:
			return None

		# Freq. filter
		if self.bb_freq != self.bts_freq:
			return None

		# Process a message
		msg = self.transform_msg(data)
		if msg is None:
			return None

		# Timeslot filter
		if msg.tn not in self.ts_pass_list:
			return None

		# Path loss simulation
		msg = self.path_loss_sim_dl(msg)
		if msg is None:
			return None

		# Burst preprocessing
		self.preprocess_dl_burst(msg)

		# Validate and generate the payload
		payload = msg.gen_msg()

		# Append two unused bytes at the end
		# in order to keep the compatibility
		payload += bytearray(2)

		# Send burst to BB
		self.bb_link.send(payload)

	# Uplink handler: BB -> BTS
	def bb2bts(self):
		# Read data from socket
		data, addr = self.bb_link.sock.recvfrom(512)

		# BTS is not connected / tuned
		if self.bts_freq is None:
			return None

		# Freq. filter
		if self.bb_freq != self.bts_freq:
			return None

		# Process a message
		msg = self.transform_msg(data)
		if msg is None:
			return None

		# Timeslot filter
		if msg.tn not in self.ts_pass_list:
			log.warning("TS %u is not configured, dropping UL burst..." % msg.tn)
			return None

		# Path loss simulation
		msg = self.path_loss_sim_ul(msg)
		if msg is None:
			return None

		# Burst preprocessing
		self.preprocess_ul_burst(msg)

		# Validate and generate the payload
		payload = msg.gen_msg()

		# Append two unused bytes at the end
		# in order to keep the compatibility
		payload += bytearray(2)

		# Send burst to BTS
		self.bts_link.send(payload)
