func (se StatusError) Error() string {
	return fmt.Sprintf("%s '0x%X'", se.msg, int(se.status))
}
Error implements error interface.
package smpp
import (
	"context"
	"crypto/rand"
	"errors"
	"flag"
	"fmt"
	"io"
	"log"
	"net"
	"sync"
	"time"
	"github.com/ajankovic/smpp/pdu"
)
var smppLogs bool
func init() {
	flag.BoolVar(&smppLogs, "smpp.logs", false, "show smpp logging")
}
type Error struct {
	Msg  string
	Temp bool
}
func (e Error) Error() string {
	return e.Msg
}
func (e Error) Temporary() bool {
	return e.Temp
}
type SessionState int
const (
	StateOpen SessionState = iota
	StateBinding
	StateBoundTx
	StateBoundRx
	StateBoundTRx
	StateUnbinding
	StateClosing
	StateClosed
)
type SessionType int
const (
	ESME SessionType = iota
	SMSC
)
type Logger interface {
	InfoF(msg string, params ...interface{})
	ErrorF(msg string, params ...interface{})
}
type DefaultLogger struct{}
func (dl DefaultLogger) InfoF(msg string, params ...interface{}) {
	if smppLogs {
		log.Printf("INFO: "+msg+"\n", params...)
	}
}
func (dl DefaultLogger) ErrorF(msg string, params ...interface{}) {
	if smppLogs {
		log.Printf("ERRO: "+msg+"\n", params...)
	}
}
type Handler interface {
	ServeSMPP(ctx *Context)
}
type HandlerFunc func(ctx *Context)
func (hc HandlerFunc) ServeSMPP(ctx *Context) {
	hc(ctx)
}
type defaultHandler struct{}
func (h defaultHandler) ServeSMPP(ctx *Context) {
	ctx.Respond(&pdu.GenericNack{}, pdu.StatusSysErr)
}
func genSessionID() string {
	b := make([]byte, 12)
	_, err := rand.Read(b)
	if err != nil {
		panic(err)
	}
	return fmt.Sprintf("%X-%X-%X", b[0:4], b[4:6], b[6:8])
}
type RemoteAddresser interface {
	RemoteAddr() net.Addr
}
type SessionConf struct {
	Type          SessionType
	SendWinSize   int
	ReqWinSize    int
	WindowTimeout time.Duration
	SessionState  func(sessionID, systemID string, state SessionState)
	SystemID      string
	ID            string
	Logger        Logger
	Handler       Handler
	Sequencer     pdu.Sequencer
}
type response struct {
	resp pdu.PDU
	err  error
}
type Session struct {
	conf     *SessionConf
	rwc      io.ReadWriteCloser
	enc      *pdu.Encoder
	dec      *pdu.Decoder
	wg       sync.WaitGroup
	mu       sync.Mutex
	seq      uint32
	reqCount int
	sent     map[uint32]chan response
	state    SessionState
	systemID string
	closed   chan struct{}
}
func NewSession(rwc io.ReadWriteCloser, conf SessionConf) *Session {
	if conf.SendWinSize == 0 {
		conf.SendWinSize = 10
	}
	if conf.Logger == nil {
		conf.Logger = DefaultLogger{}
	}
	if conf.Handler == nil {
		conf.Handler = &defaultHandler{}
	}
	if conf.WindowTimeout == 0 {
		conf.WindowTimeout = 10 * time.Second
	}
	if conf.ReqWinSize == 0 {
		conf.ReqWinSize = 10
	}
	if conf.ID == "" {
		conf.ID = genSessionID()
	}
	sess := &Session{
		conf:   &conf,
		rwc:    rwc,
		enc:    pdu.NewEncoder(rwc, conf.Sequencer),
		dec:    pdu.NewDecoder(rwc),
		sent:   make(map[uint32]chan response, conf.SendWinSize),
		closed: make(chan struct{}),
	}
	sess.wg.Add(1)
	go sess.serve()
	return sess
}
func (sess *Session) ID() string {
	return sess.conf.ID
}
func (sess *Session) SystemID() string {
	if sess.conf.SystemID != "" {
		return sess.conf.SystemID
	}
	if sess.systemID != "" {
		return sess.systemID
	}
	return "-"
}
func (sess *Session) String() string {
	return fmt.Sprintf("(%s:%s:%s)", sess.conf.Type, sess.SystemID(), sess.conf.ID)
}
func (sess *Session) remoteAddr() string {
	if ra, ok := sess.rwc.(RemoteAddresser); ok {
		return ra.RemoteAddr().String()
	}
	return ""
}
func (sess *Session) serve() {
	defer sess.wg.Done()
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()
	for {
		h, p, err := sess.dec.Decode()
		if err != nil {
			if err == io.EOF {
				sess.conf.Logger.InfoF("decoding pdu: %s %+v", sess, err)
			} else {
				sess.conf.Logger.ErrorF("decoding pdu: %s %+v", sess, err)
			}
			sess.shutdown()
			return
		}
		sess.mu.Lock()
		sess.systemID = pdu.SystemID(p)
		if err := sess.makeTransition(h.CommandID(), true); err != nil {
			sess.conf.Logger.ErrorF("transitioning upon receive: %s %+v", sess, err)
			sess.mu.Unlock()
			continue
		}
		if pdu.IsRequest(h.CommandID()) {
			sess.conf.Logger.InfoF("received request: %s %s%+v", sess, p.CommandID(), p)
			if sess.reqCount == sess.conf.ReqWinSize {
				sess.throttle(h.Sequence())
			} else {
				sess.wg.Add(1)
				sess.reqCount++
				go sess.handleRequest(ctx, h, p)
			}
			sess.mu.Unlock()
			continue
		}
		if l, ok := sess.sent[h.Sequence()]; ok {
			sess.conf.Logger.InfoF("received response: %s %s%+v", sess, p.CommandID(), p)
			delete(sess.sent, h.Sequence())
			sess.mu.Unlock()
			l <- response{
				resp: p,
				err:  toError(h.Status()),
			}
			continue
		}
		sess.conf.Logger.ErrorF("unexpected response: %s %s%+v", sess, p.CommandID(), p)
		sess.mu.Unlock()
	}
}
func (sess *Session) throttle(seq uint32) {
	resp := pdu.GenericNack{}
	if _, err := sess.enc.Encode(resp, pdu.EncodeStatus(pdu.StatusThrottled), pdu.EncodeSeq(seq)); err != nil {
		sess.conf.Logger.ErrorF("error encoding pdu: %s %+v", sess, err)
		return
	}
}
func (sess *Session) handleRequest(ctx context.Context, h pdu.Header, req pdu.PDU) {
	ctx, cancel := context.WithTimeout(ctx, sess.conf.WindowTimeout)
	defer func() {
		cancel()
		sess.mu.Lock()
		sess.reqCount--
		sess.mu.Unlock()
		sess.wg.Done()
	}()
	sessCtx := &Context{
		sess: sess,
		ctx:  ctx,
		seq:  h.Sequence(),
		req:  req,
	}
	sess.conf.Handler.ServeSMPP(sessCtx)
	if sessCtx.close {
		sess.shutdown()
	}
}
func (sess *Session) shutdown() {
	go sess.Close()
}
func (sess *Session) Close() error {
	sess.mu.Lock()
	if err := sess.setState(StateClosing); err != nil {
		sess.mu.Unlock()
		return err
	}
	for k, l := range sess.sent {
		delete(sess.sent, k)
		close(l)
	}
	sess.rwc.Close()
	if err := sess.setState(StateClosed); err != nil {
		sess.mu.Unlock()
		return err
	}
	sess.mu.Unlock()
	sess.wg.Wait()
	sess.conf.Logger.InfoF("session closed: %s", sess)
	close(sess.closed)
	return nil
}
func (sess *Session) setState(state SessionState) error {
	if sess.state == state {
		return fmt.Errorf("smpp: setting same state twice %s", state)
	}
	switch sess.state {
	case StateOpen:
		if state != StateBinding {
			return fmt.Errorf("smpp: setting open session to invalid state %s", state)
		}
	case StateBinding:
		switch state {
		case StateOpen, StateBoundRx, StateBoundTRx, StateBoundTx:
		default:
			return fmt.Errorf("smpp: setting binding session to invalid state %s", state)
		}
	case StateBoundRx, StateBoundTRx, StateBoundTx:
		switch state {
		case StateUnbinding, StateClosing:
		default:
			return fmt.Errorf("smpp: setting bound session to invalid state %s", state)
		}
	case StateUnbinding:
		if state != StateClosing {
			return fmt.Errorf("smpp: setting unbinding session to invalid state %s", state)
		}
	case StateClosing:
		if state != StateClosed {
			return fmt.Errorf("smpp: setting closing session to invalid state %s", state)
		}
	case StateClosed:
		return fmt.Errorf("smpp: session %s already in closed state %s", sess, state)
	}
	sess.state = state
	if hook := sess.conf.SessionState; hook != nil {
		hook(sess.conf.ID, sess.SystemID(), sess.state)
	}
	return nil
}
func (sess *Session) Send(ctx context.Context, req pdu.PDU) (pdu.PDU, error) {
	if req == nil {
		return nil, Error{Msg: "smpp: sending nil pdu"}
	}
	sess.mu.Lock()
	if len(sess.sent) == sess.conf.SendWinSize {
		sess.mu.Unlock()
		return nil, Error{Msg: "smpp: sending window closed", Temp: true}
	}
	if err := sess.makeTransition(req.CommandID(), false); err != nil {
		sess.conf.Logger.ErrorF("transitioning before send: %s %+v", sess, err)
		sess.mu.Unlock()
		return nil, err
	}
	seq, err := sess.enc.Encode(req)
	if err != nil {
		sess.mu.Unlock()
		return nil, err
	}
	l := make(chan response, 1)
	sess.sent[seq] = l
	sess.conf.Logger.InfoF("request sent: %s %s%+v", sess, req.CommandID(), req)
	sess.mu.Unlock()
	select {
	case resp, ok := <-l:
		if !ok {
			return nil, errors.New("smpp: session closed before receiving response")
		}
		if resp.err != nil {
			return resp.resp, resp.err
		}
		return resp.resp, nil
	case <-ctx.Done():
		return nil, ctx.Err()
	}
}
func (sess *Session) makeTransition(ID pdu.CommandID, received bool) error {
	if (sess.conf.Type == ESME && !received) || (sess.conf.Type == SMSC && received) {
		switch sess.state {
		case StateOpen:
			switch ID {
			case pdu.BindTransceiverID, pdu.BindTransmitterID, pdu.BindReceiverID:
				return sess.setState(StateBinding)
			}
		case StateBinding:
			if ID == pdu.GenericNackID {
				return sess.setState(StateOpen)
			}
		case StateBoundTx:
			switch ID {
			case pdu.UnbindID:
				return sess.setState(StateUnbinding)
			case pdu.UnbindRespID, pdu.DeliverSmRespID, pdu.DataSmID, pdu.SubmitSmID, pdu.SubmitMultiID,
				pdu.DataSmRespID, pdu.EnquireLinkID, pdu.EnquireLinkRespID, pdu.ReplaceSmID,
				pdu.GenericNackID:
				return nil
			}
		case StateBoundRx:
			switch ID {
			case pdu.UnbindID:
				return sess.setState(StateUnbinding)
			case pdu.UnbindRespID, pdu.DeliverSmRespID, pdu.DataSmID,
				pdu.DataSmRespID, pdu.EnquireLinkID, pdu.EnquireLinkRespID,
				pdu.GenericNackID:
				return nil
			}
		case StateBoundTRx:
			switch ID {
			case pdu.UnbindID:
				return sess.setState(StateUnbinding)
			case pdu.SubmitSmID, pdu.SubmitSmRespID, pdu.DeliverSmRespID,
				pdu.DataSmID, pdu.DataSmRespID, pdu.EnquireLinkID, pdu.EnquireLinkRespID, pdu.SubmitMultiID, pdu.SubmitMultiRespID,
				pdu.QuerySmID, pdu.CancelSmID, pdu.GenericNackID:
				return nil
			}
		case StateUnbinding:
			if ID == pdu.UnbindRespID {
				return nil
			}
		case StateClosing, StateClosed:
		}
	} else if (sess.conf.Type == SMSC && !received) || (sess.conf.Type == ESME && received) {
		switch sess.state {
		case StateOpen:
			switch ID {
			case pdu.OutbindID:
				return nil
			}
		case StateBinding:
			switch ID {
			case pdu.BindTransceiverRespID:
				return sess.setState(StateBoundTRx)
			case pdu.BindTransmitterRespID:
				return sess.setState(StateBoundTx)
			case pdu.BindReceiverRespID:
				return sess.setState(StateBoundRx)
			case pdu.GenericNackID:
				return sess.setState(StateOpen)
			}
		case StateBoundTx:
			switch ID {
			case pdu.UnbindID:
				return sess.setState(StateUnbinding)
			case pdu.SubmitSmRespID, pdu.SubmitMultiRespID, pdu.DataSmID, pdu.DataSmRespID,
				pdu.QuerySmRespID, pdu.CancelSmRespID, pdu.ReplaceSmRespID, pdu.EnquireLinkID, pdu.EnquireLinkRespID,
				pdu.GenericNackID:
				return nil
			}
		case StateBoundRx:
			switch ID {
			case pdu.UnbindID:
				return sess.setState(StateUnbinding)
			case pdu.DeliverSmID, pdu.DataSmID, pdu.DataSmRespID,
				pdu.EnquireLinkID, pdu.EnquireLinkRespID, pdu.AlertNotificationID, pdu.GenericNackID:
				return nil
			}
		case StateBoundTRx:
			switch ID {
			case pdu.UnbindID:
				return sess.setState(StateUnbinding)
			case pdu.SubmitSmRespID, pdu.SubmitMultiRespID, pdu.DataSmID, pdu.DataSmRespID, pdu.DeliverSmRespID,
				pdu.QuerySmRespID, pdu.CancelSmRespID, pdu.AlertNotificationID, pdu.ReplaceSmRespID, pdu.EnquireLinkID, pdu.EnquireLinkRespID,
				pdu.GenericNackID:
				return nil
			}
		case StateUnbinding:
			if ID == pdu.UnbindRespID {
				return nil
			}
		case StateClosing, StateClosed:
		}
	}
	return Error{Msg: fmt.Sprintf("smpp: processing '%s' in invalid session state '%s'", ID, sess.state), Temp: true}
}
func (sess *Session) NotifyClosed() <-chan struct{} {
	return sess.closed
}
type StatusError struct {
	msg    string
	status pdu.Status
}