|
|
|
@ -15,27 +15,20 @@ import ( |
|
|
|
) |
|
|
|
|
|
|
|
func TestCallToEchoApp(t *testing.T) { |
|
|
|
log.SetFlags(log.LstdFlags | log.Lmicroseconds | log.Lshortfile) |
|
|
|
to := &sip.Addr{Uri: &sip.URI{User: "echo", Host: "127.0.0.1", Port: 5060}} |
|
|
|
from := &sip.Addr{Uri: &sip.URI{Host: "127.0.0.1"}} |
|
|
|
|
|
|
|
// Create the SIP UDP transport layer.
|
|
|
|
tp, err := sip.NewTransport(from) |
|
|
|
if err != nil { |
|
|
|
t.Fatal(err) |
|
|
|
} |
|
|
|
defer tp.Sock.Close() |
|
|
|
|
|
|
|
// Used to notify main thread when subthreads die.
|
|
|
|
rtpDeath := make(chan bool, 2) |
|
|
|
|
|
|
|
// Create an RTP session.
|
|
|
|
session, err := rtp.NewSession(from.Uri.Host) |
|
|
|
// Create an RTP media session.
|
|
|
|
rs, err := rtp.NewSession(from.Uri.Host) |
|
|
|
if err != nil { |
|
|
|
t.Fatal("rtp listen:", err) |
|
|
|
} |
|
|
|
defer session.Sock.Close() |
|
|
|
rtpaddr := session.Sock.LocalAddr().(*net.UDPAddr) |
|
|
|
rtppeerChan := make(chan *net.UDPAddr, 1) |
|
|
|
defer rs.Sock.Close() |
|
|
|
rtpaddr := rs.Sock.LocalAddr().(*net.UDPAddr) |
|
|
|
|
|
|
|
// Create an RTP audio sender.
|
|
|
|
rtpPeer := make(chan *net.UDPAddr, 1) |
|
|
|
go func() { |
|
|
|
var frame rtp.Frame |
|
|
|
awgn := dsp.NewAWGN(-25.0) |
|
|
|
@ -43,91 +36,60 @@ func TestCallToEchoApp(t *testing.T) { |
|
|
|
defer ticker.Stop() |
|
|
|
for { |
|
|
|
select { |
|
|
|
case rs.Peer = <-rtpPeer: |
|
|
|
case <-ticker.C: |
|
|
|
// Send an audio frame containing comfort noise.
|
|
|
|
for n := 0; n < 160; n++ { |
|
|
|
frame[n] = awgn.Get() |
|
|
|
} |
|
|
|
err := session.Send(frame) |
|
|
|
err := rs.Send(&frame) |
|
|
|
if err != nil { |
|
|
|
if !util.IsUseOfClosed(err) { |
|
|
|
t.Error("rtp write", err) |
|
|
|
} |
|
|
|
rtpDeath <- true |
|
|
|
return |
|
|
|
} |
|
|
|
case session.Peer = <-rtppeerChan: |
|
|
|
if session.Peer == nil { |
|
|
|
return |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
}() |
|
|
|
defer func() { rtppeerChan <- nil }() |
|
|
|
|
|
|
|
// Create an RTP message consumer.
|
|
|
|
go func() { |
|
|
|
var frame rtp.Frame |
|
|
|
for { |
|
|
|
err := session.Recv(frame) |
|
|
|
if err != nil { |
|
|
|
if !util.IsUseOfClosed(err) { |
|
|
|
t.Errorf("rtp read: %s %#v", err, err) |
|
|
|
} |
|
|
|
rtpDeath <- true |
|
|
|
return |
|
|
|
} |
|
|
|
} |
|
|
|
}() |
|
|
|
|
|
|
|
// Create a SIP message consumer.
|
|
|
|
sipChan := make(chan *sip.Msg, 32) |
|
|
|
go func() { |
|
|
|
for { |
|
|
|
msg := tp.Recv() |
|
|
|
if msg.Error != nil && util.IsUseOfClosed(msg.Error) { |
|
|
|
return |
|
|
|
} |
|
|
|
sipChan <- msg |
|
|
|
} |
|
|
|
}() |
|
|
|
// Create the SIP UDP transport layer.
|
|
|
|
tp, err := sip.NewTransport(from) |
|
|
|
if err != nil { |
|
|
|
t.Fatal(err) |
|
|
|
} |
|
|
|
defer tp.Sock.Close() |
|
|
|
|
|
|
|
// Send an INVITE message with an SDP.
|
|
|
|
invite := sip.NewRequest(tp, "INVITE", to, from) |
|
|
|
invite := sip.NewRequest(tp, sip.MethodInvite, to, from) |
|
|
|
sip.AttachSDP(invite, sdp.New(rtpaddr, sdp.ULAWCodec, sdp.DTMFCodec)) |
|
|
|
err = tp.Send(invite) |
|
|
|
if err != nil { |
|
|
|
t.Fatal(err) |
|
|
|
} |
|
|
|
|
|
|
|
// Set up some reliability in case a UDP packet drops.
|
|
|
|
resend := invite |
|
|
|
resends := 0 |
|
|
|
resendInterval := 50 * time.Millisecond |
|
|
|
resendTimer := time.After(resendInterval) |
|
|
|
|
|
|
|
// Hangup after two seconds.
|
|
|
|
deathTimer := time.After(200 * time.Millisecond) |
|
|
|
|
|
|
|
// Create a SIP dialog handler.
|
|
|
|
ticker := time.NewTicker(2 * time.Second) |
|
|
|
defer ticker.Stop() |
|
|
|
var answered bool |
|
|
|
var msg *sip.Msg |
|
|
|
loop: |
|
|
|
for { |
|
|
|
select { |
|
|
|
case <-ticker.C: |
|
|
|
if answered { |
|
|
|
err = tp.Send(sip.NewBye(invite, msg)) |
|
|
|
} else { |
|
|
|
err = tp.Send(sip.NewCancel(invite)) |
|
|
|
} |
|
|
|
if err != nil { |
|
|
|
t.Error(err) |
|
|
|
} |
|
|
|
case <-rtpDeath: |
|
|
|
if answered { |
|
|
|
err = tp.Send(sip.NewBye(invite, msg)) |
|
|
|
} else { |
|
|
|
err = tp.Send(sip.NewCancel(invite)) |
|
|
|
} |
|
|
|
if err != nil { |
|
|
|
t.Error(err) |
|
|
|
} |
|
|
|
case msg = <-sipChan: |
|
|
|
if msg.Error != nil { |
|
|
|
t.Fatal(msg.Error) |
|
|
|
} |
|
|
|
case err = <-rs.E: |
|
|
|
t.Fatal("RTP network error", err) |
|
|
|
case err = <-tp.E: |
|
|
|
t.Fatal("SIP network error", err) |
|
|
|
case <-rs.C: |
|
|
|
// Do nothing with inbound audio.
|
|
|
|
case msg = <-tp.C: |
|
|
|
if msg.IsResponse { |
|
|
|
if msg.Status >= sip.StatusOK && msg.CSeq == invite.CSeq { |
|
|
|
err = tp.Send(sip.NewAck(invite, msg)) |
|
|
|
@ -136,29 +98,38 @@ func TestCallToEchoApp(t *testing.T) { |
|
|
|
} |
|
|
|
} |
|
|
|
if msg.Status < sip.StatusOK { |
|
|
|
log.Printf("Provisional %d %s", msg.Status, msg.Phrase) |
|
|
|
if msg.Status == sip.StatusTrying { |
|
|
|
log.Printf("Remote SIP endpoint exists!") |
|
|
|
resendTimer = nil |
|
|
|
} else if msg.Status == sip.StatusRinging { |
|
|
|
log.Printf("Ringing!") |
|
|
|
} else if msg.Status == sip.StatusSessionProgress { |
|
|
|
log.Printf("Probably Ringing!") |
|
|
|
} else { |
|
|
|
log.Printf("Provisional %d %s", msg.Status, msg.Phrase) |
|
|
|
} |
|
|
|
} else if msg.Status == sip.StatusOK { |
|
|
|
if msg.CSeqMethod == "INVITE" { |
|
|
|
if msg.CSeqMethod == sip.MethodInvite { |
|
|
|
log.Printf("Answered!") |
|
|
|
answered = true |
|
|
|
} else if msg.CSeqMethod == "BYE" { |
|
|
|
} else if msg.CSeqMethod == sip.MethodBye { |
|
|
|
log.Printf("Hungup!") |
|
|
|
return |
|
|
|
} else if msg.CSeqMethod == "CANCEL" { |
|
|
|
break loop |
|
|
|
} else if msg.CSeqMethod == sip.MethodCancel { |
|
|
|
log.Printf("Cancelled!") |
|
|
|
return |
|
|
|
break loop |
|
|
|
} |
|
|
|
} else if msg.Status > sip.StatusOK { |
|
|
|
t.Errorf("Got %d %s", msg.Status, msg.Phrase) |
|
|
|
return |
|
|
|
} |
|
|
|
if msg.Headers["Content-Type"] == "application/sdp" { |
|
|
|
if msg.Headers["Content-Type"] == sdp.ContentType { |
|
|
|
log.Printf("Establishing media session") |
|
|
|
rsdp, err := sdp.Parse(msg.Payload) |
|
|
|
ms, err := sdp.Parse(msg.Payload) |
|
|
|
if err != nil { |
|
|
|
t.Fatal("failed to parse sdp", err) |
|
|
|
} |
|
|
|
rtppeerChan <- &net.UDPAddr{IP: net.ParseIP(rsdp.Addr), Port: int(rsdp.Audio.Port)} |
|
|
|
rtpPeer <- &net.UDPAddr{IP: net.ParseIP(ms.Addr), Port: int(ms.Audio.Port)} |
|
|
|
} |
|
|
|
} else { |
|
|
|
if msg.Method == "BYE" { |
|
|
|
@ -167,9 +138,35 @@ func TestCallToEchoApp(t *testing.T) { |
|
|
|
if err != nil { |
|
|
|
t.Fatal(err) |
|
|
|
} |
|
|
|
return |
|
|
|
break loop |
|
|
|
} |
|
|
|
} |
|
|
|
case <-resendTimer: |
|
|
|
if resends == 2 { |
|
|
|
t.Fatal("Failed to send", resend.Method) |
|
|
|
} |
|
|
|
resends++ |
|
|
|
err = tp.Send(resend) |
|
|
|
if err != nil { |
|
|
|
t.Fatal(err) |
|
|
|
} |
|
|
|
case <-deathTimer: |
|
|
|
resends = 0 |
|
|
|
resendTimer = time.After(resendInterval) |
|
|
|
if answered { |
|
|
|
resend = sip.NewBye(invite, msg) |
|
|
|
} else { |
|
|
|
resend = sip.NewCancel(invite) |
|
|
|
} |
|
|
|
err = tp.Send(resend) |
|
|
|
if err != nil { |
|
|
|
t.Error(err) |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// The dialog has shut down cleanly.
|
|
|
|
if !answered { |
|
|
|
t.Error("Call didn't get answered!") |
|
|
|
} |
|
|
|
} |