emiago / sipgo Goto Github PK
View Code? Open in Web Editor NEWSIP library for writing fast SIP services in GO
License: BSD 2-Clause "Simplified" License
SIP library for writing fast SIP services in GO
License: BSD 2-Clause "Simplified" License
Example or some wrapper for handling dialogs
TCP/UDP could be larger message size which may lead lot of bad parsing and therefore processing
Original comment suggestion
#36 (comment)
NewUA
should accept customized sip.Parser
for changing default parser capabilities
Parsing with linking list was faster way todo but we could avoid this by default.
AppendHeader should keep only top first reference
Does SIP has any case where it handles comma separated values differently?
I have some trouble to understand this comment:
“Multiple readers makes problem, which can delay writing response”
Why multi UDPReadWorkers Goroutine can delay writing response?
Is it possible to generate a new SIP request based on an existing one, preserving attributes such as Call ID and body, while only modifying the 'To', 'From' and 'Contact' fields to align with the proxy's requirement for forwarding to the client?
Building client needs more features.
Does the 4** status code response also need to add a tag to the header?
// RFC 3261 - 8.2.6
func NewResponseFromRequest(
........
if statusCode == 100 {
CopyHeaders("Timestamp", req, res)
}
if statusCode == 200 {
if _, ok := res.to.Params["tag"]; !ok {
uuid, _ := uuid.NewV4()
res.to.Params["tag"] = uuid.String()
}
}
........
return res
}
Hey, im playing around with the proxy, im trying to put in in between an asterisk box and a sip client, however none of the messages make it through
Nov 10 15:35:56.865082 INF Runtime cpus=8
Nov 10 15:35:56.865839 INF Server routes setuped
Nov 10 15:35:56.866605 INF Http server started address=:8080
Nov 10 15:36:00.598282 WRN Server > SIP request handler not found
Nov 10 15:36:00.598282 WRN Server > SIP request handler not found
Nov 10 15:36:03.683762 ERR RequestWithContext failed error="udp conn 127.0.0.1:55600 err. write udp 127.0.0.1:55600->111.111.11.111:55600: sendto: can't assign requested address. transaction transport error"
Nov 10 15:36:04.183834 ERR RequestWithContext failed error="udp conn 127.0.0.1:55600 err. write udp 127.0.0.1:55600->111.111.11.111:55600: sendto: can't assign requested address. transaction transport error"
Nov 10 15:36:05.185031 ERR RequestWithContext failed error="udp conn 127.0.0.1:55600 err. write udp 127.0.0.1:55600->111.111.11.111:55600: sendto: can't assign requested address. transaction transport error"
Nov 10 15:36:07.188550 ERR RequestWithContext failed error="udp conn 127.0.0.1:55600 err. write udp 127.0.0.1:55600->111.111.11.111:55600: sendto: can't assign requested address. transaction transport error"
Nov 10 15:36:10.769874 WRN Server > SIP request handler not found
Nov 10 15:36:11.193927 ERR RequestWithContext failed error="udp conn 127.0.0.1:55600 err. write udp 127.0.0.1:55600->111.111.11.111:55600: sendto: can't assign requested address. transaction transport error"
Hey Emiago, do you have an example for an TCP SIP client for connecting to tcp sip server ?
I'm looking to make a small private utils to test if my home TCP sip server are down or not. Can you provide a TCP client example ?
Thanks -Kalvelign
To gain more performance we could implement lazy parsing within sip headers.
Currently we have some default parsing which could be reduced
https://github.com/emiago/sipgo/blob/main/parser/parse_header.go#L18
Currently lazy parsing can only be done for rest of headers as they are only stored as key:value
Some of parser optimized functions are exposed like
ParseUri
ParseAddressValue
but we could expose more?
So in case where header is not preparsed like Via, To, From .. caller must do this manually
`
hdr := msg.GetHeader("XXX")
uri, _ := parser.ParseUri(hdr.Value())
Now parsing can be also moved to fetching header, where caching is done on demand. This could
increase performance, specially in case where messages get discarded.
Hi,
Appreciate u for all of your contributions and helps.
Could you please guide how to use dialog server for recording and playback the arrive rtp packet payload. say rtp payload echo example.
I would like to streaming out/in the payload to/from an external service.
Thanks in advance.
These are constants now and can't be changed.
Probably, the values should be per client/server or per transaction. In the simplest case, they just can be made var
. This would allow to change the values at startup. Something like this:
diff --git a/transaction/transaction.go b/transaction/transaction.go
index 3f446fb..cef8198 100644
--- a/transaction/transaction.go
+++ b/transaction/transaction.go
@@ -12,23 +12,27 @@ import (
"github.com/emiago/sipgo/sip"
)
+var (
+ T1 = 500 * time.Millisecond
+ T2 = 4 * time.Second
+ T4 = 5 * time.Second
+)
+
+func Timer_A() time.Duration { return T1 }
+func Timer_B() time.Duration { return 64 * T1 }
+func Timer_E() time.Duration { return T1 }
+func Timer_F() time.Duration { return 64 * T1 }
+func Timer_G() time.Duration { return T1 }
+func Timer_H() time.Duration { return 64 * T1 }
+func Timer_I() time.Duration { return T4 }
+func Timer_J() time.Duration { return 64 * T1 }
+func Timer_K() time.Duration { return T4 }
+func Timer_L() time.Duration { return 64 * T1 }
+func Timer_M() time.Duration { return 64 * T1 }
+
const (
- T1 = 500 * time.Millisecond
- T2 = 4 * time.Second
- T4 = 5 * time.Second
- Timer_A = T1
- Timer_B = 64 * T1
Timer_D = 32 * time.Second
- Timer_E = T1
- Timer_F = 64 * T1
- Timer_G = T1
- Timer_H = 64 * T1
- Timer_I = T4
- Timer_J = 64 * T1
- Timer_K = T4
Timer_1xx = 200 * time.Millisecond
- Timer_L = 64 * T1
- Timer_M = 64 * T1
TxSeperator = "__"
)
diff --git a/transaction/client_tx.go b/transaction/client_tx.go
index 110b83b..37d428c 100644
--- a/transaction/client_tx.go
+++ b/transaction/client_tx.go
@@ -58,10 +58,10 @@ func (tx *ClientTx) Init() error {
// If a reliable transport is being used, the client transaction SHOULD NOT
// start timer A (Timer A controls request retransmissions).
// Timer A - retransmission
- // tx.log.Tracef("timer_a set to %v", Timer_A)
+ // tx.log.Tracef("timer_a set to %v", Timer_A())
tx.mu.Lock()
- tx.timer_a_time = Timer_A
+ tx.timer_a_time = Timer_A()
tx.timer_a = time.AfterFunc(tx.timer_a_time, func() {
tx.spinFsm(client_input_timer_a)
@@ -73,7 +73,7 @@ func (tx *ClientTx) Init() error {
// Timer B - timeout
tx.mu.Lock()
- tx.timer_b = time.AfterFunc(Timer_B, func() {
+ tx.timer_b = time.AfterFunc(Timer_B(), func() {
tx.mu.Lock()
tx.lastErr = fmt.Errorf("Timer_B timed out. %w", ErrTimeout)
tx.mu.Unlock()
diff --git a/transaction/client_tx_fsm.go b/transaction/client_tx_fsm.go
index ac3d318..5d1b7be 100644
--- a/transaction/client_tx_fsm.go
+++ b/transaction/client_tx_fsm.go
@@ -320,13 +320,13 @@ func (tx *ClientTx) actCancelTimeout() FsmInput {
tx.cancel()
- // tx.Log().Tracef("timer_b set to %v", Timer_B)
+ // tx.Log().Tracef("timer_b set to %v", Timer_B())
tx.mu.Lock()
if tx.timer_b != nil {
tx.timer_b.Stop()
}
- tx.timer_b = time.AfterFunc(Timer_B, func() {
+ tx.timer_b = time.AfterFunc(Timer_B(), func() {
tx.spinFsm(client_input_timer_b)
})
tx.mu.Unlock()
@@ -401,9 +401,9 @@ func (tx *ClientTx) actPassupAccept() FsmInput {
tx.timer_b = nil
}
- // tx.Log().Tracef("timer_m set to %v", Timer_M)
+ // tx.Log().Tracef("timer_m set to %v", Timer_M())
- tx.timer_m = time.AfterFunc(Timer_M, func() {
+ tx.timer_m = time.AfterFunc(Timer_M(), func() {
select {
case <-tx.done:
return
The drawback here is the changed type of Timer_*
, the values have to be functions on T1 and T4. Although the values are used only inside transaction
package in sipgo
they're still exported and available for external code.
Maybe, you have a better idea on making the timers configurable?
When processing SIP with jssip.js, there is a problem in which parsing does not work properly if the param information in the header contains a value wrapped in quoted string.
The problem is that quoted-string among the values of generic-param cannot be processed in the BNF related to the RFC2833 Contact header below.
Contact: <sip:[email protected]>;+sip.ice;reg-id=1;+sip.instance="<urn:uuid:a369bd8d-f310-4a95-8328-98c7ed3d5439>";expires=300
contact-param = (name-addr / addr-spec) *(SEMI contact-params)
contact-params = c-p-q / c-p-expires
/ contact-extension
contact-extension = generic-param
generic-param = token [ EQUAL gen-value ]
gen-value = token / host / quoted-string
It can be improved with the following modifications.
diff --git a/sip/parse_address.go b/sip/parse_address.go
index 826350d..4ff5847 100644
--- a/sip/parse_address.go
+++ b/sip/parse_address.go
@@ -15,10 +15,17 @@ func ParseAddressValue(addressText string, uri *Uri, headerParams HeaderParams)
var semicolon, equal, startQuote, endQuote int = -1, -1, -1, -1
var name string
var uriStart, uriEnd int = 0, -1
- var inBrackets bool
+ var inBrackets, inQuotes bool
for i, c := range addressText {
+ if inQuotes {
+ continue
+ }
switch c {
case '"':
+ if equal > 0 {
+ inQuotes = !inQuotes
+ continue
+ }
if startQuote < 0 {
startQuote = i
} else {
Performance dropped from v0.6.0 due to forcing on generate new via branch.
sip.GenerateBranch function needs to be fixed and optimized to remove allocs
Server may get sipVersion ends with LF if request start line ends with LF ranther than CRLF.
Although it's documented here. https://www.rfc-editor.org/rfc/rfc3261.html#section-7
If consider return err while startline ends with LF or CR ?
SIP/2.0 200 OK Via: SIP/2.0/UDP 192.168.10.8:60719;rport=60719;branch=z9hG4bK1377739937 From: <sip:34020000002000000719@3402000000>;tag=1534571172 To: <sip:34020000002000000719@3402000000>;tag=b70dda5e-f487-4b46-8e21-42b7c5dfcfa0 Call-ID: 1566767517 CSeq: 1 REGISTER Content-Length: 0
req.Clone() used in
sipgo/example/register/client/main.go
Line 95 in 2a2060a
Dec 30 19:20:35.279400 INF Received status status=401
Dec 30 19:20:35.279494 FTL Fail to create transaction error="transaction \"z9hG4bK.Bw6JZkBt28fHVwSe__REGISTER\" already exists"
By RFC3261, the branch ID must be unique for each new transaction.
Hi,
I would like to report an issue when register to an Asterisk with the example/register/client/main.go
$ ./client -ip 192.168.219.101:15060 -srv 192.168.219.101:5060 -u 100 -p 100
...
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x648827]
goroutine 1 [running]:
github.com/icholy/digest.(*Credentials).String(0x0)
/home/vagrant/go/pkg/mod/github.com/icholy/[email protected]/credentials.go:76 +0x67
main.main()
/home/vagrant/sipgo/example/register/client/main.go:101 +0xe05
Could be fixed by changing Algorithm to uppercase as it was expected by digest.Digest method.
...
wwwAuth := res.GetHeader("WWW-Authenticate")
chal, err := digest.ParseChallenge(wwwAuth.Value())
if err != nil {
log.Fatal().Str("wwwauth", wwwAuth.Value()).Err(err).Msg("Fail to parse challenge")
}
chal.Algorithm = strings.ToUpper(chal.Algorithm) //##### fixing
...
Asterisk's WWW-Authenticate parsed challenge:
github.com/icholy/digest.Challenge {Realm: "asterisk", Domain: []string len: 0, cap: 0, nil, Nonce: "1685669327/dfd8ceb694344a6f663b95f91eb48096", Opaque: "2ac5572d63798b56", Stale: false, Algorithm: "md5", QOP: []string len: 1, cap: 1, ["auth"], Charset: "", Userhash: false}
Thanks.
If recv a register request, but server did not found handler by method, this tx will be blocked, and server routinue blocked.
The next response will not be reponsed.
`func (srv *Server) handleRequest(req *sip.Request, tx sip.ServerTransaction) {
for _, mid := range srv.requestMiddlewares {
mid(req)
}
handler := srv.getHandler(req.Method)
if handler == nil {
srv.log.Warn().Msg("SIP request handler not found")
res := sip.NewResponseFromRequest(req, 405, "Method Not Allowed", nil)
if err := srv.WriteResponse(res); err != nil {
srv.log.Error().Msgf("respond '405 Method Not Allowed' failed: %s", err)
}
//. no timer trigger. or we shoud destroy this tx.
for {
select {
case <-tx.Done():
srv.log.Warn().Msgf("recv tx.Done() tx:%s", tx)
return
case err, ok := <-tx.Errors():
srv.log.Warn().Msgf("recv tx.Errors() tx:%s, err:%s", tx, err)
if !ok {
return
}
srv.log.Warn().Msgf("error from SIP server transaction %s: %s", tx, err)
}
}
}
handler(req, tx)
if tx != nil {
// Must be called to prevent any transaction leaks
tx.Terminate()
}
}`
Hi Emir,
Looks like zero port value in sipgo.Client
means sip.Default{Tcp,Udp}Port
1 and not "any random available port from the OS". It's a bit surprising: usually in client contexts zero/unspecified port is a way to say "I don't care about the port, choose any". E.g., standard net.Dial{TCP,UDP}
functions work that way, SIPp also chooses local port randomly by default (excerpt from sipp
usage):
-p : Set the local port number. Default is a random free port chosen by the
system.
What I would expect:
sipgo.NewClient(ua)
or sipgo.NewClient(ua, sipgo.WithClientPort(0))
will use any available client port.sipgo.NewClient(ua, sipgo.WithClientPort(sip.DefaultPort))
will use the registered SIP port 5060.I'm not sure whether it's too late to make such breaking change (different meaning for zero port value). If it is, maybe add some special constant (const AnyPort = -1
?) for sipgo.WithClientPort
? Something like this:
diff --git a/client.go b/client.go
index 1a0a215..0792256 100644
--- a/client.go
+++ b/client.go
@@ -41,7 +41,11 @@ func WithClientHostname(hostname string) ClientOption {
}
}
-// WithClientPort allows setting default route port
+// WithClientPort allows setting default route port.
+//
+// If zero, [sip.DefaultUdpPort] or [sip.DefaultTcpPort] will be
+// used. If -1, the client port will be chosen automatically by the
+// OS.
func WithClientPort(port int) ClientOption {
return func(s *Client) error {
s.port = port
diff --git a/transport/tcp.go b/transport/tcp.go
index 22d4bec..1dc63b5 100644
--- a/transport/tcp.go
+++ b/transport/tcp.go
@@ -94,7 +94,10 @@ func (t *TCPTransport) CreateConnection(laddr Addr, raddr Addr, handler sip.Mess
Port: laddr.Port,
}
- if tladdr.Port == 0 {
+ switch tladdr.Port {
+ case -1: // Use any available port.
+ tladdr.Port = 0
+ case 0: // Use default port.
tladdr.Port = sip.DefaultTcpPort
}
}
diff --git a/transport/udp.go b/transport/udp.go
index 8da541c..d112453 100644
--- a/transport/udp.go
+++ b/transport/udp.go
@@ -130,7 +130,10 @@ func (t *UDPTransport) CreateConnection(laddr Addr, raddr Addr, handler sip.Mess
Port: laddr.Port,
}
- if uladdr.Port == 0 {
+ switch uladdr.Port {
+ case -1: // Use any available port.
+ uladdr.Port = 0
+ case 0: // Use default port.
uladdr.Port = sip.DefaultUdpPort
}
}
diff --git a/transport/ws.go b/transport/ws.go
index 168f859..6fc9177 100644
--- a/transport/ws.go
+++ b/transport/ws.go
@@ -224,7 +224,10 @@ func (t *WSTransport) CreateConnection(laddr Addr, raddr Addr, handler sip.Messa
Port: laddr.Port,
}
- if tladdr.Port == 0 {
+ switch tladdr.Port {
+ case -1: // Use any available port.
+ tladdr.Port = 0
+ case 0: // Use default port.
tladdr.Port = sip.DefaultTcpPort
}
}
I took a look at the code and felt inspired by ghettovoice, I used ghettovoice to develop webphone, it was a wonderful process, maybe in some small scenarios, I could use this project to replace kamailio and just use freeswitch as a media server
sip.NewServer
instead sipgo.NewServer
for better ux. (same as std http?)
problems:
sip,transport,transaction need to be merged to avoid import cycle
Is it worth it? Is it better for maintance?
Goal is to have some experiment area, wrapper libs or tools that are built with sipgo
SIP parser torture tests to be added.
More we add the better.
Stateless Proxy Server
A stateless proxy server simply forwards the message it receives. This kind of server does not store any information of the call or transaction.
Stateless proxies forget about the SIP request once it has been forwarded.
Transaction will be fast via stateless proxie
onn 192.168.100.11:45080 write err=write udp 192.168.100.11:45080->192.168.100.11:5060: use of closed network connection. transaction transport error
This needs to be handled.
ClientTx.timeoutErr() may blcoked if no routinue receive errs.
Due to connected UDP connections client transaction will fail to get responses
if they sent request on unspecified IP like 0.0.0.0:5060.
It works if is send with 0.0.0.0 but not in all other cases.
This is due to Go UDP connected connection handling.
Based on #22 or #23 there could be use to have in this lib for simple SDP parsing. Manipulating SDP is needed in lot of cases.
This needs some research
This is only for SDP parsing but not for media/rtp handling.
I am planning to write a SIP REC based recorder to work with Cisco SIP telephony. Can someone mention the steps that will be involved. I believe the SIP server would be sending me all the call session details. I need to do some sort of arbitration to selectively record only desired calls and discard the remaining not required ones. Post that media starts streaming (not sure what protocol it is but I believe it will be some sort of UDP). Is my understanding correct or am I missing something?
Now can some one confirm whether this project can help me with this? And how can I get the media data stream, so I can create a store the audio file in disk, once the call is completed.
If there is some sort of example on similar lines. Please send me the link.
ListenAndServe is normally run concurently for creating multiple transport
This makes concurency on internal listenPort map
I'm interested in utilizing SIPGO as a comprehensive SIP server similar to Asterisk for my project. I have reviewed the README and examples provided in the repository, and I'm impressed with the performance and flexibility offered by SIPGO. However, I would like to understand the extent to which SIPGO can serve as a replacement for Asterisk in terms of its capabilities and features.
Specifically, I'm looking to achieve the following functionalities with SIPGO:
Handling and routing of SIP calls: I want to be able to receive incoming SIP calls, process them based on certain rules (such as authentication, call routing, etc.), and forward them to the appropriate destination.
Support for SIP extensions and features: It would be beneficial if SIPGO supports common SIP extensions and features like call transfers, call forwarding, conferencing, voicemail, IVR (Interactive Voice Response), and presence management.
Integration with external services: I would like to integrate SIPGO with external databases, caches, and other services to enhance its functionality. This would enable me to store and retrieve SIP-related information, perform authentication and authorization checks, and incorporate additional business logic.
Scalability and performance: As my application may experience high call volumes, it is crucial to have a SIP server that can handle a large number of concurrent connections and deliver reliable performance under heavy load.
I understand that SIPGO is still in development, and I appreciate any insights, recommendations, or limitations that you can provide regarding its suitability for building a production-ready SIP server.
Thank you!
p.s maybe in the future to create a similar to asterisk+freepbx
Non handled messages get 405 by default without possibility to customize.
This needs to be exported.
When trying to run register client example, get the following error
main.go:14:2: no required module provides package github.com/emiago/sipgo/parser; to add it:
go get github.com/emiago/sipgo/parser
recipient fields can be assigned individually I guess.
Running go mod tidy
return following
github.com/emiago/sipgo/parser: module github.com/emiago/sipgo@latest found (v0.15.2), but does not contain package github.com/emiago/sipgo/parser
Hi,
This is really a nice project and good to see how powerfull go is for voip solutions as well ;-)
One question remains however on the proxy part, the primary focus is now on the SIP handling, what would be needed to intregrate and RTP proxy as well?
Hello!How can I send an ACK message after receiving an invite's 200OK message?
Hello,
Are there any examples available of basic server with authentication or a client ?
Besides the 2 examples that are there.
Thanks!
Support more headers for parsing if needed
https://www.rfc-editor.org/rfc/rfc3261#section-20
Currently GenericHeader needs to be used for all unsupported headers. Can this be wrapped with key value function?
Let caller todo unmarshaling and avoid parsing non used headers
Add missing functions and docs.
This are required to be parsed: Via, Cseq, CallID, From, To
Currently this are also exported via fast reference.
Parser needs more docs!
Here sharing IDEA for extra library for building state full proxy with focus on routing like kamailio,opensips but with better control. As it is in GO lot of functions is already there by many libs out there.
There is lot of complexity that needs to be done, so it may be worth to wrap it but still keeping lot of control over request handling.
If there will be enough interest in this project I would share my current POC lib.
More info you can find on
https://github.com/emiago/sipgox/tree/main/psip
Related to questions
#31 by @leeyongda
#40 by @dekke046
I have had success using the new dialogue server. I used the examples and added my own logic to properly respond to the SDP offer sent in the INVITE. The parts that are working are the dialogue starting when the call starts after the INVITE, and the dialogue properly stops when receiving BYE. My issue is if the call is longer than 30sec, the dialogue also just stops and kills the call. I have a hunch that this is probably due to a timeout somewhere, but I'm not sure what kind of keepalives are needed other than the code here. I've tried this with multiple sip phones and all have the same behaviour. I'm using UDP. Am I doing something wrong?
Here is an excerpt of the code I'm using:
func main() {
ua, _ := sipgo.NewUA(sipgo.WithUserAgent("foobar"))
uasContact := sip.ContactHeader{
Address: sip.Uri{User: "foobar", Host: "192.168.22.122", Port: 5060},
}
cli, _ := sipgo.NewClient(ua)
srv, _ := sipgo.NewServer(ua)
dialogSrv := sipgo.NewDialogServer(cli, uasContact)
srv.OnRegister(func(req *sip.Request, tx sip.ServerTransaction) {
_ = tx.Respond(sip.NewResponseFromRequest(req, sip.StatusOK, "OK", nil))
})
srv.OnInvite(func(req *sip.Request, tx sip.ServerTransaction) {
fmt.Printf("DIALOG STARTED!!!\n")
dlg, err := dialogSrv.ReadInvite(req, tx)
if err != nil {
panic(err.Error())
}
sdpResp := generateSdpResponse(req.Body())
_ = dlg.Respond(sip.StatusOK, "OK", sdpResp, sip.NewHeader("Content-Type", "application/sdp"))
<-tx.Done()
fmt.Printf("DIALOG DONE!!!\n")
})
srv.OnAck(func(req *sip.Request, tx sip.ServerTransaction) {
_ = dialogSrv.ReadAck(req, tx)
})
srv.OnBye(func(req *sip.Request, tx sip.ServerTransaction) {
_ = dialogSrv.ReadBye(req, tx)
})
ctx, _ := signal.NotifyContext(context.TODO(), os.Interrupt)
go srv.ListenAndServe(ctx, "udp", "192.168.22.122:5060")
<-ctx.Done()
}
Here is an excerpt from my app using this library. I have to register the srv.OnAck
and srv.OnBye
otherwise they will be rejected. My issue is that after my successful sip call when the BYE
message arrives, the tx.Done()
channel will not fire. I've also noticed that after a timeout, regardless if I'm in the middle of a call or not, the transaction gets terminated and then the channel fires and the code runs. How can I control this behaviour? I tried doing a tx.Terminate()
in the BYE
handler, but even that did not work.
func onBye(req *sip.Request, tx sip.ServerTransaction) {
_ = tx.Respond(sip.NewResponseFromRequest(req, sip.StatusOK, "OK", nil))
}
func onACK(req *sip.Request, tx sip.ServerTransaction) {
return
}
func onInvite(req *sip.Request, tx sip.ServerTransaction) {
// ---- sdp offer & response ---
sdpOffer, _ := sdp.Parse(req.Body())
session, _ := engine.NewSession(*sdpOffer)
defer engine.CloseSession(&session)
res := sip.NewSDPResponseFromRequest(req, session.Response.Bytes())
_ = tx.Respond(res)
// -----------------------------
for {
select {
case m := <-tx.Cancels():
// this never gets called
_ = tx.Respond(sip.NewResponseFromRequest(m, 200, "OK", nil))
case <-tx.Done():
// this doesn't get called on BYE
return
case <-tx.Acks():
// I've also never seen this called
return
}
}
func main() {
ua, _ := sipgo.NewUA()
srv, _ := sipgo.NewServerDialog(ua)
srv.OnInvite(onInvite)
srv.OnAck(onACK)
srv.OnBye(onBye)
if err := srv.ListenAndServe(context.TODO(), "udp", *extIP); err != nil {
panic(err.Error())
}
}
Have some way to manage dialogs and make easier dialogs handling
https://datatracker.ietf.org/doc/html/rfc3261#autoid-76
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.