"""
Border Gateway Protocol.
"""

from pypacker import pypacker, triggerlist

import logging

logger = logging.getLogger("pypacker")

# 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(pypacker.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(pypacker.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
				# TODO: check if len-value is UNCLUSIVE type/len field
				off += plen
			return off

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

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

		def _dissect(self, buf):
			# temporary unpack to parse flags
			pypacker.Packet._unpack(self, buf[:4])

			# Withdrawn Routes
			# TODO: update
			off = 4
			off_end = off + self.unflen

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

			# Path Attributes
			off_end = off + self.pathlen

			while off < off_end:
				alen = 3 + buf[3 + off]
				attr = BGP.Update.Attribute(buf[off:off + alen])
				self.pathattrs.append(attr)
				off += alen

			# Announced Routes
			annc = []
			off_end = len(buf)

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

		class Attribute(pypacker.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

			def __set_e(self, e):
				# handle different header length types, what moron defined this shit?
				if hasattr(self, "len"):
					self._del_headerfield(2, True)
				if e > 0:
					self._add_headerfield("len", "H", 0)
				else:
					self._add_headerfield("len", "B", 0)

				self.flags = (self.flags & ~0x10) | ((e & 0x1) << 4)
			extended_length = property(__get_e, __set_e)

			def _dissect(self, buf):
				# temporary unpack to parse flags
				pypacker.Packet._unpack(self, buf[:2])

				if self.extended_length:
					self.e = 1

				try:
					# TODO: update
					type_instance = BGP.Update.Attribute._switch_type_attribute[type](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(pypacker.Packet):
				__hdr__ = (
					("type", "B", ORIGIN_IGP),
				)

			class ASPath(pypacker.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(pypacker.Packet):
					__hdr__ = (
						("type", "B", 0),
						("len", "B", 0)
					)
					# TODO: auto-set length

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

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

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

			class AtomicAggregate(pypacker.Packet):
				pass

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

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

			class ClusterList(pypacker.Packet):
				pass

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

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

			class Communitie(pypacker.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(pypacker.Packet):
		__hdr__ = (
			("code", "B", 0),
			("subcode", "B", 0),
		)

	class Keepalive(pypacker.Packet):
		pass

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


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

# load handler
pypacker.Packet.load_handler(BGP,
	{
		OPEN: BGP.Open,
		UPDATE: BGP.Update,
		NOTIFICATION: BGP.Notification,
		KEEPALIVE: BGP.Keepalive,
		ROUTE_REFRESH: BGP.RouteRefresh
	}
)
