// Copyright 2012 Google, Inc. All rights reserved.
// Copyright 2009-2011 Andreas Krennmair. All rights reserved.
//
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file in the root of the source
// tree.

package layers

import (
	"encoding/binary"
	"errors"
	"net"
	"time"

	"github.com/gopacket/gopacket"
)

type IGMPType uint8

const (
	IGMPMembershipQuery    IGMPType = 0x11 // General or group specific query
	IGMPMembershipReportV1 IGMPType = 0x12 // Version 1 Membership Report
	IGMPMembershipReportV2 IGMPType = 0x16 // Version 2 Membership Report
	IGMPLeaveGroup         IGMPType = 0x17 // Leave Group
	IGMPMembershipReportV3 IGMPType = 0x22 // Version 3 Membership Report
)

// String conversions for IGMP message types
func (i IGMPType) String() string {
	switch i {
	case IGMPMembershipQuery:
		return "IGMP Membership Query"
	case IGMPMembershipReportV1:
		return "IGMPv1 Membership Report"
	case IGMPMembershipReportV2:
		return "IGMPv2 Membership Report"
	case IGMPMembershipReportV3:
		return "IGMPv3 Membership Report"
	case IGMPLeaveGroup:
		return "Leave Group"
	default:
		return ""
	}
}

type IGMPv3GroupRecordType uint8

const (
	IGMPIsIn  IGMPv3GroupRecordType = 0x01 // Type MODE_IS_INCLUDE, source addresses x
	IGMPIsEx  IGMPv3GroupRecordType = 0x02 // Type MODE_IS_EXCLUDE, source addresses x
	IGMPToIn  IGMPv3GroupRecordType = 0x03 // Type CHANGE_TO_INCLUDE_MODE, source addresses x
	IGMPToEx  IGMPv3GroupRecordType = 0x04 // Type CHANGE_TO_EXCLUDE_MODE, source addresses x
	IGMPAllow IGMPv3GroupRecordType = 0x05 // Type ALLOW_NEW_SOURCES, source addresses x
	IGMPBlock IGMPv3GroupRecordType = 0x06 // Type BLOCK_OLD_SOURCES, source addresses x
)

func (i IGMPv3GroupRecordType) String() string {
	switch i {
	case IGMPIsIn:
		return "MODE_IS_INCLUDE"
	case IGMPIsEx:
		return "MODE_IS_EXCLUDE"
	case IGMPToIn:
		return "CHANGE_TO_INCLUDE_MODE"
	case IGMPToEx:
		return "CHANGE_TO_EXCLUDE_MODE"
	case IGMPAllow:
		return "ALLOW_NEW_SOURCES"
	case IGMPBlock:
		return "BLOCK_OLD_SOURCES"
	default:
		return ""
	}
}

// IGMP represents an IGMPv3 message.
type IGMP struct {
	BaseLayer
	Type                    IGMPType
	MaxResponseTime         time.Duration
	Checksum                uint16
	GroupAddress            net.IP
	SupressRouterProcessing bool
	RobustnessValue         uint8
	IntervalTime            time.Duration
	SourceAddresses         []net.IP
	NumberOfGroupRecords    uint16
	NumberOfSources         uint16
	GroupRecords            []IGMPv3GroupRecord
	Version                 uint8 // IGMP protocol version
}

// IGMPv1or2 stores header details for an IGMPv1 or IGMPv2 packet.
//
//	0                   1                   2                   3
//	0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
//
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |      Type     | Max Resp Time |           Checksum            |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                         Group Address                         |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
type IGMPv1or2 struct {
	BaseLayer
	Type            IGMPType      // IGMP message type
	MaxResponseTime time.Duration // meaningful only in Membership Query messages
	Checksum        uint16        // 16-bit checksum of entire ip payload
	GroupAddress    net.IP        // either 0 or an IP multicast address
	Version         uint8
}

// decodeResponse dissects IGMPv1 or IGMPv2 packet.
func (i *IGMPv1or2) decodeResponse(data []byte) error {
	if len(data) < 8 {
		return errors.New("IGMP packet too small")
	}

	i.MaxResponseTime = igmpTimeDecode(data[1])
	i.Checksum = binary.BigEndian.Uint16(data[2:4])
	i.GroupAddress = net.IP(data[4:8])

	return nil
}

//  0                   1                   2                   3
//  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |  Type = 0x22  |    Reserved   |           Checksum            |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |           Reserved            |  Number of Group Records (M)  |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                                                               |
// .                        Group Record [1]                       .
// |                                                               |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                                                               |
// .                        Group Record [2]                       .
// |                                                               |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                                                               |
// .                        Group Record [M]                       .
// |                                                               |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |  Record Type  |  Aux Data Len |     Number of Sources (N)     |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                       Multicast Address                       |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                       Source Address [1]                      |
// +-                                                             -+
// |                       Source Address [2]                      |
// +-                                                             -+
// |                       Source Address [N]                      |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                                                               |
// .                         Auxiliary Data                        .
// |                                                               |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

// IGMPv3GroupRecord stores individual group records for a V3 Membership Report message.
type IGMPv3GroupRecord struct {
	Type             IGMPv3GroupRecordType
	AuxDataLen       uint8 // this should always be 0 as per IGMPv3 spec.
	NumberOfSources  uint16
	MulticastAddress net.IP
	SourceAddresses  []net.IP
	AuxData          uint32 // NOT USED
}

func (i *IGMP) decodeIGMPv3MembershipReport(data []byte) error {
	if len(data) < 8 {
		return errors.New("IGMPv3 Membership Report too small #1")
	}

	i.Checksum = binary.BigEndian.Uint16(data[2:4])
	i.NumberOfGroupRecords = binary.BigEndian.Uint16(data[6:8])

	recordOffset := 8
	for j := 0; j < int(i.NumberOfGroupRecords); j++ {
		if len(data) < recordOffset+8 {
			return errors.New("IGMPv3 Membership Report too small #2")
		}

		var gr IGMPv3GroupRecord
		gr.Type = IGMPv3GroupRecordType(data[recordOffset])
		gr.AuxDataLen = data[recordOffset+1]
		gr.NumberOfSources = binary.BigEndian.Uint16(data[recordOffset+2 : recordOffset+4])
		gr.MulticastAddress = net.IP(data[recordOffset+4 : recordOffset+8])

		if len(data) < recordOffset+8+int(gr.NumberOfSources)*4 {
			return errors.New("IGMPv3 Membership Report too small #3")
		}

		// append source address records.
		for i := 0; i < int(gr.NumberOfSources); i++ {
			sourceAddr := net.IP(data[recordOffset+8+i*4 : recordOffset+12+i*4])
			gr.SourceAddresses = append(gr.SourceAddresses, sourceAddr)
		}

		i.GroupRecords = append(i.GroupRecords, gr)
		recordOffset += 8 + 4*int(gr.NumberOfSources)
	}
	return nil
}

//	0                   1                   2                   3
//	0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
//
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |  Type = 0x11  | Max Resp Code |           Checksum            |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                         Group Address                         |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | Resv  |S| QRV |     QQIC      |     Number of Sources (N)     |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                       Source Address [1]                      |
// +-                                                             -+
// |                       Source Address [2]                      |
// +-                              .                              -+
// |                       Source Address [N]                      |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//
// decodeIGMPv3MembershipQuery parses the IGMPv3 message of type 0x11
func (i *IGMP) decodeIGMPv3MembershipQuery(data []byte) error {
	if len(data) < 12 {
		return errors.New("IGMPv3 Membership Query too small #1")
	}

	i.MaxResponseTime = igmpTimeDecode(data[1])
	i.Checksum = binary.BigEndian.Uint16(data[2:4])
	i.SupressRouterProcessing = data[8]&0x8 != 0
	i.GroupAddress = net.IP(data[4:8])
	i.RobustnessValue = data[8] & 0x7
	i.IntervalTime = igmpTimeDecode(data[9])
	i.NumberOfSources = binary.BigEndian.Uint16(data[10:12])

	if len(data) < 12+int(i.NumberOfSources)*4 {
		return errors.New("IGMPv3 Membership Query too small #2")
	}

	for j := 0; j < int(i.NumberOfSources); j++ {
		i.SourceAddresses = append(i.SourceAddresses, net.IP(data[12+j*4:16+j*4]))
	}

	return nil
}

// igmpTimeDecode decodes the duration created by the given byte, using the
// algorithm in http://www.rfc-base.org/txt/rfc-3376.txt section 4.1.1.
func igmpTimeDecode(t uint8) time.Duration {
	if t&0x80 == 0 {
		return time.Millisecond * 100 * time.Duration(t)
	}
	mant := (t & 0x70) >> 4
	exp := t & 0x0F
	return time.Millisecond * 100 * time.Duration((mant|0x10)<<(exp+3))
}

// LayerType returns LayerTypeIGMP for the V1,2,3 message protocol formats.
func (i *IGMP) LayerType() gopacket.LayerType      { return LayerTypeIGMP }
func (i *IGMPv1or2) LayerType() gopacket.LayerType { return LayerTypeIGMP }

func (i *IGMPv1or2) DecodeFromBytes(data []byte, df gopacket.DecodeFeedback) error {
	if len(data) < 8 {
		return errors.New("IGMP Packet too small")
	}

	i.Type = IGMPType(data[0])
	i.MaxResponseTime = igmpTimeDecode(data[1])
	i.Checksum = binary.BigEndian.Uint16(data[2:4])
	i.GroupAddress = net.IP(data[4:8])

	return nil
}

func (i *IGMPv1or2) NextLayerType() gopacket.LayerType {
	return gopacket.LayerTypeZero
}

func (i *IGMPv1or2) CanDecode() gopacket.LayerClass {
	return LayerTypeIGMP
}

// DecodeFromBytes decodes the given bytes into this layer.
func (i *IGMP) DecodeFromBytes(data []byte, df gopacket.DecodeFeedback) error {
	if len(data) < 1 {
		return errors.New("IGMP packet is too small")
	}

	// common IGMP header values between versions 1..3 of IGMP specification..
	i.Type = IGMPType(data[0])

	switch i.Type {
	case IGMPMembershipQuery:
		i.decodeIGMPv3MembershipQuery(data)
	case IGMPMembershipReportV3:
		i.decodeIGMPv3MembershipReport(data)
	default:
		return errors.New("unsupported IGMP type")
	}

	return nil
}

// CanDecode returns the set of layer types that this DecodingLayer can decode.
func (i *IGMP) CanDecode() gopacket.LayerClass {
	return LayerTypeIGMP
}

// NextLayerType returns the layer type contained by this DecodingLayer.
func (i *IGMP) NextLayerType() gopacket.LayerType {
	return gopacket.LayerTypeZero
}

// decodeIGMP will parse IGMP v1,2 or 3 protocols. Checks against the
// IGMP type are performed against byte[0], logic then iniitalizes and
// passes the appropriate struct (IGMP or IGMPv1or2) to
// decodingLayerDecoder.
func decodeIGMP(data []byte, p gopacket.PacketBuilder) error {
	if len(data) < 1 {
		return errors.New("IGMP packet is too small")
	}

	// byte 0 contains IGMP message type.
	switch IGMPType(data[0]) {
	case IGMPMembershipQuery:
		// IGMPv3 Membership Query payload is >= 12
		if len(data) >= 12 {
			i := &IGMP{Version: 3}
			return decodingLayerDecoder(i, data, p)
		} else if len(data) == 8 {
			i := &IGMPv1or2{}
			if data[1] == 0x00 {
				i.Version = 1 // IGMPv1 has a query length of 8 and MaxResp = 0
			} else {
				i.Version = 2 // IGMPv2 has a query length of 8 and MaxResp != 0
			}

			return decodingLayerDecoder(i, data, p)
		}
	case IGMPMembershipReportV3:
		i := &IGMP{Version: 3}
		return decodingLayerDecoder(i, data, p)
	case IGMPMembershipReportV1:
		i := &IGMPv1or2{Version: 1}
		return decodingLayerDecoder(i, data, p)
	case IGMPLeaveGroup, IGMPMembershipReportV2:
		// leave group and Query Report v2 used in IGMPv2 only.
		i := &IGMPv1or2{Version: 2}
		return decodingLayerDecoder(i, data, p)
	default:
	}

	return errors.New("Unable to determine IGMP type.")
}
