package record

import (
	
	
	
	

	
	
	

	pool 

	
	
)

// Envelope contains an arbitrary []byte payload, signed by a libp2p peer.
//
// Envelopes are signed in the context of a particular "domain", which is a
// string specified when creating and verifying the envelope. You must know the
// domain string used to produce the envelope in order to verify the signature
// and access the payload.
type Envelope struct {
	// The public key that can be used to verify the signature and derive the peer id of the signer.
	PublicKey crypto.PubKey

	// A binary identifier that indicates what kind of data is contained in the payload.
	// TODO(yusef): enforce multicodec prefix
	PayloadType []byte

	// The envelope payload.
	RawPayload []byte

	// The signature of the domain string :: type hint :: payload.
	signature []byte

	// the unmarshalled payload as a Record, cached on first access via the Record accessor method
	cached         Record
	unmarshalError error
	unmarshalOnce  sync.Once
}

var ErrEmptyDomain = errors.New("envelope domain must not be empty")
var ErrEmptyPayloadType = errors.New("payloadType must not be empty")
var ErrInvalidSignature = errors.New("invalid signature or incorrect domain")

// Seal marshals the given Record, places the marshaled bytes inside an Envelope,
// and signs with the given private key.
func ( Record,  crypto.PrivKey) (*Envelope, error) {
	,  := .MarshalRecord()
	if  != nil {
		return nil, fmt.Errorf("error marshaling record: %v", )
	}

	 := .Domain()
	 := .Codec()
	if  == "" {
		return nil, ErrEmptyDomain
	}

	if len() == 0 {
		return nil, ErrEmptyPayloadType
	}

	,  := makeUnsigned(, , )
	if  != nil {
		return nil, 
	}
	defer pool.Put()

	,  := .Sign()
	if  != nil {
		return nil, 
	}

	return &Envelope{
		PublicKey:   .GetPublic(),
		PayloadType: ,
		RawPayload:  ,
		signature:   ,
	}, nil
}

// ConsumeEnvelope unmarshals a serialized Envelope and validates its
// signature using the provided 'domain' string. If validation fails, an error
// is returned, along with the unmarshalled envelope, so it can be inspected.
//
// On success, ConsumeEnvelope returns the Envelope itself, as well as the inner payload,
// unmarshalled into a concrete Record type. The actual type of the returned Record depends
// on what has been registered for the Envelope's PayloadType (see RegisterType for details).
//
// You can type assert on the returned Record to convert it to an instance of the concrete
// Record type:
//
//	envelope, rec, err := ConsumeEnvelope(envelopeBytes, peer.PeerRecordEnvelopeDomain)
//	if err != nil {
//	  handleError(envelope, err)  // envelope may be non-nil, even if errors occur!
//	  return
//	}
//	peerRec, ok := rec.(*peer.PeerRecord)
//	if ok {
//	  doSomethingWithPeerRecord(peerRec)
//	}
//
// If the Envelope signature is valid, but no Record type is registered for the Envelope's
// PayloadType, ErrPayloadTypeNotRegistered will be returned, along with the Envelope and
// a nil Record.
func ( []byte,  string) ( *Envelope,  Record,  error) {
	,  := UnmarshalEnvelope()
	if  != nil {
		return nil, nil, fmt.Errorf("failed when unmarshalling the envelope: %w", )
	}

	 = .validate()
	if  != nil {
		return nil, nil, fmt.Errorf("failed to validate envelope: %w", )
	}

	,  = .Record()
	if  != nil {
		return nil, nil, fmt.Errorf("failed to unmarshal envelope payload: %w", )
	}
	return , , nil
}

// ConsumeTypedEnvelope unmarshals a serialized Envelope and validates its
// signature. If validation fails, an error is returned, along with the unmarshalled
// envelope, so it can be inspected.
//
// Unlike ConsumeEnvelope, ConsumeTypedEnvelope does not try to automatically determine
// the type of Record to unmarshal the Envelope's payload into. Instead, the caller provides
// a destination Record instance, which will unmarshal the Envelope payload. It is the caller's
// responsibility to determine whether the given Record type is able to unmarshal the payload
// correctly.
//
//	rec := &MyRecordType{}
//	envelope, err := ConsumeTypedEnvelope(envelopeBytes, rec)
//	if err != nil {
//	  handleError(envelope, err)
//	}
//	doSomethingWithRecord(rec)
//
// Important: you MUST check the error value before using the returned Envelope. In some error
// cases, including when the envelope signature is invalid, both the Envelope and an error will
// be returned. This allows you to inspect the unmarshalled but invalid Envelope. As a result,
// you must not assume that any non-nil Envelope returned from this function is valid.
func ( []byte,  Record) ( *Envelope,  error) {
	,  := UnmarshalEnvelope()
	if  != nil {
		return nil, fmt.Errorf("failed when unmarshalling the envelope: %w", )
	}

	 = .validate(.Domain())
	if  != nil {
		return , fmt.Errorf("failed to validate envelope: %w", )
	}

	 = .UnmarshalRecord(.RawPayload)
	if  != nil {
		return , fmt.Errorf("failed to unmarshal envelope payload: %w", )
	}
	.cached = 
	return , nil
}

// UnmarshalEnvelope unmarshals a serialized Envelope protobuf message,
// without validating its contents. Most users should use ConsumeEnvelope.
func ( []byte) (*Envelope, error) {
	var  pb.Envelope
	if  := proto.Unmarshal(, &);  != nil {
		return nil, 
	}

	,  := crypto.PublicKeyFromProto(.PublicKey)
	if  != nil {
		return nil, 
	}

	return &Envelope{
		PublicKey:   ,
		PayloadType: .PayloadType,
		RawPayload:  .Payload,
		signature:   .Signature,
	}, nil
}

// Marshal returns a byte slice containing a serialized protobuf representation
// of an Envelope.
func ( *Envelope) () ( []byte,  error) {
	defer func() { catch.HandlePanic(recover(), &, "libp2p envelope marshal") }()
	,  := crypto.PublicKeyToProto(.PublicKey)
	if  != nil {
		return nil, 
	}

	 := pb.Envelope{
		PublicKey:   ,
		PayloadType: .PayloadType,
		Payload:     .RawPayload,
		Signature:   .signature,
	}
	return proto.Marshal(&)
}

// Equal returns true if the other Envelope has the same public key,
// payload, payload type, and signature. This implies that they were also
// created with the same domain string.
func ( *Envelope) ( *Envelope) bool {
	if  == nil {
		return  == nil
	}
	return .PublicKey.Equals(.PublicKey) &&
		bytes.Equal(.PayloadType, .PayloadType) &&
		bytes.Equal(.signature, .signature) &&
		bytes.Equal(.RawPayload, .RawPayload)
}

// Record returns the Envelope's payload unmarshalled as a Record.
// The concrete type of the returned Record depends on which Record
// type was registered for the Envelope's PayloadType - see record.RegisterType.
//
// Once unmarshalled, the Record is cached for future access.
func ( *Envelope) () (Record, error) {
	.unmarshalOnce.Do(func() {
		if .cached != nil {
			return
		}
		.cached, .unmarshalError = unmarshalRecordPayload(.PayloadType, .RawPayload)
	})
	return .cached, .unmarshalError
}

// TypedRecord unmarshals the Envelope's payload to the given Record instance.
// It is the caller's responsibility to ensure that the Record type is capable
// of unmarshalling the Envelope payload. Callers can inspect the Envelope's
// PayloadType field to determine the correct type of Record to use.
//
// This method will always unmarshal the Envelope payload even if a cached record
// exists.
func ( *Envelope) ( Record) error {
	return .UnmarshalRecord(.RawPayload)
}

// validate returns nil if the envelope signature is valid for the given 'domain',
// or an error if signature validation fails.
func ( *Envelope) ( string) error {
	,  := makeUnsigned(, .PayloadType, .RawPayload)
	if  != nil {
		return 
	}
	defer pool.Put()

	,  := .PublicKey.Verify(, .signature)
	if  != nil {
		return fmt.Errorf("failed while verifying signature: %w", )
	}
	if ! {
		return ErrInvalidSignature
	}
	return nil
}

// makeUnsigned is a helper function that prepares a buffer to sign or verify.
// It returns a byte slice from a pool. The caller MUST return this slice to the
// pool.
func makeUnsigned( string,  []byte,  []byte) ([]byte, error) {
	var (
		 = [][]byte{[]byte(), , }

		// fields are prefixed with their length as an unsigned varint. we
		// compute the lengths before allocating the sig buffer, so we know how
		// much space to add for the lengths
		 = make([][]byte, len())
		 = 0
	)

	for ,  := range  {
		 := len()
		[] = varint.ToUvarint(uint64())
		 +=  + len([])
	}

	 := pool.Get()

	var  int
	for ,  := range  {
		 += copy([:], [])
		 += copy([:], )
	}

	return [:], nil
}