// Copyright 2018 GoPacket Authors. 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"
	"fmt"
	"math"
	"net"
	"time"

	"github.com/google/gopacket"
)

// MLDv1Message represents the common structure of all MLDv1 messages
type MLDv1Message struct {
	BaseLayer
	// 3.4. Maximum Response Delay
	MaximumResponseDelay time.Duration
	// 3.6. Multicast Address
	// Zero in general query
	// Specific IPv6 multicast address otherwise
	MulticastAddress net.IP
}

// DecodeFromBytes decodes the given bytes into this layer.
func (m *MLDv1Message) DecodeFromBytes(data []byte, df gopacket.DecodeFeedback) error {
	if len(data) < 20 {
		df.SetTruncated()
		return errors.New("ICMP layer less than 20 bytes for Multicast Listener Query Message V1")
	}

	m.MaximumResponseDelay = time.Duration(binary.BigEndian.Uint16(data[0:2])) * time.Millisecond
	// data[2:4] is reserved and not used in mldv1
	m.MulticastAddress = data[4:20]

	return nil
}

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

// SerializeTo writes the serialized form of this layer into the
// SerializationBuffer, implementing gopacket.SerializableLayer.
// See the docs for gopacket.SerializableLayer for more info.
func (m *MLDv1Message) SerializeTo(b gopacket.SerializeBuffer, opts gopacket.SerializeOptions) error {
	buf, err := b.PrependBytes(20)
	if err != nil {
		return err
	}

	if m.MaximumResponseDelay < 0 {
		return errors.New("maximum response delay must not be negative")
	}
	dms := m.MaximumResponseDelay / time.Millisecond
	if dms > math.MaxUint16 {
		return fmt.Errorf("maximum response delay %dms is more than the allowed 65535ms", dms)
	}
	binary.BigEndian.PutUint16(buf[0:2], uint16(dms))

	copy(buf[2:4], []byte{0x0, 0x0})

	ma16 := m.MulticastAddress.To16()
	if ma16 == nil {
		return fmt.Errorf("invalid multicast address '%s'", m.MulticastAddress)
	}
	copy(buf[4:20], ma16)

	return nil
}

// Sums this layer up nicely formatted
func (m *MLDv1Message) String() string {
	return fmt.Sprintf(
		"Maximum Response Delay: %dms, Multicast Address: %s",
		m.MaximumResponseDelay/time.Millisecond,
		m.MulticastAddress)
}

// MLDv1MulticastListenerQueryMessage are sent by the router to determine
// whether there are multicast listeners on the link.
// https://tools.ietf.org/html/rfc2710 Page 5
type MLDv1MulticastListenerQueryMessage struct {
	MLDv1Message
}

// DecodeFromBytes decodes the given bytes into this layer.
func (m *MLDv1MulticastListenerQueryMessage) DecodeFromBytes(data []byte, df gopacket.DecodeFeedback) error {
	err := m.MLDv1Message.DecodeFromBytes(data, df)
	if err != nil {
		return err
	}

	if len(data) > 20 {
		m.Payload = data[20:]
	}

	return nil
}

// LayerType returns LayerTypeMLDv1MulticastListenerQuery.
func (*MLDv1MulticastListenerQueryMessage) LayerType() gopacket.LayerType {
	return LayerTypeMLDv1MulticastListenerQuery
}

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

// IsGeneralQuery is true when this is a general query.
// In a Query message, the Multicast Address field is set to zero when
// sending a General Query.
// https://tools.ietf.org/html/rfc2710#section-3.6
func (m *MLDv1MulticastListenerQueryMessage) IsGeneralQuery() bool {
	return net.IPv6zero.Equal(m.MulticastAddress)
}

// IsSpecificQuery is true when this is not a general query.
// In a Query message, the Multicast Address field is set to a specific
// IPv6 multicast address when sending a Multicast-Address-Specific Query.
// https://tools.ietf.org/html/rfc2710#section-3.6
func (m *MLDv1MulticastListenerQueryMessage) IsSpecificQuery() bool {
	return !m.IsGeneralQuery()
}

// MLDv1MulticastListenerReportMessage is sent by a client listening on
// a specific multicast address to indicate that it is (still) listening
// on the specific multicast address.
// https://tools.ietf.org/html/rfc2710 Page 6
type MLDv1MulticastListenerReportMessage struct {
	MLDv1Message
}

// LayerType returns LayerTypeMLDv1MulticastListenerReport.
func (*MLDv1MulticastListenerReportMessage) LayerType() gopacket.LayerType {
	return LayerTypeMLDv1MulticastListenerReport
}

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

// MLDv1MulticastListenerDoneMessage should be sent by a client when it ceases
// to listen to a multicast address on an interface.
// https://tools.ietf.org/html/rfc2710 Page 7
type MLDv1MulticastListenerDoneMessage struct {
	MLDv1Message
}

// LayerType returns LayerTypeMLDv1MulticastListenerDone.
func (*MLDv1MulticastListenerDoneMessage) LayerType() gopacket.LayerType {
	return LayerTypeMLDv1MulticastListenerDone
}

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

func decodeMLDv1MulticastListenerReport(data []byte, p gopacket.PacketBuilder) error {
	m := &MLDv1MulticastListenerReportMessage{}
	return decodingLayerDecoder(m, data, p)
}

func decodeMLDv1MulticastListenerQuery(data []byte, p gopacket.PacketBuilder) error {
	m := &MLDv1MulticastListenerQueryMessage{}
	return decodingLayerDecoder(m, data, p)
}

func decodeMLDv1MulticastListenerDone(data []byte, p gopacket.PacketBuilder) error {
	m := &MLDv1MulticastListenerDoneMessage{}
	return decodingLayerDecoder(m, data, p)
}