diff --git a/trunk/configure b/trunk/configure index d35e13b802d..0af21a8615e 100755 --- a/trunk/configure +++ b/trunk/configure @@ -154,6 +154,11 @@ if [[ $SRS_RTC == YES ]]; then LibSrtpRoot="${SRS_OBJS}/srtp2/include"; LibSrtpFile="${SRS_OBJS}/srtp2/lib/libsrtp2.a" fi +# sctp +if [[ $SRS_SCTP == YES ]]; then + LibSctpRoot="${SRS_OBJS}/sctp/include"; LibSctpFile="${SRS_OBJS}/sctp/lib/libusrsctp.a" +fi + # FFMPEG for WebRTC transcoding, such as aac to opus. if [[ $SRS_FFMPEG_FIT == YES ]]; then LibFfmpegRoot="${SRS_OBJS}/ffmpeg/include" @@ -289,6 +294,9 @@ fi if [[ $SRS_RTC == YES ]]; then ModuleLibIncs+=(${LibSrtpRoot}) fi +if [[ $SRS_SCTP == YES ]]; then + ModuleLibIncs+=(${LibSctpRoot}) +fi if [[ $SRS_FFMPEG_FIT == YES ]]; then ModuleLibIncs+=("${LibFfmpegRoot[*]}") fi @@ -319,6 +327,9 @@ fi if [[ $SRS_GB28181 == YES ]]; then MODULE_FILES+=("srs_app_gb28181") fi +if [[ $SRS_SCTP == YES ]]; then + MODULE_FILES+=("srs_app_sctp") +fi DEFINES="" # add each modules for app @@ -340,6 +351,9 @@ fi if [[ $SRS_FFMPEG_FIT == YES ]]; then ModuleLibIncs+=("${LibFfmpegRoot[*]}") fi +if [[ $SRS_SCTP == YES ]]; then + ModuleLibIncs+=(${LibSctpRoot}) +fi MODULE_FILES=("srs_main_server") SERVER_INCS="src/main"; MODULE_DIR=${SERVER_INCS} . $SRS_WORKDIR/auto/modules.sh SERVER_OBJS="${MODULE_OBJS[@]}" @@ -354,6 +368,9 @@ fi if [[ $SRS_FFMPEG_FIT == YES ]]; then ModuleLibIncs+=("${LibFfmpegRoot[*]}") fi +if [[ $SRS_SCTP == YES ]]; then + ModuleLibIncs+=(${LibSctpRoot}) +fi MODULE_FILES=() DEFINES="" # add each modules for main @@ -384,6 +401,9 @@ fi if [[ $SRS_FFMPEG_FIT == YES ]]; then ModuleLibFiles+=("${LibFfmpegFile[*]}") fi +if [[ $SRS_SCTP == YES ]]; then + ModuleLibFiles+=(${LibSctpFile}) +fi if [[ $SRS_SRT == YES ]]; then ModuleLibFiles+=("${LibSRTfile[*]}") fi @@ -396,6 +416,9 @@ fi if [[ $SRS_FFMPEG_FIT == YES ]]; then ModuleLibIncs+=("${LibFfmpegRoot[*]}") fi +if [[ $SRS_SCTP == YES ]]; then + ModuleLibIncs+=(${LibSctpRoot}) +fi if [[ $SRS_SRT == YES ]]; then ModuleLibIncs+=(${LibSRTRoot}) MODULE_OBJS="${MODULE_OBJS} ${SRT_OBJS[@]}" @@ -449,6 +472,9 @@ if [[ $SRS_UTEST == YES ]]; then if [[ $SRS_FFMPEG_FIT == YES ]]; then ModuleLibIncs+=("${LibFfmpegRoot[*]}") fi + if [[ $SRS_SCTP == YES ]]; then + ModuleLibIncs+=(${LibSctpRoot}) + fi if [[ $SRS_SRT == YES ]]; then ModuleLibIncs+=("${LibSRTRoot[*]}") fi @@ -459,6 +485,9 @@ if [[ $SRS_UTEST == YES ]]; then if [[ $SRS_FFMPEG_FIT == YES ]]; then ModuleLibFiles+=("${LibFfmpegFile[*]}") fi + if [[ $SRS_SCTP == YES ]]; then + ModuleLibFiles+=(${LibSctpFile}) + fi if [[ $SRS_SRT == YES ]]; then ModuleLibFiles+=("${LibSRTfile[*]}") fi diff --git a/trunk/src/app/srs_app_rtc_api.cpp b/trunk/src/app/srs_app_rtc_api.cpp index 16bdd6d07a7..9e5bd1f69d5 100644 --- a/trunk/src/app/srs_app_rtc_api.cpp +++ b/trunk/src/app/srs_app_rtc_api.cpp @@ -263,12 +263,12 @@ srs_error_t SrsGoApiRtcPlay::check_remote_sdp(const SrsSdp& remote_sdp) } for (std::vector::const_iterator iter = remote_sdp.media_descs_.begin(); iter != remote_sdp.media_descs_.end(); ++iter) { - if (iter->type_ != "audio" && iter->type_ != "video") { + if (iter->type_ != "audio" && iter->type_ != "video" && iter->type_ != "application") { return srs_error_new(ERROR_RTC_SDP_EXCHANGE, "unsupport media type=%s", iter->type_.c_str()); } - if (! iter->rtcp_mux_) { - return srs_error_new(ERROR_RTC_SDP_EXCHANGE, "now only suppor rtcp-mux"); + if (!iter->rtcp_mux_ && iter->type_ != "application") { + return srs_error_new(ERROR_RTC_SDP_EXCHANGE, "now media only support rtcp-mux"); } if (iter->sendonly_) { @@ -312,6 +312,260 @@ srs_error_t SrsGoApiRtcPlay::http_hooks_on_play(SrsRequest* req) return err; } +#ifdef SRS_SCTP + +SrsGoApiRtcDataChannel::SrsGoApiRtcDataChannel(SrsRtcServer* server) +{ + server_ = server; +} + +SrsGoApiRtcDataChannel::~SrsGoApiRtcDataChannel() +{ +} + + +// Request: +// POST /rtc/v1/data/ +// { +// "sdp":"offer...", "streamurl":"webrtc://r.ossrs.net/live/livestream", +// "api":'http...", "clientip":"..." +// } +// Response: +// {"sdp":"answer...", "sid":"..."} +// @see https://github.com/rtcdn/rtcdn-draft +srs_error_t SrsGoApiRtcDataChannel::serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessage* r) +{ + srs_error_t err = srs_success; + + SrsJsonObject* res = SrsJsonAny::object(); + SrsAutoFree(SrsJsonObject, res); + + if ((err = do_serve_http(w, r, res)) != srs_success) { + srs_warn("RTC error %s", srs_error_desc(err).c_str()); srs_freep(err); + return srs_api_response_code(w, r, SRS_CONSTS_HTTP_BadRequest); + } + + return srs_api_response(w, r, res->dumps()); +} + +srs_error_t SrsGoApiRtcDataChannel::do_serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessage* r, SrsJsonObject* res) +{ + srs_error_t err = srs_success; + + // For each RTC session, we use short-term HTTP connection. + SrsHttpHeader* hdr = w->header(); + hdr->set("Connection", "Close"); + + // Parse req, the request json object, from body. + SrsJsonObject* req = NULL; + SrsAutoFree(SrsJsonObject, req); + if (true) { + string req_json; + if ((err = r->body_read_all(req_json)) != srs_success) { + return srs_error_wrap(err, "read body"); + } + + SrsJsonAny* json = SrsJsonAny::loads(req_json); + if (!json || !json->is_object()) { + return srs_error_new(ERROR_RTC_API_BODY, "invalid body %s", req_json.c_str()); + } + + req = json->to_object(); + } + + // Fetch params from req object. + SrsJsonAny* prop = NULL; + if ((prop = req->ensure_property_string("sdp")) == NULL) { + return srs_error_wrap(err, "not sdp"); + } + string remote_sdp_str = prop->to_str(); + + if ((prop = req->ensure_property_string("streamurl")) == NULL) { + return srs_error_wrap(err, "not streamurl"); + } + string streamurl = prop->to_str(); + + string clientip; + if ((prop = req->ensure_property_string("clientip")) != NULL) { + clientip = prop->to_str(); + } + + string api; + if ((prop = req->ensure_property_string("api")) != NULL) { + api = prop->to_str(); + } + + // The RTC user config object. + SrsRtcUserConfig ruc; + ruc.req_->ip = clientip; + + srs_parse_rtmp_url(streamurl, ruc.req_->tcUrl, ruc.req_->stream); + + srs_discovery_tc_url(ruc.req_->tcUrl, ruc.req_->schema, ruc.req_->host, ruc.req_->vhost, + ruc.req_->app, ruc.req_->stream, ruc.req_->port, ruc.req_->param); + + // discovery vhost, resolve the vhost from config + SrsConfDirective* parsed_vhost = _srs_config->get_vhost(ruc.req_->vhost); + if (parsed_vhost) { + ruc.req_->vhost = parsed_vhost->arg0(); + } + + // For client to specifies the EIP of server. + string eip = r->query_get("eip"); + // For client to specifies whether encrypt by SRTP. + string srtp = r->query_get("encrypt"); + string dtls = r->query_get("dtls"); + + srs_trace("RTC data %s, api=%s, clientip=%s, app=%s, stream=%s, offer=%dB, eip=%s, srtp=%s, dtls=%s", + streamurl.c_str(), api.c_str(), clientip.c_str(), ruc.req_->app.c_str(), ruc.req_->stream.c_str(), remote_sdp_str.length(), eip.c_str(), + srtp.c_str(), dtls.c_str()); + + ruc.eip_ = eip; + ruc.publish_ = false; + ruc.dtls_ = (dtls != "false"); + + if (srtp.empty()) { + ruc.srtp_ = _srs_config->get_rtc_server_encrypt(); + } else { + ruc.srtp_ = (srtp != "false"); + } + + // TODO: FIXME: It seems remote_sdp doesn't represents the full SDP information. + if ((err = ruc.remote_sdp_.parse(remote_sdp_str)) != srs_success) { + return srs_error_wrap(err, "parse sdp failed: %s", remote_sdp_str.c_str()); + } + + if ((err = check_remote_sdp(ruc.remote_sdp_)) != srs_success) { + return srs_error_wrap(err, "remote sdp check failed"); + } + + SrsSdp local_sdp; + + // Config for SDP and session. + local_sdp.session_config_.dtls_role = _srs_config->get_rtc_dtls_role(ruc.req_->vhost); + local_sdp.session_config_.dtls_version = _srs_config->get_rtc_dtls_version(ruc.req_->vhost); + + if ((err = exchange_sdp(ruc.req_, ruc.remote_sdp_, local_sdp)) != srs_success) { + return srs_error_wrap(err, "remote sdp have error or unsupport attributes"); + } + + // Whether enabled. + bool server_enabled = _srs_config->get_rtc_server_enabled(); + bool rtc_enabled = _srs_config->get_rtc_enabled(ruc.req_->vhost); + if (server_enabled && !rtc_enabled) { + srs_warn("RTC disabled in vhost %s", ruc.req_->vhost.c_str()); + } + if (!server_enabled || !rtc_enabled) { + return srs_error_new(ERROR_RTC_DISABLED, "Disabled server=%d, rtc=%d, vhost=%s", + server_enabled, rtc_enabled, ruc.req_->vhost.c_str()); + } + + // TODO: FIXME: When server enabled, but vhost disabled, should report error. + SrsRtcConnection* session = NULL; + if ((err = server_->create_session(&ruc, local_sdp, &session)) != srs_success) { + return srs_error_wrap(err, "create session"); + } + + ostringstream os; + if ((err = local_sdp.encode(os)) != srs_success) { + return srs_error_wrap(err, "encode sdp"); + } + + string local_sdp_str = os.str(); + + srs_verbose("local_sdp=%s", local_sdp_str.c_str()); + + res->set("code", SrsJsonAny::integer(ERROR_SUCCESS)); + res->set("server", SrsJsonAny::str(SrsStatistic::instance()->server_id().c_str())); + + // TODO: add candidates in response json? + + res->set("sdp", SrsJsonAny::str(local_sdp_str.c_str())); + res->set("sessionid", SrsJsonAny::str(session->username().c_str())); + + srs_trace("RTC data username=%s, offer=%dB, answer=%dB", session->username().c_str(), + remote_sdp_str.length(), local_sdp_str.length()); + + return err; +} + +srs_error_t SrsGoApiRtcDataChannel::check_remote_sdp(const SrsSdp& remote_sdp) +{ + srs_error_t err = srs_success; + + if (remote_sdp.group_policy_ != "BUNDLE") { + return srs_error_new(ERROR_RTC_SDP_EXCHANGE, "now only support BUNDLE, group policy=%s", remote_sdp.group_policy_.c_str()); + } + + if (remote_sdp.media_descs_.empty()) { + return srs_error_new(ERROR_RTC_SDP_EXCHANGE, "no media descriptions"); + } + + for (std::vector::const_iterator iter = remote_sdp.media_descs_.begin(); iter != remote_sdp.media_descs_.end(); ++iter) { + if (iter->type_ != "application") { + return srs_error_new(ERROR_RTC_SDP_EXCHANGE, "unsupport media type=%s", iter->type_.c_str()); + } + } + + return err; +} + +srs_error_t SrsGoApiRtcDataChannel::exchange_sdp(SrsRequest* req, const SrsSdp& remote_sdp, SrsSdp& local_sdp) +{ + srs_error_t err = srs_success; + local_sdp.version_ = "0"; + + local_sdp.username_ = RTMP_SIG_SRS_SERVER; + local_sdp.session_id_ = srs_int2str((int64_t)this); + local_sdp.session_version_ = "2"; + local_sdp.nettype_ = "IN"; + local_sdp.addrtype_ = "IP4"; + local_sdp.unicast_address_ = "0.0.0.0"; + + local_sdp.session_name_ = "SRSDataSession"; + + local_sdp.msid_semantic_ = "WMS"; + local_sdp.msids_.push_back(req->app + "/" + req->stream); + + local_sdp.group_policy_ = "BUNDLE"; + + for (size_t i = 0; i < remote_sdp.media_descs_.size(); ++i) { + const SrsMediaDesc& remote_media_desc = remote_sdp.media_descs_[i]; + if (!remote_media_desc.is_application()) { + continue; + } + + local_sdp.media_descs_.push_back(SrsMediaDesc("application")); + + SrsMediaDesc& local_media_desc = local_sdp.media_descs_.back(); + + local_media_desc.mid_ = remote_media_desc.mid_; + local_sdp.groups_.push_back(local_media_desc.mid_); + + local_media_desc.port_ = 9; + local_media_desc.protos_ = "UDP/DTLS/SCTP"; + + if (remote_media_desc.session_info_.setup_ == "active") { + local_media_desc.session_info_.setup_ = "passive"; + } else if (remote_media_desc.session_info_.setup_ == "passive") { + local_media_desc.session_info_.setup_ = "active"; + } else if (remote_media_desc.session_info_.setup_ == "actpass") { + local_media_desc.session_info_.setup_ = "passive"; + } else { + // @see: https://tools.ietf.org/html/rfc4145#section-4.1 + // The default value of the setup attribute in an offer/answer exchange + // is 'active' in the offer and 'passive' in the answer. + local_media_desc.session_info_.setup_ = "passive"; + } + + local_media_desc.rtcp_mux_ = true; + local_media_desc.rtcp_rsize_ = true; + } + + return err; +} +#endif + SrsGoApiRtcPublish::SrsGoApiRtcPublish(SrsRtcServer* server) { server_ = server; diff --git a/trunk/src/app/srs_app_rtc_api.hpp b/trunk/src/app/srs_app_rtc_api.hpp index cf27f8d3d70..fb173592631 100644 --- a/trunk/src/app/srs_app_rtc_api.hpp +++ b/trunk/src/app/srs_app_rtc_api.hpp @@ -18,14 +18,14 @@ class SrsRtcUserConfig; class SrsGoApiRtcPlay : public ISrsHttpHandler { -private: +protected: SrsRtcServer* server_; public: SrsGoApiRtcPlay(SrsRtcServer* server); virtual ~SrsGoApiRtcPlay(); public: virtual srs_error_t serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessage* r); -private: +protected: virtual srs_error_t do_serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessage* r, SrsJsonObject* res); public: virtual srs_error_t serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessage* r, SrsRtcUserConfig* ruc); @@ -37,14 +37,14 @@ class SrsGoApiRtcPlay : public ISrsHttpHandler class SrsGoApiRtcPublish : public ISrsHttpHandler { -private: +protected: SrsRtcServer* server_; public: SrsGoApiRtcPublish(SrsRtcServer* server); virtual ~SrsGoApiRtcPublish(); public: virtual srs_error_t serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessage* r); -private: +protected: virtual srs_error_t do_serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessage* r, SrsJsonObject* res); public: virtual srs_error_t serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessage* r, SrsRtcUserConfig* ruc); @@ -82,5 +82,24 @@ class SrsGoApiRtcNACK : public ISrsHttpHandler virtual srs_error_t do_serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessage* r, SrsJsonObject* res); }; + + +#ifdef SRS_SCTP +class SrsGoApiRtcDataChannel : public ISrsHttpHandler +{ +private: + SrsRtcServer* server_; +public: + SrsGoApiRtcDataChannel(SrsRtcServer* server); + virtual ~SrsGoApiRtcDataChannel(); +public: + virtual srs_error_t serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessage* r); +private: + virtual srs_error_t do_serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessage* r, SrsJsonObject* res); + srs_error_t exchange_sdp(SrsRequest* req, const SrsSdp& remote_sdp, SrsSdp& local_sdp); + srs_error_t check_remote_sdp(const SrsSdp& remote_sdp); +}; +#endif + #endif diff --git a/trunk/src/app/srs_app_rtc_conn.cpp b/trunk/src/app/srs_app_rtc_conn.cpp index 3ca1ac16406..87e7bff5d35 100644 --- a/trunk/src/app/srs_app_rtc_conn.cpp +++ b/trunk/src/app/srs_app_rtc_conn.cpp @@ -69,6 +69,10 @@ extern SrsPps* _srs_pps_rnack2; extern SrsPps* _srs_pps_pub; extern SrsPps* _srs_pps_conn; +#ifdef SRS_SCTP +#include +#endif + ISrsRtcTransport::ISrsRtcTransport() { } @@ -83,6 +87,10 @@ SrsSecurityTransport::SrsSecurityTransport(ISrsRtcNetwork* s) dtls_ = new SrsDtls((ISrsDtlsCallback*)this); srtp_ = new SrsSRTP(); + +#ifdef SRS_SCTP + sctp_ = nullptr; +#endif handshake_done = false; } @@ -91,6 +99,10 @@ SrsSecurityTransport::~SrsSecurityTransport() { srs_freep(dtls_); srs_freep(srtp_); + +#ifdef SRS_SCTP + srs_freep(sctp_); +#endif } srs_error_t SrsSecurityTransport::initialize(SrsSessionConfig* cfg) @@ -158,6 +170,15 @@ srs_error_t SrsSecurityTransport::on_dtls_application_data(const char* buf, cons srs_error_t err = srs_success; // TODO: process SCTP protocol(WebRTC datachannel support) +#ifdef SRS_SCTP + if (sctp_ == NULL) { + sctp_ = new SrsSctp(dtls_); + // TODO: FIXME: Handle error. + sctp_->connect_to_class(); + } + + sctp_->feed(buf, nb_buf); +#endif return err; } diff --git a/trunk/src/app/srs_app_rtc_conn.hpp b/trunk/src/app/srs_app_rtc_conn.hpp index e47ba6f0154..94b7d8f7045 100644 --- a/trunk/src/app/srs_app_rtc_conn.hpp +++ b/trunk/src/app/srs_app_rtc_conn.hpp @@ -57,6 +57,10 @@ class SrsRtcUdpNetwork; class ISrsRtcNetwork; class SrsRtcTcpNetwork; +#ifdef SRS_SCTP +class SrsSctp; +#endif + const uint8_t kSR = 200; const uint8_t kRR = 201; const uint8_t kSDES = 202; @@ -97,6 +101,9 @@ class SrsSecurityTransport : public ISrsRtcTransport ISrsRtcNetwork* network_; SrsDtls* dtls_; SrsSRTP* srtp_; +#ifdef SRS_SCTP + SrsSctp* sctp_; +#endif bool handshake_done; public: SrsSecurityTransport(ISrsRtcNetwork* s); diff --git a/trunk/src/app/srs_app_rtc_dtls.cpp b/trunk/src/app/srs_app_rtc_dtls.cpp index 70dcb6d906f..f37f955a47b 100644 --- a/trunk/src/app/srs_app_rtc_dtls.cpp +++ b/trunk/src/app/srs_app_rtc_dtls.cpp @@ -681,6 +681,34 @@ void SrsDtlsImpl::callback_by_ssl(std::string type, std::string desc) } } +srs_error_t SrsDtlsImpl::send(const char* data, const int len) +{ + srs_error_t err = srs_success; + + int ret = SSL_write(dtls, data, len); + + // TODO: FIXME: Handle error. + // Call SSL_get_error() with the return value ret to find out the reason. + // @see https://www.openssl.org/docs/man1.0.2/man3/SSL_write.html + if (ret <= 0) { + return srs_error_new(ERROR_RTC_DTLS, "SSL_write"); + } + + uint8_t dtls_send_buffer[4096]; + + // TODO: FIXME: Handle error. + while (BIO_ctrl_pending(bio_out) > 0) { + int dtls_send_bytes = BIO_read(bio_out, dtls_send_buffer, sizeof(dtls_send_buffer)); + if (dtls_send_bytes && callback_) { + if ((err = callback_->write_dtls_data(dtls_send_buffer, dtls_send_bytes)) != srs_success) { + return srs_error_wrap(err, "send dtls packet"); + } + } + } + + return err; +} + SrsDtlsClientImpl::SrsDtlsClientImpl(ISrsDtlsCallback* callback) : SrsDtlsImpl(callback) { trd = NULL; @@ -1043,6 +1071,11 @@ srs_error_t SrsDtls::get_srtp_key(std::string& recv_key, std::string& send_key) return impl->get_srtp_key(recv_key, send_key); } +srs_error_t SrsDtls::send(const char* data, const int len) +{ + return impl->send(data, len); +} + SrsSRTP::SrsSRTP() { recv_ctx_ = NULL; diff --git a/trunk/src/app/srs_app_rtc_dtls.hpp b/trunk/src/app/srs_app_rtc_dtls.hpp index 6daf7f5c772..c3ede093f0f 100644 --- a/trunk/src/app/srs_app_rtc_dtls.hpp +++ b/trunk/src/app/srs_app_rtc_dtls.hpp @@ -125,6 +125,8 @@ class SrsDtlsImpl virtual srs_error_t on_final_out_data(uint8_t* data, int size) = 0; virtual srs_error_t on_handshake_done() = 0; virtual bool is_dtls_client() = 0; +public: + srs_error_t send(const char* data, const int len); }; class SrsDtlsClientImpl : public SrsDtlsImpl, public ISrsCoroutineHandler @@ -210,6 +212,9 @@ class SrsDtls srs_error_t on_dtls(char* data, int nb_data); public: srs_error_t get_srtp_key(std::string& recv_key, std::string& send_key); +public: + // Encrypt by DTLS and sendout, for example, DataChannel to send data. + srs_error_t send(const char* data, const int len); }; class SrsSRTP diff --git a/trunk/src/app/srs_app_rtc_sdp.cpp b/trunk/src/app/srs_app_rtc_sdp.cpp index 5e157df1e2f..299ed082fd5 100644 --- a/trunk/src/app/srs_app_rtc_sdp.cpp +++ b/trunk/src/app/srs_app_rtc_sdp.cpp @@ -7,6 +7,8 @@ #include #include +#include // for memset +#include // for ntohs in linux #include #include @@ -46,7 +48,7 @@ std::vector split_str(const std::string& str, const std::string& de return ret; } -static void skip_first_spaces(std::string& str) +void skip_first_spaces(std::string& str) { while (! str.empty() && str[0] == ' ') { str.erase(0, 1); @@ -390,6 +392,10 @@ srs_error_t SrsMediaDesc::encode(std::ostringstream& os) os << " " << iter->payload_type_; } + if (is_application()) { + os << " webrtc-datachannel"; + } + os << kCRLF; // TODO:nettype and address type diff --git a/trunk/src/app/srs_app_rtc_sdp.hpp b/trunk/src/app/srs_app_rtc_sdp.hpp index cc3b558294e..6c4ab45a18b 100644 --- a/trunk/src/app/srs_app_rtc_sdp.hpp +++ b/trunk/src/app/srs_app_rtc_sdp.hpp @@ -140,6 +140,7 @@ class SrsMediaDesc bool is_audio() const { return type_ == "audio"; } bool is_video() const { return type_ == "video"; } + bool is_application() const { return type_ == "application"; } private: srs_error_t parse_attribute(const std::string& content); srs_error_t parse_attr_rtpmap(const std::string& value); @@ -197,6 +198,7 @@ class SrsSdp void set_fingerprint_algo(const std::string& algo); void set_fingerprint(const std::string& fingerprint); void add_candidate(const std::string& protocol, const std::string& ip, const int& port, const std::string& type); + bool has_media_line() const; std::string get_ice_ufrag() const; std::string get_ice_pwd() const; diff --git a/trunk/src/app/srs_app_rtc_server.cpp b/trunk/src/app/srs_app_rtc_server.cpp index 2c34b97a5b9..b49a7bd082b 100644 --- a/trunk/src/app/srs_app_rtc_server.cpp +++ b/trunk/src/app/srs_app_rtc_server.cpp @@ -486,6 +486,12 @@ srs_error_t SrsRtcServer::listen_api() } #endif +#ifdef SRS_SCTP + if ((err = http_api_mux->handle("/rtc/v1/data/", new SrsGoApiRtcDataChannel(this))) != srs_success) { + return srs_error_wrap(err, "handle data-channel"); + } +#endif + return err; } @@ -518,12 +524,25 @@ srs_error_t SrsRtcServer::create_session(SrsRtcUserConfig* ruc, SrsSdp& local_sd return err; } + + +bool SrsSdp::has_media_line() const { + for (int i=0; ireq_; + if (ruc->remote_sdp_.has_media_line()) { // first add publisher/player for negotiate sdp media info if (ruc->publish_) { if ((err = session->add_publisher(ruc, local_sdp)) != srs_success) { @@ -534,6 +553,7 @@ srs_error_t SrsRtcServer::do_create_session(SrsRtcUserConfig* ruc, SrsSdp& local return srs_error_wrap(err, "add player"); } } + } // All tracks default as inactive, so we must enable them. session->set_all_tracks_status(req->get_stream_url(), ruc->publish_, true); diff --git a/trunk/src/app/srs_app_sctp.cpp b/trunk/src/app/srs_app_sctp.cpp new file mode 100644 index 00000000000..7a93c237339 --- /dev/null +++ b/trunk/src/app/srs_app_sctp.cpp @@ -0,0 +1,558 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2013-2020 John + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include + +using namespace std; + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +enum SrsDataChannelMessageType +{ + SrsDataChannelMessageTypeAck = 2, + SrsDataChannelMessageTypeOpen = 3, +}; + +enum SrsDataChannelType +{ + SrsDataChannelTypeReliable = 0x00, + SrsDataChannelTypeReliableUnordered = 0x80, + SrsDataChannelTypeUnreliableRexmit = 0x01, + SrsDataChannelTypeUnreliableRexmitUnordered = 0x81, + SrsDataChannelTypeUnreliableTimed = 0x02, + SrsDataChannelTypeUnreliableTimedUnordered = 0x82, +}; + +enum SrsDataChannelPPID +{ + SrsDataChannelPPIDControl = 50, + SrsDataChannelPPIDString = 51, + SrsDataChannelPPIDBinary = 53, +}; + +uint16_t event_types[] = +{ + SCTP_ADAPTATION_INDICATION, + SCTP_ASSOC_CHANGE, + SCTP_ASSOC_RESET_EVENT, + SCTP_REMOTE_ERROR, + SCTP_SHUTDOWN_EVENT, + SCTP_SEND_FAILED_EVENT, + SCTP_STREAM_RESET_EVENT, + SCTP_STREAM_CHANGE_EVENT +}; + +const int kSctpPort = 5000; +const int kMaxInSteam = 128; +const int kMaxOutStream = 128; + +#ifdef SRS_DEBUG +static void sctp_debug_log(const char* format, ...) +{ + char buffer[4096]; + va_list ap; + + va_start(ap, format); + int nb = vsnprintf(buffer, sizeof(buffer), format, ap); + if (nb > 0) { + if (buffer[nb - 1] == '\n') { + if (nb >= 2 && buffer[nb - 2] == '\r') { + buffer[nb - 2] = '\0'; + } else { + buffer[nb - 1] = '\0'; + } + } else { + buffer[nb] = '\0'; + } + srs_trace("%s", buffer); + } + + va_end(ap); +} +#endif + +static int on_recv_sctp_data(struct socket* sock, union sctp_sockstore addr, + void* data, size_t len, struct sctp_rcvinfo rcv, int flags, void* ulp_inffo) +{ + srs_error_t err = srs_success; + + SrsSctp* sctp = reinterpret_cast(ulp_inffo); + if (flags & MSG_NOTIFICATION) { + err = sctp->on_sctp_event(rcv, data, len); + } else { + err = sctp->on_sctp_data(rcv, data, len); + } + + // TODO: FIXME: Handle error. + if (err != srs_success) { + srs_warn("SCTP: ignore error=%s", srs_error_desc(err).c_str()); + srs_error_reset(err); + } + + return 1; +} + +static int on_send_sctp_data(void* addr, void* data, size_t len, uint8_t tos, uint8_t set_df) +{ + srs_error_t err = srs_success; + + SrsSctp* sctp = reinterpret_cast(addr); + srs_assert(sctp); + + err = sctp->rtc_dtls_->send(reinterpret_cast(data), len); + + // TODO: FIXME: Handle error. + if (err != srs_success) { + srs_warn("SCTP: ignore error=%s", srs_error_desc(err).c_str()); + srs_error_reset(err); + } + + return 0; +} + +SrsDataChannel::SrsDataChannel() +{ + label_ = ""; + sid_ = 0; + + channel_type_ = 0; + reliability_params_ = 0; + status_ = DataChannelStatusClosed; +}; + + +SrsSctpGlobalEnv::SrsSctpGlobalEnv() +{ +#ifdef SRS_DEBUG + usrsctp_init_nothreads(0, on_send_sctp_data, sctp_debug_log); + usrsctp_sysctl_set_sctp_debug_on(SCTP_DEBUG_ALL); +#else + usrsctp_init_nothreads(0, on_send_sctp_data, NULL); +#endif + + // TODO: FIXME: Use one hourglass for performance issue. + sctp_timer_ = new SrsHourGlass("sctp", this, 200 * SRS_UTIME_MILLISECONDS); + + srs_error_t err = srs_success; + if ((err = sctp_timer_->tick(0 * SRS_UTIME_MILLISECONDS)) != srs_success) { + srs_error_reset(err); + } + + // TODO: FIXME: Handle error. + // TODO: FIXME: Should not do it in constructor. + if ((err = sctp_timer_->start()) != srs_success) { + srs_error_reset(err); + } +} + +SrsSctpGlobalEnv::~SrsSctpGlobalEnv() +{ + usrsctp_finish(); + srs_freep(sctp_timer_); +} + +srs_error_t SrsSctpGlobalEnv::notify(int type, srs_utime_t interval, srs_utime_t tick) +{ + srs_error_t err = srs_success; + + srs_info("SCTP: timer every %ums, type=%u", srsu2ms(interval), type); + + usrsctp_handle_timers(interval / 1000); + + return err; +} + +SrsSctpGlobalEnv* _srs_sctp_env = NULL; + +SrsSctp::SrsSctp(SrsDtls* dtls) +{ + rtc_dtls_ = dtls; + + // TODO: FIXME: Should start in server init event. + if (_srs_sctp_env == NULL) { + _srs_sctp_env = new SrsSctpGlobalEnv(); + } + + if (true) { + usrsctp_register_address(static_cast(this)); + sctp_socket = usrsctp_socket(AF_CONN, SOCK_STREAM, IPPROTO_SCTP, on_recv_sctp_data, NULL, 0, static_cast(this)); + + usrsctp_set_ulpinfo(sctp_socket, static_cast(this)); + + int ret = usrsctp_set_non_blocking(sctp_socket, 1); + // TODO: FIXME: Handle error. + if (ret < 0) { + srs_warn("usrrsctp set non blocking failed, ret=%d", ret); + } + + struct sctp_assoc_value av; + + av.assoc_value = SCTP_ENABLE_RESET_STREAM_REQ | SCTP_ENABLE_RESET_ASSOC_REQ | SCTP_ENABLE_CHANGE_ASSOC_REQ; + ret = usrsctp_setsockopt(sctp_socket, IPPROTO_SCTP, SCTP_ENABLE_STREAM_RESET, &av, sizeof(av)); + // TODO: FIXME: Handle error. + if (ret < 0) { + srs_warn("usrrsctp set SCTP_ENABLE_STREAM_RESET failed, ret=%d", ret); + } + + uint32_t no_delay = 1; + ret = usrsctp_setsockopt(sctp_socket, IPPROTO_SCTP, SCTP_NODELAY, &no_delay, sizeof(no_delay)); + // TODO: FIXME: Handle error. + if (ret < 0) { + srs_warn("usrrsctp set SCTP_NODELAY failed, ret=%d", ret); + } + + struct sctp_event event; + memset(&event, 0, sizeof(event)); + event.se_on = 1; + + for (size_t i = 0; i < sizeof(event_types) / sizeof(uint16_t); ++i) + { + event.se_type = event_types[i]; + // TODO: FIXME: Handle error. + ret = usrsctp_setsockopt(sctp_socket, IPPROTO_SCTP, SCTP_EVENT, &event, sizeof(event)); + if (ret < 0) { + srs_warn("usrrsctp set SCTP_NODELAY failed, ret=%d", ret); + } + } + + // Init message. + struct sctp_initmsg initmsg; + memset(&initmsg, 0, sizeof(initmsg)); + initmsg.sinit_num_ostreams = kMaxOutStream; + initmsg.sinit_max_instreams = kMaxInSteam; + + // TODO: FIXME: Handle error. + ret = usrsctp_setsockopt(sctp_socket, IPPROTO_SCTP, SCTP_INITMSG, &initmsg, sizeof(initmsg)); + if (ret < 0) { + srs_warn("usrrsctp set SCTP_INITMSG failed, ret=%d", ret); + } + + struct sockaddr_conn sconn; + memset(&sconn, 0, sizeof(sconn)); + sconn.sconn_family = AF_CONN; + sconn.sconn_port = htons(kSctpPort); + sconn.sconn_addr = static_cast(this); + + // TODO: FIXME: Handle error. + ret = usrsctp_bind(sctp_socket, reinterpret_cast(&sconn), sizeof(sconn)); + if (ret < 0) { + srs_warn("usrrsctp bind failed, ret=%d", ret); + } + } +} + +SrsSctp::~SrsSctp() +{ + if (sctp_socket) { + usrsctp_close(sctp_socket); + } +} + +srs_error_t SrsSctp::connect_to_class() +{ + srs_error_t err = srs_success; + + struct sockaddr_conn rconn; + memset(&rconn, 0, sizeof(rconn)); + rconn.sconn_family = AF_CONN; + rconn.sconn_port = htons(kSctpPort); + rconn.sconn_addr = static_cast(this); + // usrsctp_connect is no socket connect, it just bind usrsctp socket to this class. + int ret = usrsctp_connect(sctp_socket, reinterpret_cast(&rconn), sizeof(rconn)); + if (ret < 0 && errno != EINPROGRESS) { + return srs_error_new(ERROR_RTC_SCTP, "sctp connect, ret=%d, errno=%d", ret, errno); + } + + struct sctp_paddrparams peer_addr_param; + memset(&peer_addr_param, 0, sizeof(peer_addr_param)); + memcpy(&peer_addr_param.spp_address, &rconn, sizeof(rconn)); + peer_addr_param.spp_flags = SPP_PMTUD_DISABLE; + peer_addr_param.spp_pathmtu = 1200 - sizeof(struct sctp_common_header); + + ret = usrsctp_setsockopt(sctp_socket, IPPROTO_SCTP, SCTP_PEER_ADDR_PARAMS, &peer_addr_param, sizeof(peer_addr_param)); + if (ret < 0) { + return srs_error_new(ERROR_RTC_SCTP, "sctp setsockopt, ret=%d", ret); + } + + srs_trace("SCTP: connect peer success."); + + return err; +} + +void SrsSctp::feed(const char* buf, const int nb_buf) +{ + usrsctp_conninput(this, buf, nb_buf, 0); +} + +srs_error_t SrsSctp::on_sctp_event(const struct sctp_rcvinfo& rcv, void* data, size_t len) +{ + srs_error_t err = srs_success; + + union sctp_notification* sctp_notify = reinterpret_cast(data); + if (sctp_notify->sn_header.sn_length != len) { + return srs_error_new(ERROR_RTC_SCTP, "sctp notify header, sn=%d, len=%d", sctp_notify->sn_header.sn_length, len); + } + + srs_verbose("sctp event type=%d", (int)sctp_notify->sn_header.sn_type); + + switch (sctp_notify->sn_header.sn_type) { + case SCTP_ASSOC_CHANGE: + break; + case SCTP_REMOTE_ERROR: + break; + case SCTP_SHUTDOWN_EVENT: + break; + case SCTP_ADAPTATION_INDICATION: + break; + case SCTP_PARTIAL_DELIVERY_EVENT: + break; + case SCTP_AUTHENTICATION_EVENT: + break; + case SCTP_SENDER_DRY_EVENT: + break; + case SCTP_NOTIFICATIONS_STOPPED_EVENT: + break; + case SCTP_SEND_FAILED_EVENT: { + const struct sctp_send_failed_event& ssfe = sctp_notify->sn_send_failed_event; + // TODO: FIXME: Return error or just print it? + srs_error("SCTP_SEND_FAILED_EVENT, ppid=%u, sid=%u", ntohl(ssfe.ssfe_info.snd_ppid), ssfe.ssfe_info.snd_sid); + break; + } + case SCTP_STREAM_RESET_EVENT: + break; + case SCTP_ASSOC_RESET_EVENT: + break; + case SCTP_STREAM_CHANGE_EVENT: + break; + case SCTP_PEER_ADDR_CHANGE: + break; + default: + break; + } + + return err; +} + +srs_error_t SrsSctp::on_sctp_data(const struct sctp_rcvinfo& rcv, void* data, size_t len) +{ + srs_error_t err = srs_success; + + SrsBuffer* stream = new SrsBuffer(static_cast(data), len); + SrsAutoFree(SrsBuffer, stream); + + uint32_t ppid = ntohl(rcv.rcv_ppid); + switch (ppid) { + case SrsDataChannelPPIDControl: + err = on_data_channel_control(rcv, stream); + break; + case SrsDataChannelPPIDString: + case SrsDataChannelPPIDBinary: + err = on_data_channel_msg(rcv, stream); + break; + default: + break; + } + return err; +} + +srs_error_t SrsSctp::on_data_channel_control(const struct sctp_rcvinfo& rcv, SrsBuffer* stream) +{ + srs_error_t err = srs_success; + + if (! stream->require(1)) { + return srs_error_new(ERROR_RTC_SCTP, "sctp data length invalid"); + } + + uint8_t msg_type = stream->read_1bytes(); + switch (msg_type) { + case SrsDataChannelMessageTypeOpen: { + if (data_channels_.count(rcv.rcv_sid)) { + return srs_error_new(ERROR_RTC_SCTP, "data channel %d already opened", rcv.rcv_sid); + } + /* + @see: https://tools.ietf.org/html/draft-ietf-rtcweb-data-protocol-08#section-5.1 + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Message Type | Channel Type | Priority | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Reliability Parameter | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Label Length | Protocol Length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + \ / + | Label | + / \ + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + \ / + | Protocol | + / \ + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ + if (! stream->require(11)) { + return srs_error_new(ERROR_RTC_SCTP, "sctp data length invalid"); + } + + uint8_t channel_type = stream->read_1bytes(); + /*uint16_t priority = */stream->read_2bytes(); + uint32_t reliability_params = stream->read_4bytes(); + uint16_t label_length = stream->read_2bytes(); + uint16_t protocol_length = stream->read_2bytes(); + + switch (channel_type) { + case SrsDataChannelTypeReliable: srs_trace("SCTP: reliable|ordered channel"); break; + case SrsDataChannelTypeReliableUnordered: srs_trace("SCTP: reliable|unordered channel"); break; + case SrsDataChannelTypeUnreliableRexmit: srs_trace("SCTP: unreliable|ordered|rtx(%u) channel", reliability_params); break; + case SrsDataChannelTypeUnreliableRexmitUnordered: srs_trace("SCTP: unreliable|unordered|rtx(%u) channel", reliability_params); break; + case SrsDataChannelTypeUnreliableTimed: srs_trace("SCTP: unreliable|ordered|life(%u) channel", reliability_params); break; + case SrsDataChannelTypeUnreliableTimedUnordered: srs_trace("SCTP: unreliable|unordered|life(%u) channel", reliability_params); break; + } + + string label = ""; + if (label_length > 0) { + if (! stream->require(label_length)) { + return srs_error_new(ERROR_RTC_SCTP, "label length %d", label_length); + } + + label = stream->read_string(label_length); + } + + if (protocol_length > 0) { + if (! stream->require(protocol_length)) { + return srs_error_new(ERROR_RTC_SCTP, "protocol length %d", protocol_length); + } + } + + srs_verbose("channel_type=%u, priority=%u, reliability_params=%u, label_length=%u, protocol_length=%u, label=%s", + channel_type, priority, reliability_params, label_length, protocol_length, label.c_str()); + + SrsDataChannel data_channel; + data_channel.label_ = label; + data_channel.sid_ = rcv.rcv_sid; + data_channel.channel_type_ = channel_type; + data_channel.reliability_params_ = reliability_params; + data_channel.status_ = DataChannelStatusOpen; + + data_channels_.insert(make_pair(data_channel.sid_, data_channel)); + + break; + } + case SrsDataChannelMessageTypeAck: { + break; + } + default: { + return srs_error_new(ERROR_RTC_SCTP, "invalid msg_type=%d", msg_type); + } + } + + return err; +} + +srs_error_t SrsSctp::on_data_channel_msg(const struct sctp_rcvinfo& rcv, SrsBuffer* stream) +{ + srs_error_t err = srs_success; + + srs_trace("SCTP: RECV %uB MSG: %.*s", stream->size(), stream->size(), stream->data()); + + // TODO: FIXME: echo test code. + if (true) { + // TODO: FIXME: Handle error. + send(rcv.rcv_sid, stream->data(), stream->size()); + } + + return err; +} + +srs_error_t SrsSctp::send(const uint16_t sid, const char* buf, const int len) +{ + srs_error_t err = srs_success; + + map::iterator iter = data_channels_.find(sid); + if (iter == data_channels_.end()) { + return srs_error_new(ERROR_RTC_SCTP, "can not found sid=%d", sid); + } + + const SrsDataChannel& data_channel = iter->second; + + if (data_channel.status_ != DataChannelStatusOpen) { + return srs_error_new(ERROR_RTC_SCTP, "data channel %d no opened, status=%d", sid, data_channel.status_); + } + + struct sctp_sendv_spa spa; + + memset(&spa, 0, sizeof(spa)); + spa.sendv_flags = SCTP_SEND_SNDINFO_VALID; + spa.sendv_sndinfo.snd_sid = sid; + spa.sendv_sndinfo.snd_ppid = htonl(SrsDataChannelPPIDString); + spa.sendv_sndinfo.snd_flags = SCTP_EOR; + + if (data_channel.channel_type_ & 0x80) { + spa.sendv_sndinfo.snd_flags |= SCTP_UNORDERED; + } + + if (data_channel.channel_type_ == SrsDataChannelTypeUnreliableRexmitUnordered + || data_channel.channel_type_ == SrsDataChannelTypeUnreliableRexmit) { + spa.sendv_prinfo.pr_policy = SCTP_PR_SCTP_RTX; + spa.sendv_prinfo.pr_value = data_channel.reliability_params_; + spa.sendv_flags |= SCTP_SEND_PRINFO_VALID; + } else if (data_channel.channel_type_ == SrsDataChannelTypeUnreliableTimedUnordered + || data_channel.channel_type_ == SrsDataChannelTypeUnreliableTimed) { + spa.sendv_prinfo.pr_policy = SCTP_PR_SCTP_TTL; + spa.sendv_prinfo.pr_value = data_channel.reliability_params_; + spa.sendv_flags |= SCTP_SEND_PRINFO_VALID; + } else { + spa.sendv_prinfo.pr_policy = SCTP_PR_SCTP_NONE; + spa.sendv_prinfo.pr_value = 0; + } + + // Fake IO, which call on_send_sctp_data actually. + int ret = usrsctp_sendv(sctp_socket, buf, len, NULL, 0, &spa, sizeof(spa), SCTP_SENDV_SPA, 0); + + // If error, ret is set to -1, and the variable errno is then set appropriately. + if (ret < 0) { + return srs_error_new(ERROR_RTC_SCTP, "sctp notify header"); + } + + return err; +} + +void SrsSctp::broadcast(const char* buf, const int len) +{ + map::iterator iter = data_channels_.begin(); + for ( ; iter != data_channels_.end(); ++iter) { + send(iter->first, buf, len); + } +} diff --git a/trunk/src/app/srs_app_sctp.hpp b/trunk/src/app/srs_app_sctp.hpp new file mode 100644 index 00000000000..8ee2618c093 --- /dev/null +++ b/trunk/src/app/srs_app_sctp.hpp @@ -0,0 +1,95 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2013-2020 John + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef SRS_APP_SCTP_HPP +#define SRS_APP_SCTP_HPP + +#include +#include +#include + +#include +#include +#include + +#if defined(SRS_VERBOSE) || defined(SRS_DEBUG) +#define SCTP_DEBUG 1 +#endif + +#include + +class SrsHourGlass; +class SrsDtls; + +enum DataChannelStatus +{ + DataChannelStatusClosed = 1, + DataChannelStatusOpen = 2, +}; + +struct SrsDataChannel +{ + SrsDataChannel(); + + std::string label_; + uint16_t sid_; + + uint8_t channel_type_; + uint32_t reliability_params_; + DataChannelStatus status_; +}; + +class SrsSctpGlobalEnv : virtual public ISrsHourGlass +{ +public: + SrsSctpGlobalEnv(); + ~SrsSctpGlobalEnv(); +public: + virtual srs_error_t notify(int type, srs_utime_t interval, srs_utime_t tick); +private: + SrsHourGlass* sctp_timer_; +}; + +class SrsSctp +{ +public: + SrsDtls* rtc_dtls_; + struct socket* sctp_socket; +public: + SrsSctp(SrsDtls* dtls); + virtual ~SrsSctp(); +public: + srs_error_t connect_to_class(); + srs_error_t send(const uint16_t sid, const char* buf, const int len); + void broadcast(const char* buf, const int len); + srs_error_t on_sctp_event(const struct sctp_rcvinfo& rcv, void* data, size_t len); + srs_error_t on_sctp_data(const struct sctp_rcvinfo& rcv, void* data, size_t len); + void feed(const char* buf, const int nb_buf); +private: + srs_error_t on_data_channel_control(const struct sctp_rcvinfo& rcv, SrsBuffer* stream); + srs_error_t on_data_channel_msg(const struct sctp_rcvinfo& rcv, SrsBuffer* stream); +private: + std::map data_channels_; +}; + +#endif diff --git a/trunk/src/kernel/srs_kernel_error.hpp b/trunk/src/kernel/srs_kernel_error.hpp index bda0432516a..f915a880bac 100644 --- a/trunk/src/kernel/srs_kernel_error.hpp +++ b/trunk/src/kernel/srs_kernel_error.hpp @@ -11,6 +11,8 @@ #include +#include + /**************************************************/ /* The system error. */ #define SRS_ERRNO_MAP_SYSTEM(XX) \ @@ -373,7 +375,24 @@ XX(ERROR_RTC_TCP_SIZE , 5032, "RtcTcpSize", "RTC TCP packet size is invalid") \ XX(ERROR_RTC_TCP_PACKET , 5033, "RtcTcpStun", "RTC TCP first packet must be STUN") \ XX(ERROR_RTC_TCP_STUN , 5034, "RtcTcpSession", "RTC TCP packet is invalid for session not found") \ - XX(ERROR_RTC_TCP_UNIQUE , 5035, "RtcUnique", "RTC only support one UDP or TCP network") + XX(ERROR_RTC_TCP_UNIQUE , 5035, "RtcUnique", "RTC only support one UDP or TCP network") \ + XX(ERROR_RTC_RTP_NO_SN , 5036, "RtcNoSN", "RTC no seq num") \ + XX(ERROR_RTC_NO_PLUBLSHER , 5037, "RtcNoPublisher", "RTC no publisher") \ + XX(ERROR_STREAM_URL_INVALID_FORMAT , 5038, "RtcStreamUrlInvalidFormat", "RTC stream url invaild format") \ + XX(ERROR_RTC_STREAM_DESC , 5039, "ERROR_RTC_STREAM_DESC", "ERROR_RTC_SCTP") \ + XX(ERROR_RTC_SCTP , 5040, "ERROR_RTC_SCTP", "ERROR_RTC_SCTP") \ + XX(ERROR_RTC_DATACHANNEL , 5041, "ERROR_RTC_DATACHANNEL", "ERROR_RTC_DATACHANNEL") \ + + +// note: add eror code for extra feature +// #define ERROR_RTC_RTP_NO_SN 5032 +// #define ERROR_RTC_NO_PLUBLSHER 5033 +// #define ERROR_STREAM_URL_INVALID_FORMAT 5034 + +// SCTP: For #1809: Support WebRTC DataChannel by SCTP. +// #define ERROR_RTC_STREAM_DESC 5035 +// #define ERROR_RTC_SCTP 5036 +// #define ERROR_RTC_DATACHANNEL 5037 /**************************************************/ /* SRT protocol error. */