Skip to content

Commit 8bac6bd

Browse files
FedorKiselev76yeoleobun
authored andcommitted
dialog: add BYE/hangup helpers with Reason header
1 parent f7fee5b commit 8bac6bd

2 files changed

Lines changed: 135 additions & 49 deletions

File tree

src/dialog/client_dialog.rs

Lines changed: 89 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -121,30 +121,10 @@ impl ClientInviteDialog {
121121
pub fn cancel_token(&self) -> &CancellationToken {
122122
&self.inner.cancel_token
123123
}
124-
/// Hang up the call
125-
///
126-
/// If the dialog is confirmed, send a BYE request to terminate the call.
127-
/// If the dialog is not confirmed, send a CANCEL request to cancel the call.
128-
pub async fn hangup(&self) -> Result<()> {
129-
if self.inner.can_cancel() {
130-
self.cancel().await
131-
} else {
132-
self.bye().await
133-
}
134-
}
135124

136-
/// Send a BYE request to terminate the dialog
125+
/// Send a BYE request to terminate the dialog.
137126
///
138-
/// Sends a BYE request to gracefully terminate an established dialog.
139-
/// This should only be called for confirmed dialogs. If the dialog
140-
/// is not confirmed, this method returns immediately without error.
141-
///
142-
/// # Returns
143-
///
144-
/// * `Ok(())` - BYE was sent successfully or dialog not confirmed
145-
/// * `Err(Error)` - Failed to send BYE request
146-
///
147-
/// # Examples
127+
/// Thin wrapper over `bye_with_headers(None)`.
148128
///
149129
/// ```rust,no_run
150130
/// # use rsipstack::dialog::client_dialog::ClientInviteDialog;
@@ -156,24 +136,102 @@ impl ClientInviteDialog {
156136
/// # }
157137
/// ```
158138
pub async fn bye(&self) -> Result<()> {
139+
self.bye_with_headers(None).await
140+
}
141+
142+
/// Send a BYE request with custom headers to terminate the dialog.
143+
///
144+
/// This is the low-level variant used to add SIP headers (e.g. `Reason`)
145+
/// to the outgoing BYE request.
146+
///
147+
/// The dialog must be in `Confirmed` state for BYE to be sent; otherwise
148+
/// this method is a no-op.
149+
///
150+
/// # Parameters
151+
/// * `headers` - Optional extra SIP headers to include in the BYE request.
152+
///
153+
/// # Returns
154+
/// * `Ok(())` - BYE was sent successfully or dialog is not confirmed.
155+
/// * `Err(Error)` - Failed to build/send BYE request.
156+
pub async fn bye_with_headers(&self, headers: Option<Vec<rsip::Header>>) -> Result<()> {
159157
if !self.inner.is_confirmed() {
160158
return Ok(());
161159
}
162-
let request = self
163-
.inner
164-
.make_request(rsip::Method::Bye, None, None, None, None, None)?;
165160

166-
match self.inner.do_request(request).await {
167-
Ok(_) => {}
168-
Err(e) => {
169-
info!(error = %e, "bye error");
170-
}
171-
};
161+
let request =
162+
self.inner
163+
.make_request(rsip::Method::Bye, None, None, None, headers, None)?;
164+
165+
if let Err(e) = self.inner.do_request(request).await {
166+
info!(error = %e, "bye error");
167+
}
168+
172169
self.inner
173170
.transition(DialogState::Terminated(self.id(), TerminatedReason::UacBye))?;
174171
Ok(())
175172
}
176173

174+
/// Send a BYE request with a SIP `Reason` header.
175+
///
176+
/// Convenience wrapper over `bye_with_headers()` that adds:
177+
/// `Reason: <reason>`.
178+
///
179+
/// Typical values:
180+
/// * `SIP;cause=804;text="MEDIA_TIMEOUT"`
181+
/// * `Q.850;cause=16;text="Normal call clearing"`
182+
///
183+
/// # Parameters
184+
/// * `reason` - Value of the `Reason` header (without the `Reason:` name).
185+
pub async fn bye_with_reason(&self, reason: String) -> Result<()> {
186+
self.bye_with_headers(Some(vec![rsip::Header::Other(
187+
"Reason".into(),
188+
reason.into(),
189+
)]))
190+
.await
191+
}
192+
193+
/// Hang up the call
194+
///
195+
/// If the dialog is confirmed, send a BYE request to terminate the call.
196+
/// If the dialog is not confirmed, send a CANCEL request to cancel the call.
197+
///
198+
/// Thin wrapper over `hangup_with_headers(None)`.
199+
pub async fn hangup(&self) -> Result<()> {
200+
self.hangup_with_headers(None).await
201+
}
202+
203+
/// Hang up the call with custom headers.
204+
///
205+
/// If the dialog is still in early phase and can be canceled, this sends `CANCEL`.
206+
/// Headers are not attached to the CANCEL request by default.
207+
///
208+
/// If the dialog is confirmed, this sends `BYE` and attaches the provided headers.
209+
///
210+
/// # Parameters
211+
/// * `headers` - Optional extra SIP headers to include when BYE is used.
212+
pub async fn hangup_with_headers(&self, headers: Option<Vec<rsip::Header>>) -> Result<()> {
213+
if self.inner.can_cancel() {
214+
self.cancel().await
215+
} else {
216+
self.bye_with_headers(headers).await
217+
}
218+
}
219+
220+
/// Hang up the call and attach a SIP `Reason` header when BYE is used.
221+
///
222+
/// Convenience wrapper over `hangup_with_headers()` that adds:
223+
/// `Reason: <reason>`.
224+
///
225+
/// # Parameters
226+
/// * `reason` - Value of the `Reason` header used for BYE.
227+
pub async fn hangup_with_reason(&self, reason: String) -> Result<()> {
228+
self.hangup_with_headers(Some(vec![rsip::Header::Other(
229+
"Reason".into(),
230+
reason.into(),
231+
)]))
232+
.await
233+
}
234+
177235
/// Send a CANCEL request to cancel an ongoing INVITE
178236
///
179237
/// Sends a CANCEL request to cancel an INVITE transaction that has not

src/dialog/server_dialog.rs

Lines changed: 46 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -315,55 +315,83 @@ impl ServerInviteDialog {
315315
))
316316
}
317317

318-
/// Send a BYE request to terminate the dialog
318+
/// Send a BYE request to terminate the dialog.
319319
///
320-
/// Sends a BYE request to gracefully terminate an established dialog.
321-
/// This should only be called for confirmed dialogs. If the dialog
322-
/// is not confirmed, this method returns immediately without error.
320+
/// Thin wrapper over `bye_with_headers(None)`.
323321
///
324-
/// # Returns
325-
///
326-
/// * `Ok(())` - BYE was sent successfully or dialog not confirmed
327-
/// * `Err(Error)` - Failed to send BYE request
322+
/// The dialog must be in `Confirmed` state (or `WaitAck`) for BYE to be sent;
323+
/// otherwise this method is a no-op.
328324
///
329325
/// # Examples
330326
///
331327
/// ```rust,no_run
332328
/// # use rsipstack::dialog::server_dialog::ServerInviteDialog;
333329
/// # async fn example() -> rsipstack::Result<()> {
334330
/// # let dialog: ServerInviteDialog = todo!();
335-
/// // End an established call
336331
/// dialog.bye().await?;
337332
/// # Ok(())
338333
/// # }
339334
/// ```
340335
pub async fn bye(&self) -> Result<()> {
336+
self.bye_with_headers(None).await
337+
}
338+
339+
/// Send a BYE request with custom headers to terminate the dialog.
340+
///
341+
/// This is the low-level variant used to add SIP headers (e.g. `Reason`)
342+
/// to the outgoing BYE request.
343+
///
344+
/// The dialog must be in `Confirmed` state (or `WaitAck`) for BYE to be sent;
345+
/// otherwise this method is a no-op.
346+
///
347+
/// # Parameters
348+
/// * `headers` - Optional extra SIP headers to include in the BYE request.
349+
///
350+
/// # Returns
351+
/// * `Ok(())` - BYE was sent successfully or dialog is not in a state where BYE applies.
352+
/// * `Err(Error)` - Failed to build/send BYE request.
353+
pub async fn bye_with_headers(&self, headers: Option<Vec<rsip::Header>>) -> Result<()> {
341354
if !self.inner.is_confirmed() && !self.inner.waiting_ack() {
342355
return Ok(());
343356
}
344-
debug!(id = %self.id(), "sending bye request");
345357

346358
let request = self.inner.make_request_with_vias(
347359
rsip::Method::Bye,
348360
None,
349361
self.inner.build_vias_from_request()?,
350-
None,
362+
headers,
351363
None,
352364
)?;
353365

354-
match self.inner.do_request(request).await {
355-
Ok(_) => {}
356-
Err(e) => {
357-
debug!(id = %self.id(), error = %e, "bye error");
358-
}
359-
};
366+
if let Err(e) = self.inner.do_request(request).await {
367+
debug!(id = %self.id(), error = %e, "bye error");
368+
}
369+
360370
self.inner
361371
.transition(DialogState::Terminated(self.id(), TerminatedReason::UasBye))?;
362372
Ok(())
363373
}
364374

365-
/// Send a re-INVITE request to modify the session
375+
/// Send a BYE request with a SIP `Reason` header.
366376
///
377+
/// Convenience wrapper over `bye_with_headers()` that adds:
378+
/// `Reason: <reason>`.
379+
///
380+
/// Typical values:
381+
/// * `SIP;cause=804;text="MEDIA_TIMEOUT"`
382+
/// * `Q.850;cause=16;text="Normal call clearing"`
383+
///
384+
/// # Parameters
385+
/// * `reason` - Value of the `Reason` header (without the `Reason:` name).
386+
pub async fn bye_with_reason(&self, reason: String) -> Result<()> {
387+
self.bye_with_headers(Some(vec![rsip::Header::Other(
388+
"Reason".into(),
389+
reason.into(),
390+
)]))
391+
.await
392+
}
393+
394+
/// Send a re-INVITE request to modify the session
367395
/// Sends a re-INVITE request within an established dialog to modify
368396
/// the session parameters (e.g., change media, add/remove streams).
369397
/// This can only be called for confirmed dialogs.

0 commit comments

Comments
 (0)