diago icon indicating copy to clipboard operation
diago copied to clipboard

Hold on Call Feature Request - Error when receiving empty SDP on hold (empty INVITE).

Open c2pc opened this issue 1 month ago • 3 comments

When a call is placed on hold by party B, I receive an empty INVITE (empty SDP). Media server starts playing hold music, but diago does not handle the empty SDP and returns an error.

sdp update media remote SDP applying failed: Media not found for

Possible fix func (d *DialogMedia) sdpUpdateUnsafe(sdp []byte) error { if sdp == nil { return nil } ....

Steps to reproduce

  1. Incoming call established between A and B.
  2. Party B places the call on hold.
  3. An INVITE with an empty SDP (no media lines) is received.
  4. Media server plays hold music.
  5. diago returns an error instead of handling the empty SDP gracefully.

INVITE sip:[email protected]:49729;transport=tcp SIP/2.0 From: "A" sip:[email protected]:5060;tag=1c88713638 To: "B" sip:[email protected]:49729 Via: SIP/2.0/TCP 1.1.1.1:5060;branch=z9hG4bK2889097a-54ea10d6-adfd86c4-005218574 Contact: sip:[email protected]:5060;transport=TCP P-Asserted-Identity: "A" sip:[email protected] Call-ID: FCafbab5ae-9d75907b-73b34615-005218574-00000 CSeq: 1 INVITE Allow: PRACK,INVITE,ACK,BYE,CANCEL,UPDATE,INFO,SUBSCRIBE,NOTIFY,REFER,MESSAGE,OPTIONS Alert-Info: Max-Forwards: 68 Allow-Events: conference User-Agent: release Content-Type: application/sdp Content-Length: 324

v=0 o=- 1972018706 1520871528 IN IP4 2.2.2.2 s=pjmedia b=AS:84 t=0 0 a=X-nat:0 m=audio 12616 RTP/AVP 8 0 9 120 c=IN IP4 2.2.2.2 b=TIAS:64000 a=rtcp:12617 IN IP4 2.2.2.2 a=sendrecv a=rtpmap:8 PCMA/8000 a=rtpmap:0 PCMU/8000 a=rtpmap:9 G722/8000 a=rtpmap:120 telephone-event/8000 a=fmtp:120 0-16

SIP/2.0 200 OK Via: SIP/2.0/TCP 1.1.1.1:5060;branch=z9hG4bK2889097a-54ea10d6-adfd86c4-005218574 From: "A" sip:[email protected]:5060;tag=1c88713638 To: "B" sip:[email protected]:49729;tag=df4fea1a-3b64-401b-850a-35a05dffac7b Call-ID: FCafbab5ae-9d75907b-73b34615-005218574-00000 CSeq: 1 INVITE Content-Length: 226 Content-Type: application/sdp Contact: sip:[email protected]:46871;transport=tcp

v=0 o=- 17056526055590580349 17056526055590580349 IN IP4 0.0.0.0 s=Sip Go Media c=IN IP4 0.0.0.0 t=0 0 m=audio 49660 RTP/AVP 8 0 a=rtpmap:8 PCMA/8000 a=rtpmap:0 PCMU/8000 a=ptime:20 a=maxptime:20 a=sendrecv

INVITE sip:[email protected]:46871;transport=tcp SIP/2.0 From: "A" sip:[email protected]:5060;tag=1c88713638 To: "B" sip:[email protected]:49729;tag=df4fea1a-3b64-401b-850a-35a05dffac7b Via: SIP/2.0/TCP 1.1.1.1:5060;branch=z9hG4bK132f2a65-7af8171f-90ee4d2f-005223833 Contact: sip:[email protected]:5060;transport=TCP Call-ID: FCafbab5ae-9d75907b-73b34615-005218574-00000 CSeq: 2 INVITE Max-Forwards: 67 Allow-Events: conference User-Agent: release Content-Length: 0

c2pc avatar Nov 04 '25 22:11 c2pc

Hi @c2pc . Welcome.

So is it just issue with Error handling (logging) or you think we need to support hold on a call. If I am aware, hold on call should have media, but with different parameters. So it is interesting to see this behavior

What phone is Party B?

emiago avatar Nov 10 '25 08:11 emiago

I think you need to support hold on a call.

Party B has different phones: Cisco, Avaya, pjsip, Diago

I implemented hold on the Diago side

type Holder interface {
	MediaSession() *media.MediaSession
	ReInvite(context.Context) error
}

func ToggleHold(ctx context.Context, dialog Holder) (string, error) {
	mediaSession := dialog.MediaSession()
	
	if mediaSession == nil {
		return "", errors.New("client session has no media session")
	}

	if mediaSession.Mode == sdp.ModeSendonly {
		mediaSession.Mode = sdp.ModeSendrecv
	} else {
		mediaSession.Mode = sdp.ModeSendonly
	}

	return mediaSession.Mode, dialog.ReInvite(ctx)
}

The second Diago client receives an empty SDP and Diago returns an error sip.StatusRequestTerminated instead of switching to music.

I added a line to ignore the empty SDP and everything worked. func (d *DialogMedia) sdpUpdateUnsafe(sdp []byte) error { if sdp == nil { return nil } ....

or

func (d *DialogMedia) handleMediaUpdate(req *sip.Request, tx sip.ServerTransaction, contactHDR sip.Header) error {
	d.mu.Lock()
	defer d.mu.Unlock()
	d.lastInvite = req

	if req.Body() != nil {
		if err := d.sdpReInviteUnsafe(req.Body()); err != nil {
			return tx.Respond(sip.NewResponseFromRequest(req, sip.StatusRequestTerminated, "Request Terminated - "+err.Error(), nil))
		}
	}

	// Reply with updated SDP
	sd := d.mediaSession.LocalSDP()
	res := sip.NewResponseFromRequest(req, sip.StatusOK, "OK", sd)
	res.AppendHeader(contactHDR)
	res.AppendHeader(sip.NewHeader("Content-Type", "application/sdp"))
	return tx.Respond(res)
}

c2pc avatar Nov 10 '25 11:11 c2pc

Yes we need to support hold on a call.

MediaSession updating also needs to be supported. Right now it is not safe to update fields, like in your example. sdpUpdateUnsafe you can explore how it done. There call Fork before

So if Diago is used to generate SDP hold on, I am still interested why it was empty? If you can investigate this, it would be helpful.

emiago avatar Nov 10 '25 19:11 emiago