// Copyright 2012 Google, Inc. 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"
	"fmt"
	"github.com/google/gopacket"
)

// EAPOL defines an EAP over LAN (802.1x) layer.
type EAPOL struct {
	BaseLayer
	Version uint8
	Type    EAPOLType
	Length  uint16
}

// LayerType returns LayerTypeEAPOL.
func (e *EAPOL) LayerType() gopacket.LayerType { return LayerTypeEAPOL }

// DecodeFromBytes decodes the given bytes into this layer.
func (e *EAPOL) DecodeFromBytes(data []byte, df gopacket.DecodeFeedback) error {
	e.Version = data[0]
	e.Type = EAPOLType(data[1])
	e.Length = binary.BigEndian.Uint16(data[2:4])
	e.BaseLayer = BaseLayer{data[:4], data[4:]}
	return nil
}

// SerializeTo writes the serialized form of this layer into the
// SerializationBuffer, implementing gopacket.SerializableLayer
func (e *EAPOL) SerializeTo(b gopacket.SerializeBuffer, opts gopacket.SerializeOptions) error {
	bytes, _ := b.PrependBytes(4)
	bytes[0] = e.Version
	bytes[1] = byte(e.Type)
	binary.BigEndian.PutUint16(bytes[2:], e.Length)
	return nil
}

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

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

func decodeEAPOL(data []byte, p gopacket.PacketBuilder) error {
	e := &EAPOL{}
	return decodingLayerDecoder(e, data, p)
}

// EAPOLKeyDescriptorType is an enumeration of key descriptor types
// as specified by 802.1x in the EAPOL-Key frame
type EAPOLKeyDescriptorType uint8

// Enumeration of EAPOLKeyDescriptorType
const (
	EAPOLKeyDescriptorTypeRC4   EAPOLKeyDescriptorType = 1
	EAPOLKeyDescriptorTypeDot11 EAPOLKeyDescriptorType = 2
	EAPOLKeyDescriptorTypeWPA   EAPOLKeyDescriptorType = 254
)

func (kdt EAPOLKeyDescriptorType) String() string {
	switch kdt {
	case EAPOLKeyDescriptorTypeRC4:
		return "RC4"
	case EAPOLKeyDescriptorTypeDot11:
		return "802.11"
	case EAPOLKeyDescriptorTypeWPA:
		return "WPA"
	default:
		return fmt.Sprintf("unknown descriptor type %d", kdt)
	}
}

// EAPOLKeyDescriptorVersion is an enumeration of versions specifying the
// encryption algorithm for the key data and the authentication for the
// message integrity code (MIC)
type EAPOLKeyDescriptorVersion uint8

// Enumeration of EAPOLKeyDescriptorVersion
const (
	EAPOLKeyDescriptorVersionOther       EAPOLKeyDescriptorVersion = 0
	EAPOLKeyDescriptorVersionRC4HMACMD5  EAPOLKeyDescriptorVersion = 1
	EAPOLKeyDescriptorVersionAESHMACSHA1 EAPOLKeyDescriptorVersion = 2
	EAPOLKeyDescriptorVersionAES128CMAC  EAPOLKeyDescriptorVersion = 3
)

func (v EAPOLKeyDescriptorVersion) String() string {
	switch v {
	case EAPOLKeyDescriptorVersionOther:
		return "Other"
	case EAPOLKeyDescriptorVersionRC4HMACMD5:
		return "RC4-HMAC-MD5"
	case EAPOLKeyDescriptorVersionAESHMACSHA1:
		return "AES-HMAC-SHA1-128"
	case EAPOLKeyDescriptorVersionAES128CMAC:
		return "AES-128-CMAC"
	default:
		return fmt.Sprintf("unknown version %d", v)
	}
}

// EAPOLKeyType is an enumeration of key derivation types describing
// the purpose of the keys being derived.
type EAPOLKeyType uint8

// Enumeration of EAPOLKeyType
const (
	EAPOLKeyTypeGroupSMK EAPOLKeyType = 0
	EAPOLKeyTypePairwise EAPOLKeyType = 1
)

func (kt EAPOLKeyType) String() string {
	switch kt {
	case EAPOLKeyTypeGroupSMK:
		return "Group/SMK"
	case EAPOLKeyTypePairwise:
		return "Pairwise"
	default:
		return fmt.Sprintf("unknown key type %d", kt)
	}
}

// EAPOLKey defines an EAPOL-Key frame for 802.1x authentication
type EAPOLKey struct {
	BaseLayer
	KeyDescriptorType    EAPOLKeyDescriptorType
	KeyDescriptorVersion EAPOLKeyDescriptorVersion
	KeyType              EAPOLKeyType
	KeyIndex             uint8
	Install              bool
	KeyACK               bool
	KeyMIC               bool
	Secure               bool
	MICError             bool
	Request              bool
	HasEncryptedKeyData  bool
	SMKMessage           bool
	KeyLength            uint16
	ReplayCounter        uint64
	Nonce                []byte
	IV                   []byte
	RSC                  uint64
	ID                   uint64
	MIC                  []byte
	KeyDataLength        uint16
	EncryptedKeyData     []byte
}

// LayerType returns LayerTypeEAPOLKey.
func (ek *EAPOLKey) LayerType() gopacket.LayerType {
	return LayerTypeEAPOLKey
}

// CanDecode returns the set of layer types that this DecodingLayer can decode.
func (ek *EAPOLKey) CanDecode() gopacket.LayerType {
	return LayerTypeEAPOLKey
}

// NextLayerType returns layers.LayerTypeDot11InformationElement if the key
// data exists and is unencrypted, otherwise it does not expect a next layer.
func (ek *EAPOLKey) NextLayerType() gopacket.LayerType {
	if !ek.HasEncryptedKeyData && ek.KeyDataLength > 0 {
		return LayerTypeDot11InformationElement
	}
	return gopacket.LayerTypePayload
}

const eapolKeyFrameLen = 95

// DecodeFromBytes decodes the given bytes into this layer.
func (ek *EAPOLKey) DecodeFromBytes(data []byte, df gopacket.DecodeFeedback) error {
	if len(data) < eapolKeyFrameLen {
		df.SetTruncated()
		return fmt.Errorf("EAPOLKey length %v too short, %v required",
			len(data), eapolKeyFrameLen)
	}

	ek.KeyDescriptorType = EAPOLKeyDescriptorType(data[0])

	info := binary.BigEndian.Uint16(data[1:3])
	ek.KeyDescriptorVersion = EAPOLKeyDescriptorVersion(info & 0x0007)
	ek.KeyType = EAPOLKeyType((info & 0x0008) >> 3)
	ek.KeyIndex = uint8((info & 0x0030) >> 4)
	ek.Install = (info & 0x0040) != 0
	ek.KeyACK = (info & 0x0080) != 0
	ek.KeyMIC = (info & 0x0100) != 0
	ek.Secure = (info & 0x0200) != 0
	ek.MICError = (info & 0x0400) != 0
	ek.Request = (info & 0x0800) != 0
	ek.HasEncryptedKeyData = (info & 0x1000) != 0
	ek.SMKMessage = (info & 0x2000) != 0

	ek.KeyLength = binary.BigEndian.Uint16(data[3:5])
	ek.ReplayCounter = binary.BigEndian.Uint64(data[5:13])

	ek.Nonce = data[13:45]
	ek.IV = data[45:61]
	ek.RSC = binary.BigEndian.Uint64(data[61:69])
	ek.ID = binary.BigEndian.Uint64(data[69:77])
	ek.MIC = data[77:93]

	ek.KeyDataLength = binary.BigEndian.Uint16(data[93:95])

	totalLength := eapolKeyFrameLen + int(ek.KeyDataLength)
	if len(data) < totalLength {
		df.SetTruncated()
		return fmt.Errorf("EAPOLKey data length %d too short, %d required",
			len(data)-eapolKeyFrameLen, ek.KeyDataLength)
	}

	if ek.HasEncryptedKeyData {
		ek.EncryptedKeyData = data[eapolKeyFrameLen:totalLength]
		ek.BaseLayer = BaseLayer{
			Contents: data[:totalLength],
			Payload:  data[totalLength:],
		}
	} else {
		ek.BaseLayer = BaseLayer{
			Contents: data[:eapolKeyFrameLen],
			Payload:  data[eapolKeyFrameLen:],
		}
	}

	return nil
}

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

	buf[0] = byte(ek.KeyDescriptorType)

	var info uint16
	info |= uint16(ek.KeyDescriptorVersion)
	info |= uint16(ek.KeyType) << 3
	info |= uint16(ek.KeyIndex) << 4
	if ek.Install {
		info |= 0x0040
	}
	if ek.KeyACK {
		info |= 0x0080
	}
	if ek.KeyMIC {
		info |= 0x0100
	}
	if ek.Secure {
		info |= 0x0200
	}
	if ek.MICError {
		info |= 0x0400
	}
	if ek.Request {
		info |= 0x0800
	}
	if ek.HasEncryptedKeyData {
		info |= 0x1000
	}
	if ek.SMKMessage {
		info |= 0x2000
	}
	binary.BigEndian.PutUint16(buf[1:3], info)

	binary.BigEndian.PutUint16(buf[3:5], ek.KeyLength)
	binary.BigEndian.PutUint64(buf[5:13], ek.ReplayCounter)

	copy(buf[13:45], ek.Nonce)
	copy(buf[45:61], ek.IV)
	binary.BigEndian.PutUint64(buf[61:69], ek.RSC)
	binary.BigEndian.PutUint64(buf[69:77], ek.ID)
	copy(buf[77:93], ek.MIC)

	binary.BigEndian.PutUint16(buf[93:95], ek.KeyDataLength)
	if len(ek.EncryptedKeyData) > 0 {
		copy(buf[95:95+len(ek.EncryptedKeyData)], ek.EncryptedKeyData)
	}

	return nil
}

func decodeEAPOLKey(data []byte, p gopacket.PacketBuilder) error {
	ek := &EAPOLKey{}
	return decodingLayerDecoder(ek, data, p)
}
