build: Update config.guess and config.sub
[asterisk/asterisk.git] / res / res_fax_spandsp.c
index 9ecd783..045dbc7 100644 (file)
@@ -37,6 +37,7 @@
  * \brief Spandsp T.38 and G.711 FAX Resource
  *
  * \author Matthew Nicholson <mnicholson@digium.com>
+ * \author Gregory H. Nietsky <gregory@distrotech.co.za>
  *
  * This module registers the Spandsp FAX technology with the res_fax module.
  */
 /*** MODULEINFO
        <depend>spandsp</depend>
        <depend>res_fax</depend>
+       <support_level>extended</support_level>
 ***/
 
+/* Needed for spandsp headers */
+#define ASTMM_LIBC ASTMM_IGNORE
 #include "asterisk.h"
 
-ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
-
-#define SPANDSP_EXPOSE_INTERNAL_STRUCTURES
-#include <spandsp.h>
-#include <spandsp/version.h>
-
 #include "asterisk/logger.h"
 #include "asterisk/module.h"
 #include "asterisk/strings.h"
@@ -63,6 +61,11 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/astobj2.h"
 #include "asterisk/res_fax.h"
 #include "asterisk/channel.h"
+#include "asterisk/format_cache.h"
+
+#define SPANDSP_EXPOSE_INTERNAL_STRUCTURES
+#include <spandsp.h>
+#include <spandsp/version.h>
 
 #define SPANDSP_FAX_SAMPLES 160
 #define SPANDSP_FAX_TIMER_RATE 8000 / SPANDSP_FAX_SAMPLES      /* 50 ticks per second, 20ms, 160 samples per second */
@@ -78,9 +81,14 @@ static int spandsp_fax_switch_to_t38(struct ast_fax_session *s);
 static int spandsp_fax_gateway_start(struct ast_fax_session *s);
 static int spandsp_fax_gateway_process(struct ast_fax_session *s, const struct ast_frame *f);
 static void spandsp_fax_gateway_cleanup(struct ast_fax_session *s);
+static int spandsp_v21_detect(struct ast_fax_session *s, const struct ast_frame *f);
+static void spandsp_v21_cleanup(struct ast_fax_session *s);
+static void spandsp_v21_tone(void *data, int code, int level, int delay);
 
 static char *spandsp_fax_cli_show_capabilities(int fd);
 static char *spandsp_fax_cli_show_session(struct ast_fax_session *s, int fd);
+static void spandsp_manager_fax_session(struct mansession *s,
+       const char *id_text, struct ast_fax_session *session);
 static char *spandsp_fax_cli_show_stats(int fd);
 static char *spandsp_fax_cli_show_settings(int fd);
 
@@ -96,7 +104,9 @@ static struct ast_fax_tech spandsp_fax_tech = {
         */
        .version = "pre-20090220",
 #endif
-       .caps = AST_FAX_TECH_AUDIO | AST_FAX_TECH_T38 | AST_FAX_TECH_SEND | AST_FAX_TECH_RECEIVE | AST_FAX_TECH_GATEWAY,
+       .caps = AST_FAX_TECH_AUDIO | AST_FAX_TECH_T38 | AST_FAX_TECH_SEND
+               | AST_FAX_TECH_RECEIVE | AST_FAX_TECH_GATEWAY
+               | AST_FAX_TECH_V21_DETECT,
        .new_session = spandsp_fax_new,
        .destroy_session = spandsp_fax_destroy,
        .read = spandsp_fax_read,
@@ -106,6 +116,7 @@ static struct ast_fax_tech spandsp_fax_tech = {
        .switch_to_t38 = spandsp_fax_switch_to_t38,
        .cli_show_capabilities = spandsp_fax_cli_show_capabilities,
        .cli_show_session = spandsp_fax_cli_show_session,
+       .manager_fax_session = spandsp_manager_fax_session,
        .cli_show_stats = spandsp_fax_cli_show_stats,
        .cli_show_settings = spandsp_fax_cli_show_settings,
 };
@@ -148,13 +159,18 @@ struct spandsp_pvt {
 
        struct ast_timer *timer;
        AST_LIST_HEAD(frame_queue, ast_frame) read_frames;
+
+       int v21_detected;
+       modem_connect_tones_rx_state_t *tone_state;
 };
 
+static int spandsp_v21_new(struct spandsp_pvt *p);
 static void session_destroy(struct spandsp_pvt *p);
 static int t38_tx_packet_handler(t38_core_state_t *t38_core_state, void *data, const uint8_t *buf, int len, int count);
 static void t30_phase_e_handler(t30_state_t *t30_state, void *data, int completion_code);
 static void spandsp_log(int level, const char *msg);
 static int update_stats(struct spandsp_pvt *p, int completion_code);
+static int spandsp_modems(struct ast_fax_session_details *details);
 
 static void set_logging(logging_state_t *state, struct ast_fax_session_details *details);
 static void set_local_info(t30_state_t *t30_state, struct ast_fax_session_details *details);
@@ -164,12 +180,29 @@ static void set_ecm(t30_state_t *t30_state, struct ast_fax_session_details *deta
 static void session_destroy(struct spandsp_pvt *p)
 {
        struct ast_frame *f;
+       t30_state_t *t30_to_terminate;
 
-       t30_terminate(p->t30_state);
+       if (p->t30_state) {
+               t30_to_terminate = p->t30_state;
+       } else if (p->ist38) {
+#if SPANDSP_RELEASE_DATE >= 20080725
+               t30_to_terminate = &p->t38_state.t30;
+#else
+               t30_to_terminate = &p->t38_state.t30_state;
+#endif
+       } else {
+#if SPANDSP_RELEASE_DATE >= 20080725
+               t30_to_terminate = &p->fax_state.t30;
+#else
+               t30_to_terminate = &p->fax_state.t30_state;
+#endif
+       }
+
+       t30_terminate(t30_to_terminate);
        p->isdone = 1;
 
        ast_timer_close(p->timer);
-
+       p->timer = NULL;
        fax_release(&p->fax_state);
        t38_terminal_release(&p->t38_state);
 
@@ -215,6 +248,7 @@ static int t38_tx_packet_handler(t38_core_state_t *t38_core_state, void *data, c
        } else {
                /* no need to lock, this all runs in the same thread */
                AST_LIST_INSERT_TAIL(&p->read_frames, f, frame_list);
+               res = 0;
        }
 
        return res;
@@ -349,7 +383,7 @@ static void t30_phase_e_handler(t30_state_t *t30_state, void *data, int completi
        const char *c;
        t30_stats_t stats;
 
-       ast_debug(5, "FAX session '%d' entering phase E\n", s->id);
+       ast_debug(5, "FAX session '%u' entering phase E\n", s->id);
 
        p->isdone = 1;
 
@@ -366,7 +400,7 @@ static void t30_phase_e_handler(t30_state_t *t30_state, void *data, int completi
 
        ast_string_field_set(s->details, resultstr, t30_completion_code_to_str(completion_code));
 
-       ast_debug(5, "FAX session '%d' completed with result: %s (%s)\n", s->id, s->details->result, s->details->resultstr);
+       ast_debug(5, "FAX session '%u' completed with result: %s (%s)\n", s->id, s->details->result, s->details->resultstr);
 
        if ((c = t30_get_tx_ident(t30_state))) {
                ast_string_field_set(s->details, localstationid, c);
@@ -448,6 +482,45 @@ static void set_ecm(t30_state_t *t30_state, struct ast_fax_session_details *deta
        t30_set_supported_compressions(t30_state, T30_SUPPORT_T4_1D_COMPRESSION | T30_SUPPORT_T4_2D_COMPRESSION | T30_SUPPORT_T6_COMPRESSION);
 }
 
+static int spandsp_v21_new(struct spandsp_pvt *p)
+{
+       /* XXX Here we use MODEM_CONNECT_TONES_FAX_CED_OR_PREAMBLE even though
+        * we don't care about CED tones. Using MODEM_CONNECT_TONES_PREAMBLE
+        * doesn't seem to work right all the time.
+        */
+       p->tone_state = modem_connect_tones_rx_init(NULL, MODEM_CONNECT_TONES_FAX_CED_OR_PREAMBLE, spandsp_v21_tone, p);
+       if (!p->tone_state) {
+               return -1;
+       }
+
+       return 0;
+}
+
+static int spandsp_modems(struct ast_fax_session_details *details)
+{
+       int modems = 0;
+       if (AST_FAX_MODEM_V17 & details->modems) {
+               modems |= T30_SUPPORT_V17;
+       }
+       if (AST_FAX_MODEM_V27TER & details->modems) {
+               modems |= T30_SUPPORT_V27TER;
+       }
+       if (AST_FAX_MODEM_V29 & details->modems) {
+               modems |= T30_SUPPORT_V29;
+       }
+       if (AST_FAX_MODEM_V34 & details->modems) {
+#if defined(T30_SUPPORT_V34)
+               modems |= T30_SUPPORT_V34;
+#elif defined(T30_SUPPORT_V34HDX)
+               modems |= T30_SUPPORT_V34HDX;
+#else
+               ast_log(LOG_WARNING, "v34 not supported in this version of spandsp\n");
+#endif
+       }
+
+       return modems;
+}
+
 /*! \brief create an instance of the spandsp tech_pvt for a fax session */
 static void *spandsp_fax_new(struct ast_fax_session *s, struct ast_fax_tech_token *token)
 {
@@ -459,6 +532,15 @@ static void *spandsp_fax_new(struct ast_fax_session *s, struct ast_fax_tech_toke
                goto e_return;
        }
 
+       if (s->details->caps & AST_FAX_TECH_V21_DETECT) {
+               if (spandsp_v21_new(p)) {
+                       ast_log(LOG_ERROR, "Cannot initialize the spandsp private v21 technology structure.\n");
+                       goto e_return;
+               }
+               s->state = AST_FAX_STATE_ACTIVE;
+               return p;
+       }
+
        if (s->details->caps & AST_FAX_TECH_GATEWAY) {
                s->state = AST_FAX_STATE_INITIALIZED;
                return p;
@@ -476,7 +558,7 @@ static void *spandsp_fax_new(struct ast_fax_session *s, struct ast_fax_tech_toke
        }
 
        if (!(p->timer = ast_timer_open())) {
-               ast_log(LOG_ERROR, "Channel '%s' FAX session '%d' failed to create timing source.\n", s->channame, s->id);
+               ast_log(LOG_ERROR, "Channel '%s' FAX session '%u' failed to create timing source.\n", s->channame, s->id);
                goto e_free;
        }
 
@@ -484,7 +566,7 @@ static void *spandsp_fax_new(struct ast_fax_session *s, struct ast_fax_tech_toke
 
        p->stats = &spandsp_global_stats.g711;
 
-       if (s->details->caps & AST_FAX_TECH_T38) {
+       if (s->details->caps & (AST_FAX_TECH_T38 | AST_FAX_TECH_AUDIO)) {
                if ((s->details->caps & AST_FAX_TECH_AUDIO) == 0) {
                        /* audio mode was not requested, start in T.38 mode */
                        p->ist38 = 1;
@@ -494,9 +576,7 @@ static void *spandsp_fax_new(struct ast_fax_session *s, struct ast_fax_tech_toke
                /* init t38 stuff */
                t38_terminal_init(&p->t38_state, caller_mode, t38_tx_packet_handler, s);
                set_logging(&p->t38_state.logging, s->details);
-       }
 
-       if (s->details->caps & AST_FAX_TECH_AUDIO) {
                /* init audio stuff */
                fax_init(&p->fax_state, caller_mode);
                set_logging(&p->fax_state.logging, s->details);
@@ -511,6 +591,13 @@ e_return:
        return NULL;
 }
 
+static void spandsp_v21_cleanup(struct ast_fax_session *s)
+{
+       struct spandsp_pvt *p = s->tech_pvt;
+
+       modem_connect_tones_rx_free(p->tone_state);
+}
+
 /*! \brief Destroy a spandsp fax session.
  */
 static void spandsp_fax_destroy(struct ast_fax_session *s)
@@ -519,6 +606,8 @@ static void spandsp_fax_destroy(struct ast_fax_session *s)
 
        if (s->details->caps & AST_FAX_TECH_GATEWAY) {
                spandsp_fax_gateway_cleanup(s);
+       } else if (s->details->caps & AST_FAX_TECH_V21_DETECT) {
+               spandsp_v21_cleanup(s);
        } else {
                session_destroy(p);
        }
@@ -540,16 +629,19 @@ static struct ast_frame *spandsp_fax_read(struct ast_fax_session *s)
        struct ast_frame fax_frame = {
                .frametype = AST_FRAME_VOICE,
                .src = "res_fax_spandsp_g711",
+               .subclass.format = ast_format_slin,
        };
        struct ast_frame *f = &fax_frame;
-       ast_format_set(&fax_frame.subclass.format, AST_FORMAT_SLINEAR, 0);
 
-       ast_timer_ack(p->timer, 1);
+       if (ast_timer_ack(p->timer, 1) < 0) {
+               ast_log(LOG_ERROR, "Failed to acknowledge timer for FAX session '%u'\n", s->id);
+               return NULL;
+       }
 
        /* XXX do we need to lock here? */
        if (p->isdone) {
                s->state = AST_FAX_STATE_COMPLETE;
-               ast_debug(5, "FAX session '%d' is complete.\n", s->id);
+               ast_debug(5, "FAX session '%u' is complete.\n", s->id);
                return NULL;
        }
 
@@ -569,6 +661,66 @@ static struct ast_frame *spandsp_fax_read(struct ast_fax_session *s)
        return &ast_null_frame;
 }
 
+static void spandsp_v21_tone(void *data, int code, int level, int delay)
+{
+       struct spandsp_pvt *p = data;
+
+       if (code == MODEM_CONNECT_TONES_FAX_PREAMBLE) {
+               p->v21_detected = 1;
+       }
+}
+
+static int spandsp_v21_detect(struct ast_fax_session *s, const struct ast_frame *f)
+{
+       struct spandsp_pvt *p = s->tech_pvt;
+       int16_t *slndata;
+       g711_state_t *decoder;
+
+       if (p->v21_detected) {
+               return 0;
+       }
+
+       /*invalid frame*/
+       if (!f->data.ptr || !f->datalen) {
+               return -1;
+       }
+
+       ast_debug(5, "frame={ datalen=%d, samples=%d, mallocd=%d, src=%s, flags=%u, ts=%ld, len=%ld, seqno=%d, data.ptr=%p, subclass.format=%s  }\n", f->datalen, f->samples, f->mallocd, f->src, f->flags, f->ts, f->len, f->seqno, f->data.ptr, ast_format_get_name(f->subclass.format));
+
+       /* slinear frame can be passed to spandsp */
+       if (ast_format_cmp(f->subclass.format, ast_format_slin) == AST_FORMAT_CMP_EQUAL) {
+               modem_connect_tones_rx(p->tone_state, f->data.ptr, f->samples);
+
+       /* alaw/ulaw frame must be converted to slinear before passing to spandsp */
+       } else if (ast_format_cmp(f->subclass.format, ast_format_alaw) == AST_FORMAT_CMP_EQUAL ||
+                  ast_format_cmp(f->subclass.format, ast_format_ulaw) == AST_FORMAT_CMP_EQUAL) {
+               if (!(slndata = ast_malloc(sizeof(*slndata) * f->samples))) {
+                       return -1;
+               }
+               decoder = g711_init(NULL, (ast_format_cmp(f->subclass.format, ast_format_alaw) == AST_FORMAT_CMP_EQUAL ? G711_ALAW : G711_ULAW));
+               g711_decode(decoder, slndata, f->data.ptr, f->samples);
+               ast_debug(5, "spandsp transcoding frame from %s to slinear for v21 detection\n", ast_format_get_name(f->subclass.format));
+               modem_connect_tones_rx(p->tone_state, slndata, f->samples);
+               g711_release(decoder);
+#if SPANDSP_RELEASE_DATE >= 20090220
+               g711_free(decoder);
+#endif
+               ast_free(slndata);
+
+       /* frame in other formats cannot be passed to spandsp, it could cause segfault */
+       } else {
+               ast_log(LOG_WARNING, "Frame format %s not supported, v.21 detection skipped\n", ast_format_get_name(f->subclass.format));
+               return -1;
+       }
+
+       if (p->v21_detected) {
+               s->details->option.v21_detected = 1;
+               ast_debug(5, "v.21 detected\n");
+       }
+
+       return 0;
+}
+
 /*! \brief Write a frame to the spandsp fax stack.
  * \param s a fax session
  * \param f the frame to write
@@ -583,13 +735,17 @@ static int spandsp_fax_write(struct ast_fax_session *s, const struct ast_frame *
 {
        struct spandsp_pvt *p = s->tech_pvt;
 
+       if (s->details->caps & AST_FAX_TECH_V21_DETECT) {
+               return spandsp_v21_detect(s, f);
+       }
+
        if (s->details->caps & AST_FAX_TECH_GATEWAY) {
                return spandsp_fax_gateway_process(s, f);
        }
 
        /* XXX do we need to lock here? */
        if (s->state == AST_FAX_STATE_COMPLETE) {
-               ast_log(LOG_WARNING, "FAX session '%d' is in the '%s' state.\n", s->id, ast_fax_state_to_str(s->state));
+               ast_log(LOG_WARNING, "FAX session '%u' is in the '%s' state.\n", s->id, ast_fax_state_to_str(s->state));
                return -1;
        }
 
@@ -615,6 +771,7 @@ static int spandsp_fax_gw_t30_gen(struct ast_channel *chan, void *data, int len,
        struct ast_frame *f;
        struct ast_frame t30_frame = {
                .frametype = AST_FRAME_VOICE,
+               .subclass.format = ast_format_slin,
                .src = "res_fax_spandsp_g711",
                .samples = samples,
                .flags = AST_FAX_FRFLAG_GATEWAY,
@@ -622,7 +779,6 @@ static int spandsp_fax_gw_t30_gen(struct ast_channel *chan, void *data, int len,
 
        AST_FRAME_SET_BUFFER(&t30_frame, buffer, AST_FRIENDLY_OFFSET, t30_frame.samples * sizeof(int16_t));
 
-       ast_format_set(&t30_frame.subclass.format, AST_FORMAT_SLINEAR, 0);
        if (!(f = ast_frisolate(&t30_frame))) {
                return p->isdone ? -1 : res;
        }
@@ -640,27 +796,30 @@ static int spandsp_fax_gw_t30_gen(struct ast_channel *chan, void *data, int len,
  * \param chan channel
  * \param params generator data
  * \return data to use in generator call*/
-static void *spandsp_fax_gw_gen_alloc(struct ast_channel *chan, void *params) {
+static void *spandsp_fax_gw_gen_alloc(struct ast_channel *chan, void *params)
+{
        ao2_ref(params, +1);
        return params;
 }
 
-static void spandsp_fax_gw_gen_release(struct ast_channel *chan, void *data) {
+static void spandsp_fax_gw_gen_release(struct ast_channel *chan, void *data)
+{
        ao2_ref(data, -1);
 }
 
 /*! \brief activate a spandsp gateway based on the information in the given fax session
  * \param s fax session
  * \return -1 on error 0 on sucess*/
-static int spandsp_fax_gateway_start(struct ast_fax_session *s) {
+static int spandsp_fax_gateway_start(struct ast_fax_session *s)
+{
        struct spandsp_pvt *p = s->tech_pvt;
        struct ast_fax_t38_parameters *t38_param;
-       int i, modems = 0;
-       struct ast_channel *peer;
+       int i;
+       RAII_VAR(struct ast_channel *, peer, NULL, ao2_cleanup);
        static struct ast_generator t30_gen = {
-               alloc: spandsp_fax_gw_gen_alloc,
-               release: spandsp_fax_gw_gen_release,
-               generate: spandsp_fax_gw_t30_gen,
+               .alloc = spandsp_fax_gw_gen_alloc,
+               .release = spandsp_fax_gw_gen_release,
+               .generate = spandsp_fax_gw_t30_gen,
        };
 
 #if SPANDSP_RELEASE_DATE >= 20081012
@@ -677,10 +836,17 @@ static int spandsp_fax_gateway_start(struct ast_fax_session *s) {
 
        p->ist38 = 1;
        p->ast_t38_state = ast_channel_get_t38_state(s->chan);
-       if (!(peer = ast_bridged_channel(s->chan))) {
-               ast_channel_unlock(s->chan);
+       peer = ast_channel_bridge_peer(s->chan);
+       if (!peer) {
                return -1;
        }
+
+       /* we can be in T38_STATE_NEGOTIATING or T38_STATE_NEGOTIATED when the
+        * gateway is started. We treat both states the same. */
+       if (p->ast_t38_state == T38_STATE_NEGOTIATING) {
+               p->ast_t38_state = T38_STATE_NEGOTIATED;
+       }
+
        ast_activate_generator(p->ast_t38_state == T38_STATE_NEGOTIATED ? peer : s->chan, &t30_gen , s);
 
        set_logging(&p->t38_gw_state.logging, s->details);
@@ -693,34 +859,16 @@ static int spandsp_fax_gateway_start(struct ast_fax_session *s) {
        t38_set_fill_bit_removal(p->t38_core_state, t38_param->fill_bit_removal);
        t38_set_mmr_transcoding(p->t38_core_state, t38_param->transcoding_mmr);
        t38_set_jbig_transcoding(p->t38_core_state, t38_param->transcoding_jbig);
-       t38_set_data_rate_management_method(p->t38_core_state, 
+       t38_set_data_rate_management_method(p->t38_core_state,
                        (t38_param->rate_management == AST_T38_RATE_MANAGEMENT_TRANSFERRED_TCF)? 1 : 2);
 
        t38_gateway_set_transmit_on_idle(&p->t38_gw_state, TRUE);
        t38_set_sequence_number_handling(p->t38_core_state, TRUE);
 
-       if (AST_FAX_MODEM_V17 & s->details->modems) {
-               modems |= T30_SUPPORT_V17;
-       }
-       if (AST_FAX_MODEM_V27 & s->details->modems) {
-               modems |= T30_SUPPORT_V27TER;
-       }
-       if (AST_FAX_MODEM_V29 & s->details->modems) {
-               modems |= T30_SUPPORT_V29;
-       }
-       if (AST_FAX_MODEM_V34 & s->details->modems) {
-#if defined(T30_SUPPORT_V34)
-               modems |= T30_SUPPORT_V34;
-#elif defined(T30_SUPPORT_V34HDX)
-               modems |= T30_SUPPORT_V34HDX;
-#else
-               ast_log(LOG_WARNING, "v34 not supported in this version of spandsp\n");
-#endif
-       }
 
-       t38_gateway_set_supported_modems(&p->t38_gw_state, modems);
+       t38_gateway_set_supported_modems(&p->t38_gw_state, spandsp_modems(s->details));
 
-       /* engage udptl nat on other side of T38 line 
+       /* engage udptl nat on other side of T38 line
         * (Asterisk changes media ports thus we send a few packets to reinitialize
         * pinholes in NATs and FWs
         */
@@ -755,7 +903,8 @@ static int spandsp_fax_gateway_process(struct ast_fax_session *s, const struct a
        /* Process a IFP packet */
        if ((f->frametype == AST_FRAME_MODEM) && (f->subclass.integer == AST_MODEM_T38)) {
                return t38_core_rx_ifp_packet(p->t38_core_state, f->data.ptr, f->datalen, f->seqno);
-       } else if ((f->frametype == AST_FRAME_VOICE) && (f->subclass.format.id == AST_FORMAT_SLINEAR)) {
+       } else if ((f->frametype == AST_FRAME_VOICE) &&
+               (ast_format_cmp(f->subclass.format, ast_format_slin) == AST_FORMAT_CMP_EQUAL)) {
                return t38_gateway_rx(&p->t38_gw_state, f->data.ptr, f->samples);
        }
 
@@ -813,6 +962,7 @@ static int spandsp_fax_start(struct ast_fax_session *s)
        set_local_info(p->t30_state, s->details);
        set_file(p->t30_state, s->details);
        set_ecm(p->t30_state, s->details);
+       t30_set_supported_modems(p->t30_state, spandsp_modems(s->details));
 
        /* perhaps set_transmit_on_idle() should be called */
 
@@ -843,7 +993,7 @@ static int spandsp_fax_start(struct ast_fax_session *s)
 
        /* start the timer */
        if (ast_timer_set_rate(p->timer, SPANDSP_FAX_TIMER_RATE)) {
-               ast_log(LOG_ERROR, "FAX session '%d' error setting rate on timing source.\n", s->id);
+               ast_log(LOG_ERROR, "FAX session '%u' error setting rate on timing source.\n", s->id);
                return -1;
        }
 
@@ -897,11 +1047,11 @@ static char *spandsp_fax_cli_show_capabilities(int fd)
 /*! \brief */
 static char *spandsp_fax_cli_show_session(struct ast_fax_session *s, int fd)
 {
-       struct spandsp_pvt *p = s->tech_pvt;
-
        ao2_lock(s);
        if (s->details->caps & AST_FAX_TECH_GATEWAY) {
-               ast_cli(fd, "%-22s : %d\n", "session", s->id);
+               struct spandsp_pvt *p = s->tech_pvt;
+
+               ast_cli(fd, "%-22s : %u\n", "session", s->id);
                ast_cli(fd, "%-22s : %s\n", "operation", "Gateway");
                ast_cli(fd, "%-22s : %s\n", "state", ast_fax_state_to_str(s->state));
                if (s->state != AST_FAX_STATE_UNINITIALIZED) {
@@ -911,8 +1061,14 @@ static char *spandsp_fax_cli_show_session(struct ast_fax_session *s, int fd)
                        ast_cli(fd, "%-22s : %d\n", "Data Rate", stats.bit_rate);
                        ast_cli(fd, "%-22s : %d\n", "Page Number", stats.pages_transferred + 1);
                }
+       } else if (s->details->caps & AST_FAX_TECH_V21_DETECT) {
+               ast_cli(fd, "%-22s : %u\n", "session", s->id);
+               ast_cli(fd, "%-22s : %s\n", "operation", "V.21 Detect");
+               ast_cli(fd, "%-22s : %s\n", "state", ast_fax_state_to_str(s->state));
        } else {
-               ast_cli(fd, "%-22s : %d\n", "session", s->id);
+               struct spandsp_pvt *p = s->tech_pvt;
+
+               ast_cli(fd, "%-22s : %u\n", "session", s->id);
                ast_cli(fd, "%-22s : %s\n", "operation", (s->details->caps & AST_FAX_TECH_RECEIVE) ? "Receive" : "Transmit");
                ast_cli(fd, "%-22s : %s\n", "state", ast_fax_state_to_str(s->state));
                if (s->state != AST_FAX_STATE_UNINITIALIZED) {
@@ -946,6 +1102,97 @@ static char *spandsp_fax_cli_show_session(struct ast_fax_session *s, int fd)
        return CLI_SUCCESS;
 }
 
+static void spandsp_manager_fax_session(struct mansession *s,
+       const char *id_text, struct ast_fax_session *session)
+{
+       struct ast_str *message_string;
+       struct spandsp_pvt *span_pvt = session->tech_pvt;
+       int res;
+
+       message_string = ast_str_create(128);
+
+       if (!message_string) {
+               return;
+       }
+
+       ao2_lock(session);
+       res = ast_str_append(&message_string, 0, "SessionNumber: %u\r\n", session->id);
+       res |= ast_str_append(&message_string, 0, "Operation: %s\r\n", ast_fax_session_operation_str(session));
+       res |= ast_str_append(&message_string, 0, "State: %s\r\n", ast_fax_state_to_str(session->state));
+
+       if (session->details->caps & AST_FAX_TECH_GATEWAY) {
+               t38_stats_t stats;
+
+               if (session->state == AST_FAX_STATE_UNINITIALIZED) {
+                       goto skip_cap_additions;
+               }
+
+               t38_gateway_get_transfer_statistics(&span_pvt->t38_gw_state, &stats);
+               res |= ast_str_append(&message_string, 0, "ErrorCorrectionMode: %s\r\n",
+                       stats.error_correcting_mode ? "yes" : "no");
+               res |= ast_str_append(&message_string, 0, "DataRate: %d\r\n",
+                       stats.bit_rate);
+               res |= ast_str_append(&message_string, 0, "PageNumber: %d\r\n",
+                       stats.pages_transferred + 1);
+       } else if (!(session->details->caps & AST_FAX_TECH_V21_DETECT)) { /* caps is SEND/RECEIVE */
+               t30_stats_t stats;
+
+               if (session->state == AST_FAX_STATE_UNINITIALIZED) {
+                       goto skip_cap_additions;
+               }
+
+               t30_get_transfer_statistics(span_pvt->t30_state, &stats);
+               res |= ast_str_append(&message_string, 0, "ErrorCorrectionMode: %s\r\n",
+                       stats.error_correcting_mode ? "Yes" : "No");
+               res |= ast_str_append(&message_string, 0, "DataRate: %d\r\n",
+                       stats.bit_rate);
+               res |= ast_str_append(&message_string, 0, "ImageResolution: %dx%d\r\n",
+                       stats.x_resolution, stats.y_resolution);
+#if SPANDSP_RELEASE_DATE >= 20090220
+               res |= ast_str_append(&message_string, 0, "PageNumber: %d\r\n",
+                       ((session->details->caps & AST_FAX_TECH_RECEIVE) ? stats.pages_rx : stats.pages_tx) + 1);
+#else
+               res |= ast_str_append(&message_string, 0, "PageNumber: %d\r\n",
+                       stats.pages_transferred + 1);
+#endif
+               res |= ast_str_append(&message_string, 0, "FileName: %s\r\n",
+                       session->details->caps & AST_FAX_TECH_RECEIVE ? span_pvt->t30_state->rx_file :
+                       span_pvt->t30_state->tx_file);
+#if SPANDSP_RELEASE_DATE >= 20090220
+               res |= ast_str_append(&message_string, 0, "PagesTransmitted: %d\r\n",
+                       stats.pages_tx);
+               res |= ast_str_append(&message_string, 0, "PagesReceived: %d\r\n",
+                       stats.pages_rx);
+#else
+               res |= ast_str_append(&message_string, 0, "PagesTransmitted: %d\r\n",
+                       (session->details->caps & AST_FAX_TECH_SEND) ? stats.pages_transferred : 0);
+               res |= ast_str_append(&message_string, 0, "PagesReceived: %d\r\n",
+                       (session->details->caps & AST_FAX_TECH_RECEIVE) ? stats.pages_transferred : 0);
+#endif
+               res |= ast_str_append(&message_string, 0, "TotalBadLines: %d\r\n",
+                       stats.bad_rows);
+       }
+
+skip_cap_additions:
+
+       ao2_unlock(session);
+
+       if (res < 0) {
+               /* One or more of the ast_str_append attempts failed, cancel the message */
+               ast_free(message_string);
+               return;
+       }
+
+       astman_append(s, "Event: FAXSession\r\n"
+               "%s"
+               "%s"
+               "\r\n",
+               id_text,
+               ast_str_buffer(message_string));
+
+       ast_free(message_string);
+}
+
 /*! \brief */
 static char *spandsp_fax_cli_show_stats(int fd)
 {
@@ -1016,6 +1263,8 @@ static int load_module(void)
 
 
 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Spandsp G.711 and T.38 FAX Technologies",
-               .load = load_module,
-               .unload = unload_module,
-              );
+       .support_level = AST_MODULE_SUPPORT_EXTENDED,
+       .load = load_module,
+       .unload = unload_module,
+       .enhances = "res_fax",
+);