/*
 *
 * Copyright 2018 gRPC authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package binarylog

import (
	
	
	
	

	
	
	binlogpb 
	
	
)

type callIDGenerator struct {
	id uint64
}

func ( *callIDGenerator) () uint64 {
	 := atomic.AddUint64(&.id, 1)
	return 
}

// reset is for testing only, and doesn't need to be thread safe.
func ( *callIDGenerator) () {
	.id = 0
}

var idGen callIDGenerator

// MethodLogger is the sub-logger for each method.
type MethodLogger interface {
	Log(LogEntryConfig)
}

// TruncatingMethodLogger is a method logger that truncates headers and messages
// based on configured fields.
type TruncatingMethodLogger struct {
	headerMaxLen, messageMaxLen uint64

	callID          uint64
	idWithinCallGen *callIDGenerator

	sink Sink // TODO(blog): make this plugable.
}

// NewTruncatingMethodLogger returns a new truncating method logger.
func (,  uint64) *TruncatingMethodLogger {
	return &TruncatingMethodLogger{
		headerMaxLen:  ,
		messageMaxLen: ,

		callID:          idGen.next(),
		idWithinCallGen: &callIDGenerator{},

		sink: DefaultSink, // TODO(blog): make it plugable.
	}
}

// Build is an internal only method for building the proto message out of the
// input event. It's made public to enable other library to reuse as much logic
// in TruncatingMethodLogger as possible.
func ( *TruncatingMethodLogger) ( LogEntryConfig) *binlogpb.GrpcLogEntry {
	 := .toProto()
	,  := ptypes.TimestampProto(time.Now())
	.Timestamp = 
	.CallId = .callID
	.SequenceIdWithinCall = .idWithinCallGen.next()

	switch pay := .Payload.(type) {
	case *binlogpb.GrpcLogEntry_ClientHeader:
		.PayloadTruncated = .truncateMetadata(.ClientHeader.GetMetadata())
	case *binlogpb.GrpcLogEntry_ServerHeader:
		.PayloadTruncated = .truncateMetadata(.ServerHeader.GetMetadata())
	case *binlogpb.GrpcLogEntry_Message:
		.PayloadTruncated = .truncateMessage(.Message)
	}
	return 
}

// Log creates a proto binary log entry, and logs it to the sink.
func ( *TruncatingMethodLogger) ( LogEntryConfig) {
	.sink.Write(.Build())
}

func ( *TruncatingMethodLogger) ( *binlogpb.Metadata) ( bool) {
	if .headerMaxLen == maxUInt {
		return false
	}
	var (
		 = .headerMaxLen
		      int
	)
	// At the end of the loop, index will be the first entry where the total
	// size is greater than the limit:
	//
	// len(entry[:index]) <= ml.hdr && len(entry[:index+1]) > ml.hdr.
	for ;  < len(.Entry); ++ {
		 := .Entry[]
		if .Key == "grpc-trace-bin" {
			// "grpc-trace-bin" is a special key. It's kept in the log entry,
			// but not counted towards the size limit.
			continue
		}
		 := uint64(len(.GetKey())) + uint64(len(.GetValue()))
		if  >  {
			break
		}
		 -= 
	}
	 =  < len(.Entry)
	.Entry = .Entry[:]
	return 
}

func ( *TruncatingMethodLogger) ( *binlogpb.Message) ( bool) {
	if .messageMaxLen == maxUInt {
		return false
	}
	if .messageMaxLen >= uint64(len(.Data)) {
		return false
	}
	.Data = .Data[:.messageMaxLen]
	return true
}

// LogEntryConfig represents the configuration for binary log entry.
type LogEntryConfig interface {
	toProto() *binlogpb.GrpcLogEntry
}

// ClientHeader configs the binary log entry to be a ClientHeader entry.
type ClientHeader struct {
	OnClientSide bool
	Header       metadata.MD
	MethodName   string
	Authority    string
	Timeout      time.Duration
	// PeerAddr is required only when it's on server side.
	PeerAddr net.Addr
}

func ( *ClientHeader) () *binlogpb.GrpcLogEntry {
	// This function doesn't need to set all the fields (e.g. seq ID). The Log
	// function will set the fields when necessary.
	 := &binlogpb.ClientHeader{
		Metadata:   mdToMetadataProto(.Header),
		MethodName: .MethodName,
		Authority:  .Authority,
	}
	if .Timeout > 0 {
		.Timeout = ptypes.DurationProto(.Timeout)
	}
	 := &binlogpb.GrpcLogEntry{
		Type: binlogpb.GrpcLogEntry_EVENT_TYPE_CLIENT_HEADER,
		Payload: &binlogpb.GrpcLogEntry_ClientHeader{
			ClientHeader: ,
		},
	}
	if .OnClientSide {
		.Logger = binlogpb.GrpcLogEntry_LOGGER_CLIENT
	} else {
		.Logger = binlogpb.GrpcLogEntry_LOGGER_SERVER
	}
	if .PeerAddr != nil {
		.Peer = addrToProto(.PeerAddr)
	}
	return 
}

// ServerHeader configs the binary log entry to be a ServerHeader entry.
type ServerHeader struct {
	OnClientSide bool
	Header       metadata.MD
	// PeerAddr is required only when it's on client side.
	PeerAddr net.Addr
}

func ( *ServerHeader) () *binlogpb.GrpcLogEntry {
	 := &binlogpb.GrpcLogEntry{
		Type: binlogpb.GrpcLogEntry_EVENT_TYPE_SERVER_HEADER,
		Payload: &binlogpb.GrpcLogEntry_ServerHeader{
			ServerHeader: &binlogpb.ServerHeader{
				Metadata: mdToMetadataProto(.Header),
			},
		},
	}
	if .OnClientSide {
		.Logger = binlogpb.GrpcLogEntry_LOGGER_CLIENT
	} else {
		.Logger = binlogpb.GrpcLogEntry_LOGGER_SERVER
	}
	if .PeerAddr != nil {
		.Peer = addrToProto(.PeerAddr)
	}
	return 
}

// ClientMessage configs the binary log entry to be a ClientMessage entry.
type ClientMessage struct {
	OnClientSide bool
	// Message can be a proto.Message or []byte. Other messages formats are not
	// supported.
	Message interface{}
}

func ( *ClientMessage) () *binlogpb.GrpcLogEntry {
	var (
		 []byte
		  error
	)
	if ,  := .Message.(proto.Message);  {
		,  = proto.Marshal()
		if  != nil {
			grpclogLogger.Infof("binarylogging: failed to marshal proto message: %v", )
		}
	} else if ,  := .Message.([]byte);  {
		 = 
	} else {
		grpclogLogger.Infof("binarylogging: message to log is neither proto.message nor []byte")
	}
	 := &binlogpb.GrpcLogEntry{
		Type: binlogpb.GrpcLogEntry_EVENT_TYPE_CLIENT_MESSAGE,
		Payload: &binlogpb.GrpcLogEntry_Message{
			Message: &binlogpb.Message{
				Length: uint32(len()),
				Data:   ,
			},
		},
	}
	if .OnClientSide {
		.Logger = binlogpb.GrpcLogEntry_LOGGER_CLIENT
	} else {
		.Logger = binlogpb.GrpcLogEntry_LOGGER_SERVER
	}
	return 
}

// ServerMessage configs the binary log entry to be a ServerMessage entry.
type ServerMessage struct {
	OnClientSide bool
	// Message can be a proto.Message or []byte. Other messages formats are not
	// supported.
	Message interface{}
}

func ( *ServerMessage) () *binlogpb.GrpcLogEntry {
	var (
		 []byte
		  error
	)
	if ,  := .Message.(proto.Message);  {
		,  = proto.Marshal()
		if  != nil {
			grpclogLogger.Infof("binarylogging: failed to marshal proto message: %v", )
		}
	} else if ,  := .Message.([]byte);  {
		 = 
	} else {
		grpclogLogger.Infof("binarylogging: message to log is neither proto.message nor []byte")
	}
	 := &binlogpb.GrpcLogEntry{
		Type: binlogpb.GrpcLogEntry_EVENT_TYPE_SERVER_MESSAGE,
		Payload: &binlogpb.GrpcLogEntry_Message{
			Message: &binlogpb.Message{
				Length: uint32(len()),
				Data:   ,
			},
		},
	}
	if .OnClientSide {
		.Logger = binlogpb.GrpcLogEntry_LOGGER_CLIENT
	} else {
		.Logger = binlogpb.GrpcLogEntry_LOGGER_SERVER
	}
	return 
}

// ClientHalfClose configs the binary log entry to be a ClientHalfClose entry.
type ClientHalfClose struct {
	OnClientSide bool
}

func ( *ClientHalfClose) () *binlogpb.GrpcLogEntry {
	 := &binlogpb.GrpcLogEntry{
		Type:    binlogpb.GrpcLogEntry_EVENT_TYPE_CLIENT_HALF_CLOSE,
		Payload: nil, // No payload here.
	}
	if .OnClientSide {
		.Logger = binlogpb.GrpcLogEntry_LOGGER_CLIENT
	} else {
		.Logger = binlogpb.GrpcLogEntry_LOGGER_SERVER
	}
	return 
}

// ServerTrailer configs the binary log entry to be a ServerTrailer entry.
type ServerTrailer struct {
	OnClientSide bool
	Trailer      metadata.MD
	// Err is the status error.
	Err error
	// PeerAddr is required only when it's on client side and the RPC is trailer
	// only.
	PeerAddr net.Addr
}

func ( *ServerTrailer) () *binlogpb.GrpcLogEntry {
	,  := status.FromError(.Err)
	if ! {
		grpclogLogger.Info("binarylogging: error in trailer is not a status error")
	}
	var (
		 []byte
		          error
	)
	 := .Proto()
	if  != nil && len(.Details) != 0 {
		,  = proto.Marshal()
		if  != nil {
			grpclogLogger.Infof("binarylogging: failed to marshal status proto: %v", )
		}
	}
	 := &binlogpb.GrpcLogEntry{
		Type: binlogpb.GrpcLogEntry_EVENT_TYPE_SERVER_TRAILER,
		Payload: &binlogpb.GrpcLogEntry_Trailer{
			Trailer: &binlogpb.Trailer{
				Metadata:      mdToMetadataProto(.Trailer),
				StatusCode:    uint32(.Code()),
				StatusMessage: .Message(),
				StatusDetails: ,
			},
		},
	}
	if .OnClientSide {
		.Logger = binlogpb.GrpcLogEntry_LOGGER_CLIENT
	} else {
		.Logger = binlogpb.GrpcLogEntry_LOGGER_SERVER
	}
	if .PeerAddr != nil {
		.Peer = addrToProto(.PeerAddr)
	}
	return 
}

// Cancel configs the binary log entry to be a Cancel entry.
type Cancel struct {
	OnClientSide bool
}

func ( *Cancel) () *binlogpb.GrpcLogEntry {
	 := &binlogpb.GrpcLogEntry{
		Type:    binlogpb.GrpcLogEntry_EVENT_TYPE_CANCEL,
		Payload: nil,
	}
	if .OnClientSide {
		.Logger = binlogpb.GrpcLogEntry_LOGGER_CLIENT
	} else {
		.Logger = binlogpb.GrpcLogEntry_LOGGER_SERVER
	}
	return 
}

// metadataKeyOmit returns whether the metadata entry with this key should be
// omitted.
func ( string) bool {
	switch  {
	case "lb-token", ":path", ":authority", "content-encoding", "content-type", "user-agent", "te":
		return true
	case "grpc-trace-bin": // grpc-trace-bin is special because it's visiable to users.
		return false
	}
	return strings.HasPrefix(, "grpc-")
}

func ( metadata.MD) *binlogpb.Metadata {
	 := &binlogpb.Metadata{}
	for ,  := range  {
		if metadataKeyOmit() {
			continue
		}
		for ,  := range  {
			.Entry = append(.Entry,
				&binlogpb.MetadataEntry{
					Key:   ,
					Value: []byte(),
				},
			)
		}
	}
	return 
}

func ( net.Addr) *binlogpb.Address {
	 := &binlogpb.Address{}
	switch a := .(type) {
	case *net.TCPAddr:
		if .IP.To4() != nil {
			.Type = binlogpb.Address_TYPE_IPV4
		} else if .IP.To16() != nil {
			.Type = binlogpb.Address_TYPE_IPV6
		} else {
			.Type = binlogpb.Address_TYPE_UNKNOWN
			// Do not set address and port fields.
			break
		}
		.Address = .IP.String()
		.IpPort = uint32(.Port)
	case *net.UnixAddr:
		.Type = binlogpb.Address_TYPE_UNIX
		.Address = .String()
	default:
		.Type = binlogpb.Address_TYPE_UNKNOWN
	}
	return 
}