diago

package module
v0.0.0-...-1aa92ef Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 20, 2025 License: MPL-2.0 Imports: 27 Imported by: 0

README

DIAGO

Go Report Card Coverage GitHub go.mod Go version

Short of dialog + GO.
Library for building VOIP solutions in GO!

Built on top of optimized SIPgo library!
In short it allows developing fast and easy testable VOIP apps to handle calls, registrations and more...

Diago is mainly project driven lib, so lot of API design will/should be challenged with real working apps needs

For more information and documentation visit the website

Quick links:

If you find this project useful and you want to support/sponzor or need help with your projects, you can contact me more on mail.

Follow me on X/Twitter for regular updates

Contributions

Please open first issues instead PRs. Library is under development and could not have latest code pushed.

Usage

Checkout more on Getting started, but for quick view here is echotest (hello world) example.

ua, _ := sipgo.NewUA()
dg := diago.NewDiago(ua)

dg.Serve(ctx, func(inDialog *diago.DialogServerSession) {
	inDialog.Progress() // Progress -> 100 Trying
	inDialog.Answer(); // Answer

	// Make sure file below exists in work dir
	playfile, err := os.Open("demo-echotest.wav")
	if err != nil {
		fmt.Println("Failed to open file", err)
		return
	}
	defer playfile.Close()

	// Create playback and play file.
	pb, _ := inDialog.PlaybackCreate()
	if err := pb.Play(playfile, "audio/wav"); err != nil {
		fmt.Println("Playing failed", err)
	}
}

See more examples in this repo

Tracing SIP, RTP

While openning issue, consider having some traces enabled.

sip.SIPDebug = true // Enables SIP tracing
media.RTCPDebug = true // Enables RTCP tracing
media.RTPDebug = true // Enables RTP tracing. NOTE: It will dump every RTP Packet

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrDigestAuthNoChallenge = errors.New("no challenge")
	ErrDigestAuthBadCreds    = errors.New("bad credentials")
)
View Source
var (
	HTTPDebug = os.Getenv("HTTP_DEBUG") == "true"
)
View Source
var (
	PlaybackBufferSize = 3840 // For now largest we support. 48000 sample rate with 2 channels
)

Functions

func NewConnRecorder

func NewConnRecorder() *connRecorder

Types

type AnswerOptions

type AnswerOptions struct {
	// OnMediaUpdate triggers when media update happens. It is blocking func, so make sure you exit
	OnMediaUpdate func(d *DialogMedia)
	OnRefer       func(referDialog *DialogClientSession)
	// Codecs that will be used
	Codecs []media.Codec
}

type AudioPlayback

type AudioPlayback struct {

	// Read only values
	// This will influence playout sampling buffer
	BitDepth    int
	NumChannels int
	// contains filtered or unexported fields
}

func NewAudioPlayback

func NewAudioPlayback(writer io.Writer, codec media.Codec) AudioPlayback

NewAudioPlayback creates a playback where writer is encoder/streamer to media codec Use dialog.PlaybackCreate() instead creating manually playback

func (*AudioPlayback) Play

func (p *AudioPlayback) Play(reader io.Reader, mimeType string) (int64, error)

Play is generic approach to play supported audio contents Empty mimeType will stream reader as buffer. Make sure that bitdepth and numchannels is set correctly

func (*AudioPlayback) PlayFile

func (p *AudioPlayback) PlayFile(filename string) (int64, error)

PlayFile will play file and close file when finished playing If you need to play same file multiple times, that use generic Play function

func (*AudioPlayback) PlayURL

func (p *AudioPlayback) PlayURL(urlStr string) (int64, error)

func (*AudioPlayback) PlayWithContext

func (p *AudioPlayback) PlayWithContext(ctx context.Context, reader io.Reader, mimeType string) (int64, error)

type AudioPlaybackControl

type AudioPlaybackControl struct {
	AudioPlayback
	// contains filtered or unexported fields
}

func (*AudioPlaybackControl) Mute

func (p *AudioPlaybackControl) Mute(mute bool)

func (*AudioPlaybackControl) Stop

func (p *AudioPlaybackControl) Stop()

type AudioReaderOption

type AudioReaderOption func(d *DialogMedia) error

func WithAudioReaderDTMF

func WithAudioReaderDTMF(r *DTMFReader) AudioReaderOption

WithAudioReaderDTMF creates DTMF interceptor

func WithAudioReaderMediaProps

func WithAudioReaderMediaProps(p *MediaProps) AudioReaderOption

func WithAudioReaderRTPStats

func WithAudioReaderRTPStats(hook media.OnRTPReadStats) AudioReaderOption

WithAudioReaderRTPStats creates RTP Statistics interceptor on audio reader

type AudioWriterOption

type AudioWriterOption func(d *DialogMedia) error

func WithAudioWriterDTMF

func WithAudioWriterDTMF(r *DTMFWriter) AudioWriterOption

WithAudioWriterDTMF creates DTMF interceptor

func WithAudioWriterMediaProps

func WithAudioWriterMediaProps(p *MediaProps) AudioWriterOption

func WithAudioWriterRTPStats

func WithAudioWriterRTPStats(hook media.OnRTPWriteStats) AudioWriterOption

WithAudioReaderRTPStats creates RTP Statistics interceptor on audio reader

type Bridge

type Bridge struct {
	// Originator is dialog session that created bridge
	Originator DialogSession
	// DTMFpass is also dtmf pipeline and proxy. By default only audio media is proxied
	DTMFpass bool
	// contains filtered or unexported fields
}

func NewBridge

func NewBridge() Bridge

func (*Bridge) AddDialogSession

func (b *Bridge) AddDialogSession(d DialogSession) error

func (*Bridge) GetDialogs

func (b *Bridge) GetDialogs() []DialogSession

func (*Bridge) Init

func (b *Bridge) Init(log *slog.Logger)

type Bridger

type Bridger interface {
	AddDialogSession(d DialogSession) error
}

type DTMFReader

type DTMFReader struct {
	// contains filtered or unexported fields
}

func (*DTMFReader) Listen

func (d *DTMFReader) Listen(onDTMF func(dtmf rune) error, dur time.Duration) error

func (*DTMFReader) OnDTMF

func (d *DTMFReader) OnDTMF(onDTMF func(dtmf rune) error)

OnDTMF must be called before audio reading

func (*DTMFReader) Read

func (d *DTMFReader) Read(buf []byte) (n int, err error)

Read exposes io.Reader that can be used as AudioReader

type DTMFWriter

type DTMFWriter struct {
	// contains filtered or unexported fields
}

func (*DTMFWriter) AudioWriter

func (w *DTMFWriter) AudioWriter() *media.RTPDtmfWriter

AudioReader exposes DTMF audio writer. You should use this for parallel audio processing

func (*DTMFWriter) Write

func (w *DTMFWriter) Write(buf []byte) (n int, err error)

Write exposes as io.Writer that can be used as AudioWriter

func (*DTMFWriter) WriteDTMF

func (w *DTMFWriter) WriteDTMF(dtmf rune) error

type Diago

type Diago struct {
	// contains filtered or unexported fields
}

func NewDiago

func NewDiago(ua *sipgo.UserAgent, opts ...DiagoOption) *Diago

NewDiago construct b2b user agent that will act as server and client

func (*Diago) HandleFunc

func (dg *Diago) HandleFunc(f ServeDialogFunc)

HandleFunc registers you handler function for dialog. Must be called before serving request

func (*Diago) Invite

func (dg *Diago) Invite(ctx context.Context, recipient sip.Uri, opts InviteOptions) (d *DialogClientSession, err error)

Invite makes outgoing call leg and waits for answer. If you want to bridge call then use helper InviteBridge

func (*Diago) InviteBridge

func (dg *Diago) InviteBridge(ctx context.Context, recipient sip.Uri, bridge *Bridge, opts InviteOptions) (d *DialogClientSession, err error)

InviteBridge makes outgoing call leg and does bridging. Outgoing session will be added into bridge on answer If bridge has Originator (first participant) it will be used for creating outgoing call leg as in B2BUA When bridge is provided then this call will be bridged with any participant already present in bridge

func (*Diago) NewDialog

func (dg *Diago) NewDialog(recipient sip.Uri, opts NewDialogOptions) (d *DialogClientSession, err error)

NewDialog creates a new client dialog session after you can perform dialog Invite

func (*Diago) Register

func (dg *Diago) Register(ctx context.Context, recipient sip.Uri, opts RegisterOptions) error

Register will create register transaction and keep registration ongoing until error is hit. For more granular control over registraions user RegisterTransaction

func (*Diago) RegisterTransaction

func (dg *Diago) RegisterTransaction(ctx context.Context, recipient sip.Uri, opts RegisterOptions) (*RegisterTransaction, error)

Register transaction creates register transaction object that can be used for Register Unregister requests

func (*Diago) Serve

func (dg *Diago) Serve(ctx context.Context, f ServeDialogFunc) error

func (*Diago) ServeBackground

func (dg *Diago) ServeBackground(ctx context.Context, f ServeDialogFunc) error

Serve starts serving in background but waits server listener started before returning

type DiagoOption

type DiagoOption func(dg *Diago)

func WithAuth

func WithAuth(auth sipgo.DigestAuth) DiagoOption

func WithClient

func WithClient(client *sipgo.Client) DiagoOption

WithClient allows providing custom client handle. Consider still it needs to use same UA as diago

func WithLogger

func WithLogger(l *slog.Logger) DiagoOption

func WithMediaConfig

func WithMediaConfig(conf MediaConfig) DiagoOption

func WithServer

func WithServer(srv *sipgo.Server) DiagoOption

WithServer allows providing custom server handle. Consider still it needs to use same UA as diago

func WithTransport

func WithTransport(t Transport) DiagoOption

type DialogCache

type DialogCache[T DialogSession] interface {
	DialogStore(ctx context.Context, id string, v T) error
	DialogLoad(ctx context.Context, id string) (T, error)
	DialogDelete(ctx context.Context, id string) error
	DialogRange(ctx context.Context, f func(id string, d T) bool) error
}
var (
	// TODO, replace with typed versions
	DialogsClientCache DialogCache[*DialogClientSession] = &dialogCacheMap[*DialogClientSession]{sync.Map{}}
	DialogsServerCache DialogCache[*DialogServerSession] = &dialogCacheMap[*DialogServerSession]{sync.Map{}}
)

type DialogClientSession

type DialogClientSession struct {
	*sipgo.DialogClientSession

	DialogMedia
	// contains filtered or unexported fields
}

DialogClientSession represents outbound channel

func MatchDialogClient

func MatchDialogClient(req *sip.Request) (*DialogClientSession, error)

func (*DialogClientSession) Ack

Ack acknowledgeds media Before Ack normally you want to setup more stuff like bridging

func (*DialogClientSession) Close

func (d *DialogClientSession) Close() error

func (*DialogClientSession) DialogSIP

func (d *DialogClientSession) DialogSIP() *sipgo.Dialog

func (*DialogClientSession) FromUser

func (d *DialogClientSession) FromUser() string

func (*DialogClientSession) Hangup

func (d *DialogClientSession) Hangup(ctx context.Context) error

func (*DialogClientSession) Id

func (d *DialogClientSession) Id() string

func (*DialogClientSession) Invite

Invite sends Invite request and establishes early media. NOTE: You must call Ack after to acknowledge session. NOTE: It updates internal invite request so NOT THREAD SAFE. If you pass originator it will use originator to set correct from header and avoid media transcoding

Experimental: Note API may have changes

func (*DialogClientSession) ReInvite

func (d *DialogClientSession) ReInvite(ctx context.Context) error

ReInvite sends new invite based on current media session

func (*DialogClientSession) Refer

func (d *DialogClientSession) Refer(ctx context.Context, referTo sip.Uri) error

Refer tries todo refer (blind transfer) on call TODO: not complete

func (*DialogClientSession) RemoteContact

func (d *DialogClientSession) RemoteContact() *sip.ContactHeader

func (*DialogClientSession) ToUser

func (d *DialogClientSession) ToUser() string

type DialogData

type DialogData struct {
	InviteRequest sip.Request
	State         sip.DialogState
}

type DialogMedia

type DialogMedia struct {

	// Packet reader is default reader for RTP audio stream
	// Use always AudioReader to get current Audio reader
	// Use this only as read only
	// It MUST be always created on Media Session Init
	// Only safe to use after dialog Answered (Completed state)
	RTPPacketReader *media.RTPPacketReader

	// Packet writer is default writer for RTP audio stream
	// Use always AudioWriter to get current Audio reader
	// Use this only as read only
	RTPPacketWriter *media.RTPPacketWriter
	// contains filtered or unexported fields
}

DialogMedia is common struct for server and client session and it shares same functionality which is mostly arround media

func (*DialogMedia) AudioReader

func (d *DialogMedia) AudioReader(opts ...AudioReaderOption) (io.Reader, error)

AudioReader gets current audio reader. It MUST be called after Answer. Use AuidioListen for optimized reading. Reading buffer should be equal or bigger of media.RTPBufSize Options allow more intercepting audio reading like Stats or DTMF NOTE that this interceptors will stay,

func (*DialogMedia) AudioReaderDTMF

func (m *DialogMedia) AudioReaderDTMF() *DTMFReader

AudioReaderDTMF is DTMF over RTP. It reads audio and provides hook for dtmf while listening for audio Use Listen or OnDTMF after this call

func (*DialogMedia) AudioWriter

func (d *DialogMedia) AudioWriter(opts ...AudioWriterOption) (io.Writer, error)

func (*DialogMedia) AudioWriterDTMF

func (m *DialogMedia) AudioWriterDTMF() *DTMFWriter

func (*DialogMedia) Close

func (d *DialogMedia) Close() error

func (*DialogMedia) Echo

func (d *DialogMedia) Echo() error

Echo does audio echo for you

func (*DialogMedia) InitMediaSession

func (d *DialogMedia) InitMediaSession(m *media.MediaSession, r *media.RTPPacketReader, w *media.RTPPacketWriter)

func (*DialogMedia) Listen

func (d *DialogMedia) Listen() error

func (*DialogMedia) ListenContext

func (d *DialogMedia) ListenContext(ctx context.Context) error

func (*DialogMedia) ListenUntil

func (d *DialogMedia) ListenUntil(dur time.Duration) error

func (*DialogMedia) Media

func (d *DialogMedia) Media() *DialogMedia

func (*DialogMedia) MediaSession

func (d *DialogMedia) MediaSession() *media.MediaSession

func (*DialogMedia) OnClose

func (d *DialogMedia) OnClose(f func() error)

func (*DialogMedia) PlaybackControlCreate

func (d *DialogMedia) PlaybackControlCreate() (AudioPlaybackControl, error)

PlaybackControlCreate creates playback for audio with controls like mute unmute

func (*DialogMedia) PlaybackCreate

func (d *DialogMedia) PlaybackCreate() (AudioPlayback, error)

PlaybackCreate creates playback for audio

func (*DialogMedia) RTPSession

func (d *DialogMedia) RTPSession() *media.RTPSession

RTPSession returns underhood rtp session NOTE: this can be nil

func (*DialogMedia) SetAudioReader

func (d *DialogMedia) SetAudioReader(r io.Reader)

SetAudioReader adds/changes audio reader. Use this when you want to have interceptors of your audio

func (*DialogMedia) SetAudioWriter

func (d *DialogMedia) SetAudioWriter(r io.Writer)

SetAudioWriter adds/changes audio reader. Use this when you want to have interceptors of your audio

func (*DialogMedia) StartRTP

func (d *DialogMedia) StartRTP(rw int8, dur time.Duration)

func (*DialogMedia) StopRTP

func (d *DialogMedia) StopRTP(rw int8, dur time.Duration)

type DialogServerSession

type DialogServerSession struct {
	*sipgo.DialogServerSession

	// MediaSession *media.MediaSession
	DialogMedia
	// contains filtered or unexported fields
}

DialogServerSession represents inbound channel

func MatchDialogServer

func MatchDialogServer(req *sip.Request) (*DialogServerSession, error)

func (*DialogServerSession) Answer

func (d *DialogServerSession) Answer() error

Answer creates media session and answers NOTE: Not final API

func (*DialogServerSession) AnswerLate

func (d *DialogServerSession) AnswerLate() error

AnswerLate does answer with Late offer.

func (*DialogServerSession) AnswerOptions

func (d *DialogServerSession) AnswerOptions(opt AnswerOptions) error

AnswerOptions allows to answer dialog with options Experimental

NOTE: API may change

func (*DialogServerSession) Close

func (d *DialogServerSession) Close() error

func (*DialogServerSession) DialogSIP

func (d *DialogServerSession) DialogSIP() *sipgo.Dialog

func (*DialogServerSession) FromUser

func (d *DialogServerSession) FromUser() string

func (*DialogServerSession) Hangup

func (d *DialogServerSession) Hangup(ctx context.Context) error

func (*DialogServerSession) Id

func (d *DialogServerSession) Id() string

func (*DialogServerSession) Progress

func (d *DialogServerSession) Progress() error

func (*DialogServerSession) ReInvite

func (d *DialogServerSession) ReInvite(ctx context.Context) error

func (*DialogServerSession) ReadAck

func (*DialogServerSession) Refer

func (d *DialogServerSession) Refer(ctx context.Context, referTo sip.Uri) error

Refer tries todo refer (blind transfer) on call

func (*DialogServerSession) RemoteContact

func (d *DialogServerSession) RemoteContact() *sip.ContactHeader

func (*DialogServerSession) RespondSDP

func (d *DialogServerSession) RespondSDP(body []byte) error

func (*DialogServerSession) Ringing

func (d *DialogServerSession) Ringing() error

func (*DialogServerSession) ToUser

func (d *DialogServerSession) ToUser() string

User that was dialed

func (*DialogServerSession) Transport

func (d *DialogServerSession) Transport() string

type DialogSession

type DialogSession interface {
	Id() string
	Context() context.Context
	Hangup(ctx context.Context) error
	Media() *DialogMedia
	DialogSIP() *sipgo.Dialog
	Do(ctx context.Context, req *sip.Request) (*sip.Response, error)
}

type DigestAuth

type DigestAuth struct {
	Username string
	Password string
	Realm    string
	Expire   time.Duration
}

type DigestAuthServer

type DigestAuthServer struct {
	// contains filtered or unexported fields
}

func NewDigestServer

func NewDigestServer() *DigestAuthServer

func (*DigestAuthServer) AuthorizeDialog

func (s *DigestAuthServer) AuthorizeDialog(d *DialogServerSession, auth DigestAuth) error

func (*DigestAuthServer) AuthorizeRequest

func (s *DigestAuthServer) AuthorizeRequest(req *sip.Request, auth DigestAuth) (res *sip.Response, err error)

AuthorizeRequest authorizes request. Returns SIP response that can be passed with error

func (*DigestAuthServer) Close

func (s *DigestAuthServer) Close()

type InviteClientOptions

type InviteClientOptions struct {
	Originator DialogSession
	OnResponse func(res *sip.Response) error
	// OnMediaUpdate called when media is changed. NOTE: you should not block this call
	OnMediaUpdate func(d *DialogMedia)
	OnRefer       func(referDialog *DialogClientSession)
	// For digest authentication
	Username string
	Password string

	// Custom headers to pass. DO NOT SET THIS to nil
	Headers []sip.Header
}

type InviteOptions

type InviteOptions struct {
	Originator DialogSession
	OnResponse func(res *sip.Response) error
	// For digest authentication
	Username string
	Password string
	// Custom headers to pass. DO NOT SET THIS to nil
	Headers []sip.Header
}

func (*InviteOptions) SetAnonymousCaller

func (o *InviteOptions) SetAnonymousCaller()

Sets from user to RFC anonymous

func (*InviteOptions) SetCaller

func (o *InviteOptions) SetCaller(displayName string, callerID string)

type MediaConfig

type MediaConfig struct {
	Codecs []media.Codec
	// contains filtered or unexported fields
}

type MediaProps

type MediaProps struct {
	Codec media.Codec
	Laddr string
	Raddr string
}

type NewDialogOptions

type NewDialogOptions struct {
	// Transport or protocol that should be used
	Transport string
	// TransportID matches diago transport by ID instead protocol
	TransportID string
}

type RegisterOptions

type RegisterOptions struct {
	// Digest auth
	Username  string
	Password  string
	ProxyHost string

	// Expiry is for Expire header
	Expiry time.Duration
	// Retry interval is interval before next Register is sent
	RetryInterval time.Duration
	AllowHeaders  []string
}

type RegisterResponseError

type RegisterResponseError struct {
	RegisterReq *sip.Request
	RegisterRes *sip.Response

	Msg string
}

func (RegisterResponseError) Error

func (e RegisterResponseError) Error() string

func (*RegisterResponseError) StatusCode

func (e *RegisterResponseError) StatusCode() int

type RegisterTransaction

type RegisterTransaction struct {
	Origin *sip.Request
	// contains filtered or unexported fields
}

func (*RegisterTransaction) Qualify

func (t *RegisterTransaction) Qualify(ctx context.Context) error

func (*RegisterTransaction) QualifyLoop

func (t *RegisterTransaction) QualifyLoop(ctx context.Context) error

func (*RegisterTransaction) Register

func (t *RegisterTransaction) Register(ctx context.Context) error

func (*RegisterTransaction) Unregister

func (t *RegisterTransaction) Unregister(ctx context.Context) error

type ServeDialogFunc

type ServeDialogFunc func(d *DialogServerSession)

type Transport

type Transport struct {
	ID string

	// Transport must be udp,tcp or ws.
	Transport string
	BindHost  string
	BindPort  int

	ExternalHost string // SIP signaling and media external addr
	ExternalPort int

	// MediaExternalIP changes SDP IP, by default it tries to use external host if it is IP defined
	MediaExternalIP net.IP

	// In case TLS protocol
	TLSConf *tls.Config

	RewriteContact bool
	// contains filtered or unexported fields
}

Directories

Path Synopsis
bridge command
dtmf command
playback command
readmedia command
register command
sdp

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL