Compare commits

...

5 Commits

Author SHA1 Message Date
Naveen Albert
4a1a8987c2 sig_analog: Fix SEGV due to calling strcmp on NULL.
Add an additional check to guard against the channel application being
NULL.

Resolves: #1380
2025-08-18 18:14:06 +00:00
Sven Kube
71b538e79f ARI: Add command to indicate progress to a channel
Adds an ARI command to send a progress indication to a channel.

DeveloperNote: A new ARI endpoint is available at `/channels/{channelId}/progress` to indicate progress to a channel.
2025-08-18 16:29:44 +00:00
Naveen Albert
8bfa3be27f dsp.c: Improve debug logging in tone_detect().
The debug logging during DSP processing has always been kind
of overwhelming and annoying to troubleshoot. Simplify and
improve the logging in a few ways to aid DSP debugging:

* If we had a DSP hit, don't also emit the previous debug message that
  was always logged. It is duplicated by the hit message, so this can
  reduce the number of debug messages during detection by 50%.
* Include the hit count and required number of hits in the message so
  on partial detections can be more easily troubleshot.
* Use debug level 9 for hits instead of 10, so we can focus on hits
  without all the noise from the per-frame debug message.
* 1-index the hit count in the debug messages. On the first hit, it
  currently logs '0', just as when we are not detecting anything,
  which can be confusing.

Resolves: #1375
2025-08-18 16:25:59 +00:00
Jose Lopes
8837723f7d res_stasis_device_state: Fix delete ARI Devicestates after asterisk restart.
After an asterisk restart, the deletion of ARI Devicestates didn't
return error, but the devicestate was not deleted.
Found a typo on populate_cache function that created wrong cache for
device states.
This bug caused wrong assumption that devicestate didn't exist,
since it was not in cache, so deletion didn't returned error.

Fixes: #1327
2025-08-18 14:53:15 +00:00
Naveen Albert
94afcffce9 app_chanspy: Add option to not automatically answer channel.
Add an option for ChanSpy and ExtenSpy to not answer the channel
automatically. Most applications that auto-answer by default
already have an option to disable this behavior if unwanted.

Resolves: #1358

UserNote: ChanSpy and ExtenSpy can now be configured to not
automatically answer the channel by using the 'N' option.
2025-08-18 14:19:14 +00:00
10 changed files with 193 additions and 8 deletions

View File

@@ -132,6 +132,9 @@
<argument name="mailbox" />
<argument name="context" />
</option>
<option name="N">
<para>Do not answer the channel automatically.</para>
</option>
<option name="o">
<para>Only listen to audio coming from this channel.</para>
</option>
@@ -289,6 +292,9 @@
<argument name="mailbox" />
<argument name="context" />
</option>
<option name="N">
<para>Do not answer the channel automatically.</para>
</option>
<option name="o">
<para>Only listen to audio coming from this channel.</para>
</option>
@@ -408,6 +414,7 @@ enum {
OPTION_UNIQUEID = (1 << 19), /* The chanprefix is a channel uniqueid or fully specified channel name. */
OPTION_LONG_QUEUE = (1 << 20), /* Allow usage of a long queue to store audio frames. */
OPTION_INTERLEAVED = (1 << 21), /* Interleave the Read and Write frames in the output frame. */
OPTION_NOANSWER = (1 << 22), /* Do not automatically answer the channel */
};
enum {
@@ -432,6 +439,7 @@ AST_APP_OPTIONS(spy_opts, {
AST_APP_OPTION_ARG('g', OPTION_GROUP, OPT_ARG_GROUP),
AST_APP_OPTION('l', OPTION_LONG_QUEUE),
AST_APP_OPTION_ARG('n', OPTION_NAME, OPT_ARG_NAME),
AST_APP_OPTION('N', OPTION_NOANSWER),
AST_APP_OPTION('o', OPTION_READONLY),
AST_APP_OPTION('q', OPTION_QUIET),
AST_APP_OPTION_ARG('r', OPTION_RECORD, OPT_ARG_RECORD),
@@ -999,8 +1007,9 @@ static int common_exec(struct ast_channel *chan, struct ast_flags *flags,
ast_channel_unlock(chan);
}
if (ast_channel_state(chan) != AST_STATE_UP)
if (!ast_test_flag(flags, OPTION_NOANSWER) && ast_channel_state(chan) != AST_STATE_UP) {
ast_answer(chan);
}
ast_channel_set_flag(chan, AST_FLAG_SPYING);

View File

@@ -3168,6 +3168,10 @@ static struct ast_frame *__analog_handle_event(struct analog_pvt *p, struct ast_
ast_debug(2, "Letting this call hang up normally, since it's not the only call\n");
} else if (!p->owner || !p->subs[ANALOG_SUB_REAL].owner || ast_channel_state(ast) != AST_STATE_UP) {
ast_debug(2, "Called Subscriber Held does not apply: channel state is %d\n", ast_channel_state(ast));
} else if (p->owner && p->subs[ANALOG_SUB_REAL].owner && ast_strlen_zero(ast_channel_appl(p->subs[ANALOG_SUB_REAL].owner))) {
/* If the channel application is empty, it is likely a masquerade has occured, in which case don't hold any calls.
* This conditional matches only executions that would have reached the strcmp below. */
ast_debug(1, "Skipping Called Subscriber Held; channel has no application\n");
} else if (!p->owner || !p->subs[ANALOG_SUB_REAL].owner || strcmp(ast_channel_appl(p->subs[ANALOG_SUB_REAL].owner), "AppDial")) {
/* Called Subscriber held only applies to incoming calls, not outgoing calls.
* We can't use p->outgoing because that is always true, for both incoming and outgoing calls, so it's not accurate.

View File

@@ -568,6 +568,16 @@ int stasis_app_control_ring(struct stasis_app_control *control);
*/
int stasis_app_control_ring_stop(struct stasis_app_control *control);
/*!
* \brief Indicate progress to the channel associated with this control.
*
* \param control Control for \c res_stasis.
*
* \return 0 for success.
* \return -1 for error.
*/
int stasis_app_control_progress(struct stasis_app_control *control);
/*!
* \brief Send DTMF to the channel associated with this control.
*

View File

@@ -618,12 +618,14 @@ static int tone_detect(struct ast_dsp *dsp, tone_detect_state_t *s, int16_t *amp
tone_energy *= 2.0;
s->energy *= s->block_size;
ast_debug(10, "%d Hz tone %2d Ew=%.4E, Et=%.4E, s/n=%10.2f\n", s->freq, s->hit_count, tone_energy, s->energy, tone_energy / (s->energy - tone_energy));
hit = 0;
/* Add 1 to hit_count when logging, since it doesn't get incremented until later in the loop. */
if (TONE_THRESHOLD <= tone_energy
&& tone_energy > s->energy * s->threshold) {
ast_debug(10, "%d Hz tone Hit! %2d Ew=%.4E, Et=%.4E, s/n=%10.2f\n", s->freq, s->hit_count, tone_energy, s->energy, tone_energy / (s->energy - tone_energy));
ast_debug(9, "%d Hz tone Hit! [%d/%d] Ew=%.4E, Et=%.4E, s/n=%10.2f\n", s->freq, s->hit_count + 1, s->hits_required, tone_energy, s->energy, tone_energy / (s->energy - tone_energy));
hit = 1;
} else {
ast_debug(10, "%d Hz tone [%d/%d] Ew=%.4E, Et=%.4E, s/n=%10.2f\n", s->freq, s->hit_count + 1, s->hits_required, tone_energy, s->energy, tone_energy / (s->energy - tone_energy));
hit = 0;
}
if (s->hit_count) {
@@ -633,11 +635,11 @@ static int tone_detect(struct ast_dsp *dsp, tone_detect_state_t *s, int16_t *amp
if (hit == s->last_hit) {
if (!hit) {
/* Two successive misses. Tone ended */
ast_debug(9, "Partial detect expired after %d/%d hits\n", s->hit_count, s->hits_required);
s->hit_count = 0;
} else if (!s->hit_count) {
s->hit_count++;
}
}
if (s->hit_count == s->hits_required) {

View File

@@ -401,6 +401,26 @@ void ast_ari_channels_ring_stop(struct ast_variable *headers,
ast_ari_response_no_content(response);
}
void ast_ari_channels_progress(struct ast_variable *headers,
struct ast_ari_channels_progress_args *args,
struct ast_ari_response *response)
{
RAII_VAR(struct stasis_app_control *, control, NULL, ao2_cleanup);
control = find_control(response, args->channel_id);
if (control == NULL) {
return;
}
if (channel_state_invalid(control, response)) {
return;
}
stasis_app_control_progress(control);
ast_ari_response_no_content(response);
}
void ast_ari_channels_mute(struct ast_variable *headers,
struct ast_ari_channels_mute_args *args,
struct ast_ari_response *response)

View File

@@ -358,6 +358,19 @@ struct ast_ari_channels_ring_stop_args {
* \param[out] response HTTP response
*/
void ast_ari_channels_ring_stop(struct ast_variable *headers, struct ast_ari_channels_ring_stop_args *args, struct ast_ari_response *response);
/*! Argument struct for ast_ari_channels_progress() */
struct ast_ari_channels_progress_args {
/*! Channel's id */
const char *channel_id;
};
/*!
* \brief Indicate progress on a channel.
*
* \param headers HTTP headers
* \param args Swagger parameters
* \param[out] response HTTP response
*/
void ast_ari_channels_progress(struct ast_variable *headers, struct ast_ari_channels_progress_args *args, struct ast_ari_response *response);
/*! Argument struct for ast_ari_channels_send_dtmf() */
struct ast_ari_channels_send_dtmf_args {
/*! Channel's id */

View File

@@ -1157,6 +1157,68 @@ static void ast_ari_channels_ring_stop_cb(
}
#endif /* AST_DEVMODE */
fin: __attribute__((unused))
return;
}
/*!
* \brief Parameter parsing callback for /channels/{channelId}/progress.
* \param ser TCP/TLS session object
* \param get_params GET parameters in the HTTP request.
* \param path_vars Path variables extracted from the request.
* \param headers HTTP headers.
* \param body
* \param[out] response Response to the HTTP request.
*/
static void ast_ari_channels_progress_cb(
struct ast_tcptls_session_instance *ser,
struct ast_variable *get_params, struct ast_variable *path_vars,
struct ast_variable *headers, struct ast_json *body, struct ast_ari_response *response)
{
struct ast_ari_channels_progress_args args = {};
struct ast_variable *i;
#if defined(AST_DEVMODE)
int is_valid;
int code;
#endif /* AST_DEVMODE */
for (i = path_vars; i; i = i->next) {
if (strcmp(i->name, "channelId") == 0) {
args.channel_id = (i->value);
} else
{}
}
ast_ari_channels_progress(headers, &args, response);
#if defined(AST_DEVMODE)
code = response->response_code;
switch (code) {
case 0: /* Implementation is still a stub, or the code wasn't set */
is_valid = response->message == NULL;
break;
case 500: /* Internal Server Error */
case 501: /* Not Implemented */
case 404: /* Channel not found */
case 409: /* Channel not in a Stasis application */
case 412: /* Channel in invalid state */
is_valid = 1;
break;
default:
if (200 <= code && code <= 299) {
is_valid = ast_ari_validate_void(
response->message);
} else {
ast_log(LOG_ERROR, "Invalid error response %d for /channels/{channelId}/progress\n", code);
is_valid = 0;
}
}
if (!is_valid) {
ast_log(LOG_ERROR, "Response validation failed for /channels/{channelId}/progress\n");
ast_ari_response_error(response, 500,
"Internal Server Error", "Response validation failed");
}
#endif /* AST_DEVMODE */
fin: __attribute__((unused))
return;
}
@@ -3145,6 +3207,15 @@ static struct stasis_rest_handlers channels_channelId_ring = {
.children = { }
};
/*! \brief REST handler for /api-docs/channels.json */
static struct stasis_rest_handlers channels_channelId_progress = {
.path_segment = "progress",
.callbacks = {
[AST_HTTP_POST] = ast_ari_channels_progress_cb,
},
.num_children = 0,
.children = { }
};
/*! \brief REST handler for /api-docs/channels.json */
static struct stasis_rest_handlers channels_channelId_dtmf = {
.path_segment = "dtmf",
.callbacks = {
@@ -3286,8 +3357,8 @@ static struct stasis_rest_handlers channels_channelId = {
[AST_HTTP_POST] = ast_ari_channels_originate_with_id_cb,
[AST_HTTP_DELETE] = ast_ari_channels_hangup_cb,
},
.num_children = 17,
.children = { &channels_channelId_continue,&channels_channelId_move,&channels_channelId_redirect,&channels_channelId_answer,&channels_channelId_ring,&channels_channelId_dtmf,&channels_channelId_mute,&channels_channelId_hold,&channels_channelId_moh,&channels_channelId_silence,&channels_channelId_play,&channels_channelId_record,&channels_channelId_variable,&channels_channelId_snoop,&channels_channelId_dial,&channels_channelId_rtp_statistics,&channels_channelId_transfer_progress, }
.num_children = 18,
.children = { &channels_channelId_continue,&channels_channelId_move,&channels_channelId_redirect,&channels_channelId_answer,&channels_channelId_ring,&channels_channelId_progress,&channels_channelId_dtmf,&channels_channelId_mute,&channels_channelId_hold,&channels_channelId_moh,&channels_channelId_silence,&channels_channelId_play,&channels_channelId_record,&channels_channelId_variable,&channels_channelId_snoop,&channels_channelId_dial,&channels_channelId_rtp_statistics,&channels_channelId_transfer_progress, }
};
/*! \brief REST handler for /api-docs/channels.json */
static struct stasis_rest_handlers channels_externalMedia = {

View File

@@ -283,7 +283,7 @@ static void populate_cache(void)
if (!ast_strlen_zero(name)) {
ast_devstate_changed(
ast_devstate_val(entry->data),
AST_DEVSTATE_CACHABLE, "%s%s\n",
AST_DEVSTATE_CACHABLE, "%s%s",
DEVICE_STATE_SCHEME_STASIS, name + 1);
}
}

View File

@@ -637,6 +637,21 @@ int stasis_app_control_ring_stop(struct stasis_app_control *control)
return 0;
}
static int app_control_progress(struct stasis_app_control *control,
struct ast_channel *chan, void *data)
{
ast_indicate(control->channel, AST_CONTROL_PROGRESS);
return 0;
}
int stasis_app_control_progress(struct stasis_app_control *control)
{
stasis_app_send_command_async(control, app_control_progress, NULL, NULL);
return 0;
}
struct stasis_app_control_mute_data {
enum ast_frame_type frametype;
unsigned int direction;

View File

@@ -772,6 +772,47 @@
}
]
},
{
"path": "/channels/{channelId}/progress",
"description": "Indicate progress on a channel",
"operations": [
{
"httpMethod": "POST",
"since": [
"22.6.0",
"21.11.0",
"20.16.0"
],
"summary": "Indicate progress on a channel.",
"nickname": "progress",
"responseClass": "void",
"parameters": [
{
"name": "channelId",
"description": "Channel's id",
"paramType": "path",
"required": true,
"allowMultiple": false,
"dataType": "string"
}
],
"errorResponses": [
{
"code": 404,
"reason": "Channel not found"
},
{
"code": 409,
"reason": "Channel not in a Stasis application"
},
{
"code": 412,
"reason": "Channel in invalid state"
}
]
}
]
},
{
"path": "/channels/{channelId}/dtmf",
"description": "Send DTMF to a channel",