/src/gpac/src/ietf/rtsp_common.c
Line | Count | Source |
1 | | /* |
2 | | * GPAC - Multimedia Framework C SDK |
3 | | * |
4 | | * Authors: Jean Le Feuvre |
5 | | * Copyright (c) Telecom ParisTech 2000-2022 |
6 | | * All rights reserved |
7 | | * |
8 | | * This file is part of GPAC / IETF RTP/RTSP/SDP sub-project |
9 | | * |
10 | | * GPAC is free software; you can redistribute it and/or modify |
11 | | * it under the terms of the GNU Lesser General Public License as published by |
12 | | * the Free Software Foundation; either version 2, or (at your option) |
13 | | * any later version. |
14 | | * |
15 | | * GPAC is distributed in the hope that it will be useful, |
16 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
17 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
18 | | * GNU Lesser General Public License for more details. |
19 | | * |
20 | | * You should have received a copy of the GNU Lesser General Public |
21 | | * License along with this library; see the file COPYING. If not, write to |
22 | | * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. |
23 | | * |
24 | | */ |
25 | | |
26 | | #include <gpac/internal/ietf_dev.h> |
27 | | |
28 | | #ifndef GPAC_DISABLE_STREAMING |
29 | | |
30 | | #include <gpac/token.h> |
31 | | |
32 | | |
33 | | GF_Err gf_rtsp_read_reply(GF_RTSPSession *sess) |
34 | 0 | { |
35 | 0 | GF_Err e; |
36 | 0 | u32 res, body_size = 0; |
37 | 0 | u32 BodyStart = 0; |
38 | | |
39 | | //fetch more data on the socket if needed |
40 | 0 | while (1) { |
41 | | //Locate header / body |
42 | 0 | if (!BodyStart) gf_rtsp_get_body_info(sess, &BodyStart, &body_size, GF_FALSE); |
43 | |
|
44 | 0 | if (BodyStart) { |
45 | | //enough data |
46 | 0 | res = sess->CurrentSize - sess->CurrentPos; |
47 | 0 | if (!body_size || (res >= body_size + BodyStart)) { |
48 | | //done |
49 | 0 | break; |
50 | 0 | } |
51 | 0 | } |
52 | | //this is the tricky part: if we do NOT have a body start -> we refill |
53 | 0 | e = gf_rtsp_refill_buffer(sess); |
54 | 0 | if (e) { |
55 | | //we have a buffer, nothing to follow and this is an http tunnel, this means no '=' at the end of the base64 string |
56 | | //assume we got the entire message |
57 | 0 | if ((e==GF_IP_NETWORK_EMPTY) && (sess->CurrentPos<sess->CurrentSize) && (sess->tunnel_mode==RTSP_HTTP_SERVER)) { |
58 | 0 | return GF_OK; |
59 | 0 | } |
60 | 0 | return e; |
61 | 0 | } |
62 | 0 | } |
63 | 0 | return GF_OK; |
64 | 0 | } |
65 | | |
66 | | void gf_rtsp_get_body_info(GF_RTSPSession *sess, u32 *body_start, u32 *body_size, Bool skip_tunnel) |
67 | 0 | { |
68 | 0 | s32 start; |
69 | 0 | Bool is_post=GF_FALSE; |
70 | 0 | char *buffer; |
71 | 0 | char *cl_str; |
72 | |
|
73 | 0 | *body_start = *body_size = 0; |
74 | |
|
75 | 0 | buffer = sess->tcp_buffer + sess->CurrentPos; |
76 | 0 | if (!skip_tunnel && (sess->tunnel_mode==RTSP_HTTP_SERVER)) { |
77 | 0 | start = gf_token_find(buffer, 0, sess->CurrentSize - sess->CurrentPos, "="); |
78 | 0 | if (start<=0) return; |
79 | 0 | *body_start = start + 1; |
80 | 0 | if (buffer[start+1]=='=') |
81 | 0 | *body_start += 1; |
82 | 0 | return; |
83 | 0 | } else { |
84 | 0 | start = gf_token_find(buffer, 0, sess->CurrentSize - sess->CurrentPos, "\r\n\r\n"); |
85 | 0 | if (start<=0) return; |
86 | | //if found add the 2 "\r\n" and parse it |
87 | 0 | *body_start = start + 4; |
88 | 0 | } |
89 | 0 | if (!strncmp(buffer, "POST ", 5)) { |
90 | 0 | is_post = GF_TRUE; |
91 | 0 | } |
92 | | |
93 | | //get the content length |
94 | 0 | cl_str = strstr(buffer, "Content-Length: "); |
95 | 0 | if (!cl_str) cl_str = strstr(buffer, "Content-length: "); |
96 | |
|
97 | 0 | if (cl_str) { |
98 | 0 | char *sep; |
99 | 0 | cl_str += 16; |
100 | 0 | sep = strchr(cl_str, '\r'); |
101 | 0 | if (sep) { |
102 | 0 | sep[0] = 0; |
103 | 0 | *body_size = atoi(cl_str); |
104 | 0 | sep[0] = '\r'; |
105 | 0 | } |
106 | 0 | } |
107 | 0 | if (is_post) *body_size = 0; |
108 | 0 | } |
109 | | |
110 | | |
111 | | GF_Err gf_rstp_do_read_sock(GF_RTSPSession *sess, GF_Socket *sock, u8 *data, u32 data_size, u32 *out_read) |
112 | 0 | { |
113 | 0 | GF_Err e; |
114 | 0 | #ifdef GPAC_HAS_SSL |
115 | 0 | SSL *ssl_sock = (sock == sess->http) ? sess->ssl_http : sess->ssl; |
116 | 0 | if (ssl_sock) { |
117 | | //receive on null buffer (select only, check if data available) |
118 | 0 | e = gf_sk_receive(sock, NULL, 0, NULL); |
119 | | //empty and no pending bytes in SSL, network empty |
120 | 0 | if ((e==GF_IP_NETWORK_EMPTY) && !SSL_pending(sess->ssl)) |
121 | 0 | return GF_IP_NETWORK_EMPTY; |
122 | | |
123 | 0 | int size = SSL_read(ssl_sock, data, data_size); |
124 | 0 | if (size < 0) { |
125 | 0 | *out_read = 0; |
126 | 0 | e = gf_sk_probe(sock); |
127 | 0 | if (!e) { |
128 | 0 | int err = SSL_get_error(ssl_sock, size); |
129 | 0 | if (err==SSL_ERROR_SSL) { |
130 | 0 | e = GF_IO_ERR; |
131 | 0 | } else if ((err==SSL_ERROR_WANT_READ) || (err==SSL_ERROR_WANT_WRITE)) { |
132 | 0 | e = GF_IP_NETWORK_EMPTY; |
133 | 0 | } else { |
134 | 0 | e = GF_IP_NETWORK_FAILURE; |
135 | 0 | } |
136 | 0 | } |
137 | 0 | } else if (!size) { |
138 | 0 | e = GF_IP_NETWORK_EMPTY; |
139 | 0 | } else { |
140 | 0 | e = GF_OK; |
141 | 0 | data[size] = 0; |
142 | 0 | *out_read = size; |
143 | 0 | } |
144 | 0 | } else |
145 | 0 | #endif |
146 | 0 | { |
147 | 0 | e = gf_sk_receive(sock, data, data_size, out_read); |
148 | 0 | } |
149 | 0 | return e; |
150 | 0 | } |
151 | | |
152 | | |
153 | | |
154 | | GF_Err gf_rtsp_refill_buffer(GF_RTSPSession *sess) |
155 | 0 | { |
156 | 0 | GF_Err e; |
157 | 0 | u32 res; |
158 | 0 | char *ptr; |
159 | |
|
160 | 0 | if (!sess) return GF_BAD_PARAM; |
161 | 0 | if (!sess->connection) return GF_IP_NETWORK_EMPTY; |
162 | | |
163 | 0 | res = sess->CurrentSize - sess->CurrentPos; |
164 | 0 | if (!res) return gf_rtsp_fill_buffer(sess); |
165 | | |
166 | 0 | ptr = (char *)gf_malloc(sizeof(char) * res); |
167 | 0 | memcpy(ptr, sess->tcp_buffer + sess->CurrentPos, res); |
168 | 0 | memcpy(sess->tcp_buffer, ptr, res); |
169 | 0 | gf_free(ptr); |
170 | |
|
171 | 0 | sess->CurrentPos = 0; |
172 | 0 | sess->CurrentSize = res; |
173 | | |
174 | | //now read from current pos |
175 | 0 | e = gf_rstp_do_read_sock(sess, sess->connection, sess->tcp_buffer + sess->CurrentSize, sess->SockBufferSize - sess->CurrentSize, &res); |
176 | |
|
177 | 0 | if (!e) { |
178 | 0 | sess->CurrentSize += res; |
179 | 0 | } |
180 | 0 | return e; |
181 | 0 | } |
182 | | |
183 | | GF_Err gf_rtsp_fill_buffer(GF_RTSPSession *sess) |
184 | 0 | { |
185 | 0 | GF_Err e = GF_OK; |
186 | |
|
187 | 0 | if (!sess->connection) return GF_IP_NETWORK_EMPTY; |
188 | | |
189 | 0 | if (sess->CurrentSize == sess->CurrentPos) { |
190 | 0 | if (sess->tunnel_mode==RTSP_HTTP_SERVER) { |
191 | 0 | e = gf_rstp_do_read_sock(sess, sess->http, sess->tcp_buffer, sess->SockBufferSize, &sess->CurrentSize); |
192 | 0 | } else { |
193 | 0 | e = gf_rstp_do_read_sock(sess, sess->connection, sess->tcp_buffer, sess->SockBufferSize, &sess->CurrentSize); |
194 | 0 | } |
195 | 0 | sess->CurrentPos = 0; |
196 | 0 | sess->tcp_buffer[sess->CurrentSize] = 0; |
197 | 0 | if (e) sess->CurrentSize = 0; |
198 | 0 | } else if (!sess->CurrentSize) e = GF_IP_NETWORK_EMPTY; |
199 | 0 | return e; |
200 | 0 | } |
201 | | |
202 | | |
203 | | GF_RTSPTransport *gf_rtsp_transport_parse(u8 *buffer) |
204 | 0 | { |
205 | 0 | Bool IsFirst; |
206 | 0 | char buf[100], param_name[100], param_val[100]; |
207 | 0 | s32 pos, nPos; |
208 | 0 | u32 v1, v2; |
209 | 0 | GF_RTSPTransport *tmp; |
210 | 0 | if (!buffer) return NULL; |
211 | | //only support for RTP/AVP for now |
212 | 0 | if (strnicmp(buffer, "RTP/AVP", 7) && strnicmp(buffer, "RTP/SAVP", 8)) return NULL; |
213 | | |
214 | 0 | GF_SAFEALLOC(tmp, GF_RTSPTransport); |
215 | 0 | if (!tmp) return NULL; |
216 | | |
217 | 0 | IsFirst = GF_TRUE; |
218 | 0 | pos = 0; |
219 | 0 | while (1) { |
220 | 0 | pos = gf_token_get(buffer, pos, " ;", buf, 100); |
221 | 0 | if (pos <= 0) break; |
222 | 0 | if (strstr(buf, "=")) { |
223 | 0 | nPos = gf_token_get(buf, 0, "=", param_name, 100); |
224 | 0 | /*nPos = */gf_token_get(buf, nPos, "=", param_val, 100); |
225 | 0 | } else { |
226 | 0 | strcpy(param_name, buf); |
227 | 0 | } |
228 | | |
229 | | //very first param is the profile |
230 | 0 | if (IsFirst) { |
231 | 0 | tmp->Profile = gf_strdup(param_name); |
232 | 0 | IsFirst = GF_FALSE; |
233 | 0 | continue; |
234 | 0 | } |
235 | | |
236 | 0 | if (!stricmp(param_name, "destination")) { |
237 | 0 | if (tmp->destination) gf_free(tmp->destination); |
238 | 0 | tmp->destination = gf_strdup(param_val); |
239 | 0 | } |
240 | 0 | else if (!stricmp(param_name, "source")) { |
241 | 0 | if (tmp->source) gf_free(tmp->source); |
242 | 0 | tmp->source = gf_strdup(param_val); |
243 | 0 | } |
244 | 0 | else if (!stricmp(param_name, "unicast")) tmp->IsUnicast = GF_TRUE; |
245 | 0 | else if (!stricmp(param_name, "RECORD")) tmp->IsRecord = GF_TRUE; |
246 | 0 | else if (!stricmp(param_name, "append")) tmp->Append = GF_TRUE; |
247 | 0 | else if (!stricmp(param_name, "interleaved")) { |
248 | 0 | u32 rID, rcID; |
249 | 0 | tmp->IsInterleaved = GF_TRUE; |
250 | 0 | if (sscanf(param_val, "%u-%u", &rID, &rcID) == 1) { |
251 | 0 | sscanf(param_val, "%u", &rID); |
252 | 0 | tmp->rtcpID = tmp->rtpID = (u8) rID; |
253 | 0 | } else { |
254 | 0 | tmp->rtpID = (u8) rID; |
255 | 0 | tmp->rtcpID = (u8) rcID; |
256 | 0 | } |
257 | 0 | } |
258 | 0 | else if (!stricmp(param_name, "layers")) sscanf(param_val, "%u", &tmp->MulticastLayers); |
259 | 0 | else if (!stricmp(param_name, "ttl")) sscanf(param_val, "%c ", &tmp->TTL); |
260 | 0 | else if (!stricmp(param_name, "port")) { |
261 | 0 | sscanf(param_val, "%u-%u", &v1, &v2); |
262 | 0 | tmp->port_first = (u16) v1; |
263 | 0 | tmp->port_last = (u16) v2; |
264 | 0 | } |
265 | | /*do not use %hud here, broken on Win32 (sscanf returns 1)*/ |
266 | 0 | else if (!stricmp(param_name, "server_port")) { |
267 | 0 | sscanf(param_val, "%d-%d", &v1, &v2); |
268 | 0 | tmp->port_first = (u16) v1; |
269 | 0 | tmp->port_last = (u16) v2; |
270 | 0 | } |
271 | | /*do not use %hud here, broken on Win32 (sscanf returns 1)*/ |
272 | 0 | else if (!stricmp(param_name, "client_port")) { |
273 | 0 | sscanf(param_val, "%d-%d", &v1, &v2); |
274 | 0 | tmp->client_port_first = (u16) v1; |
275 | 0 | tmp->client_port_last = (u16) v2; |
276 | 0 | } |
277 | 0 | else if (!stricmp(param_name, "ssrc")) sscanf(param_val, "%X", &tmp->SSRC); |
278 | 0 | } |
279 | 0 | return tmp; |
280 | 0 | } |
281 | | |
282 | | |
283 | | |
284 | | GF_Err gf_rtsp_parse_header(u8 *buffer, u32 BufferSize, u32 BodyStart, GF_RTSPCommand *com, GF_RTSPResponse *rsp) |
285 | 0 | { |
286 | 0 | char LineBuffer[1024]; |
287 | 0 | char HeaderBuf[100], ValBuf[1024], temp[400]; |
288 | 0 | s32 Pos, LinePos; |
289 | 0 | u32 HeaderLine; |
290 | | |
291 | | //then parse the full header |
292 | 0 | LinePos = 0; |
293 | 0 | strcpy(HeaderBuf, ""); |
294 | 0 | while (1) { |
295 | 0 | LinePos = gf_token_get_line(buffer, LinePos, BufferSize, LineBuffer, 1024); |
296 | 0 | if (LinePos <= 0) return GF_REMOTE_SERVICE_ERROR; |
297 | | |
298 | | //extract field header and value. Warning: some params (transport, ..) may be on several lines |
299 | 0 | Pos = gf_token_get(LineBuffer, 0, ":\r\n", temp, 400); |
300 | | |
301 | | //end of header |
302 | 0 | if (Pos <= 0) { |
303 | 0 | HeaderLine = 2; |
304 | 0 | } |
305 | | //this is a header |
306 | 0 | else if (LineBuffer[0] != ' ') { |
307 | 0 | HeaderLine = 1; |
308 | 0 | } else { |
309 | 0 | Pos = gf_token_get(LineBuffer, 0, ", \r\n", temp, 400); |
310 | | //end of header - process any pending one |
311 | 0 | if (Pos <= 0) { |
312 | 0 | HeaderLine = 2; |
313 | 0 | } else { |
314 | | //n-line value - append |
315 | 0 | strcat(ValBuf, "\r\n"); |
316 | 0 | strcat(ValBuf, temp); |
317 | 0 | continue; |
318 | 0 | } |
319 | 0 | } |
320 | | //process current value |
321 | 0 | if (HeaderLine && strlen(HeaderBuf)) { |
322 | 0 | if (rsp) { |
323 | 0 | gf_rtsp_set_response_value(rsp, HeaderBuf, ValBuf); |
324 | 0 | } |
325 | 0 | else { |
326 | 0 | gf_rtsp_set_command_value(com, HeaderBuf, ValBuf); |
327 | 0 | } |
328 | 0 | } |
329 | | //done with the header |
330 | 0 | if ( (HeaderLine == 2) || ((u32) LinePos >= BodyStart) ) return GF_OK; |
331 | | |
332 | | //process current line |
333 | 0 | strcpy(HeaderBuf, temp); |
334 | | |
335 | | //skip ':' |
336 | 0 | Pos += 1; |
337 | | //a server should normally reply with a space, but check it |
338 | 0 | if (LineBuffer[Pos] == ' ') Pos += 1; |
339 | | /*!! empty value !! - DSS may send these for CSeq if something goes wrong*/ |
340 | 0 | if (!strcmp(LineBuffer+Pos, "\r\n")) { |
341 | 0 | HeaderBuf[0] = 0; |
342 | 0 | continue; |
343 | 0 | } |
344 | 0 | Pos = gf_token_get(LineBuffer, Pos, "\r\n", ValBuf, 400); |
345 | 0 | if (Pos <= 0) break; |
346 | |
|
347 | 0 | } |
348 | | //if we get here we haven't reached the BodyStart |
349 | 0 | return GF_REMOTE_SERVICE_ERROR; |
350 | 0 | } |
351 | | |
352 | | |
353 | | GF_EXPORT |
354 | | const char *gf_rtsp_nc_to_string(u32 ErrCode) |
355 | 0 | { |
356 | 0 | switch (ErrCode) { |
357 | 0 | case NC_RTSP_Continue: |
358 | 0 | return "Continue"; |
359 | 0 | case NC_RTSP_OK: |
360 | 0 | return "OK"; |
361 | 0 | case NC_RTSP_Created: |
362 | 0 | return "Created"; |
363 | 0 | case NC_RTSP_Low_on_Storage_Space: |
364 | 0 | return "Low on Storage Space"; |
365 | 0 | case NC_RTSP_Multiple_Choice: |
366 | 0 | return "Multiple Choice"; |
367 | 0 | case NC_RTSP_Moved_Permanently: |
368 | 0 | return "Moved Permanently"; |
369 | 0 | case NC_RTSP_Moved_Temporarily: |
370 | 0 | return "Moved Temporarily"; |
371 | 0 | case NC_RTSP_See_Other: |
372 | 0 | return "See Other"; |
373 | 0 | case NC_RTSP_Use_Proxy: |
374 | 0 | return "Use Proxy"; |
375 | 0 | case NC_RTSP_Bad_Request: |
376 | 0 | return "Bad Request"; |
377 | 0 | case NC_RTSP_Unauthorized: |
378 | 0 | return "Unauthorized"; |
379 | 0 | case NC_RTSP_Payment_Required: |
380 | 0 | return "Payment Required"; |
381 | 0 | case NC_RTSP_Forbidden: |
382 | 0 | return "Forbidden"; |
383 | 0 | case NC_RTSP_Not_Found: |
384 | 0 | return "Not Found"; |
385 | 0 | case NC_RTSP_Method_Not_Allowed: |
386 | 0 | return "Method Not Allowed"; |
387 | 0 | case NC_RTSP_Not_Acceptable: |
388 | 0 | return "Not Acceptable"; |
389 | 0 | case NC_RTSP_Proxy_Authentication_Required: |
390 | 0 | return "Proxy Authentication Required"; |
391 | 0 | case NC_RTSP_Request_Timeout: |
392 | 0 | return "Request Timeout"; |
393 | 0 | case NC_RTSP_Gone: |
394 | 0 | return "Gone"; |
395 | 0 | case NC_RTSP_Length_Required: |
396 | 0 | return "Length Required"; |
397 | 0 | case NC_RTSP_Precondition_Failed: |
398 | 0 | return "Precondition Failed"; |
399 | 0 | case NC_RTSP_Request_Entity_Too_Large: |
400 | 0 | return "Request Entity Too Large"; |
401 | 0 | case NC_RTSP_Request_URI_Too_Long: |
402 | 0 | return "Request URI Too Long"; |
403 | 0 | case NC_RTSP_Unsupported_Media_Type: |
404 | 0 | return "Unsupported Media Type"; |
405 | 0 | case NC_RTSP_Invalid_parameter: |
406 | 0 | return "Invalid parameter"; |
407 | 0 | case NC_RTSP_Illegal_Conference_Identifier: |
408 | 0 | return "Illegal Conference Identifier"; |
409 | 0 | case NC_RTSP_Not_Enough_Bandwidth: |
410 | 0 | return "Not Enough Bandwidth"; |
411 | 0 | case NC_RTSP_Session_Not_Found: |
412 | 0 | return "Session Not Found"; |
413 | 0 | case NC_RTSP_Method_Not_Valid_In_This_State: |
414 | 0 | return "Method Not Valid In This State"; |
415 | 0 | case NC_RTSP_Header_Field_Not_Valid: |
416 | 0 | return "Header Field Not Valid"; |
417 | 0 | case NC_RTSP_Invalid_Range: |
418 | 0 | return "Invalid Range"; |
419 | 0 | case NC_RTSP_Parameter_Is_ReadOnly: |
420 | 0 | return "Parameter Is Read-Only"; |
421 | 0 | case NC_RTSP_Aggregate_Operation_Not_Allowed: |
422 | 0 | return "Aggregate Operation Not Allowed"; |
423 | 0 | case NC_RTSP_Only_Aggregate_Operation_Allowed: |
424 | 0 | return "Only Aggregate Operation Allowed"; |
425 | 0 | case NC_RTSP_Unsupported_Transport: |
426 | 0 | return "Unsupported Transport"; |
427 | 0 | case NC_RTSP_Destination_Unreachable: |
428 | 0 | return "Destination Unreachable"; |
429 | 0 | case NC_RTSP_Internal_Server_Error: |
430 | 0 | return "Internal Server Error"; |
431 | 0 | case NC_RTSP_Bad_Gateway: |
432 | 0 | return "Bad Gateway"; |
433 | 0 | case NC_RTSP_Service_Unavailable: |
434 | 0 | return "Service Unavailable"; |
435 | 0 | case NC_RTSP_Gateway_Timeout: |
436 | 0 | return "Gateway Timeout"; |
437 | 0 | case NC_RTSP_RTSP_Version_Not_Supported: |
438 | 0 | return "RTSP Version Not Supported"; |
439 | 0 | case NC_RTSP_Option_not_support: |
440 | 0 | return "Option not support"; |
441 | | |
442 | 0 | case NC_RTSP_Not_Implemented: |
443 | 0 | default: |
444 | 0 | return "Not Implemented"; |
445 | 0 | } |
446 | 0 | } |
447 | | |
448 | | #endif /*GPAC_DISABLE_STREAMING*/ |