/*
 * etherpausesource.{cc,hh} -- generates Ethernet PAUSE MAC control frames
 * Eddie Kohler, based on etherpause.cc by Roman Chertov
 *
 * Copyright (c) 2008 Meraki, Inc.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, subject to the conditions
 * listed in the Click LICENSE file. These conditions include: you must
 * preserve this copyright notice, and you cannot mention the copyright
 * holders in advertising related to the Software without their permission.
 * The Software is provided WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED. This
 * notice is a summary of the Click LICENSE file; the license in that file is
 * legally binding.
 */

#include <click/config.h>
#include "etherpausesource.hh"
#include <click/confparse.hh>
#include <click/etheraddress.hh>
#include <clicknet/ether.h>
#include <click/error.hh>
#include <click/glue.hh>
CLICK_DECLS

static const unsigned char default_destination[6] = {
    0x01, 0x80, 0xC2, 0x00, 0x00, 0x01
};

EtherPauseSource::EtherPauseSource()
    : _packet(0), _timer(this)
{
}

EtherPauseSource::~EtherPauseSource()
{
}

int
EtherPauseSource::configure(Vector<String> &conf, ErrorHandler *errh)
{
    EtherAddress src, dst(default_destination);
    uint16_t pausetime;
    _limit = -1;
    _active = true;
    _interval = 1000;
    if (cp_va_kparse(conf, this, errh,
		     "SRC", cpkP+cpkM, cpEtherAddress, &src,
		     "PAUSETIME", cpkP+cpkM, cpUnsignedShort, &pausetime,
		     "DST", 0, cpEtherAddress, &dst,
		     "LIMIT", 0, cpInteger, &_limit,
		     "ACTIVE", 0, cpBool, &_active,
		     "INTERVAL", 0, cpSecondsAsMilli, &_interval,
		     cpEnd) < 0)
        return -1;

    // build PAUSE frame
    WritablePacket *q;
    if (!(q = Packet::make(64)))
	return errh->error("out of memory!"), -ENOMEM;

    q->set_mac_header(q->data(), sizeof(click_ether));
    click_ether *ethh = q->ether_header();
    memcpy(ethh->ether_dhost, &dst, 6);
    memcpy(ethh->ether_shost, &src, 6);
    ethh->ether_type = htons(ETHERTYPE_MACCONTROL);

    click_ether_macctl *emch = (click_ether_macctl *) q->network_header();
    emch->ether_macctl_opcode = htons(ETHER_MACCTL_OP_PAUSE);
    emch->ether_macctl_param = htons(pausetime);
    memset(emch->ether_macctl_reserved, 0, sizeof(emch->ether_macctl_reserved));

    _packet = q;
    return 0;
}

int
EtherPauseSource::initialize(ErrorHandler *)
{
    _count = 0;
    _timer.initialize(this);
    if (_limit != 0 && _active && output_is_push(0))
	_timer.schedule_after_msec(_interval);
    return 0;
}

void
EtherPauseSource::cleanup(CleanupStage)
{
    if (_packet) {
        _packet->kill();
        _packet = 0;
    }
}

void
EtherPauseSource::run_timer(Timer *)
{
    if (Packet *p = _packet->clone()) {
	++_count;
	output(0).push(p);
    }
    if (_limit < 0 || _count < _limit)
	_timer.reschedule_after_msec(_interval);
}

Packet *
EtherPauseSource::pull(int)
{
    if (!_active || (_limit >= 0 && _count >= _limit))
        return 0;
    if (Packet *p = _packet->clone()) {
	++_count;
	return p;
    } else
	return 0;
}

String
EtherPauseSource::reader(Element *e, void *user_data)
{
    EtherPauseSource *eps = static_cast<EtherPauseSource *>(e);
    switch ((intptr_t) user_data) {
    case h_src:
	return EtherAddress(eps->_packet->ether_header()->ether_shost).unparse();
    case h_dst:
	return EtherAddress(eps->_packet->ether_header()->ether_dhost).unparse();
    case h_pausetime: {
	const click_ether_macctl *emc = (const click_ether_macctl *) eps->_packet->network_header();
	return String(ntohs(emc->ether_macctl_param));
    }
    default:
	return String();
    }
}

int
EtherPauseSource::rewrite_packet(const void *data, uint32_t offset, uint32_t size, ErrorHandler *errh)
{
    if (WritablePacket *q = Packet::make(64)) {
	memcpy(q->data(), _packet->data(), 64);
	memcpy(q->data() + offset, data, size);
	q->set_mac_header(q->data(), sizeof(click_ether));
	_packet->kill();
	_packet = q;
	return 0;
    } else
	return errh->error("out of memory!"), -ENOMEM;
}

void
EtherPauseSource::check_awake()
{
    if (output_is_push(0) && !_timer.scheduled() && _active)
	_timer.schedule_now();
}

int
EtherPauseSource::writer(const String &str, Element *e, void *user_data, ErrorHandler *errh)
{
    EtherPauseSource *eps = static_cast<EtherPauseSource *>(e);
    switch ((intptr_t) user_data) {
    case h_src:
    case h_dst: {
	EtherAddress a;
	if (!cp_ethernet_address(str, &a, e))
	    return errh->error("type mismatch");
	return eps->rewrite_packet(&a, ((intptr_t) user_data == h_src ? offsetof(click_ether, ether_shost) : offsetof(click_ether, ether_dhost)), 6, errh);
    }
    case h_pausetime: {
	uint32_t x;
	if (!cp_integer(str, &x) || x > 0xFFFF)
	    return errh->error("type mismatch");
	uint16_t param = htons((uint16_t) x);
	return eps->rewrite_packet(&param, sizeof(click_ether) + offsetof(click_ether_macctl, ether_macctl_param), 2, errh);
    }
    case h_limit:
	if (!cp_integer(str, &eps->_limit))
	    return errh->error("type mismatch");
	eps->check_awake();
	return 0;
    case h_active:
	if (!cp_bool(str, &eps->_active))
	    return errh->error("type mismatch");
	eps->check_awake();
	return 0;
    case h_reset_counts:
	eps->_count = 0;
	eps->check_awake();
	return 0;
    default:
	return 0;
    }
}

void
EtherPauseSource::add_handlers()
{
    add_data_handlers("count", Handler::OP_READ, &_count);
    add_data_handlers("limit", Handler::OP_READ, &_limit);
    add_write_handler("limit", writer, h_limit);
    add_data_handlers("active", Handler::OP_READ | Handler::CHECKBOX, &_active);
    add_write_handler("active", writer, h_active);
    add_read_handler("src", reader, h_src);
    add_write_handler("src", writer, h_src);
    add_read_handler("dst", reader, h_dst);
    add_write_handler("dst", writer, h_dst);
    add_read_handler("pausetime", reader, h_pausetime);
    add_write_handler("pausetime", writer, h_pausetime);
    add_write_handler("reset_counts", writer, h_reset_counts);
}

CLICK_ENDDECLS
EXPORT_ELEMENT(EtherPauseSource)
