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
}
Close implements Closer interface. It MUST be called to dispose session cleanly.
It gracefully waits for all handlers to finish execution before returning.
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()
}