# Copyright (C) 2012 Nippon Telegraph and Telephone 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.

# vim: tabstop=4 shiftwidth=4 softtabstop=4

import logging
import array
import netaddr

from os_ken.base import app_manager
from os_ken.controller import dpset
from os_ken.controller import ofp_event
from os_ken.controller import handler
from os_ken.ofproto import ofproto_v1_2
from os_ken.ofproto import ether
from os_ken.ofproto import inet
from os_ken.lib import mac
from os_ken.lib.packet import packet
from os_ken.lib.packet import ethernet
from os_ken.lib.packet import arp
from os_ken.lib.packet import ipv4
from os_ken.lib.packet import icmp


LOG = logging.getLogger(__name__)


class RunTestMininet(app_manager.OSKenApp):

    _CONTEXTS = {'dpset': dpset.DPSet}
    OFP_VERSIONS = [ofproto_v1_2.OFP_VERSION]

    ZERO_MAC = mac.haddr_to_bin('00:00:00:00:00:00')
    BROADCAST_MAC = mac.haddr_to_bin('ff:ff:ff:ff:ff:ff')
    OSKEN_MAC = mac.haddr_to_bin('fe:ee:ee:ee:ee:ef')
    HOST_MAC = mac.haddr_to_bin('00:00:00:00:00:01')
    OSKEN_IP = int(netaddr.IPAddress('10.0.0.100'))
    HOST_IP = int(netaddr.IPAddress('10.0.0.1'))

    def __init__(self, *args, **kwargs):
        super(RunTestMininet, self).__init__(*args, **kwargs)

    def _send_msg(self, dp, data):
        buffer_id = 0xffffffff
        in_port = dp.ofproto.OFPP_LOCAL
        actions = [dp.ofproto_parser.OFPActionOutput(1, 0)]
        msg = dp.ofproto_parser.OFPPacketOut(
            dp, buffer_id, in_port, actions, data)
        dp.send_msg(msg)

    def _add_flow(self, dp, match, actions):
        inst = [dp.ofproto_parser.OFPInstructionActions(
            dp.ofproto.OFPIT_APPLY_ACTIONS, actions)]
        mod = dp.ofproto_parser.OFPFlowMod(
            dp, cookie=0, cookie_mask=0, table_id=0,
            command=dp.ofproto.OFPFC_ADD, idle_timeout=0, hard_timeout=0,
            priority=0xff, buffer_id=0xffffffff,
            out_port=dp.ofproto.OFPP_ANY, out_group=dp.ofproto.OFPG_ANY,
            flags=0, match=match, instructions=inst)
        dp.send_msg(mod)

    def _find_protocol(self, pkt, name):
        for p in pkt.protocols:
            if hasattr(p, 'protocol_name'):
                if p.protocol_name == name:
                    return p

    def _get_protocols(self, pkt):
        protocols = {}
        for p in pkt:
            if hasattr(p, 'protocol_name'):
                protocols[p.protocol_name] = p
            else:
                protocols['payload'] = p
        return protocols

    def _build_ether(self, ethertype, dst_mac=HOST_MAC):
        e = ethernet.ethernet(dst_mac, self.OSKEN_MAC, ethertype)
        return e

    def _build_arp(self, opcode, dst_ip=HOST_IP):
        if opcode == arp.ARP_REQUEST:
            _eth_dst_mac = self.BROADCAST_MAC
            _arp_dst_mac = self.ZERO_MAC
        elif opcode == arp.ARP_REPLY:
            _eth_dst_mac = self.HOST_MAC
            _arp_dst_mac = self.HOST_MAC

        e = self._build_ether(ether.ETH_TYPE_ARP, _eth_dst_mac)
        a = arp.arp(hwtype=1, proto=ether.ETH_TYPE_IP, hlen=6, plen=4,
                    opcode=opcode, src_mac=self.OSKEN_MAC, src_ip=self.OSKEN_IP,
                    dst_mac=_arp_dst_mac, dst_ip=dst_ip)
        p = packet.Packet()
        p.add_protocol(e)
        p.add_protocol(a)
        p.serialize()

        return p

    def _build_echo(self, _type, echo):
        e = self._build_ether(ether.ETH_TYPE_IP)
        ip = ipv4.ipv4(version=4, header_length=5, tos=0, total_length=84,
                       identification=0, flags=0, offset=0, ttl=64,
                       proto=inet.IPPROTO_ICMP, csum=0,
                       src=self.OSKEN_IP, dst=self.HOST_IP)
        ping = icmp.icmp(_type, code=0, csum=0, data=echo)

        p = packet.Packet()
        p.add_protocol(e)
        p.add_protocol(ip)
        p.add_protocol(ping)
        p.serialize()
        return p

    def _garp(self):
        p = self._build_arp(arp.ARP_REQUEST, self.OSKEN_IP)
        return p.data

    def _arp_request(self):
        p = self._build_arp(arp.ARP_REQUEST, self.HOST_IP)
        return p.data

    def _arp_reply(self):
        p = self._build_arp(arp.ARP_REPLY, self.HOST_IP)
        return p.data

    def _echo_request(self, echo):
        p = self._build_echo(icmp.ICMP_ECHO_REQUEST, echo)
        return p.data

    def _echo_reply(self, echo):
        p = self._build_echo(icmp.ICMP_ECHO_REPLY, echo)
        return p.data

    @handler.set_ev_cls(ofp_event.EventOFPPacketIn, handler.MAIN_DISPATCHER)
    def packet_in_handler(self, ev):
        msg = ev.msg
        dp = msg.datapath

        pkt = packet.Packet(array.array('B', msg.data))
        p_arp = self._find_protocol(pkt, "arp")
        p_icmp = self._find_protocol(pkt, "icmp")
        p_ipv4 = self._find_protocol(pkt, "ipv4")

        if p_arp:
            src_ip = str(netaddr.IPAddress(p_arp.src_ip))
            dst_ip = str(netaddr.IPAddress(p_arp.dst_ip))
            if p_arp.opcode == arp.ARP_REQUEST:
                LOG.debug("--- PacketIn: ARP_Request: %s->%s", src_ip, dst_ip)
                if p_arp.dst_ip == self.OSKEN_IP:
                    LOG.debug("--- send Pkt: ARP_Reply")
                    data = self._arp_reply()
                    self._send_msg(dp, data)
                elif p_arp.dst_ip == self.HOST_IP:
                    LOG.debug("    PacketIn: GARP")
                    LOG.debug("--- send Pkt: ARP_Request")
                    data = self._arp_request()
                    self._send_msg(dp, data)
            elif p_arp.opcode == arp.ARP_REPLY:
                LOG.debug("--- PacketIn: ARP_Reply: %s->%s", src_ip, dst_ip)
                LOG.debug("--- send Pkt: Echo_Request")
                echo = icmp.echo(id_=66, seq=1)
                data = self._echo_request(echo)
                self._send_msg(dp, data)

        if p_icmp:
            src = str(netaddr.IPAddress(p_ipv4.src))
            dst = str(netaddr.IPAddress(p_ipv4.dst))
            if p_icmp.type == icmp.ICMP_ECHO_REQUEST:
                LOG.debug("--- PacketIn: Echo_Request: %s->%s", src, dst)
                if p_ipv4.dst == self.OSKEN_IP:
                    LOG.debug("--- send Pkt: Echo_Reply")
                    echo = p_icmp.data
                    echo.data = bytearray(echo.data)
                    data = self._echo_reply(echo)
                    self._send_msg(dp, data)
            elif p_icmp.type == icmp.ICMP_ECHO_REPLY:
                LOG.debug("--- PacketIn: Echo_Reply: %s->%s", src, dst)

    @handler.set_ev_cls(dpset.EventDP, dpset.DPSET_EV_DISPATCHER)
    def handler_datapath(self, ev):
        if ev.enter:
            dp = ev.dp

            LOG.debug("--- send Pkt: Gratuitous ARP_Request")
            data = self._garp()
            self._send_msg(dp, data)
