// Copyright (c) HashiCorp, Inc
// SPDX-License-Identifier: MPL-2.0

package segment

import (
	
	
	
	
	

	
)

const (
	// MaxEntrySize is the largest we allow any single raft log entry to be. This
	// is larger than our raft implementation ever allows so seems safe to encode
	// statically for now. We could make this configurable. It's main purpose it
	// to limit allocation when reading entries back if their lengths are
	// corrupted.
	MaxEntrySize = 64 * 1024 * 1024 // 64 MiB

	// minBufSize is the size we allocate read and write buffers. Setting it
	// larger wastes more memory but increases the chances that we'll read the
	// whole frame in a single shot and not need a second allocation and trip to
	// the disk.
	minBufSize = 64 * 1024

	fileHeaderLen = 32
	version       = 0
	magic         = 0x58eb6b0d

	// Note that this must remain a power of 2 to ensure aligning to this also
	// aligns to sector boundaries.
	frameHeaderLen = 8
)

const ( // Start iota from 0
	FrameInvalid uint8 = iota
	FrameEntry
	FrameIndex
	FrameCommit
)

var (
	// ErrTooBig indicates that the caller tried to write a logEntry with a
	// payload that's larger than we are prepared to support.
	ErrTooBig = errors.New("entries larger than 64MiB are not supported")
)

/*

  File Header functions

	0      1      2      3      4      5      6      7      8
	+------+------+------+------+------+------+------+------+
	| Magic                     | Reserved           | Vsn  |
	+------+------+------+------+------+------+------+------+
	| BaseIndex                                             |
	+------+------+------+------+------+------+------+------+
	| SegmentID                                             |
	+------+------+------+------+------+------+------+------+
	| Codec (unused)                                        |
	+------+------+------+------+------+------+------+------+

*/

// writeFileHeader writes a file header into buf for the given file metadata.
func writeFileHeader( []byte,  types.SegmentInfo) error {
	if len() < fileHeaderLen {
		return io.ErrShortBuffer
	}

	binary.LittleEndian.PutUint32([0:4], magic)
	// Explicitly zero Reserved bytes just in case
	[4] = 0
	[5] = 0
	[6] = 0
	[7] = version
	binary.LittleEndian.PutUint64([8:16], .BaseIndex)
	binary.LittleEndian.PutUint64([16:24], .ID)
	// I removed the codec option from this library since we let the caller
	// define how they want to encode/decode bytes. Keep a placeholder so that
	// nothing breaks.
	binary.LittleEndian.PutUint64([24:32], 1)
	return nil
}

// readFileHeader reads a file header from buf.
func readFileHeader( []byte) (*types.SegmentInfo, error) {
	if len() < fileHeaderLen {
		return nil, io.ErrShortBuffer
	}

	var  types.SegmentInfo
	 := binary.LittleEndian.Uint64([0:8])
	if  != magic {
		return nil, types.ErrCorrupt
	}
	if [7] != version {
		return nil, types.ErrCorrupt
	}
	.BaseIndex = binary.LittleEndian.Uint64([8:16])
	.ID = binary.LittleEndian.Uint64([16:24])
	return &, nil
}

func validateFileHeader(,  types.SegmentInfo) error {
	if .ID != .ID {
		return fmt.Errorf("%w: segment header ID %x doesn't match metadata %x",
			types.ErrCorrupt, .ID, .ID)
	}
	if .BaseIndex != .BaseIndex {
		return fmt.Errorf("%w: segment header BaseIndex %d doesn't match metadata %d",
			types.ErrCorrupt, .BaseIndex, .BaseIndex)
	}

	return nil
}

/*
	Frame Functions

	0      1      2      3      4      5      6      7      8
	+------+------+------+------+------+------+------+------+
	| Type | Reserved           | Length/CRC                |
	+------+------+------+------+------+------+------+------+
*/

type frameHeader struct {
	typ uint8
	len uint32
	crc uint32
}

func writeFrame( []byte,  frameHeader,  []byte) error {
	if len() < encodedFrameSize(int(.len)) {
		return io.ErrShortBuffer
	}
	if  := writeFrameHeader(, );  != nil {
		return 
	}
	copy([frameHeaderLen:], [:.len])
	// Explicitly write null bytes for padding
	 := padLen(int(.len))
	for  := 0;  < ; ++ {
		[frameHeaderLen+int(.len)+] = 0x0
	}
	return nil
}

func writeFrameHeader( []byte,  frameHeader) error {
	if len() < frameHeaderLen {
		return io.ErrShortBuffer
	}
	[0] = .typ
	[1] = 0
	[2] = 0
	[3] = 0
	 := .len
	if .typ == FrameCommit {
		 = .crc
	}
	binary.LittleEndian.PutUint32([4:8], )
	return nil
}

var zeroHeader [frameHeaderLen]byte

func readFrameHeader( []byte) (frameHeader, error) {
	var  frameHeader
	if len() < frameHeaderLen {
		return , io.ErrShortBuffer
	}

	switch [0] {
	default:
		return , fmt.Errorf("%w: corrupt frame header with unknown type %d", types.ErrCorrupt, [0])

	case FrameInvalid:
		// Check if the whole header is zero and return a zero frame as this could
		// just indicate we've read right off the end of the written data during
		// recovery.
		if bytes.Equal([:frameHeaderLen], zeroHeader[:]) {
			return , nil
		}
		return , fmt.Errorf("%w: corrupt frame header with type 0 but non-zero other fields", types.ErrCorrupt)

	case FrameEntry, FrameIndex:
		.typ = [0]
		.len = binary.LittleEndian.Uint32([4:8])

	case FrameCommit:
		.typ = [0]
		.crc = binary.LittleEndian.Uint32([4:8])
	}
	return , nil
}

// padLen returns how many bytes of padding should be added to a frame of length
// n to ensure it is a multiple of headerLen. We ensure frameHeaderLen is a
// power of two so that it's always a multiple of a typical sector size (e.g.
// 512 bytes) to reduce the risk that headers are torn by being written across
// sector boundaries. It will return an int in the range [0, 7].
func padLen( int) int {
	// This looks a bit awful but it's just doing (n % 8) and subtracting that
	// from 8 to get the number of bytes extra needed to get up to the next 8-byte
	// boundary. The extra & 7 is to handle the case where n is a multiple of 8
	// already and so n%8 is 0 and 8-0 is 8. By &ing 8 (0b1000) with 7 (0b111) we
	// effectively wrap it back around to 0. This only works as long as
	// frameHeaderLen is a power of 2 but that's necessary per comment above.
	return (frameHeaderLen - ( % frameHeaderLen)) & (frameHeaderLen - 1)
}

func encodedFrameSize( int) int {
	return frameHeaderLen +  + padLen()
}

func indexFrameSize( int) int {
	// Index frames are completely unnecessary if the whole block is a
	// continuation with no new entries.
	if  == 0 {
		return 0
	}
	return encodedFrameSize( * 4)
}

func writeIndexFrame( []byte,  []uint32) error {
	if len() < indexFrameSize(len()) {
		return io.ErrShortBuffer
	}
	 := frameHeader{
		typ: FrameIndex,
		len: uint32(len() * 4),
	}
	if  := writeFrameHeader(, );  != nil {
		return 
	}
	 := frameHeaderLen
	for ,  := range  {
		binary.LittleEndian.PutUint32([:], )
		 += 4
	}
	if (len() % 2) == 1 {
		// Odd number of entries, zero pad to keep it 8-byte aligned
		binary.LittleEndian.PutUint32([:], 0)
	}
	return nil
}