// -*- c-basic-offset: 4; tab-width: 8; indent-tabs-mode: t -*-

// Copyright (c) 2001-2011 XORP, Inc and Others
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License, Version 2, June
// 1991 as published by the Free Software Foundation. Redistribution
// and/or modification of this program under the terms of any other
// version of the GNU General Public License is not permitted.
// 
// 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. For more details,
// see the GNU General Public License, Version 2, a copy of which can be
// found in the XORP LICENSE.gpl file.
// 
// XORP Inc, 2953 Bunker Hill Lane, Suite 204, Santa Clara, CA 95054, USA;
// http://xorp.net

// $XORP: xorp/contrib/mld6igmp_lite/mld6igmp_vif.hh,v 1.3 2008/10/02 21:56:32 bms Exp $

#ifndef __MLD6IGMP_MLD6IGMP_VIF_HH__
#define __MLD6IGMP_MLD6IGMP_VIF_HH__


//
// IGMP and MLD virtual interface definition.
//




#include "libxorp/config_param.hh"
#include "libxorp/timer.hh"
#include "libxorp/vif.hh"
#include "libproto/proto_unit.hh"
#include "mrt/buffer.h"
#include "mrt/multicast_defs.h"
#include "igmp_proto.h"
#include "mld6_proto.h"
#include "mld6igmp_node.hh"
#include "mld6igmp_group_record.hh"


//
// Constants definitions
//

//
// Structures/classes, typedefs and macros
//

/**
 * @short A class for MLD/IGMP-specific virtual interface.
 */
class Mld6igmpVif : public ProtoUnit, public Vif {
public:
    /**
     * Constructor for a given MLD/IGMP node and a generic virtual interface.
     * 
     * @param mld6igmp_node the @ref Mld6igmpNode this interface belongs to.
     * @param vif the generic Vif interface that contains various information.
     */
    Mld6igmpVif(Mld6igmpNode& mld6igmp_node, const Vif& vif);
    
    /**
     * Destructor
     */
    virtual	~Mld6igmpVif();

    /**
     * Set the current protocol version.
     * 
     * The protocol version must be in the interval
     * [IGMP_VERSION_MIN, IGMP_VERSION_MAX]
     * or [MLD_VERSION_MIN, MLD_VERSION_MAX]
     * 
     * @param proto_version the protocol version to set.
     * @return XORP_OK on success, otherwise XORP_ERROR.
     */
    int		set_proto_version(int proto_version);
    
    /**
     *  Start MLD/IGMP on a single virtual interface.
     * 
     * @param error_msg the error message (if error).
     * @return XORP_OK on success, otherwise XORP_ERROR.
     */
    int		start(string& error_msg);
    
    /**
     *  Stop MLD/IGMP on a single virtual interface.
     * 
     * @param error_msg the error message (if error).
     * @return XORP_OK on success, otherwise XORP_ERROR.
     */
    int		stop(string& error_msg);

    /**
     * Enable MLD/IGMP on a single virtual interface.
     * 
     * If an unit is not enabled, it cannot be start, or pending-start.
     */
    void	enable();
    
    /**
     * Disable MLD/IGMP on a single virtual interface.
     * 
     * If an unit is disabled, it cannot be start or pending-start.
     * If the unit was runnning, it will be stop first.
     */
    void	disable();

    /**
     * Receive a protocol message.
     * 
     * @param src the source address of the message.
     * @param dst the destination address of the message.
     * @param ip_ttl the IP TTL of the message. If it has a negative value
     * it should be ignored.
     * @param ip_ttl the IP TOS of the message. If it has a negative value,
     * it should be ignored.
     * @param ip_router_alert if true, the IP Router Alert option in the IP
     * packet was set (when applicable).
     * @param ip_internet_control if true, then this is IP control traffic.
     * @param buffer the data buffer with the received message.
     * @param error_msg the error message (if error).
     * @return XORP_OK on success, otherwise XORP_ERROR.
     */
    int		mld6igmp_recv(const IPvX& src, const IPvX& dst,
			      int ip_ttl, int ip_tos, bool ip_router_alert,
			      bool ip_internet_control,
			      buffer_t *buffer, string& error_msg);
    
    /**
     * Get the string with the flags about the vif status.
     * 
     * TODO: temporary here. Should go to the Vif class after the Vif
     * class starts using the Proto class.
     * 
     * @return the C++ style string with the flags about the vif status
     * (e.g., UP/DOWN/DISABLED, etc).
     */
    string	flags_string() const;
    
    /**
     * Get the MLD6IGMP node (@ref Mld6igmpNode).
     * 
     * @return a reference to the MLD6IGMP node (@ref Mld6igmpNode).
     */
    Mld6igmpNode& mld6igmp_node() const { return (_mld6igmp_node); }

    /**
     * Get my primary address on this interface.
     * 
     * @return my primary address on this interface.
     */
    const IPvX&	primary_addr() const	{ return (_primary_addr); }

    /**
     * Set my primary address on this interface.
     * 
     * @param v the value of the primary address.
     */
    void	set_primary_addr(const IPvX& v) { _primary_addr = v; }

    /**
     * Update the primary address.
     * 
     * The primary address should be a link-local unicast address, and
     * is used for transmitting the multicast control packets on the LAN.
     * 
     * @param error_msg the error message (if error).
     * @return XORP_OK on success, otherwise XORP_ERROR.
     */
    int		update_primary_address(string& error_msg);
    
    /**
     * Get the MLD/IGMP querier address.
     * 
     * @return the MLD/IGMP querier address.
     */
    const IPvX&	querier_addr()	const		{ return (_querier_addr); }
    
    /**
     * Set the MLD6/IGMP querier address.
     * 
     * @param v the value of the MLD/IGMP querier address.
     */
    void	set_querier_addr(const IPvX& v) { _querier_addr = v;	}

    /**
     * Get the set with the multicast group records information
     * (@ref Mld6igmpGroupSet).
     * 
     * @return the set with the multicast group records information
     * (@ref Mld6igmpGroupSet).
     */
    Mld6igmpGroupSet& group_records() { return (_group_records); }

    /**
     * Get the const set with the multicast group records information
     * (@ref Mld6igmpGroupSet).
     * 
     * @return the const set with the multicast group records information
     * (@ref Mld6igmpGroupSet).
     */
    const Mld6igmpGroupSet& group_records() const { return (_group_records); }

    /**
     * Test if the protocol is Source-Specific Multicast (e.g., IGMPv3
     * or MLDv2).
     * 
     * @return true if the protocol is Source-Specific Multicast (e.g., IGMPv3
     * or MLDv2).
     */
    bool	proto_is_ssm() const;
    
    /**
     * Get the timer to timeout the (other) MLD/IGMP querier.
     * 
     * @return a reference to the timer to timeout the (other)
     * MLD/IGMP querier.
     *
     */
    const XorpTimer& const_other_querier_timer() const { return (_other_querier_timer); }

    /**
     * Optain a reference to the "IP Router Alert option check" flag.
     *
     * @return a reference to the "IP Router Alert option check" flag.
     */
    ConfigParam<bool>& ip_router_alert_option_check() { return (_ip_router_alert_option_check); }
    
    /**
     * Optain a reference to the configured Query Interval.
     *
     * @return a reference to the configured Query Interval.
     */
    ConfigParam<TimeVal>& configured_query_interval() { return (_configured_query_interval); }

    /**
     * Get the effective Query Interval value.
     *
     * Note that this value may be modified by reconfiguring the router,
     * or by the Query message from the current Querier.
     *
     * @return the value of the effective Query Interval.
     */
    const TimeVal& effective_query_interval() const { return (_effective_query_interval); }

    /**
     * Set the effective Query Interval.
     *
     * Note that this value may be modified by reconfiguring the router,
     * or by the Query message from the current Querier.
     *
     * @param v the value of the effective Query Interval.
     */
    void	set_effective_query_interval(const TimeVal& v);

    /**
     * Optain a reference to the Last Member Query Interval.
     *
     * @return a reference to the Last Member Query Interval.
     */
    ConfigParam<TimeVal>& query_last_member_interval() { return (_query_last_member_interval); }

    /**
     * Optain a reference to the Query Response Interval.
     *
     * @return a reference to the Query Response Interval.
     */
    ConfigParam<TimeVal>& query_response_interval() { return (_query_response_interval); }

    /**
     * Optain a reference to the configured Robustness Variable count.
     *
     * @return a reference to the configured Robustness Variable count.
     */
    ConfigParam<uint32_t>& configured_robust_count() { return (_configured_robust_count); }

    /**
     * Get the effective Robustness Variable value.
     *
     * Note that this value may be modified by reconfiguring the router,
     * or by the Query messages from the current Querier.
     *
     * @return the value of the effective Robustness Variable.
     */
    uint32_t	effective_robustness_variable() const { return (_effective_robustness_variable); }

    /**
     * Set the effective Robustness Variable.
     *
     * Note that this value may be modified by reconfiguring the router,
     * or by the Query messages from the current Querier.
     *
     * @param v the value of the effective Robustness Variable.
     */
    void	set_effective_robustness_variable(uint32_t v);

    /**
     * Get the Last Member Query Count value.
     *
     * Note: According to the IGMP/MLD spec, the default value for the
     * Last Member Query Count is the Robustness Variable.
     * Hence, the Last Member Query Count itself should be configurable.
     * For simplicity (and for consistency with other router vendors), it
     * is always same as the Robustness Variable.
     *
     * @return the value of the Last Member Query Count.
     */
    uint32_t	last_member_query_count() const { return (_last_member_query_count); }

    /**
     * Obtain a reference to the Group Membership Interval.
     *
     * Note that it is not directly configurable, but may be tuned by
     * changing the values of the parameters it depends on.
     *
     * @return a reference to the Group Membership Interval.
     */
    const TimeVal& group_membership_interval() const { return (_group_membership_interval); }

    /**
     * Obtain a reference to the Last Member Query Time.
     *
     * Note that it is not directly configurable, but may be tuned by
     * changing the values of the parameters it depends on.
     *
     * @return a reference to the Last Member Query Time.
     */
    const TimeVal& last_member_query_time() const { return (_last_member_query_time); }

    /**
     * Obtain a reference to the Older Version Host Present Interval.
     *
     * Note that it is not directly configurable, but may be tuned by
     * changing the values of the parameters it depends on.
     *
     * @return a reference to the Older Version Host Present Interval.
     */
    const TimeVal& older_version_host_present_interval() const { return (_older_version_host_present_interval); }

    //
    // Add/delete routing protocols that need to be notified for membership
    // changes.
    //

    /**
     * Add a protocol that needs to be notified about multicast membership
     * changes.
     * 
     * Add a protocol to the list of entries that would be notified
     * if there is membership change on a particular interface.
     * 
     * @param module_instance_name the module instance name of the
     * protocol to add.
     * @param module_id the module ID (@ref xorp_module_id) of the
     * protocol to add.
     * @return XORP_OK on success, otherwise XORP_ERROR.
     */
    int		add_protocol(xorp_module_id module_id,
			     const string& module_instance_name);
    
    /**
     * Delete a protocol that needs to be notified about multicast membership
     * changes.
     * 
     * Delete a protocol from the list of entries that would be notified
     * if there is membership change on a particular interface.
     * 
     * @param module_instance_name the module instance name of the
     * protocol to delete.
     * @param module_id the module ID (@ref xorp_module_id) of the
     * protocol to delete.
     * @return XORP_OK on success, otherwise XORP_ERROR.
     */
    int		delete_protocol(xorp_module_id module_id,
				const string& module_instance_name);

    /**
     * Notify the interested parties that there is membership change among
     * the local members.
     *
     * @param source the source address of the (S,G) entry that has changed.
     * In case of group-specific membership, it could be IPvX::ZERO().
     * @param group the group address of the (S,G) entry that has changed.
     * @param action_jp the membership change: @ref ACTION_JOIN
     * or @ref ACTION_PRUNE.
     * @return XORP_OK on success, otherwise XORP_ERROR.
     */
    int		join_prune_notify_routing(const IPvX& source,
					  const IPvX& group,
					  action_jp_t action_jp) const;
    //
    // Functions for sending protocol messages
    //

    /**
     * Send MLD or IGMP message.
     *
     * @param src the message source address.
     * @param dst the message destination address.
     * @param message_type the MLD or IGMP type of the message.
     * @param max_resp_code the "Maximum Response Code" or "Max Resp Code"
     * field in the MLD or IGMP headers respectively (in the particular
     * protocol resolution).
     * @param group_address the "Multicast Address" or "Group Address" field
     * in the MLD or IGMP headers respectively.
     * @param buffer the buffer with the rest of the message.
     * @param error_msg the error message (if error).
     * @return XORP_OK on success, otherwise XORP_ERROR.
     **/
    int		mld6igmp_send(const IPvX& src, const IPvX& dst,
			      uint8_t message_type, uint16_t max_resp_code,
			      const IPvX& group_address, buffer_t *buffer,
			      string& error_msg);

    /**
     * Send Group-Specific Query message.
     *
     * @param group_address the "Multicast Address" or "Group Address" field
     * in the MLD or IGMP headers respectively.
     * @param error_msg the error message (if error).
     * @return XORP_OK on success, otherwise XORP_ERROR.
     **/
    int mld6igmp_group_query_send(const IPvX& group_address,
				  string& error_msg);

    /**
     * Send MLDv2 or IGMPv3 Group-and-Source-Specific Query message.
     *
     * @param group_address the "Multicast Address" or "Group Address" field
     * in the MLD or IGMP headers respectively.
     * @param sources the set of source addresses.
     * @param error_msg the error message (if error).
     * @return XORP_OK on success, otherwise XORP_ERROR.
     **/
    int		mld6igmp_group_source_query_send(const IPvX& group_address,
						 const set<IPvX>& sources,
						 string& error_msg);

    /**
     * Send MLD or IGMP Query message.
     *
     * @param src the message source address.
     * @param dst the message destination address.
     * @param max_resp_time the maximum response time.
     * @param group_address the "Multicast Address" or "Group Address" field
     * in the MLD or IGMP headers respectively.
     * @param sources the set of source addresses (for IGMPv3 or MLDv2 only).
     * @param s_flag the "Suppress Router-Side Processing" bit (for IGMPv3
     * or MLDv2 only; in all other cases it should be set to false).
     * @param error_msg the error message (if error).
     * @return XORP_OK on success, otherwise XORP_ERROR.
     **/
    int		mld6igmp_query_send(const IPvX& src, const IPvX& dst,
				    const TimeVal& max_resp_time,
				    const IPvX& group_address,
				    const set<IPvX>& sources,
				    bool s_flag,
				    string& error_msg);

    /**
     * Test if the interface is running in IGMPv1 mode.
     *
     * @return true if the interface is running in IGMPv1 mode,
     * otherwise false.
     */
    bool	is_igmpv1_mode() const;

    /**
     * Test if the interface is running in IGMPv2 mode.
     *
     * @return true if the interface is running in IGMPv2 mode,
     * otherwise false.
     */
    bool	is_igmpv2_mode() const;

    /**
     * Test if the interface is running in IGMPv3 mode.
     *
     * @return true if the interface is running in IGMPv3 mode,
     * otherwise false.
     */
    bool	is_igmpv3_mode() const;

    /**
     * Test if the interface is running in MLDv1 mode.
     *
     * @return true if the interface is running in MLDv1 mode,
     * otherwise false.
     */
    bool	is_mldv1_mode() const;

    /**
     * Test if the interface is running in MLDv2 mode.
     *
     * @return true if the interface is running in MLDv2 mode,
     * otherwise false.
     */
    bool	is_mldv2_mode() const;

    /**
     * Test if a group is running in IGMPv1 mode.
     *
     * Note that if @ref group_record is NULL, then we test whether the
     * interface itself is running in IGMPv1 mode.
     * @param group_record the group record to test.
     * @return true if the group is running in IGMPv1 mode,
     * otherwise false.
     */
    bool	is_igmpv1_mode(const Mld6igmpGroupRecord* group_record) const;

    /**
     * Test if a group is running in IGMPv2 mode.
     *
     * Note that if @ref group_record is NULL, then we test whether the
     * interface itself is running in IGMPv2 mode.
     * @param group_record the group record to test.
     * @return true if the group is running in IGMPv2 mode,
     * otherwise false.
     */
    bool	is_igmpv2_mode(const Mld6igmpGroupRecord* group_record) const;

    /**
     * Test if a group is running in IGMPv3 mode.
     *
     * Note that if @ref group_record is NULL, then we test whether the
     * interface itself is running in IGMPv3 mode.
     * @param group_record the group record to test.
     * @return true if the group is running in IGMPv3 mode,
     * otherwise false.
     */
    bool	is_igmpv3_mode(const Mld6igmpGroupRecord* group_record) const;

    /**
     * Test if a group is running in MLDv1 mode.
     *
     * Note that if @ref group_record is NULL, then we test whether the
     * interface itself is running in MLDv1 mode.
     * @param group_record the group record to test.
     * @return true if the group is running in MLDv1 mode,
     * otherwise false.
     */
    bool	is_mldv1_mode(const Mld6igmpGroupRecord* group_record) const;

    /**
     * Test if a group is running in MLDv2 mode.
     *
     * Note that if @ref group_record is NULL, then we test whether the
     * interface itself is running in MLDv2 mode.
     * @param group_record the group record to test.
     * @return true if the group is running in MLDv2 mode,
     * otherwise false.
     */
    bool	is_mldv2_mode(const Mld6igmpGroupRecord* group_record) const;

private:
    //
    // Private functions
    //

    /**
     * Return the ASCII text description of the protocol message.
     *
     * @param message_type the protocol message type.
     * @return the ASCII text descrpition of the protocol message.
     */
    const char	*proto_message_type2ascii(uint8_t message_type) const;

    /**
     * Reset and prepare the buffer for sending data.
     *
     * @return the prepared buffer.
     */
    buffer_t	*buffer_send_prepare();

    /**
     * Calculate the checksum of an IPv6 "pseudo-header" as described
     * in RFC 2460.
     * 
     * @param src the source address of the pseudo-header.
     * @param dst the destination address of the pseudo-header.
     * @param len the upper-layer packet length of the pseudo-header
     * (in host-order).
     * @param protocol the upper-layer protocol number.
     * @return the checksum of the IPv6 "pseudo-header".
     */
    uint16_t	calculate_ipv6_pseudo_header_checksum(const IPvX& src,
						      const IPvX& dst,
						      size_t len,
						      uint8_t protocol);

    /**
     * Test whether I am the querier for this vif.
     *
     * @return true if I am the querier for this vif, otherwise false.
     */
    bool	i_am_querier() const;

    /**
     * Set the state whether I am the querier for this vif.
     *
     * @param v if true, then I am the querier for this vif.
     */
    void	set_i_am_querier(bool v);

    //
    // Callbacks for configuration and non-configurable parameters
    //
    void	set_configured_query_interval_cb(TimeVal v);
    void	set_query_last_member_interval_cb(TimeVal v);
    void	set_query_response_interval_cb(TimeVal v);
    void	set_configured_robust_count_cb(uint32_t v);
    void	recalculate_effective_query_interval();
    void	recalculate_effective_robustness_variable();
    void	recalculate_last_member_query_count();
    void	recalculate_group_membership_interval();
    void	recalculate_last_member_query_time();
    void	recalculate_older_version_host_present_interval();
    void	restore_effective_variables();

    //
    // Private state
    //
    Mld6igmpNode& _mld6igmp_node;	// The MLD6IGMP node I belong to
    buffer_t	*_buffer_send;		// Buffer for sending messages
    enum {
	MLD6IGMP_VIF_QUERIER	= 1 << 0 // I am the querier
    };
    uint32_t	_proto_flags;		// Various flags (MLD6IGMP_VIF_*)
    IPvX	_primary_addr;		// The primary address on this vif
    IPvX	_querier_addr;		// IP address of the current querier
    XorpTimer	_other_querier_timer;	// To timeout the (other) 'querier'
    XorpTimer	_query_timer;		// Timer to send queries
    uint8_t	_startup_query_count;	// Number of queries to send quickly
					// during startup
    Mld6igmpGroupSet _group_records;	// The group records

    //
    // Misc configuration parameters
    //
    ConfigParam<bool> _ip_router_alert_option_check; // The IP Router Alert option check flag
    ConfigParam<TimeVal> _configured_query_interval;	// The configured Query Interval
    TimeVal	_effective_query_interval;	// The effective Query Interval
    ConfigParam<TimeVal> _query_last_member_interval;	// The Last Member Query Interval
    ConfigParam<TimeVal> _query_response_interval;	// The Query Response Interval
    ConfigParam<uint32_t> _configured_robust_count;	// The configured Robustness Variable count
    uint32_t	_effective_robustness_variable;	// The effective Robustness Variable

    //
    // Other parameters that are not directly configurable
    //
    uint32_t	_last_member_query_count;	// The Last Member Query Count
    TimeVal	_group_membership_interval;	// The Group Membership Interval
    TimeVal	_last_member_query_time;	// The Last Member Query Time
    TimeVal	_older_version_host_present_interval;	// The Older Version Host Present Interval

    //
    // Misc. other state
    //
    // Registered protocols to notify for membership change.
    vector<pair<xorp_module_id, string> > _notify_routing_protocols;
    bool _dummy_flag;			// Dummy flag
    
    //
    // Not-so handy private functions that should go somewhere else
    //
    // MLD/IGMP control messages recv functions
    int		mld6igmp_membership_query_recv(const IPvX& src,
					       const IPvX& dst,
					       uint8_t message_type,
					       uint16_t max_resp_code,
					       const IPvX& group_address,
					       buffer_t *buffer);
    int		mld6igmp_ssm_membership_query_recv(const IPvX& src,
						   const IPvX& dst,
						   uint8_t message_type,
						   uint16_t max_resp_code,
						   const IPvX& group_address,
						   buffer_t *buffer);
    int		mld6igmp_membership_report_recv(const IPvX& src,
						const IPvX& dst,
						uint8_t message_type,
						uint16_t max_resp_code,
						const IPvX& group_address,
						buffer_t *buffer);
    int		mld6igmp_leave_group_recv(const IPvX& src,
					  const IPvX& dst,
					  uint8_t message_type,
					  uint16_t max_resp_code,
					  const IPvX& group_address,
					  buffer_t *buffer);
    int		mld6igmp_ssm_membership_report_recv(const IPvX& src,
						    const IPvX& dst,
						    uint8_t message_type,
						    buffer_t *buffer);
    int		mld6igmp_query_version_consistency_check(const IPvX& src,
							 const IPvX& dst,
							 uint8_t message_type,
							 int message_version);

    // MLD/IGMP control messages process functions
    int		mld6igmp_process(const IPvX& src,
				 const IPvX& dst,
				 int ip_ttl,
				 int ip_tos,
				 bool ip_router_alert,
				 bool ip_internet_control,
				 buffer_t *buffer,
				 string& error_msg);

    // MLD/IGMP uniform interface for protocol-related constants
    size_t	mld6igmp_constant_minlen() const;
    uint32_t	mld6igmp_constant_timer_scale() const;
    uint8_t	mld6igmp_constant_membership_query() const;

    void	other_querier_timer_timeout();
    void	query_timer_timeout();

    void	decode_exp_time_code8(uint8_t code, TimeVal& timeval,
				      uint32_t timer_scale);
    void	decode_exp_time_code16(uint16_t code, TimeVal& timeval,
				       uint32_t timer_scale);
    void	encode_exp_time_code8(const TimeVal& timeval,
				      uint8_t& code,
				      uint32_t timer_scale);
    void	encode_exp_time_code16(const TimeVal& timeval,
				       uint16_t& code,
				       uint32_t timer_scale);
};

//
// Global variables
//

//
// Global functions prototypes
//

#endif // __MLD6IGMP_MLD6IGMP_VIF_HH__
