PageRenderTime 92ms CodeModel.GetById 2ms app.highlight 82ms RepoModel.GetById 1ms app.codeStats 0ms

/apps/rtsp/src/rtsp_socket.erl

https://github.com/brahimalaya/publisher
Erlang | 590 lines | 350 code | 134 blank | 106 comment | 16 complexity | 32c58f0ab25053465d3ab0c32e3fd720 MD5 | raw file
  1
  2%%% @author     Max Lapshin <max@maxidoors.ru> [http://erlyvideo.org]
  3%%% @copyright  2010 Max Lapshin
  4%%% @doc        RTSP socket module
  5%%%
  6%%%
  7%%% 1. connect
  8%%% 2. describe
  9%%% 3. each setup
 10%%% 4. play, possible Rtp-Sync
 11%%% 5. get each packet
 12%%% 6. decode
 13%%%
 14%%%
 15%%% @end
 16%%% @reference  See <a href="http://erlyvideo.org/rtsp" target="_top">http://erlyvideo.org</a> for common information.
 17%%% @end
 18%%%
 19%%% This file is part of erlang-rtsp.
 20%%%
 21%%% erlang-rtsp is free software: you can redistribute it and/or modify
 22%%% it under the terms of the GNU General Public License as published by
 23%%% the Free Software Foundation, either version 3 of the License, or
 24%%% (at your option) any later version.
 25%%%
 26%%% erlang-rtsp is distributed in the hope that it will be useful,
 27%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
 28%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 29%%% GNU General Public License for more details.
 30%%%
 31%%% You should have received a copy of the GNU General Public License
 32%%% along with erlang-rtsp.  If not, see <http://www.gnu.org/licenses/>.
 33%%%
 34%%%---------------------------------------------------------------------------------------
 35-module(rtsp_socket).
 36-author('Max Lapshin <max@maxidoors.ru>').
 37-behaviour(gen_server).
 38
 39-include("log.hrl").
 40-include_lib("erlmedia/include/video_frame.hrl").
 41-include_lib("erlmedia/include/media_info.hrl").
 42-include_lib("erlmedia/include/sdp.hrl").
 43-include("rtsp.hrl").
 44
 45-export([start_link/1, set_socket/2]).
 46%% gen_server callbacks
 47-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
 48
 49-export([read/2, connect/3, options/2, describe/2, setup/3, play/2, teardown/1]).
 50
 51-export([handle_sdp/3, reply/3, reply/4, save_media_info/2, generate_session/0]).
 52
 53read(URL, Options) when is_binary(URL) ->
 54  read(binary_to_list(URL), Options);
 55
 56read(URL, Options) when is_list(URL) ->
 57  % try read_raw(URL, Options) of
 58  %   {ok, RTSP, MediaInfo} -> {ok, RTSP, MediaInfo}
 59  % catch
 60  %   _Class:{error,Reason} -> {error, Reason};
 61  %   exit:Reason -> {error, Reason};
 62  %   Class:Reason -> {Class, Reason}
 63  % end.
 64  read_raw(URL, Options).
 65
 66read_raw(URL, Options) ->
 67  {ok, RTSP} = rtsp_sup:start_rtsp_socket([{consumer, proplists:get_value(consumer, Options, self())}]),
 68  ConnectResult = rtsp_socket:connect(RTSP, URL, Options),
 69  ok == ConnectResult orelse erlang:throw(ConnectResult),
 70  {ok, _Methods} = rtsp_socket:options(RTSP, Options),
 71  {ok, MediaInfo, AvailableTracks} = rtsp_socket:describe(RTSP, Options),
 72  Tracks = 
 73    case proplists:get_value(tracks, Options) of
 74      undefined -> AvailableTracks;
 75      RequestedTracks -> [T || T <- AvailableTracks, lists:member(T,RequestedTracks)]
 76  end,
 77  [ok = rtsp_socket:setup(RTSP, Track, Options) || Track <- Tracks],
 78  ok = rtsp_socket:play(RTSP, Options),
 79  {ok,RTSP,MediaInfo}.
 80
 81options(RTSP, Options) ->
 82  Timeout = proplists:get_value(timeout, Options, 5000)*2,
 83  gen_server:call(RTSP, {request, options}, Timeout).
 84
 85describe(RTSP, Options) -> 
 86  Timeout = proplists:get_value(timeout, Options, 5000)*2,
 87  gen_server:call(RTSP, {request, describe}, Timeout).
 88
 89setup(RTSP, Stream, Options) ->
 90  Timeout = proplists:get_value(timeout, Options, 5000)*2,
 91  gen_server:call(RTSP, {request, setup, Stream}, Timeout).
 92
 93play(RTSP, Options) ->
 94  Timeout = proplists:get_value(timeout, Options, 5000)*2,
 95  gen_server:call(RTSP, {request, play}, Timeout).
 96
 97connect(RTSP, URL, Options) ->
 98  Timeout = proplists:get_value(timeout, Options, 10000)*2,
 99  gen_server:call(RTSP, {connect, URL, Options}, Timeout).
100
101teardown(RTSP) ->
102  gen_server:call(RTSP, teardown).
103
104start_link(Options) ->
105  gen_server:start_link(?MODULE, [Options], []).
106
107
108set_socket(Pid, Socket) when is_pid(Pid), is_port(Socket) ->
109  gen_tcp:controlling_process(Socket, Pid),
110  gen_server:cast(Pid, {socket_ready, Socket}).
111
112
113init([Options]) ->
114  Callback = proplists:get_value(callback, Options),
115  Consumer = proplists:get_value(consumer, Options),
116  case Consumer of
117    undefined -> ok;
118    _ -> erlang:monitor(process, Consumer)
119  end,
120  {ok, #rtsp_socket{callback = Callback, options = Options, media = Consumer, auth = fun empty_auth/2, timeout = ?DEFAULT_TIMEOUT}}.
121
122
123%%-------------------------------------------------------------------------
124%% @spec (Request, From, State) -> {reply, Reply, State}          |
125%%                                 {reply, Reply, State, Timeout} |
126%%                                 {noreply, State}               |
127%%                                 {noreply, State, Timeout}      |
128%%                                 {stop, Reason, Reply, State}   |
129%%                                 {stop, Reason, State}
130%% @doc Callback for synchronous server calls.  If `{stop, ...}' tuple
131%%      is returned, the server is stopped and `terminate/2' is called.
132%% @end
133%% @private
134%%-------------------------------------------------------------------------
135
136
137handle_call({connect, _, Options} = Call, From, #rtsp_socket{} = RTSP) ->
138  rtsp_inbound:handle_call(Call, From, RTSP#rtsp_socket{dump_traffic = proplists:get_value(dump_traffic, Options, true)});
139
140handle_call({consume, _Consumer} = Call, From, RTSP) ->
141  rtsp_inbound:handle_call(Call, From, RTSP);
142
143handle_call({request, _Request} = Call, From, RTSP) ->
144  rtsp_inbound:handle_call(Call, From, RTSP);
145
146handle_call({request, setup, _Num} = Call, From, RTSP) ->
147  rtsp_inbound:handle_call(Call, From, RTSP);
148
149handle_call(teardown, _From, RTSP) ->
150  send_teardown(RTSP),
151  {stop, normal, ok, RTSP};
152
153handle_call(Request, _From, #rtsp_socket{} = RTSP) ->
154  {stop, {unknown_call, Request}, RTSP}.
155
156
157
158%%-------------------------------------------------------------------------
159%% @spec (Msg, State) ->{noreply, State}          |
160%%                      {noreply, State, Timeout} |
161%%                      {stop, Reason, State}
162%% @doc Callback for asyncrous server calls.  If `{stop, ...}' tuple
163%%      is returned, the server is stopped and `terminate/2' is called.
164%% @end
165%% @private
166%%-------------------------------------------------------------------------
167handle_cast({socket_ready, Socket}, #rtsp_socket{timeout = Timeout} = State) ->
168  {ok, {IP, Port}} = inet:peername(Socket),
169  inet:setopts(Socket, [{active, once}]),
170  {noreply, State#rtsp_socket{socket = Socket, addr = IP, port = Port}, Timeout};
171
172handle_cast(Request, #rtsp_socket{} = Socket) ->
173  {stop, {unknown_cast, Request}, Socket}.
174
175
176%%-------------------------------------------------------------------------
177%% @spec (Msg, State) ->{noreply, State}          |
178%%                      {noreply, State, Timeout} |
179%%                      {stop, Reason, State}
180%% @doc Callback for messages sent directly to server's mailbox.
181%%      If `{stop, ...}' tuple is returned, the server is stopped and
182%%      `terminate/2' is called.
183%% @end
184%% @private
185%%-------------------------------------------------------------------------
186
187
188handle_info({tcp_closed, _Socket}, State) ->
189  ?D({"RTSP socket closed"}),
190  {stop, normal, State};
191
192handle_info({udp, _Socket, Addr, Port, Bin}, #rtsp_socket{media = Consumer, timeout = Timeout, rtp = RTP} = RTSP) ->
193  {ok, RTP1, NewFrames} = rtp:handle_data(RTP, {Addr, Port}, Bin),
194  [Consumer ! Frame || Frame <- NewFrames],
195  {noreply, RTSP#rtsp_socket{rtp = RTP1}, Timeout};
196
197handle_info({tcp, Socket, Bin}, #rtsp_socket{buffer = Buf, timeout = Timeout} = RTSPSocket) ->
198  inet:setopts(Socket, [{active, once}]),
199  {noreply, handle_packet(RTSPSocket#rtsp_socket{buffer = <<Buf/binary, Bin/binary>>}), Timeout};
200
201% handle_info({'DOWN', _, process, Consumer, _Reason}, #rtsp_socket{rtp = Consumer} = Socket) ->
202%   ?D({"RTSP RTP process died", Consumer}),
203%   {stop, normal, Socket};
204
205handle_info({'DOWN', _, process, Consumer, _Reason}, #rtsp_socket{media = Consumer} = Socket) ->
206  ?D({"RTSP consumer died", Consumer}),
207  {stop, normal, Socket};
208
209handle_info(#video_frame{} = Frame, #rtsp_socket{timeout = Timeout} = Socket) ->
210  {noreply, rtsp_outbound:encode_frame(Frame, Socket), Timeout};
211
212handle_info({ems_stream, _, play_complete, _}, Socket) ->
213  {stop, normal, Socket};
214
215handle_info(timeout, #rtsp_socket{} = Socket) ->
216  {stop, timeout, Socket};
217
218handle_info(send_sr, #rtsp_socket{rtp = RTP} = Socket) ->
219  rtp:send_rtcp(RTP, sender_report, []),
220  {noreply, Socket};
221
222handle_info({ems_stream,_Num,_}, #rtsp_socket{} = Socket) ->
223  {noreply, Socket};
224
225handle_info({ems_stream,_Num,_Key,_}, #rtsp_socket{} = Socket) ->
226  {noreply, Socket};
227
228handle_info({'EXIT', _, _}, RTSP) ->
229  {noreply, RTSP};
230
231handle_info(Message, #rtsp_socket{} = Socket) ->
232  {stop, {unknown_message, Message}, Socket}.
233
234dump_io(false, _) -> ok;
235dump_io(true, IO) -> dump_io(IO).
236
237dump_io({request, Method, URL, Headers, undefined}) ->
238  HeaderS = lists:flatten([io_lib:format("~p: ~p~n", [K, V]) || {K,V} <- Headers]),
239  io:format("<<<<<< RTSP IN (~p:~p)  <<<<<~n~s ~s RTSP/1.0~n~s~n", [?MODULE, ?LINE, Method, URL, HeaderS]);
240
241dump_io({request, Method, URL, Headers, Body}) ->
242  HeaderS = lists:flatten([io_lib:format("~p: ~p~n", [K, V]) || {K,V} <- Headers]),
243  io:format("<<<<<< RTSP IN (~p:~p)  <<<<<~n~s ~s RTSP/1.0~n~s~n~s~n", [?MODULE, ?LINE, Method, URL, HeaderS, Body]);
244
245dump_io({response, Code, Message, Headers, undefined}) ->
246  HeaderS = lists:flatten([io_lib:format("~p: ~p~n", [K, V]) || {K,V} <- Headers]),
247  io:format("<<<<<< RTSP IN (~p:~p)  <<<<<~nRTSP/1.0 ~p ~s~n~s~n", [?MODULE, ?LINE, Code, Message, HeaderS]);
248
249dump_io({response, Code, Message, Headers, Body}) ->
250  HeaderS = lists:flatten([io_lib:format("~p: ~p~n", [K, V]) || {K,V} <- Headers]),
251  io:format("<<<<<< RTSP IN (~p:~p)  <<<<<~nRTSP/1.0 ~p ~s~n~s~n~s~n", [?MODULE, ?LINE, Code, Message, HeaderS, Body]).
252
253-define(DUMP_REQUEST(Flag, X), dump_io(Flag, X)).
254-define(DUMP_RESPONSE(Flag, X), dump_io(Flag, X)).
255
256handle_packet(#rtsp_socket{buffer = Data, rtp = RTP, media = Consumer, dump_traffic = Dump} = Socket) ->
257  case packet_codec:decode(Data) of
258    {more, Data} ->
259      Socket;
260    {ok, {rtp, Channel, Packet}, Rest} ->
261      {ok, RTP1, NewFrames} = rtp:handle_data(RTP, Channel, Packet),
262      Socket1 = case NewFrames of
263        [#video_frame{dts = DTS}|_] when Socket#rtsp_socket.sent_sdp_config == false ->
264          [Consumer ! Frame#video_frame{dts = DTS, pts = DTS} || Frame <- video_frame:config_frames(Socket#rtsp_socket.media_info)],
265          Socket#rtsp_socket{sent_sdp_config = true};
266        _ ->
267          Socket
268      end,
269      [Consumer ! Frame || Frame <- NewFrames],
270      handle_packet(Socket1#rtsp_socket{buffer = Rest, rtp = RTP1});
271    {ok, {response, _Code, _Message, Headers, _Body} = Response, Rest} ->
272      ?DUMP_RESPONSE(Dump, Response),
273      Socket1 = handle_response(extract_session(Socket#rtsp_socket{buffer = Rest}, Headers), Response),
274      handle_packet(Socket1);
275    {ok, {request, _Method, _URL, _Headers, _Body} = Request, Rest} ->
276      ?DUMP_REQUEST(Dump, Request),
277      Socket1 = handle_request(Request, Socket#rtsp_socket{buffer = Rest}),
278      handle_packet(Socket1)
279  end.
280
281% Special case for server, rejecting Basic auth
282handle_response(#rtsp_socket{state = Request, auth_type = basic, auth_info = AuthInfo, pending = From} = Socket, {response, 401, _Message, Headers, _Body}) ->
283  case proplists:get_value('Www-Authenticate', Headers) of
284    [digest|Digest] ->
285      [Username, Password] = string:tokens(AuthInfo, ":"),
286
287      DigestAuth = fun(ReqName, URL) ->
288        digest_auth(Digest, Username, Password, URL, ReqName)
289      end,
290      {noreply, Socket1, _T} = rtsp_inbound:handle_call({request,Request}, From, Socket#rtsp_socket{auth_type = digest, auth = DigestAuth}),
291      Socket1;
292    _ ->
293      reply_pending(Socket#rtsp_socket{state = undefined, pending_reply = {error, unauthorized}})
294  end;
295
296
297handle_response(#rtsp_socket{state = options} = Socket, {response, _Code, _Message, Headers, _Body}) ->
298  Available = string:tokens(binary_to_list(proplists:get_value('Public', Headers, <<"">>)), ", "),
299  reply_pending(Socket#rtsp_socket{pending_reply = {ok, Available}});
300
301handle_response(#rtsp_socket{state = describe} = Socket, {response, 200, _Message, Headers, Body}) ->
302  Socket1 = handle_sdp(Socket, Headers, Body),
303  reply_pending(Socket1#rtsp_socket{state = undefined});
304
305handle_response(#rtsp_socket{state = play} = Socket, {response, 200, _Message, Headers, _Body}) ->
306  Socket1 = rtsp_inbound:sync_rtp(Socket, Headers),
307  reply_pending(Socket1#rtsp_socket{state = undefined});
308
309handle_response(#rtsp_socket{state = {setup, StreamId}, rtp = RTP, transport = Transport} = Socket, {response, 200, _Message, Headers, _Body}) ->
310  TransportHeader = proplists:get_value('Transport', Headers, []),
311  PortOpts = case proplists:get_value(server_port, TransportHeader) of
312    {SPort1,SPort2} -> [{remote_rtp_port,SPort1},{remote_rtcp_port,SPort2}];
313    undefined -> []
314  end,
315  {ok, RTP1, _} = rtp:setup_channel(RTP, StreamId, [{proto,Transport}]++PortOpts),
316  reply_pending(Socket#rtsp_socket{state = undefined, pending_reply = ok, rtp = RTP1});
317
318handle_response(Socket, {response, 401, _Message, _Headers, _Body}) ->
319  reply_pending(Socket#rtsp_socket{state = undefined, pending_reply = {error, unauthorized}});
320
321handle_response(Socket, {response, 404, _Message, _Headers, _Body}) ->
322  reply_pending(Socket#rtsp_socket{state = undefined, pending_reply = {error, not_found}});
323
324handle_response(Socket, {response, _Code, _Message, _Headers, _Body}) ->
325  reply_pending(Socket#rtsp_socket{state = undefined, pending_reply = {error, _Code}}).
326
327
328reply_pending(#rtsp_socket{pending = undefined} = Socket) ->
329  Socket;
330
331reply_pending(#rtsp_socket{state = {Method, Count}} = Socket) when Count > 1 ->
332  Socket#rtsp_socket{state = {Method, Count - 1}};
333
334reply_pending(#rtsp_socket{pending = From, pending_reply = Reply} = Socket) ->
335  gen_server:reply(From, Reply),
336  Socket#rtsp_socket{pending = undefined, pending_reply = ok}.
337
338handle_sdp(#rtsp_socket{media = Consumer, content_base = OldContentBase, url = URL} = Socket, Headers, Body) ->
339  <<"application/sdp">> = proplists:get_value('Content-Type', Headers),
340  MI1 = #media_info{streams = Streams} = sdp:decode(Body),
341  MI2 = MI1#media_info{streams = [S || #stream_info{content = Content, codec = Codec} = S <- Streams, 
342    (Content == audio orelse Content == video) andalso Codec =/= undefined]},
343  MediaInfo = MI2,
344  RTP = rtp:init(local, MediaInfo),
345  ContentBase = case proplists:get_value('Content-Base', Headers) of
346    undefined -> OldContentBase;
347    NewContentBase -> % Here we must handle important case when Content-Base is given with local network
348      {match, [_Host, BasePath]} = re:run(NewContentBase, "rtsp://([^/]+)(/.*)$", [{capture,all_but_first,list}]),
349      {match, [Host, _Path]} = re:run(URL, "rtsp://([^/]+)(/.*)$", [{capture,all_but_first,list}]),
350      "rtsp://" ++ Host ++ "/" ++ BasePath
351  end,
352  Socket1 = save_media_info(Socket#rtsp_socket{rtp = RTP, content_base = ContentBase}, MediaInfo),
353  case Consumer of
354    undefined -> ok;
355    _ -> Consumer ! Socket1#rtsp_socket.media_info
356  end,
357  Socket1.
358  
359
360save_media_info(#rtsp_socket{options = Options} = Socket, #media_info{streams = Streams1} = MediaInfo) ->
361  StreamNums = lists:seq(1, length(Streams1)),
362
363  Streams2 = lists:sort(fun(#stream_info{track_id = Id1}, #stream_info{track_id = Id2}) ->
364    Id1 =< Id2
365  end, Streams1),
366  
367  Streams3 = [lists:nth(Track, Streams2) || Track <- proplists:get_value(tracks, Options, lists:seq(1,length(Streams2)))],  
368  
369  Streams = Streams3,
370
371  StreamInfos = list_to_tuple(Streams3),
372  ControlMap = [{proplists:get_value(control, Opt),S} || #stream_info{options = Opt, track_id = S} <- Streams],
373  MediaInfo1 = MediaInfo#media_info{streams = Streams},
374
375  % ?D({"Streams", StreamInfos, StreamNums, ControlMap}),
376  Socket#rtsp_socket{rtp_streams = StreamInfos, media_info = MediaInfo1, control_map = ControlMap, pending_reply = {ok, MediaInfo1, StreamNums}}.
377
378
379generate_session() ->
380  {_A1, A2, A3} = now(),
381  lists:flatten(io_lib:format("~p~p", [A2*1000,A3 div 1000])).
382
383seq(Headers) ->
384  proplists:get_value('Cseq', Headers, 1).
385
386%
387% Wirecast goes:
388%
389% ANNOUNCE with SDP
390% OPTIONS
391% SETUP
392
393user_agents() ->
394  [
395    {"RealMedia", mplayer},
396    {"LibVLC", vlc}
397  ].
398
399detect_user_agent(Headers) ->
400  case proplists:get_value('User-Agent', Headers) of
401    undefined -> undefined;
402    UA -> find_user_agent(UA, user_agents())
403  end.
404
405find_user_agent(_UA, []) -> undefined;
406find_user_agent(UA, [{Match, Name}|Matches]) ->
407  case re:run(UA, Match, [{capture,all_but_first,list}]) of
408    {match, _} -> Name;
409    _ -> find_user_agent(UA, Matches)
410  end.
411
412
413setup_user_agent_preferences(#rtsp_socket{} = Socket, Headers) ->
414  UserAgent = detect_user_agent(Headers),
415  Socket#rtsp_socket{user_agent = UserAgent}.
416
417
418handle_request({request, 'DESCRIBE', URL, Headers, Body}, Socket) ->
419  rtsp_outbound:handle_describe_request(Socket, URL, Headers, Body);
420
421
422handle_request({request, 'RECORD', URL, Headers, Body}, #rtsp_socket{callback = Callback} = State) ->
423  case Callback:record(URL, Headers, Body) of
424    ok ->
425      reply(State, "200 OK", [{'Cseq', seq(Headers)}]);
426    {error, authentication} ->
427      reply(State, "401 Unauthorized", [{"WWW-Authenticate", "Basic realm=\"Erlyvideo Streaming Server\""}, {'Cseq', seq(Headers)}])
428  end;
429
430
431handle_request({request, 'PLAY', URL, Headers, Body}, #rtsp_socket{direction = in} = State) ->
432  handle_request({request, 'RECORD', URL, Headers, Body}, State);
433
434handle_request({request, 'PLAY', URL, Headers, Body}, #rtsp_socket{} = Socket) ->
435  rtsp_outbound:handle_play_request(Socket, URL, Headers, Body);
436
437handle_request({request, 'OPTIONS', _URL, Headers, _Body}, State) ->
438  reply(setup_user_agent_preferences(State, Headers), "200 OK",
439      [{'Server', ?SERVER_NAME}, {'Cseq', seq(Headers)}, {"Supported", "play.basic, con.persistent"},
440       {'Public', "SETUP, TEARDOWN, PLAY, PAUSE, OPTIONS, ANNOUNCE, DESCRIBE, RECORD, GET_PARAMETER"}]);
441
442handle_request({request, 'ANNOUNCE', URL, Headers, Body}, Socket) ->
443  rtsp_inbound:handle_announce_request(Socket, URL, Headers, Body);
444
445handle_request({request, 'PAUSE', _URL, Headers, _Body}, #rtsp_socket{direction = in} = State) ->
446  rtsp_inbound:handle_pause(State, _URL, Headers, _Body);
447
448handle_request({request, 'PAUSE', _URL, Headers, _Body}, #rtsp_socket{direction = out} = State) ->
449  rtsp_outbound:handle_pause_request(State, _URL, Headers, _Body);
450%
451% handle_request({request, 'PAUSE', _URL, Headers, _Body}, #rtsp_socket{rtp = Consumer} = State) ->
452%   gen_server:call(Consumer, {pause, self()}),
453%   reply(State, "200 OK", [{'Cseq', seq(Headers)}]);
454
455handle_request({request, 'SETUP', URL, Headers, Body}, #rtsp_socket{} = Socket) ->
456  Transport = proplists:get_value('Transport', Headers),
457  case proplists:get_value(mode, Transport) of
458    'receive' -> rtsp_inbound:handle_receive_setup(Socket, URL, Headers, Body);
459    _ -> rtsp_outbound:handle_play_setup(Socket, URL, Headers, Body)
460  end;
461
462handle_request({request, 'GET_PARAMETER', URL, Headers, Body}, #rtsp_socket{} = Socket) ->
463  handle_request({request, 'OPTIONS', URL, Headers, Body}, Socket);
464
465handle_request({request, 'TEARDOWN', _URL, Headers, _Body}, #rtsp_socket{} = State) ->
466  reply(State, "200 OK", [{'Cseq', seq(Headers)}]).
467
468
469
470reply(State, Code, Headers) ->
471  reply(State, Code, Headers, undefined).
472
473reply(#rtsp_socket{socket = Socket} = State, Code, Headers, Body) ->
474  {State1, Headers1} = append_session(State, Headers),
475  Headers2 = case Body of
476    undefined -> Headers1;
477    _ -> [{'Content-Length', iolist_size(Body)}, {'Content-Type', <<"application/sdp">>}|Headers1]
478  end,
479  Reply = iolist_to_binary(["RTSP/1.0 ", Code, <<"\r\n">>, packet_codec:encode_headers(Headers2), <<"\r\n">>,
480  case Body of
481    undefined -> <<>>;
482    _ -> Body
483  end]),
484  io:format("[RTSP Response to Client]~n~s", [Reply]),
485  gen_tcp:send(Socket, Reply),
486  State1.
487
488
489
490extract_session(Socket, Headers) ->
491  case proplists:get_value('Session', Headers) of
492    undefined ->
493      Socket;
494    FullSession ->
495      Socket#rtsp_socket{session = "Session: "++hd(string:tokens(binary_to_list(FullSession), ";"))++"\r\n"}
496  end.
497
498parse_session(Session) when is_binary(Session) -> parse_session(binary_to_list(Session));
499parse_session(Session) -> hd(string:tokens(Session, ";")).
500
501append_session(#rtsp_socket{session = undefined} = Socket, Headers) ->
502  case proplists:get_value('Session', Headers) of
503    undefined -> {Socket, Headers};
504    Session -> {Socket#rtsp_socket{session = parse_session(Session)}, Headers}
505  end;
506
507append_session(#rtsp_socket{session = Session, timeout = Timeout} = Socket, Headers) ->
508  Sess = lists:flatten(io_lib:format("~s;timeout=~p", [Session, Timeout div 1000])),
509  {Socket#rtsp_socket{session = Session}, [{'Session', Sess}|Headers]}.
510
511
512
513send_teardown(#rtsp_socket{socket = undefined}) ->
514  % ?D({warning, teardown,"on closed socket"}),
515  ok;
516
517send_teardown(#rtsp_socket{socket = Socket, url = URL, auth = Auth, seq = Seq} = RTSP) ->
518  Call = io_lib:format("TEARDOWN ~s RTSP/1.0\r\nCSeq: ~p\r\nAccept: application/sdp\r\n"++Auth("TEARDOWN", URL)++"\r\n", [URL, Seq+1]),
519  gen_tcp:send(Socket, Call),
520  rtsp_inbound:dump_io(RTSP, Call),
521  gen_tcp:close(Socket).
522
523
524%%-------------------------------------------------------------------------
525%% @spec (Reason, State) -> any
526%% @doc  Callback executed on server shutdown. It is only invoked if
527%%       `process_flag(trap_exit, true)' is set by the server process.
528%%       The return value is ignored.
529%% @end
530%% @private
531%%-------------------------------------------------------------------------
532terminate(_Reason, RTSP) ->
533  send_teardown(RTSP),
534  ok.
535
536%%-------------------------------------------------------------------------
537%% @spec (OldVsn, State, Extra) -> {ok, NewState}
538%% @doc  Convert process state when code is changed.
539%% @end
540%% @private
541%%-------------------------------------------------------------------------
542code_change(_OldVsn, State, _Extra) ->
543  {ok, State}.
544
545
546
547to_hex(L) when is_binary(L) ->
548  to_hex(binary_to_list(L));
549
550to_hex(L) when is_list(L) -> 
551  list_to_binary(lists:flatten(lists:map(fun(X) -> int_to_hex(X) end, L))).
552
553int_to_hex(N) when N < 256 ->
554  [hex(N div 16), hex(N rem 16)].
555
556hex(N) when N < 10 ->
557  $0+N;
558hex(N) when N >= 10, N < 16 ->
559  $a + (N-10).
560
561empty_auth(_Method, _URL) ->
562  "".
563
564
565digest_auth(Digest, Username, Password, URL, Request) ->
566  Realm = proplists:get_value(realm, Digest),
567  Nonce = proplists:get_value(nonce, Digest),
568  % CNonce = to_hex(crypto:md5(iolist_to_binary([Username, ":erlyvideo:", Password]))),
569  % CNonce = <<>>,
570  % NonceCount = <<"00000000">>,
571  _Qop = proplists:get_value(qop, Digest),
572
573  % <<"auth">> == Qop orelse erlang:throw({unsupported_digest_auth, Qop}),
574  HA1 = to_hex(crypto:md5(iolist_to_binary([Username, ":", Realm, ":", Password]))),
575  HA2 = to_hex(crypto:md5(iolist_to_binary([Request, ":", URL]))),
576  Response = to_hex(crypto:md5(iolist_to_binary([HA1, ":", Nonce, ":", HA2]))),
577
578
579  DigestAuth = io_lib:format("Authorization: Digest username=\"~s\", realm=\"~s\", nonce=\"~s\", uri=\"~s\", response=\"~s\"\r\n",
580  [Username, Realm, Nonce, URL, Response]),
581  lists:flatten(DigestAuth).
582
583
584
585-include_lib("eunit/include/eunit.hrl").
586
587digest_auth_test() ->
588  ?assertEqual("Authorization: Digest username=\"admin\", realm=\"Avigilon-12045784\", nonce=\"dh9U5wffmjzbGZguCeXukieLz277ckKgelszUk86230000\", uri=\"rtsp://admin:admin@94.80.16.122:554/defaultPrimary0?streamType=u\", response=\"99a9e6b080a96e25547b9425ff5d68bf\"\r\n",
589  digest_auth([{realm, <<"Avigilon-12045784">>}, {nonce, <<"dh9U5wffmjzbGZguCeXukieLz277ckKgelszUk86230000">>}, {qop, <<"auth">>}], "admin", "admin", "rtsp://admin:admin@94.80.16.122:554/defaultPrimary0?streamType=u", "OPTIONS")).
590