/*! \brief Function which adds ICE attributes to a media stream */ static void add_ice_to_stream(struct ast_sip_session *session, struct ast_sip_session_media *session_media, pj_pool_t *pool, pjmedia_sdp_media *media) { struct ast_rtp_engine_ice *ice; struct ao2_container *candidates; const char *username, *password; pj_str_t stmp; pjmedia_sdp_attr *attr; struct ao2_iterator it_candidates; struct ast_rtp_engine_ice_candidate *candidate; if (!session->endpoint->media.rtp.ice_support || !(ice = ast_rtp_instance_get_ice(session_media->rtp)) || !(candidates = ice->get_local_candidates(session_media->rtp))) { return; } if ((username = ice->get_ufrag(session_media->rtp))) { attr = pjmedia_sdp_attr_create(pool, "ice-ufrag", pj_cstr(&stmp, username)); media->attr[media->attr_count++] = attr; } if ((password = ice->get_password(session_media->rtp))) { attr = pjmedia_sdp_attr_create(pool, "ice-pwd", pj_cstr(&stmp, password)); media->attr[media->attr_count++] = attr; } it_candidates = ao2_iterator_init(candidates, 0); for (; (candidate = ao2_iterator_next(&it_candidates)); ao2_ref(candidate, -1)) { struct ast_str *attr_candidate = ast_str_create(128); ast_str_set(&attr_candidate, -1, "%s %u %s %d %s ", candidate->foundation, candidate->id, candidate->transport, candidate->priority, ast_sockaddr_stringify_addr_remote(&candidate->address)); ast_str_append(&attr_candidate, -1, "%s typ ", ast_sockaddr_stringify_port(&candidate->address)); switch (candidate->type) { case AST_RTP_ICE_CANDIDATE_TYPE_HOST: ast_str_append(&attr_candidate, -1, "host"); break; case AST_RTP_ICE_CANDIDATE_TYPE_SRFLX: ast_str_append(&attr_candidate, -1, "srflx"); break; case AST_RTP_ICE_CANDIDATE_TYPE_RELAYED: ast_str_append(&attr_candidate, -1, "relay"); break; } if (!ast_sockaddr_isnull(&candidate->relay_address)) { ast_str_append(&attr_candidate, -1, " raddr %s rport", ast_sockaddr_stringify_addr_remote(&candidate->relay_address)); ast_str_append(&attr_candidate, -1, " %s", ast_sockaddr_stringify_port(&candidate->relay_address)); } attr = pjmedia_sdp_attr_create(pool, "candidate", pj_cstr(&stmp, ast_str_buffer(attr_candidate))); media->attr[media->attr_count++] = attr; ast_free(attr_candidate); } ao2_iterator_destroy(&it_candidates); ao2_ref(candidates, -1); }
void Sdp::addZrtpAttribute(pjmedia_sdp_media* media, std::string hash) { /* Format: ":version value" */ std::string val = "1.10 " + hash; pj_str_t value = { (char*)val.c_str(), val.size() }; pjmedia_sdp_attr *attr = pjmedia_sdp_attr_create(memPool_, "zrtp-hash", &value); if (pjmedia_sdp_media_add_attr(media, attr) != PJ_SUCCESS) throw SdpException("Could not add zrtp attribute to media"); }
void Sdp::addSdesAttribute(const std::vector<std::string>& crypto) { for (std::vector<std::string>::const_iterator iter = crypto.begin(); iter != crypto.end(); ++iter) { pj_str_t val = { (char*)(*iter).c_str(), (*iter).size() }; pjmedia_sdp_attr *attr = pjmedia_sdp_attr_create(memPool_, "crypto", &val); for (unsigned i = 0; i < localSession_->media_count; i++) if (pjmedia_sdp_media_add_attr(localSession_->media[i], attr) != PJ_SUCCESS) throw SdpException("Could not add sdes attribute to media"); } }
pjmedia_sdp_media *Sdp::setMediaDescriptorLine() { pjmedia_sdp_media *med = PJ_POOL_ZALLOC_T(memPool_, pjmedia_sdp_media); med->desc.media = pj_str((char*)"audio"); med->desc.port_count = 1; med->desc.port = localAudioPort_; // in case of sdes, media are tagged as "RTP/SAVP", RTP/AVP elsewhere med->desc.transport = pj_str(srtpCrypto_.empty() ? (char*)"RTP/AVP" : (char*)"RTP/SAVP"); med->desc.fmt_count = codec_list_.size(); for (unsigned i=0; i<med->desc.fmt_count; i++) { sfl::Codec *codec = codec_list_[i]; std::ostringstream result; result << (int)codec->getPayloadType(); pj_strdup2(memPool_, &med->desc.fmt[i], result.str().c_str()); // Add a rtpmap field for each codec // We could add one only for dynamic payloads because the codecs with static RTP payloads // are entirely defined in the RFC 3351, but if we want to add other attributes like an asymmetric // connection, the rtpmap attribute will be useful to specify for which codec it is applicable pjmedia_sdp_rtpmap rtpmap; rtpmap.pt = med->desc.fmt[i]; rtpmap.enc_name = pj_str((char*) codec->getMimeSubtype().c_str()); // G722 require G722/8000 media description even if it is 16000 codec if (codec->getPayloadType() == 9) { rtpmap.clock_rate = 8000; } else { rtpmap.clock_rate = codec->getClockRate(); } rtpmap.param.ptr = ((char* const)""); rtpmap.param.slen = 0; pjmedia_sdp_attr *attr; pjmedia_sdp_rtpmap_to_attr(memPool_, &rtpmap, &attr); med->attr[med->attr_count++] = attr; } med->attr[ med->attr_count++] = pjmedia_sdp_attr_create(memPool_, "sendrecv", NULL); if (!zrtpHelloHash_.empty()) addZrtpAttribute(med, zrtpHelloHash_); setTelephoneEventRtpmap(med); return med; }
static pjmedia_sdp_attr* generate_fmtp_attr(pj_pool_t *pool, struct ast_format *format, int rtp_code) { struct ast_str *fmtp0 = ast_str_alloca(256); pj_str_t fmtp1; pjmedia_sdp_attr *attr = NULL; char *tmp; ast_format_generate_sdp_fmtp(format, rtp_code, &fmtp0); if (ast_str_strlen(fmtp0)) { tmp = ast_str_buffer(fmtp0) + ast_str_strlen(fmtp0) - 1; /* remove any carriage return line feeds */ while (*tmp == '\r' || *tmp == '\n') --tmp; *++tmp = '\0'; /* ast...generate gives us everything, just need value */ tmp = strchr(ast_str_buffer(fmtp0), ':'); if (tmp && tmp + 1) { fmtp1 = pj_str(tmp + 1); } else { fmtp1 = pj_str(ast_str_buffer(fmtp0)); } attr = pjmedia_sdp_attr_create(pool, "fmtp", &fmtp1); } return attr; }
static int add_crypto_to_stream(struct ast_sip_session *session, struct ast_sip_session_media *session_media, pj_pool_t *pool, pjmedia_sdp_media *media) { pj_str_t stmp; pjmedia_sdp_attr *attr; enum ast_rtp_dtls_hash hash; const char *crypto_attribute; struct ast_rtp_engine_dtls *dtls; static const pj_str_t STR_NEW = { "new", 3 }; static const pj_str_t STR_EXISTING = { "existing", 8 }; static const pj_str_t STR_ACTIVE = { "active", 6 }; static const pj_str_t STR_PASSIVE = { "passive", 7 }; static const pj_str_t STR_ACTPASS = { "actpass", 7 }; static const pj_str_t STR_HOLDCONN = { "holdconn", 8 }; switch (session_media->encryption) { case AST_SIP_MEDIA_ENCRYPT_NONE: case AST_SIP_MEDIA_TRANSPORT_INVALID: break; case AST_SIP_MEDIA_ENCRYPT_SDES: if (!session_media->srtp) { session_media->srtp = ast_sdp_srtp_alloc(); if (!session_media->srtp) { return -1; } } crypto_attribute = ast_sdp_srtp_get_attrib(session_media->srtp, 0 /* DTLS running? No */, session->endpoint->media.rtp.srtp_tag_32 /* 32 byte tag length? */); if (!crypto_attribute) { /* No crypto attribute to add, bad news */ return -1; } attr = pjmedia_sdp_attr_create(pool, "crypto", pj_cstr(&stmp, crypto_attribute)); media->attr[media->attr_count++] = attr; break; case AST_SIP_MEDIA_ENCRYPT_DTLS: if (setup_dtls_srtp(session, session_media)) { return -1; } dtls = ast_rtp_instance_get_dtls(session_media->rtp); if (!dtls) { return -1; } switch (dtls->get_connection(session_media->rtp)) { case AST_RTP_DTLS_CONNECTION_NEW: attr = pjmedia_sdp_attr_create(pool, "connection", &STR_NEW); media->attr[media->attr_count++] = attr; break; case AST_RTP_DTLS_CONNECTION_EXISTING: attr = pjmedia_sdp_attr_create(pool, "connection", &STR_EXISTING); media->attr[media->attr_count++] = attr; break; default: break; } switch (dtls->get_setup(session_media->rtp)) { case AST_RTP_DTLS_SETUP_ACTIVE: attr = pjmedia_sdp_attr_create(pool, "setup", &STR_ACTIVE); media->attr[media->attr_count++] = attr; break; case AST_RTP_DTLS_SETUP_PASSIVE: attr = pjmedia_sdp_attr_create(pool, "setup", &STR_PASSIVE); media->attr[media->attr_count++] = attr; break; case AST_RTP_DTLS_SETUP_ACTPASS: attr = pjmedia_sdp_attr_create(pool, "setup", &STR_ACTPASS); media->attr[media->attr_count++] = attr; break; case AST_RTP_DTLS_SETUP_HOLDCONN: attr = pjmedia_sdp_attr_create(pool, "setup", &STR_HOLDCONN); media->attr[media->attr_count++] = attr; break; default: break; } hash = dtls->get_fingerprint_hash(session_media->rtp); crypto_attribute = dtls->get_fingerprint(session_media->rtp); if (crypto_attribute && (hash == AST_RTP_DTLS_HASH_SHA1 || hash == AST_RTP_DTLS_HASH_SHA256)) { RAII_VAR(struct ast_str *, fingerprint, ast_str_create(64), ast_free); if (!fingerprint) { return -1; } if (hash == AST_RTP_DTLS_HASH_SHA1) { ast_str_set(&fingerprint, 0, "SHA-1 %s", crypto_attribute); } else { ast_str_set(&fingerprint, 0, "SHA-256 %s", crypto_attribute); } attr = pjmedia_sdp_attr_create(pool, "fingerprint", pj_cstr(&stmp, ast_str_buffer(fingerprint))); media->attr[media->attr_count++] = attr; } break; }
static pj_status_t transport_encode_sdp(pjmedia_transport *tp, pj_pool_t *sdp_pool, pjmedia_sdp_session *sdp_local, const pjmedia_sdp_session *sdp_remote, unsigned media_index) { struct transport_srtp *srtp = (struct transport_srtp*) tp; pjmedia_sdp_media *m_rem, *m_loc; enum { MAXLEN = 512 }; char buffer[MAXLEN]; int buffer_len; pj_status_t status; pjmedia_sdp_attr *attr; pj_str_t attr_value; unsigned i, j; PJ_ASSERT_RETURN(tp && sdp_pool && sdp_local, PJ_EINVAL); srtp->offerer_side = sdp_remote == NULL; m_rem = sdp_remote ? sdp_remote->media[media_index] : NULL; m_loc = sdp_local->media[media_index]; /* Bypass if media transport is not RTP/AVP or RTP/SAVP */ if (pj_stricmp(&m_loc->desc.transport, &ID_RTP_AVP) != 0 && pj_stricmp(&m_loc->desc.transport, &ID_RTP_SAVP) != 0) goto BYPASS_SRTP; /* If the media is inactive, do nothing. */ if (pjmedia_sdp_media_find_attr(m_loc, &ID_INACTIVE, NULL) || (m_rem && pjmedia_sdp_media_find_attr(m_rem, &ID_INACTIVE, NULL))) goto BYPASS_SRTP; /* Check remote media transport & set local media transport * based on SRTP usage option. */ if (srtp->offerer_side) { /* Generate transport */ switch (srtp->setting.use) { case PJMEDIA_SRTP_DISABLED: goto BYPASS_SRTP; case PJMEDIA_SRTP_OPTIONAL: m_loc->desc.transport = (srtp->peer_use == PJMEDIA_SRTP_MANDATORY)? ID_RTP_SAVP : ID_RTP_AVP; break; case PJMEDIA_SRTP_MANDATORY: m_loc->desc.transport = ID_RTP_SAVP; break; } /* Generate crypto attribute if not yet */ if (pjmedia_sdp_media_find_attr(m_loc, &ID_CRYPTO, NULL) == NULL) { for (i=0; i<srtp->setting.crypto_count; ++i) { /* Offer crypto-suites based on setting. */ buffer_len = MAXLEN; status = generate_crypto_attr_value(srtp->pool, buffer, &buffer_len, &srtp->setting.crypto[i], i+1); if (status != PJ_SUCCESS) return status; /* If buffer_len==0, just skip the crypto attribute. */ if (buffer_len) { pj_strset(&attr_value, buffer, buffer_len); attr = pjmedia_sdp_attr_create(srtp->pool, ID_CRYPTO.ptr, &attr_value); m_loc->attr[m_loc->attr_count++] = attr; } } } } else { /* Answerer side */ pj_assert(sdp_remote && m_rem); /* Generate transport */ switch (srtp->setting.use) { case PJMEDIA_SRTP_DISABLED: if (pj_stricmp(&m_rem->desc.transport, &ID_RTP_SAVP) == 0) return PJMEDIA_SRTP_ESDPINTRANSPORT; goto BYPASS_SRTP; case PJMEDIA_SRTP_OPTIONAL: m_loc->desc.transport = m_rem->desc.transport; break; case PJMEDIA_SRTP_MANDATORY: if (pj_stricmp(&m_rem->desc.transport, &ID_RTP_SAVP) != 0) return PJMEDIA_SRTP_ESDPINTRANSPORT; m_loc->desc.transport = ID_RTP_SAVP; break; } /* Generate crypto attribute if not yet */ if (pjmedia_sdp_media_find_attr(m_loc, &ID_CRYPTO, NULL) == NULL) { pjmedia_srtp_crypto tmp_rx_crypto; pj_bool_t has_crypto_attr = PJ_FALSE; int matched_idx = -1; int chosen_tag = 0; int tags[64]; /* assume no more than 64 crypto attrs in a media */ unsigned cr_attr_count = 0; /* Find supported crypto-suite, get the tag, and assign policy_local */ for (i=0; i<m_rem->attr_count; ++i) { if (pj_stricmp(&m_rem->attr[i]->name, &ID_CRYPTO) != 0) continue; has_crypto_attr = PJ_TRUE; status = parse_attr_crypto(srtp->pool, m_rem->attr[i], &tmp_rx_crypto, &tags[cr_attr_count]); if (status != PJ_SUCCESS) return status; /* Check duplicated tag */ for (j=0; j<cr_attr_count; ++j) { if (tags[j] == tags[cr_attr_count]) { DEACTIVATE_MEDIA(sdp_pool, m_loc); return PJMEDIA_SRTP_ESDPDUPCRYPTOTAG; } } if (matched_idx == -1) { /* lets see if the crypto-suite offered is supported */ for (j=0; j<srtp->setting.crypto_count; ++j) if (pj_stricmp(&tmp_rx_crypto.name, &srtp->setting.crypto[j].name) == 0) { int cs_idx = get_crypto_idx(&tmp_rx_crypto.name); /* Force to use test key */ /* bad keys for snom: */ //char *hex_test_key = "58b29c5c8f42308120ce857e439f2d" // "7810a8b10ad0b1446be5470faea496"; //char *hex_test_key = "20a26aac7ba062d356ff52b61e3993" // "ccb78078f12c64db94b9c294927fd0"; //pj_str_t *test_key = &srtp->setting.crypto[j].key; //char *raw_test_key = pj_pool_zalloc(srtp->pool, 64); //hex_string_to_octet_string( // raw_test_key, // hex_test_key, // strlen(hex_test_key)); //pj_strset(test_key, raw_test_key, // crypto_suites[cs_idx].cipher_key_len); /* EO Force to use test key */ if (tmp_rx_crypto.key.slen != (int)crypto_suites[cs_idx].cipher_key_len) return PJMEDIA_SRTP_EINKEYLEN; srtp->rx_policy_neg = tmp_rx_crypto; chosen_tag = tags[cr_attr_count]; matched_idx = j; break; } } cr_attr_count++; } /* Check crypto negotiation result */ switch (srtp->setting.use) { case PJMEDIA_SRTP_DISABLED: pj_assert(!"Should never reach here"); break; case PJMEDIA_SRTP_OPTIONAL: /* bypass SRTP when no crypto-attr and remote uses RTP/AVP */ if (!has_crypto_attr && pj_stricmp(&m_rem->desc.transport, &ID_RTP_AVP) == 0) goto BYPASS_SRTP; /* bypass SRTP when nothing match and remote uses RTP/AVP */ else if (matched_idx == -1 && pj_stricmp(&m_rem->desc.transport, &ID_RTP_AVP) == 0) goto BYPASS_SRTP; break; case PJMEDIA_SRTP_MANDATORY: /* Do nothing, intentional */ break; } /* No crypto attr */ if (!has_crypto_attr) { DEACTIVATE_MEDIA(sdp_pool, m_loc); return PJMEDIA_SRTP_ESDPREQCRYPTO; } /* No crypto match */ if (matched_idx == -1) { DEACTIVATE_MEDIA(sdp_pool, m_loc); return PJMEDIA_SRTP_ENOTSUPCRYPTO; } /* we have to generate crypto answer, * with srtp->tx_policy_neg matched the offer * and rem_tag contains matched offer tag. */ buffer_len = MAXLEN; status = generate_crypto_attr_value(srtp->pool, buffer, &buffer_len, &srtp->setting.crypto[matched_idx], chosen_tag); if (status != PJ_SUCCESS) return status; srtp->tx_policy_neg = srtp->setting.crypto[matched_idx]; /* If buffer_len==0, just skip the crypto attribute. */ if (buffer_len) { pj_strset(&attr_value, buffer, buffer_len); attr = pjmedia_sdp_attr_create(sdp_pool, ID_CRYPTO.ptr, &attr_value); m_loc->attr[m_loc->attr_count++] = attr; } /* At this point, we get valid rx_policy_neg & tx_policy_neg. */ } } goto PROPAGATE_MEDIA_CREATE; BYPASS_SRTP: srtp->bypass_srtp = PJ_TRUE; PROPAGATE_MEDIA_CREATE: return pjmedia_transport_encode_sdp(srtp->member_tp, sdp_pool, sdp_local, sdp_remote, media_index); }
/* Update media direction based on peer's media direction */ static void update_media_direction(pj_pool_t *pool, const pjmedia_sdp_media *remote, pjmedia_sdp_media *local) { pjmedia_dir old_dir = PJMEDIA_DIR_ENCODING_DECODING, new_dir; /* Get the media direction of local SDP */ if (pjmedia_sdp_media_find_attr2(local, "sendonly", NULL)) old_dir = PJMEDIA_DIR_ENCODING; else if (pjmedia_sdp_media_find_attr2(local, "recvonly", NULL)) old_dir = PJMEDIA_DIR_DECODING; else if (pjmedia_sdp_media_find_attr2(local, "inactive", NULL)) old_dir = PJMEDIA_DIR_NONE; new_dir = old_dir; /* Adjust local media direction based on remote media direction */ if (pjmedia_sdp_media_find_attr2(remote, "inactive", NULL) != NULL) { /* If remote has "a=inactive", then local is inactive too */ new_dir = PJMEDIA_DIR_NONE; } else if(pjmedia_sdp_media_find_attr2(remote, "sendonly", NULL) != NULL) { /* If remote has "a=sendonly", then set local to "recvonly" if * it is currently "sendrecv". Otherwise if local is NOT "recvonly", * then set local direction to "inactive". */ switch (old_dir) { case PJMEDIA_DIR_ENCODING_DECODING: new_dir = PJMEDIA_DIR_DECODING; break; case PJMEDIA_DIR_DECODING: /* No change */ break; default: new_dir = PJMEDIA_DIR_NONE; break; } } else if(pjmedia_sdp_media_find_attr2(remote, "recvonly", NULL) != NULL) { /* If remote has "a=recvonly", then set local to "sendonly" if * it is currently "sendrecv". Otherwise if local is NOT "sendonly", * then set local direction to "inactive" */ switch (old_dir) { case PJMEDIA_DIR_ENCODING_DECODING: new_dir = PJMEDIA_DIR_ENCODING; break; case PJMEDIA_DIR_ENCODING: /* No change */ break; default: new_dir = PJMEDIA_DIR_NONE; break; } } else { /* Remote indicates "sendrecv" capability. No change to local * direction */ } if (new_dir != old_dir) { pjmedia_sdp_attr *a = NULL; remove_all_media_directions(local); switch (new_dir) { case PJMEDIA_DIR_NONE: a = pjmedia_sdp_attr_create(pool, "inactive", NULL); break; case PJMEDIA_DIR_ENCODING: a = pjmedia_sdp_attr_create(pool, "sendonly", NULL); break; case PJMEDIA_DIR_DECODING: a = pjmedia_sdp_attr_create(pool, "recvonly", NULL); break; default: /* sendrecv */ break; } if (a) { pjmedia_sdp_media_add_attr(local, a); } } }
/* * The encode_sdp() is called when we're about to send SDP to remote party, * either as SDP offer or as SDP answer. */ static pj_status_t transport_encode_sdp(pjmedia_transport *tp, pj_pool_t *sdp_pool, pjmedia_sdp_session *local_sdp, const pjmedia_sdp_session *rem_sdp, unsigned media_index) { struct tp_zrtp *zrtp = (struct tp_zrtp*)tp; int32_t numVersions, i; PJ_ASSERT_RETURN(tp, PJ_EINVAL); /* If "rem_sdp" is not NULL, it means we're encoding SDP answer. You may * do some more checking on the SDP's once again to make sure that * everything is okay before we send SDP. */ if (rem_sdp) { /* Do checking stuffs here.. */ } /* Add zrtp-hash attributes to both INVITE and 200 OK. */ numVersions = zrtp_getNumberSupportedVersions(zrtp->zrtpCtx); for (i = 0; i < numVersions; i++) { char *zrtp_hello_hash = zrtp_getHelloHash(zrtp->zrtpCtx, i); if (zrtp_hello_hash && *zrtp_hello_hash) { int zrtp_hello_hash_len = strlen(zrtp_hello_hash); pj_str_t *zrtp_hash_str = PJ_POOL_ALLOC_T(sdp_pool, pj_str_t); pjmedia_sdp_attr *zrtp_hash = NULL; zrtp_hash_str->ptr = zrtp_hello_hash; zrtp_hash_str->slen = zrtp_hello_hash_len; zrtp_hash = pjmedia_sdp_attr_create(sdp_pool, "zrtp-hash", zrtp_hash_str); if (zrtp_hash && pjmedia_sdp_attr_add(&local_sdp->media[media_index]->attr_count, local_sdp->media[media_index]->attr, zrtp_hash) == PJ_SUCCESS) { PJ_LOG(4, (THIS_FILE, "attribute added: a=zrtp-hash:%s", zrtp_hello_hash)); } else { PJ_LOG(4, (THIS_FILE, "error adding attribute: a=zrtp-hash:%s", zrtp_hello_hash)); } } } /* You may do anything to the local_sdp, e.g. adding new attributes, or * even modifying the SDP if you want. */ if (0) { /* Say we add a proprietary attribute here.. */ pjmedia_sdp_attr *my_attr; my_attr = PJ_POOL_ALLOC_T(sdp_pool, pjmedia_sdp_attr); pj_strdup2(sdp_pool, &my_attr->name, "X-zrtp"); pj_strdup2(sdp_pool, &my_attr->value, "some value"); pjmedia_sdp_attr_add(&local_sdp->media[media_index]->attr_count, local_sdp->media[media_index]->attr, my_attr); } /* And then pass the call to slave transport to let it encode its * information in the SDP. You may choose to call encode_sdp() to slave * first before adding your custom attributes if you want. */ return pjmedia_transport_encode_sdp(zrtp->slave_tp, sdp_pool, local_sdp, rem_sdp, media_index); }
/*! \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; }
/* Encode ICE information in SDP */ static pj_status_t encode_session_in_sdp(struct transport_ice *tp_ice, pj_pool_t *sdp_pool, pjmedia_sdp_session *sdp_local, unsigned media_index, unsigned comp_cnt, pj_bool_t restart_session) { enum { ATTR_BUF_LEN = 160, /* Max len of a=candidate attr */ RATTR_BUF_LEN= 160 /* Max len of a=remote-candidates attr */ }; pjmedia_sdp_media *m = sdp_local->media[media_index]; pj_str_t local_ufrag, local_pwd; pjmedia_sdp_attr *attr; pj_status_t status; /* Must have a session */ PJ_ASSERT_RETURN(pj_ice_strans_has_sess(tp_ice->ice_st), PJ_EBUG); /* Get ufrag and pwd from current session */ pj_ice_strans_get_ufrag_pwd(tp_ice->ice_st, &local_ufrag, &local_pwd, NULL, NULL); /* The listing of candidates depends on whether ICE has completed * or not. When ICE has completed: * * 9.1.2.2: Existing Media Streams with ICE Completed * The agent MUST include a candidate attributes for candidates * matching the default destination for each component of the * media stream, and MUST NOT include any other candidates. * * When ICE has not completed, we shall include all candidates. * * Except when we have detected that remote is offering to restart * the session, in this case we will answer with full ICE SDP and * new ufrag/pwd pair. */ if (!restart_session && pj_ice_strans_sess_is_complete(tp_ice->ice_st)) { const pj_ice_sess_check *check; char *attr_buf; pjmedia_sdp_conn *conn; pjmedia_sdp_attr *a_rtcp; pj_str_t rem_cand; unsigned comp; /* Encode ice-ufrag attribute */ attr = pjmedia_sdp_attr_create(sdp_pool, STR_ICE_UFRAG.ptr, &local_ufrag); pjmedia_sdp_attr_add(&m->attr_count, m->attr, attr); /* Encode ice-pwd attribute */ attr = pjmedia_sdp_attr_create(sdp_pool, STR_ICE_PWD.ptr, &local_pwd); pjmedia_sdp_attr_add(&m->attr_count, m->attr, attr); /* Prepare buffer */ attr_buf = (char*) pj_pool_alloc(sdp_pool, ATTR_BUF_LEN); rem_cand.ptr = (char*) pj_pool_alloc(sdp_pool, RATTR_BUF_LEN); rem_cand.slen = 0; /* 9.1.2.2: Existing Media Streams with ICE Completed * The default destination for media (i.e., the values of * the IP addresses and ports in the m and c line used for * that media stream) MUST be the local candidate from the * highest priority nominated pair in the valid list for each * component. */ check = pj_ice_strans_get_valid_pair(tp_ice->ice_st, 1); if (check == NULL) { pj_assert(!"Shouldn't happen"); return PJ_EBUG; } /* Override connection line address and media port number */ conn = m->conn; if (conn == NULL) conn = sdp_local->conn; conn->addr.ptr = (char*) pj_pool_alloc(sdp_pool, PJ_INET6_ADDRSTRLEN); pj_sockaddr_print(&check->lcand->addr, conn->addr.ptr, PJ_INET6_ADDRSTRLEN, 0); conn->addr.slen = pj_ansi_strlen(conn->addr.ptr); m->desc.port = pj_sockaddr_get_port(&check->lcand->addr); /* Override address RTCP attribute if it's present */ if (comp_cnt == 2 && (check = pj_ice_strans_get_valid_pair(tp_ice->ice_st, COMP_RTCP)) != NULL && (a_rtcp = pjmedia_sdp_attr_find(m->attr_count, m->attr, &STR_RTCP, 0)) != NULL) { pjmedia_sdp_attr_remove(&m->attr_count, m->attr, a_rtcp); a_rtcp = pjmedia_sdp_attr_create_rtcp(sdp_pool, &check->lcand->addr); if (a_rtcp) pjmedia_sdp_attr_add(&m->attr_count, m->attr, a_rtcp); } /* Encode only candidates matching the default destination * for each component */ for (comp=0; comp < comp_cnt; ++comp) { int len; pj_str_t value; /* Get valid pair for this component */ check = pj_ice_strans_get_valid_pair(tp_ice->ice_st, comp+1); if (check == NULL) continue; /* Print and add local candidate in the pair */ value.ptr = attr_buf; value.slen = print_sdp_cand_attr(attr_buf, ATTR_BUF_LEN, check->lcand); if (value.slen < 0) { pj_assert(!"Not enough attr_buf to print candidate"); return PJ_EBUG; } attr = pjmedia_sdp_attr_create(sdp_pool, STR_CANDIDATE.ptr, &value); pjmedia_sdp_attr_add(&m->attr_count, m->attr, attr); /* Append to a=remote-candidates attribute */ if (pj_ice_strans_get_role(tp_ice->ice_st) == PJ_ICE_SESS_ROLE_CONTROLLING) { char rem_addr[PJ_INET6_ADDRSTRLEN]; pj_sockaddr_print(&check->rcand->addr, rem_addr, sizeof(rem_addr), 0); len = pj_ansi_snprintf( rem_cand.ptr + rem_cand.slen, RATTR_BUF_LEN - rem_cand.slen, "%s%u %s %u", (rem_cand.slen==0? "" : " "), comp+1, rem_addr, pj_sockaddr_get_port(&check->rcand->addr) ); if (len < 1 || len >= RATTR_BUF_LEN) { pj_assert(!"Not enough buffer to print " "remote-candidates"); return PJ_EBUG; } rem_cand.slen += len; } } /* 9.1.2.2: Existing Media Streams with ICE Completed * In addition, if the agent is controlling, it MUST include * the a=remote-candidates attribute for each media stream * whose check list is in the Completed state. The attribute * contains the remote candidates from the highest priority * nominated pair in the valid list for each component of that * media stream. */ if (pj_ice_strans_get_role(tp_ice->ice_st) == PJ_ICE_SESS_ROLE_CONTROLLING) { attr = pjmedia_sdp_attr_create(sdp_pool, STR_REM_CAND.ptr, &rem_cand); pjmedia_sdp_attr_add(&m->attr_count, m->attr, attr); } } else if (pj_ice_strans_has_sess(tp_ice->ice_st)) { /* Encode all candidates to SDP media */ char *attr_buf; unsigned comp; /* If ICE is not restarted, encode current ICE ufrag/pwd. * Otherwise generate new one. */ if (!restart_session) { attr = pjmedia_sdp_attr_create(sdp_pool, STR_ICE_UFRAG.ptr, &local_ufrag); pjmedia_sdp_attr_add(&m->attr_count, m->attr, attr); attr = pjmedia_sdp_attr_create(sdp_pool, STR_ICE_PWD.ptr, &local_pwd); pjmedia_sdp_attr_add(&m->attr_count, m->attr, attr); } else { pj_str_t str; str.slen = PJ_ICE_UFRAG_LEN; str.ptr = (char*) pj_pool_alloc(sdp_pool, str.slen); pj_create_random_string(str.ptr, str.slen); attr = pjmedia_sdp_attr_create(sdp_pool, STR_ICE_UFRAG.ptr, &str); pjmedia_sdp_attr_add(&m->attr_count, m->attr, attr); str.ptr = (char*) pj_pool_alloc(sdp_pool, str.slen); pj_create_random_string(str.ptr, str.slen); attr = pjmedia_sdp_attr_create(sdp_pool, STR_ICE_PWD.ptr, &str); pjmedia_sdp_attr_add(&m->attr_count, m->attr, attr); } /* Create buffer to encode candidates as SDP attribute */ attr_buf = (char*) pj_pool_alloc(sdp_pool, ATTR_BUF_LEN); for (comp=0; comp < comp_cnt; ++comp) { unsigned cand_cnt; pj_ice_sess_cand cand[PJ_ICE_ST_MAX_CAND]; unsigned i; cand_cnt = PJ_ARRAY_SIZE(cand); status = pj_ice_strans_enum_cands(tp_ice->ice_st, comp+1, &cand_cnt, cand); if (status != PJ_SUCCESS) return status; for (i=0; i<cand_cnt; ++i) { pj_str_t value; value.slen = print_sdp_cand_attr(attr_buf, ATTR_BUF_LEN, &cand[i]); if (value.slen < 0) { pj_assert(!"Not enough attr_buf to print candidate"); return PJ_EBUG; } value.ptr = attr_buf; attr = pjmedia_sdp_attr_create(sdp_pool, STR_CANDIDATE.ptr, &value); pjmedia_sdp_attr_add(&m->attr_count, m->attr, attr); } } } else { /* ICE has failed, application should have terminated this call */ } /* Removing a=rtcp line when there is only one component. */ if (comp_cnt == 1) { attr = pjmedia_sdp_attr_find(m->attr_count, m->attr, &STR_RTCP, NULL); if (attr) pjmedia_sdp_attr_remove(&m->attr_count, m->attr, attr); } return PJ_SUCCESS; }
void Sdp::addAttributeToLocalAudioMedia(const char *attr) { if (localSession_) pjmedia_sdp_media_add_attr(localSession_->media[0], pjmedia_sdp_attr_create(memPool_, attr, NULL)); }