"""
Border Gateway Protocol.
"""
import logging

from packetracer import packetracer, triggerlist
from packetracer.structcbs import unpack_H

logger = logging.getLogger("packetracer")

# Border Gateway Protocol 4 - RFC 4271
# Communities Attribute - RFC 1997
# Capabilities - RFC 3392
# Route Refresh - RFC 2918
# Route Reflection - RFC 4456
# Confederations - RFC 3065
# Cease Subcodes - RFC 4486
# NOPEER Community - RFC 3765
# Multiprotocol Extensions - 2858

# Message Types
OPEN				= 1
UPDATE				= 2
NOTIFICATION			= 3
KEEPALIVE			= 4
ROUTE_REFRESH			= 5

# Attribute Types
ORIGIN				= 1
AS_PATH				= 2
NEXT_HOP			= 3
MULTI_EXIT_DISC			= 4
LOCAL_PREF			= 5
ATOMIC_AGGREGATE		= 6
AGGREGATOR			= 7
COMMUNITIES			= 8
ORIGINATOR_ID			= 9
CLUSTER_LIST			= 10
MP_REACH_NLRI			= 14
MP_UNREACH_NLRI			= 15

# Origin Types
ORIGIN_IGP			= 0
ORIGIN_EGP			= 1
INCOMPLETE			= 2

# AS Path Types
AS_SET				= 1
AS_SEQUENCE			= 2
AS_CONFED_SEQUENCE		= 3
AS_CONFED_SET			= 4

# Reserved Communities Types
NO_EXPORT			= 0xFFFFFF01
NO_ADVERTISE			= 0xFFFFFF02
NO_EXPORT_SUBCONFED		= 0xFFFFFF03
NO_PEER				= 0xFFFFFF04

# Common AFI types
AFI_IPV4			= 1
AFI_IPV6			= 2

# Multiprotocol SAFI types
SAFI_UNICAST			= 1
SAFI_MULTICAST			= 2
SAFI_UNICAST_MULTICAST		= 3

# OPEN Message Optional Parameters
AUTHENTICATION			= 1
CAPABILITY			= 2

# Capability Types
CAP_MULTIPROTOCOL		= 1
CAP_ROUTE_REFRESH		= 2

# NOTIFICATION Error Codes
MESSAGE_HEADER_ERROR		= 1
OPEN_MESSAGE_ERROR		= 2
UPDATE_MESSAGE_ERROR		= 3
HOLD_TIMER_EXPIRED		= 4
FSM_ERROR			= 5
CEASE				= 6

# Message Header Error Subcodes
CONNECTION_NOT_SYNCHRONIZED	= 1
BAD_MESSAGE_LENGTH		= 2
BAD_MESSAGE_TYPE		= 3

# OPEN Message Error Subcodes
UNSUPPORTED_VERSION_NUMBER	= 1
BAD_PEER_AS			= 2
BAD_BGP_IDENTIFIER		= 3
UNSUPPORTED_OPTIONAL_PARAMETER	= 4
AUTHENTICATION_FAILURE		= 5
UNACCEPTABLE_HOLD_TIME		= 6
UNSUPPORTED_CAPABILITY		= 7

# UPDATE Message Error Subcodes
MALFORMED_ATTRIBUTE_LIST	= 1
UNRECOGNIZED_ATTRIBUTE		= 2
MISSING_ATTRIBUTE		= 3
ATTRIBUTE_FLAGS_ERROR		= 4
ATTRIBUTE_LENGTH_ERROR		= 5
INVALID_ORIGIN_ATTRIBUTE	= 6
AS_ROUTING_LOOP			= 7
INVALID_NEXT_HOP_ATTRIBUTE	= 8
OPTIONAL_ATTRIBUTE_ERROR	= 9
INVALID_NETWORK_FIELD		= 10
MALFORMED_AS_PATH		= 11

# Cease Error Subcodes
MAX_NUMBER_OF_PREFIXES_REACHED	= 1
ADMINISTRATIVE_SHUTDOWN		= 2
PEER_DECONFIGURED		= 3
ADMINISTRATIVE_RESET		= 4
CONNECTION_REJECTED		= 5
OTHER_CONFIGURATION_CHANGE	= 6
CONNECTION_COLLISION_RESOLUTION	= 7
OUT_OF_RESOURCES		= 8


class BGP(packetracer.Packet):
	__hdr__ = (
		("marker", "16s", b"\xff" * 16),
		("len", "H", 0),
		("type", "B", OPEN)
	)

	def _dissect(self, buf):
		htype = buf[18]
		self._init_handler(htype, buf[19:])
		return 19

	class Open(packetracer.Packet):
		__hdr__ = (
			("v", "B", 4),
			("asn", "H", 0),
			("holdtime", "H", 0),
			("identifier", "I", 0),
			("param_len", "B", 0),
			("params", None, triggerlist.TriggerList)
		)

		def _dissect(self, buf):
			#logger.debug("parsing Parameter")
			pcount = buf[9]
			off = 10

			while pcount > 0:
				plen = buf[off + 2]
				param = self.Parameter(buf[off:off + plen])
				self.params.append(param)
				pcount -= 1
				off += plen
			return off

		class Parameter(packetracer.Packet):
			__hdr__ = (
				("type", "B", 0),
				("len", "B", 0)
			)

	class Update(packetracer.Packet):
		__hdr__ = (
			("withdrawnlen", "H", 0),
			("pathlen", "H", 0),
			("wroutes", None, triggerlist.TriggerList),
			("pathattrs", None, triggerlist.TriggerList),
			("anncroutes", None, triggerlist.TriggerList),
		)

		def _dissect(self, buf):
			# Withdrawn Routes
			#logger.debug("Dissecting BGP update")
			off = 4
			off_end = off + unpack_H(buf[:2])[0]

			while off < off_end:
				rlen = 3 + 0
				route = Route(buf[off:])
				self.wroutes.append(route)
				off += rlen

			# Path attributes
			off_end = off + unpack_H(buf[2:4])[0]
			#logger.debug("Unpacking attributes")

			while off < off_end:
				alen = 3 + buf[off + 2]
				#logger.debug("bytes for attribute: %r" % buf[off: off + alen])
				attr = BGP.Update.Attribute(buf[off: off + alen])
				self.pathattrs.append(attr)
				off += alen

			# Announced routes
			off_end = len(buf)
			#logger.debug("Unpacking routes")

			while off + 3 <= off_end:
				rlen = 3 + 0
				#logger.debug("bytes for route: %r" % buf[off:off + rlen])
				route = Route(buf[off: off + rlen])
				self.anncroutes.append(route)
				off += rlen
			#logger.debug("Finished, off=%d off_end=%d" % (off, off_end))
			return off

		class Attribute(packetracer.Packet):
			__hdr__ = (
				("flags", "B", 0),
				("type", "B", 0),
				("len", "B", 0)
			)

			def __get_o(self):
				return (self.flags >> 7) & 0x1

			def __set_o(self, o):
				self.flags = (self.flags & ~0x80) | ((o & 0x1) << 7)
			optional = property(__get_o, __set_o)

			def __get_t(self):
				return (self.flags >> 6) & 0x1

			def __set_t(self, t):
				self.flags = (self.flags & ~0x40) | ((t & 0x1) << 6)
			transitive = property(__get_t, __set_t)

			def __get_p(self):
				return (self.flags >> 5) & 0x1

			def __set_p(self, p):
				self.flags = (self.flags & ~0x20) | ((p & 0x1) << 5)
			partial = property(__get_p, __set_p)

			def __get_e(self):
				return (self.flags >> 4) & 0x1

			extended_length = property(__get_e)

			def _dissect(self, buf):
				if len(buf) > 3:
					try:
						atype = buf[2]
						type_instance = BGP.Update.Attribute._switch_type_attribute[atype](buf[3:])
						self._set_bodyhandler(type_instance)
						# any exception will lead to: body = raw bytes
					except Exception:
						#logger.debug("BGP > Update > Attribute failed to set handler: %s" % e)
						pass
				return 3

			class Origin(packetracer.Packet):
				__hdr__ = (
					("type", "B", ORIGIN_IGP),
				)

			class ASPath(packetracer.Packet):
				__hdr__ = (
					("segments", None, triggerlist.TriggerList),
				)

				def _dissect(self, buf):
					off = 1
					buflen = len(buf)

					while off < buflen:
						seglen = buf[off + 2]
						seg = self.ASPathSegment(buf[off + 1:seglen])
						self.segments.append(seg)
						off += seglen
					return off

				class ASPathSegment(packetracer.Packet):
					__hdr__ = (
						("type", "B", 0),
						("len", "B", 0)
					)

			class NextHop(packetracer.Packet):
				__hdr__ = (
					("ip", "I", 0),
				)

			class MultiExitDisc(packetracer.Packet):
				__hdr__ = (
					("value", "I", 0),
				)

			class LocalPref(packetracer.Packet):
				__hdr__ = (
					("value", "I", 0),
				)

			class AtomicAggregate(packetracer.Packet):
				pass

			class Aggregator(packetracer.Packet):
				__hdr__ = (
					("asn", "H", 0),
					("ip", "I", 0)
				)

			class OriginatorID(packetracer.Packet):
				__hdr__ = (
					("value", "I", 0),
				)

			class ClusterList(packetracer.Packet):
				pass

			class MPReachNLRI(packetracer.Packet):
				__hdr__ = (
					("afi", "H", AFI_IPV4),
					("safi", "B", SAFI_UNICAST),
				)

			class MPUnreachNLRI(packetracer.Packet):
				__hdr__ = (
					("afi", "H", AFI_IPV4),
					("safi", "B", SAFI_UNICAST),
				)

			class Communitie(packetracer.Packet):
				pass

			_switch_type_attribute = {
				ORIGIN			: Origin,
				AS_PATH			: ASPath,
				NEXT_HOP		: NextHop,
				MULTI_EXIT_DISC		: MultiExitDisc,
				LOCAL_PREF		: LocalPref,
				ATOMIC_AGGREGATE	: AtomicAggregate,
				AGGREGATOR		: Aggregator,
				COMMUNITIES		: Communitie,
				ORIGINATOR_ID		: OriginatorID,
				CLUSTER_LIST		: ClusterList,
				MP_REACH_NLRI		: MPReachNLRI,
				MP_UNREACH_NLRI		: MPUnreachNLRI
			}

	class Notification(packetracer.Packet):
		__hdr__ = (
			("code", "B", 0),
			("subcode", "B", 0),
		)

	class Keepalive(packetracer.Packet):
		pass

	class RouteRefresh(packetracer.Packet):
		__hdr__ = (
			("afi", "H", AFI_IPV4),
			("rsvd", "B", 0),
			("safi", "B", SAFI_UNICAST)
		)

	__handler__ = {
		OPEN: Open,
		UPDATE: Update,
		NOTIFICATION: Notification,
		KEEPALIVE: Keepalive,
		ROUTE_REFRESH: RouteRefresh
	}


class Route(packetracer.Packet):
	__hdr__ = (
		("len", "B", 0),
	)
