/*! \brief Function which applies a negotiated stream */ static int apply_negotiated_sdp_stream(struct ast_sip_session *session, struct ast_sip_session_media *session_media, const struct pjmedia_sdp_session *local, const struct pjmedia_sdp_media *local_stream, const struct pjmedia_sdp_session *remote, const struct pjmedia_sdp_media *remote_stream) { RAII_VAR(struct ast_sockaddr *, addrs, NULL, ast_free); char host[NI_MAXHOST]; struct t38_state *state; if (!session_media->udptl) { return 0; } if (!(state = t38_state_get_or_alloc(session))) { return -1; } ast_copy_pj_str(host, remote_stream->conn ? &remote_stream->conn->addr : &remote->conn->addr, sizeof(host)); /* Ensure that the address provided is valid */ if (ast_sockaddr_resolve(&addrs, host, PARSE_PORT_FORBID, AST_AF_UNSPEC) <= 0) { /* The provided host was actually invalid so we error out this negotiation */ return -1; } ast_sockaddr_set_port(addrs, remote_stream->desc.port); ast_udptl_set_peer(session_media->udptl, addrs); t38_interpret_sdp(state, session, session_media, remote_stream); return 0; }
/*! \brief Function which defers an incoming media stream */ static enum ast_sip_session_sdp_stream_defer defer_incoming_sdp_stream( struct ast_sip_session *session, struct ast_sip_session_media *session_media, const struct pjmedia_sdp_session *sdp, const struct pjmedia_sdp_media *stream) { struct t38_state *state; if (!session->endpoint->media.t38.enabled) { return AST_SIP_SESSION_SDP_DEFER_NOT_HANDLED; } if (t38_initialize_session(session, session_media)) { return AST_SIP_SESSION_SDP_DEFER_ERROR; } if (!(state = t38_state_get_or_alloc(session))) { return AST_SIP_SESSION_SDP_DEFER_ERROR; } t38_interpret_sdp(state, session, session_media, stream); /* If they are initiating the re-invite we need to defer responding until later */ if (session->t38state == T38_DISABLED) { t38_change_state(session, session_media, state, T38_PEER_REINVITE); return AST_SIP_SESSION_SDP_DEFER_NEEDED; } return AST_SIP_SESSION_SDP_DEFER_NOT_NEEDED; }
/*! \brief Function which negotiates an incoming media stream */ static int negotiate_incoming_sdp_stream(struct ast_sip_session *session, struct ast_sip_session_media *session_media, const struct pjmedia_sdp_session *sdp, const struct pjmedia_sdp_media *stream) { struct t38_state *state; char host[NI_MAXHOST]; RAII_VAR(struct ast_sockaddr *, addrs, NULL, ast_free); if (!session->endpoint->media.t38.enabled) { ast_debug(3, "Declining; T.38 not enabled on session\n"); return -1; } if (!(state = t38_state_get_or_alloc(session))) { return -1; } if ((session->t38state == T38_REJECTED) || (session->t38state == T38_DISABLED)) { ast_debug(3, "Declining; T.38 state is rejected or declined\n"); t38_change_state(session, session_media, state, T38_DISABLED); return -1; } ast_copy_pj_str(host, stream->conn ? &stream->conn->addr : &sdp->conn->addr, sizeof(host)); /* Ensure that the address provided is valid */ if (ast_sockaddr_resolve(&addrs, host, PARSE_PORT_FORBID, AST_AF_INET) <= 0) { /* The provided host was actually invalid so we error out this negotiation */ ast_debug(3, "Declining; provided host is invalid\n"); return -1; } /* Check the address family to make sure it matches configured */ if ((ast_sockaddr_is_ipv6(addrs) && !session->endpoint->media.t38.ipv6) || (ast_sockaddr_is_ipv4(addrs) && session->endpoint->media.t38.ipv6)) { /* The address does not match configured */ ast_debug(3, "Declining, provided host does not match configured address family\n"); return -1; } return 1; }
/*! \brief Callback for when a response is received for a T.38 re-invite */ static int t38_reinvite_response_cb(struct ast_sip_session *session, pjsip_rx_data *rdata) { struct pjsip_status_line status = rdata->msg_info.msg->line.status; struct t38_state *state; RAII_VAR(struct ast_sip_session_media *, session_media, NULL, ao2_cleanup); if (status.code == 100) { return 0; } if (!(state = t38_state_get_or_alloc(session)) || !(session_media = ao2_find(session->media, "image", OBJ_KEY))) { ast_log(LOG_WARNING, "Received response to T.38 re-invite on '%s' but state unavailable\n", ast_channel_name(session->channel)); return 0; } t38_change_state(session, session_media, state, (status.code == 200) ? T38_ENABLED : T38_REJECTED); return 0; }
/*! \brief Function which creates an outgoing stream */ static int create_outgoing_sdp_stream(struct ast_sip_session *session, struct ast_sip_session_media *session_media, struct pjmedia_sdp_session *sdp) { pj_pool_t *pool = session->inv_session->pool_prov; static const pj_str_t STR_IN = { "IN", 2 }; static const pj_str_t STR_IP4 = { "IP4", 3}; static const pj_str_t STR_IP6 = { "IP6", 3}; static const pj_str_t STR_UDPTL = { "udptl", 5 }; static const pj_str_t STR_T38 = { "t38", 3 }; static const pj_str_t STR_TRANSFERREDTCF = { "transferredTCF", 14 }; static const pj_str_t STR_LOCALTCF = { "localTCF", 8 }; static const pj_str_t STR_T38UDPFEC = { "t38UDPFEC", 9 }; static const pj_str_t STR_T38UDPREDUNDANCY = { "t38UDPRedundancy", 16 }; struct t38_state *state; pjmedia_sdp_media *media; const char *hostip = NULL; struct ast_sockaddr addr; char tmp[512]; pj_str_t stmp; if (!session->endpoint->media.t38.enabled) { return 1; } else if ((session->t38state != T38_LOCAL_REINVITE) && (session->t38state != T38_PEER_REINVITE) && (session->t38state != T38_ENABLED)) { return 1; } else if (!(state = t38_state_get_or_alloc(session))) { return -1; } else if (t38_initialize_session(session, session_media)) { return -1; } if (!(media = pj_pool_zalloc(pool, sizeof(struct pjmedia_sdp_media))) || !(media->conn = pj_pool_zalloc(pool, sizeof(struct pjmedia_sdp_conn)))) { return -1; } media->desc.media = pj_str(session_media->stream_type); media->desc.transport = STR_UDPTL; if (ast_strlen_zero(session->endpoint->media.address)) { hostip = ast_sip_get_host_ip_string(session->endpoint->media.t38.ipv6 ? pj_AF_INET6() : pj_AF_INET()); } else { hostip = session->endpoint->media.address; } if (ast_strlen_zero(hostip)) { return -1; } media->conn->net_type = STR_IN; media->conn->addr_type = session->endpoint->media.t38.ipv6 ? STR_IP6 : STR_IP4; pj_strdup2(pool, &media->conn->addr, hostip); ast_udptl_get_us(session_media->udptl, &addr); media->desc.port = (pj_uint16_t) ast_sockaddr_port(&addr); media->desc.port_count = 1; media->desc.fmt[media->desc.fmt_count++] = STR_T38; snprintf(tmp, sizeof(tmp), "%u", state->our_parms.version); media->attr[media->attr_count++] = pjmedia_sdp_attr_create(pool, "T38FaxVersion", pj_cstr(&stmp, tmp)); snprintf(tmp, sizeof(tmp), "%u", t38_get_rate(state->our_parms.rate)); media->attr[media->attr_count++] = pjmedia_sdp_attr_create(pool, "T38MaxBitRate", pj_cstr(&stmp, tmp)); if (state->our_parms.fill_bit_removal) { media->attr[media->attr_count++] = pjmedia_sdp_attr_create(pool, "T38FaxFillBitRemoval", NULL); } if (state->our_parms.transcoding_mmr) { media->attr[media->attr_count++] = pjmedia_sdp_attr_create(pool, "T38FaxTranscodingMMR", NULL); } if (state->our_parms.transcoding_jbig) { media->attr[media->attr_count++] = pjmedia_sdp_attr_create(pool, "T38FaxTranscodingJBIG", NULL); } switch (state->our_parms.rate_management) { case AST_T38_RATE_MANAGEMENT_TRANSFERRED_TCF: media->attr[media->attr_count++] = pjmedia_sdp_attr_create(pool, "T38FaxRateManagement", &STR_TRANSFERREDTCF); break; case AST_T38_RATE_MANAGEMENT_LOCAL_TCF: media->attr[media->attr_count++] = pjmedia_sdp_attr_create(pool, "T38FaxRateManagement", &STR_LOCALTCF); break; } snprintf(tmp, sizeof(tmp), "%u", ast_udptl_get_local_max_datagram(session_media->udptl)); media->attr[media->attr_count++] = pjmedia_sdp_attr_create(pool, "T38FaxMaxDatagram", pj_cstr(&stmp, tmp)); switch (ast_udptl_get_error_correction_scheme(session_media->udptl)) { case UDPTL_ERROR_CORRECTION_NONE: break; case UDPTL_ERROR_CORRECTION_FEC: media->attr[media->attr_count++] = pjmedia_sdp_attr_create(pool, "T38FaxUdpEC", &STR_T38UDPFEC); break; case UDPTL_ERROR_CORRECTION_REDUNDANCY: media->attr[media->attr_count++] = pjmedia_sdp_attr_create(pool, "T38FaxUdpEC", &STR_T38UDPREDUNDANCY); break; } sdp->media[sdp->media_count++] = media; return 1; }
/*! \brief Task for reacting to T.38 control frame */ static int t38_interpret_parameters(void *obj) { RAII_VAR(struct t38_parameters_task_data *, data, obj, ao2_cleanup); const struct ast_control_t38_parameters *parameters = data->frame->data.ptr; struct t38_state *state = t38_state_get_or_alloc(data->session); RAII_VAR(struct ast_sip_session_media *, session_media, ao2_find(data->session->media, "image", OBJ_KEY), ao2_cleanup); /* Without session media or state we can't interpret parameters */ if (!session_media || !state) { return 0; } switch (parameters->request_response) { case AST_T38_NEGOTIATED: case AST_T38_REQUEST_NEGOTIATE: /* Request T38 */ /* Negotiation can not take place without a valid max_ifp value. */ if (!parameters->max_ifp) { if (data->session->t38state == T38_PEER_REINVITE) { t38_change_state(data->session, session_media, state, T38_REJECTED); ast_sip_session_resume_reinvite(data->session); } else if (data->session->t38state == T38_ENABLED) { t38_change_state(data->session, session_media, state, T38_DISABLED); ast_sip_session_refresh(data->session, NULL, NULL, NULL, AST_SIP_SESSION_REFRESH_METHOD_INVITE, 1); } break; } else if (data->session->t38state == T38_PEER_REINVITE) { state->our_parms = *parameters; /* modify our parameters to conform to the peer's parameters, * based on the rules in the ITU T.38 recommendation */ if (!state->their_parms.fill_bit_removal) { state->our_parms.fill_bit_removal = 0; } if (!state->their_parms.transcoding_mmr) { state->our_parms.transcoding_mmr = 0; } if (!state->their_parms.transcoding_jbig) { state->our_parms.transcoding_jbig = 0; } state->our_parms.version = MIN(state->our_parms.version, state->their_parms.version); state->our_parms.rate_management = state->their_parms.rate_management; ast_udptl_set_local_max_ifp(session_media->udptl, state->our_parms.max_ifp); t38_change_state(data->session, session_media, state, T38_ENABLED); ast_sip_session_resume_reinvite(data->session); } else if (data->session->t38state != T38_ENABLED) { if (t38_initialize_session(data->session, session_media)) { break; } state->our_parms = *parameters; ast_udptl_set_local_max_ifp(session_media->udptl, state->our_parms.max_ifp); t38_change_state(data->session, session_media, state, T38_LOCAL_REINVITE); ast_sip_session_refresh(data->session, NULL, t38_reinvite_sdp_cb, t38_reinvite_response_cb, AST_SIP_SESSION_REFRESH_METHOD_INVITE, 1); } break; case AST_T38_TERMINATED: case AST_T38_REFUSED: case AST_T38_REQUEST_TERMINATE: /* Shutdown T38 */ if (data->session->t38state == T38_PEER_REINVITE) { t38_change_state(data->session, session_media, state, T38_REJECTED); ast_sip_session_resume_reinvite(data->session); } else if (data->session->t38state == T38_ENABLED) { t38_change_state(data->session, session_media, state, T38_DISABLED); ast_sip_session_refresh(data->session, NULL, NULL, NULL, AST_SIP_SESSION_REFRESH_METHOD_INVITE, 1); } break; case AST_T38_REQUEST_PARMS: { /* Application wants remote's parameters re-sent */ struct ast_control_t38_parameters parameters = state->their_parms; if (data->session->t38state == T38_PEER_REINVITE) { parameters.max_ifp = ast_udptl_get_far_max_ifp(session_media->udptl); parameters.request_response = AST_T38_REQUEST_NEGOTIATE; ast_queue_control_data(data->session->channel, AST_CONTROL_T38_PARAMETERS, ¶meters, sizeof(parameters)); } break; } default: break; } return 0; }