Go to most recent revision | Details | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
4349 | Serge | 1 | /* |
2 | * RTMP network protocol |
||
3 | * Copyright (c) 2009 Konstantin Shishkov |
||
4 | * |
||
5 | * This file is part of FFmpeg. |
||
6 | * |
||
7 | * FFmpeg is free software; you can redistribute it and/or |
||
8 | * modify it under the terms of the GNU Lesser General Public |
||
9 | * License as published by the Free Software Foundation; either |
||
10 | * version 2.1 of the License, or (at your option) any later version. |
||
11 | * |
||
12 | * FFmpeg is distributed in the hope that it will be useful, |
||
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
||
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
||
15 | * Lesser General Public License for more details. |
||
16 | * |
||
17 | * You should have received a copy of the GNU Lesser General Public |
||
18 | * License along with FFmpeg; if not, write to the Free Software |
||
19 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
||
20 | */ |
||
21 | |||
22 | /** |
||
23 | * @file |
||
24 | * RTMP protocol |
||
25 | */ |
||
26 | |||
27 | #include "libavcodec/bytestream.h" |
||
28 | #include "libavutil/avstring.h" |
||
29 | #include "libavutil/base64.h" |
||
30 | #include "libavutil/intfloat.h" |
||
31 | #include "libavutil/lfg.h" |
||
32 | #include "libavutil/md5.h" |
||
33 | #include "libavutil/opt.h" |
||
34 | #include "libavutil/random_seed.h" |
||
35 | #include "libavutil/sha.h" |
||
36 | #include "avformat.h" |
||
37 | #include "internal.h" |
||
38 | |||
39 | #include "network.h" |
||
40 | |||
41 | #include "flv.h" |
||
42 | #include "rtmp.h" |
||
43 | #include "rtmpcrypt.h" |
||
44 | #include "rtmppkt.h" |
||
45 | #include "url.h" |
||
46 | |||
47 | #if CONFIG_ZLIB |
||
48 | #include |
||
49 | #endif |
||
50 | |||
51 | #define APP_MAX_LENGTH 1024 |
||
52 | #define PLAYPATH_MAX_LENGTH 256 |
||
53 | #define TCURL_MAX_LENGTH 512 |
||
54 | #define FLASHVER_MAX_LENGTH 64 |
||
55 | #define RTMP_PKTDATA_DEFAULT_SIZE 4096 |
||
56 | #define RTMP_HEADER 11 |
||
57 | |||
58 | /** RTMP protocol handler state */ |
||
59 | typedef enum { |
||
60 | STATE_START, ///< client has not done anything yet |
||
61 | STATE_HANDSHAKED, ///< client has performed handshake |
||
62 | STATE_FCPUBLISH, ///< client FCPublishing stream (for output) |
||
63 | STATE_PLAYING, ///< client has started receiving multimedia data from server |
||
64 | STATE_SEEKING, ///< client has started the seek operation. Back on STATE_PLAYING when the time comes |
||
65 | STATE_PUBLISHING, ///< client has started sending multimedia data to server (for output) |
||
66 | STATE_RECEIVING, ///< received a publish command (for input) |
||
67 | STATE_SENDING, ///< received a play command (for output) |
||
68 | STATE_STOPPED, ///< the broadcast has been stopped |
||
69 | } ClientState; |
||
70 | |||
71 | typedef struct TrackedMethod { |
||
72 | char *name; |
||
73 | int id; |
||
74 | } TrackedMethod; |
||
75 | |||
76 | /** protocol handler context */ |
||
77 | typedef struct RTMPContext { |
||
78 | const AVClass *class; |
||
79 | URLContext* stream; ///< TCP stream used in interactions with RTMP server |
||
80 | RTMPPacket *prev_pkt[2]; ///< packet history used when reading and sending packets ([0] for reading, [1] for writing) |
||
81 | int nb_prev_pkt[2]; ///< number of elements in prev_pkt |
||
82 | int in_chunk_size; ///< size of the chunks incoming RTMP packets are divided into |
||
83 | int out_chunk_size; ///< size of the chunks outgoing RTMP packets are divided into |
||
84 | int is_input; ///< input/output flag |
||
85 | char *playpath; ///< stream identifier to play (with possible "mp4:" prefix) |
||
86 | int live; ///< 0: recorded, -1: live, -2: both |
||
87 | char *app; ///< name of application |
||
88 | char *conn; ///< append arbitrary AMF data to the Connect message |
||
89 | ClientState state; ///< current state |
||
90 | int stream_id; ///< ID assigned by the server for the stream |
||
91 | uint8_t* flv_data; ///< buffer with data for demuxer |
||
92 | int flv_size; ///< current buffer size |
||
93 | int flv_off; ///< number of bytes read from current buffer |
||
94 | int flv_nb_packets; ///< number of flv packets published |
||
95 | RTMPPacket out_pkt; ///< rtmp packet, created from flv a/v or metadata (for output) |
||
96 | uint32_t client_report_size; ///< number of bytes after which client should report to server |
||
97 | uint32_t bytes_read; ///< number of bytes read from server |
||
98 | uint32_t last_bytes_read; ///< number of bytes read last reported to server |
||
99 | int skip_bytes; ///< number of bytes to skip from the input FLV stream in the next write call |
||
100 | uint8_t flv_header[RTMP_HEADER]; ///< partial incoming flv packet header |
||
101 | int flv_header_bytes; ///< number of initialized bytes in flv_header |
||
102 | int nb_invokes; ///< keeps track of invoke messages |
||
103 | char* tcurl; ///< url of the target stream |
||
104 | char* flashver; ///< version of the flash plugin |
||
105 | char* swfhash; ///< SHA256 hash of the decompressed SWF file (32 bytes) |
||
106 | int swfhash_len; ///< length of the SHA256 hash |
||
107 | int swfsize; ///< size of the decompressed SWF file |
||
108 | char* swfurl; ///< url of the swf player |
||
109 | char* swfverify; ///< URL to player swf file, compute hash/size automatically |
||
110 | char swfverification[42]; ///< hash of the SWF verification |
||
111 | char* pageurl; ///< url of the web page |
||
112 | char* subscribe; ///< name of live stream to subscribe |
||
113 | int server_bw; ///< server bandwidth |
||
114 | int client_buffer_time; ///< client buffer time in ms |
||
115 | int flush_interval; ///< number of packets flushed in the same request (RTMPT only) |
||
116 | int encrypted; ///< use an encrypted connection (RTMPE only) |
||
117 | TrackedMethod*tracked_methods; ///< tracked methods buffer |
||
118 | int nb_tracked_methods; ///< number of tracked methods |
||
119 | int tracked_methods_size; ///< size of the tracked methods buffer |
||
120 | int listen; ///< listen mode flag |
||
121 | int listen_timeout; ///< listen timeout to wait for new connections |
||
122 | int nb_streamid; ///< The next stream id to return on createStream calls |
||
123 | char username[50]; |
||
124 | char password[50]; |
||
125 | char auth_params[500]; |
||
126 | int do_reconnect; |
||
127 | int auth_tried; |
||
128 | } RTMPContext; |
||
129 | |||
130 | #define PLAYER_KEY_OPEN_PART_LEN 30 ///< length of partial key used for first client digest signing |
||
131 | /** Client key used for digest signing */ |
||
132 | static const uint8_t rtmp_player_key[] = { |
||
133 | 'G', 'e', 'n', 'u', 'i', 'n', 'e', ' ', 'A', 'd', 'o', 'b', 'e', ' ', |
||
134 | 'F', 'l', 'a', 's', 'h', ' ', 'P', 'l', 'a', 'y', 'e', 'r', ' ', '0', '0', '1', |
||
135 | |||
136 | 0xF0, 0xEE, 0xC2, 0x4A, 0x80, 0x68, 0xBE, 0xE8, 0x2E, 0x00, 0xD0, 0xD1, 0x02, |
||
137 | 0x9E, 0x7E, 0x57, 0x6E, 0xEC, 0x5D, 0x2D, 0x29, 0x80, 0x6F, 0xAB, 0x93, 0xB8, |
||
138 | 0xE6, 0x36, 0xCF, 0xEB, 0x31, 0xAE |
||
139 | }; |
||
140 | |||
141 | #define SERVER_KEY_OPEN_PART_LEN 36 ///< length of partial key used for first server digest signing |
||
142 | /** Key used for RTMP server digest signing */ |
||
143 | static const uint8_t rtmp_server_key[] = { |
||
144 | 'G', 'e', 'n', 'u', 'i', 'n', 'e', ' ', 'A', 'd', 'o', 'b', 'e', ' ', |
||
145 | 'F', 'l', 'a', 's', 'h', ' ', 'M', 'e', 'd', 'i', 'a', ' ', |
||
146 | 'S', 'e', 'r', 'v', 'e', 'r', ' ', '0', '0', '1', |
||
147 | |||
148 | 0xF0, 0xEE, 0xC2, 0x4A, 0x80, 0x68, 0xBE, 0xE8, 0x2E, 0x00, 0xD0, 0xD1, 0x02, |
||
149 | 0x9E, 0x7E, 0x57, 0x6E, 0xEC, 0x5D, 0x2D, 0x29, 0x80, 0x6F, 0xAB, 0x93, 0xB8, |
||
150 | 0xE6, 0x36, 0xCF, 0xEB, 0x31, 0xAE |
||
151 | }; |
||
152 | |||
153 | static int add_tracked_method(RTMPContext *rt, const char *name, int id) |
||
154 | { |
||
155 | int err; |
||
156 | |||
157 | if (rt->nb_tracked_methods + 1 > rt->tracked_methods_size) { |
||
158 | rt->tracked_methods_size = (rt->nb_tracked_methods + 1) * 2; |
||
159 | if ((err = av_reallocp(&rt->tracked_methods, rt->tracked_methods_size * |
||
160 | sizeof(*rt->tracked_methods))) < 0) { |
||
161 | rt->nb_tracked_methods = 0; |
||
162 | rt->tracked_methods_size = 0; |
||
163 | return err; |
||
164 | } |
||
165 | } |
||
166 | |||
167 | rt->tracked_methods[rt->nb_tracked_methods].name = av_strdup(name); |
||
168 | if (!rt->tracked_methods[rt->nb_tracked_methods].name) |
||
169 | return AVERROR(ENOMEM); |
||
170 | rt->tracked_methods[rt->nb_tracked_methods].id = id; |
||
171 | rt->nb_tracked_methods++; |
||
172 | |||
173 | return 0; |
||
174 | } |
||
175 | |||
176 | static void del_tracked_method(RTMPContext *rt, int index) |
||
177 | { |
||
178 | memmove(&rt->tracked_methods[index], &rt->tracked_methods[index + 1], |
||
179 | sizeof(*rt->tracked_methods) * (rt->nb_tracked_methods - index - 1)); |
||
180 | rt->nb_tracked_methods--; |
||
181 | } |
||
182 | |||
183 | static int find_tracked_method(URLContext *s, RTMPPacket *pkt, int offset, |
||
184 | char **tracked_method) |
||
185 | { |
||
186 | RTMPContext *rt = s->priv_data; |
||
187 | GetByteContext gbc; |
||
188 | double pkt_id; |
||
189 | int ret; |
||
190 | int i; |
||
191 | |||
192 | bytestream2_init(&gbc, pkt->data + offset, pkt->size - offset); |
||
193 | if ((ret = ff_amf_read_number(&gbc, &pkt_id)) < 0) |
||
194 | return ret; |
||
195 | |||
196 | for (i = 0; i < rt->nb_tracked_methods; i++) { |
||
197 | if (rt->tracked_methods[i].id != pkt_id) |
||
198 | continue; |
||
199 | |||
200 | *tracked_method = rt->tracked_methods[i].name; |
||
201 | del_tracked_method(rt, i); |
||
202 | break; |
||
203 | } |
||
204 | |||
205 | return 0; |
||
206 | } |
||
207 | |||
208 | static void free_tracked_methods(RTMPContext *rt) |
||
209 | { |
||
210 | int i; |
||
211 | |||
212 | for (i = 0; i < rt->nb_tracked_methods; i ++) |
||
213 | av_free(rt->tracked_methods[i].name); |
||
214 | av_free(rt->tracked_methods); |
||
215 | rt->tracked_methods = NULL; |
||
216 | rt->tracked_methods_size = 0; |
||
217 | rt->nb_tracked_methods = 0; |
||
218 | } |
||
219 | |||
220 | static int rtmp_send_packet(RTMPContext *rt, RTMPPacket *pkt, int track) |
||
221 | { |
||
222 | int ret; |
||
223 | |||
224 | if (pkt->type == RTMP_PT_INVOKE && track) { |
||
225 | GetByteContext gbc; |
||
226 | char name[128]; |
||
227 | double pkt_id; |
||
228 | int len; |
||
229 | |||
230 | bytestream2_init(&gbc, pkt->data, pkt->size); |
||
231 | if ((ret = ff_amf_read_string(&gbc, name, sizeof(name), &len)) < 0) |
||
232 | goto fail; |
||
233 | |||
234 | if ((ret = ff_amf_read_number(&gbc, &pkt_id)) < 0) |
||
235 | goto fail; |
||
236 | |||
237 | if ((ret = add_tracked_method(rt, name, pkt_id)) < 0) |
||
238 | goto fail; |
||
239 | } |
||
240 | |||
241 | ret = ff_rtmp_packet_write(rt->stream, pkt, rt->out_chunk_size, |
||
242 | &rt->prev_pkt[1], &rt->nb_prev_pkt[1]); |
||
243 | fail: |
||
244 | ff_rtmp_packet_destroy(pkt); |
||
245 | return ret; |
||
246 | } |
||
247 | |||
248 | static int rtmp_write_amf_data(URLContext *s, char *param, uint8_t **p) |
||
249 | { |
||
250 | char *field, *value; |
||
251 | char type; |
||
252 | |||
253 | /* The type must be B for Boolean, N for number, S for string, O for |
||
254 | * object, or Z for null. For Booleans the data must be either 0 or 1 for |
||
255 | * FALSE or TRUE, respectively. Likewise for Objects the data must be |
||
256 | * 0 or 1 to end or begin an object, respectively. Data items in subobjects |
||
257 | * may be named, by prefixing the type with 'N' and specifying the name |
||
258 | * before the value (ie. NB:myFlag:1). This option may be used multiple times |
||
259 | * to construct arbitrary AMF sequences. */ |
||
260 | if (param[0] && param[1] == ':') { |
||
261 | type = param[0]; |
||
262 | value = param + 2; |
||
263 | } else if (param[0] == 'N' && param[1] && param[2] == ':') { |
||
264 | type = param[1]; |
||
265 | field = param + 3; |
||
266 | value = strchr(field, ':'); |
||
267 | if (!value) |
||
268 | goto fail; |
||
269 | *value = '\0'; |
||
270 | value++; |
||
271 | |||
272 | ff_amf_write_field_name(p, field); |
||
273 | } else { |
||
274 | goto fail; |
||
275 | } |
||
276 | |||
277 | switch (type) { |
||
278 | case 'B': |
||
279 | ff_amf_write_bool(p, value[0] != '0'); |
||
280 | break; |
||
281 | case 'S': |
||
282 | ff_amf_write_string(p, value); |
||
283 | break; |
||
284 | case 'N': |
||
285 | ff_amf_write_number(p, strtod(value, NULL)); |
||
286 | break; |
||
287 | case 'Z': |
||
288 | ff_amf_write_null(p); |
||
289 | break; |
||
290 | case 'O': |
||
291 | if (value[0] != '0') |
||
292 | ff_amf_write_object_start(p); |
||
293 | else |
||
294 | ff_amf_write_object_end(p); |
||
295 | break; |
||
296 | default: |
||
297 | goto fail; |
||
298 | break; |
||
299 | } |
||
300 | |||
301 | return 0; |
||
302 | |||
303 | fail: |
||
304 | av_log(s, AV_LOG_ERROR, "Invalid AMF parameter: %s\n", param); |
||
305 | return AVERROR(EINVAL); |
||
306 | } |
||
307 | |||
308 | /** |
||
309 | * Generate 'connect' call and send it to the server. |
||
310 | */ |
||
311 | static int gen_connect(URLContext *s, RTMPContext *rt) |
||
312 | { |
||
313 | RTMPPacket pkt; |
||
314 | uint8_t *p; |
||
315 | int ret; |
||
316 | |||
317 | if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SYSTEM_CHANNEL, RTMP_PT_INVOKE, |
||
318 | 0, 4096 + APP_MAX_LENGTH)) < 0) |
||
319 | return ret; |
||
320 | |||
321 | p = pkt.data; |
||
322 | |||
323 | ff_amf_write_string(&p, "connect"); |
||
324 | ff_amf_write_number(&p, ++rt->nb_invokes); |
||
325 | ff_amf_write_object_start(&p); |
||
326 | ff_amf_write_field_name(&p, "app"); |
||
327 | ff_amf_write_string2(&p, rt->app, rt->auth_params); |
||
328 | |||
329 | if (!rt->is_input) { |
||
330 | ff_amf_write_field_name(&p, "type"); |
||
331 | ff_amf_write_string(&p, "nonprivate"); |
||
332 | } |
||
333 | ff_amf_write_field_name(&p, "flashVer"); |
||
334 | ff_amf_write_string(&p, rt->flashver); |
||
335 | |||
336 | if (rt->swfurl) { |
||
337 | ff_amf_write_field_name(&p, "swfUrl"); |
||
338 | ff_amf_write_string(&p, rt->swfurl); |
||
339 | } |
||
340 | |||
341 | ff_amf_write_field_name(&p, "tcUrl"); |
||
342 | ff_amf_write_string2(&p, rt->tcurl, rt->auth_params); |
||
343 | if (rt->is_input) { |
||
344 | ff_amf_write_field_name(&p, "fpad"); |
||
345 | ff_amf_write_bool(&p, 0); |
||
346 | ff_amf_write_field_name(&p, "capabilities"); |
||
347 | ff_amf_write_number(&p, 15.0); |
||
348 | |||
349 | /* Tell the server we support all the audio codecs except |
||
350 | * SUPPORT_SND_INTEL (0x0008) and SUPPORT_SND_UNUSED (0x0010) |
||
351 | * which are unused in the RTMP protocol implementation. */ |
||
352 | ff_amf_write_field_name(&p, "audioCodecs"); |
||
353 | ff_amf_write_number(&p, 4071.0); |
||
354 | ff_amf_write_field_name(&p, "videoCodecs"); |
||
355 | ff_amf_write_number(&p, 252.0); |
||
356 | ff_amf_write_field_name(&p, "videoFunction"); |
||
357 | ff_amf_write_number(&p, 1.0); |
||
358 | |||
359 | if (rt->pageurl) { |
||
360 | ff_amf_write_field_name(&p, "pageUrl"); |
||
361 | ff_amf_write_string(&p, rt->pageurl); |
||
362 | } |
||
363 | } |
||
364 | ff_amf_write_object_end(&p); |
||
365 | |||
366 | if (rt->conn) { |
||
367 | char *param = rt->conn; |
||
368 | |||
369 | // Write arbitrary AMF data to the Connect message. |
||
370 | while (param != NULL) { |
||
371 | char *sep; |
||
372 | param += strspn(param, " "); |
||
373 | if (!*param) |
||
374 | break; |
||
375 | sep = strchr(param, ' '); |
||
376 | if (sep) |
||
377 | *sep = '\0'; |
||
378 | if ((ret = rtmp_write_amf_data(s, param, &p)) < 0) { |
||
379 | // Invalid AMF parameter. |
||
380 | ff_rtmp_packet_destroy(&pkt); |
||
381 | return ret; |
||
382 | } |
||
383 | |||
384 | if (sep) |
||
385 | param = sep + 1; |
||
386 | else |
||
387 | break; |
||
388 | } |
||
389 | } |
||
390 | |||
391 | pkt.size = p - pkt.data; |
||
392 | |||
393 | return rtmp_send_packet(rt, &pkt, 1); |
||
394 | } |
||
395 | |||
396 | static int read_connect(URLContext *s, RTMPContext *rt) |
||
397 | { |
||
398 | RTMPPacket pkt = { 0 }; |
||
399 | uint8_t *p; |
||
400 | const uint8_t *cp; |
||
401 | int ret; |
||
402 | char command[64]; |
||
403 | int stringlen; |
||
404 | double seqnum; |
||
405 | uint8_t tmpstr[256]; |
||
406 | GetByteContext gbc; |
||
407 | |||
408 | if ((ret = ff_rtmp_packet_read(rt->stream, &pkt, rt->in_chunk_size, |
||
409 | &rt->prev_pkt[0], &rt->nb_prev_pkt[0])) < 0) |
||
410 | return ret; |
||
411 | cp = pkt.data; |
||
412 | bytestream2_init(&gbc, cp, pkt.size); |
||
413 | if (ff_amf_read_string(&gbc, command, sizeof(command), &stringlen)) { |
||
414 | av_log(s, AV_LOG_ERROR, "Unable to read command string\n"); |
||
415 | ff_rtmp_packet_destroy(&pkt); |
||
416 | return AVERROR_INVALIDDATA; |
||
417 | } |
||
418 | if (strcmp(command, "connect")) { |
||
419 | av_log(s, AV_LOG_ERROR, "Expecting connect, got %s\n", command); |
||
420 | ff_rtmp_packet_destroy(&pkt); |
||
421 | return AVERROR_INVALIDDATA; |
||
422 | } |
||
423 | ret = ff_amf_read_number(&gbc, &seqnum); |
||
424 | if (ret) |
||
425 | av_log(s, AV_LOG_WARNING, "SeqNum not found\n"); |
||
426 | /* Here one could parse an AMF Object with data as flashVers and others. */ |
||
427 | ret = ff_amf_get_field_value(gbc.buffer, |
||
428 | gbc.buffer + bytestream2_get_bytes_left(&gbc), |
||
429 | "app", tmpstr, sizeof(tmpstr)); |
||
430 | if (ret) |
||
431 | av_log(s, AV_LOG_WARNING, "App field not found in connect\n"); |
||
432 | if (!ret && strcmp(tmpstr, rt->app)) |
||
433 | av_log(s, AV_LOG_WARNING, "App field don't match up: %s <-> %s\n", |
||
434 | tmpstr, rt->app); |
||
435 | ff_rtmp_packet_destroy(&pkt); |
||
436 | |||
437 | // Send Window Acknowledgement Size (as defined in speficication) |
||
438 | if ((ret = ff_rtmp_packet_create(&pkt, RTMP_NETWORK_CHANNEL, |
||
439 | RTMP_PT_SERVER_BW, 0, 4)) < 0) |
||
440 | return ret; |
||
441 | p = pkt.data; |
||
442 | bytestream_put_be32(&p, rt->server_bw); |
||
443 | pkt.size = p - pkt.data; |
||
444 | ret = ff_rtmp_packet_write(rt->stream, &pkt, rt->out_chunk_size, |
||
445 | &rt->prev_pkt[1], &rt->nb_prev_pkt[1]); |
||
446 | ff_rtmp_packet_destroy(&pkt); |
||
447 | if (ret < 0) |
||
448 | return ret; |
||
449 | // Send Peer Bandwidth |
||
450 | if ((ret = ff_rtmp_packet_create(&pkt, RTMP_NETWORK_CHANNEL, |
||
451 | RTMP_PT_CLIENT_BW, 0, 5)) < 0) |
||
452 | return ret; |
||
453 | p = pkt.data; |
||
454 | bytestream_put_be32(&p, rt->server_bw); |
||
455 | bytestream_put_byte(&p, 2); // dynamic |
||
456 | pkt.size = p - pkt.data; |
||
457 | ret = ff_rtmp_packet_write(rt->stream, &pkt, rt->out_chunk_size, |
||
458 | &rt->prev_pkt[1], &rt->nb_prev_pkt[1]); |
||
459 | ff_rtmp_packet_destroy(&pkt); |
||
460 | if (ret < 0) |
||
461 | return ret; |
||
462 | |||
463 | // Ping request |
||
464 | if ((ret = ff_rtmp_packet_create(&pkt, RTMP_NETWORK_CHANNEL, |
||
465 | RTMP_PT_PING, 0, 6)) < 0) |
||
466 | return ret; |
||
467 | |||
468 | p = pkt.data; |
||
469 | bytestream_put_be16(&p, 0); // 0 -> Stream Begin |
||
470 | bytestream_put_be32(&p, 0); |
||
471 | ret = ff_rtmp_packet_write(rt->stream, &pkt, rt->out_chunk_size, |
||
472 | &rt->prev_pkt[1], &rt->nb_prev_pkt[1]); |
||
473 | ff_rtmp_packet_destroy(&pkt); |
||
474 | if (ret < 0) |
||
475 | return ret; |
||
476 | |||
477 | // Chunk size |
||
478 | if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SYSTEM_CHANNEL, |
||
479 | RTMP_PT_CHUNK_SIZE, 0, 4)) < 0) |
||
480 | return ret; |
||
481 | |||
482 | p = pkt.data; |
||
483 | bytestream_put_be32(&p, rt->out_chunk_size); |
||
484 | ret = ff_rtmp_packet_write(rt->stream, &pkt, rt->out_chunk_size, |
||
485 | &rt->prev_pkt[1], &rt->nb_prev_pkt[1]); |
||
486 | ff_rtmp_packet_destroy(&pkt); |
||
487 | if (ret < 0) |
||
488 | return ret; |
||
489 | |||
490 | // Send result_ NetConnection.Connect.Success to connect |
||
491 | if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SYSTEM_CHANNEL, |
||
492 | RTMP_PT_INVOKE, 0, |
||
493 | RTMP_PKTDATA_DEFAULT_SIZE)) < 0) |
||
494 | return ret; |
||
495 | |||
496 | p = pkt.data; |
||
497 | ff_amf_write_string(&p, "_result"); |
||
498 | ff_amf_write_number(&p, seqnum); |
||
499 | |||
500 | ff_amf_write_object_start(&p); |
||
501 | ff_amf_write_field_name(&p, "fmsVer"); |
||
502 | ff_amf_write_string(&p, "FMS/3,0,1,123"); |
||
503 | ff_amf_write_field_name(&p, "capabilities"); |
||
504 | ff_amf_write_number(&p, 31); |
||
505 | ff_amf_write_object_end(&p); |
||
506 | |||
507 | ff_amf_write_object_start(&p); |
||
508 | ff_amf_write_field_name(&p, "level"); |
||
509 | ff_amf_write_string(&p, "status"); |
||
510 | ff_amf_write_field_name(&p, "code"); |
||
511 | ff_amf_write_string(&p, "NetConnection.Connect.Success"); |
||
512 | ff_amf_write_field_name(&p, "description"); |
||
513 | ff_amf_write_string(&p, "Connection succeeded."); |
||
514 | ff_amf_write_field_name(&p, "objectEncoding"); |
||
515 | ff_amf_write_number(&p, 0); |
||
516 | ff_amf_write_object_end(&p); |
||
517 | |||
518 | pkt.size = p - pkt.data; |
||
519 | ret = ff_rtmp_packet_write(rt->stream, &pkt, rt->out_chunk_size, |
||
520 | &rt->prev_pkt[1], &rt->nb_prev_pkt[1]); |
||
521 | ff_rtmp_packet_destroy(&pkt); |
||
522 | if (ret < 0) |
||
523 | return ret; |
||
524 | |||
525 | if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SYSTEM_CHANNEL, |
||
526 | RTMP_PT_INVOKE, 0, 30)) < 0) |
||
527 | return ret; |
||
528 | p = pkt.data; |
||
529 | ff_amf_write_string(&p, "onBWDone"); |
||
530 | ff_amf_write_number(&p, 0); |
||
531 | ff_amf_write_null(&p); |
||
532 | ff_amf_write_number(&p, 8192); |
||
533 | pkt.size = p - pkt.data; |
||
534 | ret = ff_rtmp_packet_write(rt->stream, &pkt, rt->out_chunk_size, |
||
535 | &rt->prev_pkt[1], &rt->nb_prev_pkt[1]); |
||
536 | ff_rtmp_packet_destroy(&pkt); |
||
537 | |||
538 | return ret; |
||
539 | } |
||
540 | |||
541 | /** |
||
542 | * Generate 'releaseStream' call and send it to the server. It should make |
||
543 | * the server release some channel for media streams. |
||
544 | */ |
||
545 | static int gen_release_stream(URLContext *s, RTMPContext *rt) |
||
546 | { |
||
547 | RTMPPacket pkt; |
||
548 | uint8_t *p; |
||
549 | int ret; |
||
550 | |||
551 | if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SYSTEM_CHANNEL, RTMP_PT_INVOKE, |
||
552 | 0, 29 + strlen(rt->playpath))) < 0) |
||
553 | return ret; |
||
554 | |||
555 | av_log(s, AV_LOG_DEBUG, "Releasing stream...\n"); |
||
556 | p = pkt.data; |
||
557 | ff_amf_write_string(&p, "releaseStream"); |
||
558 | ff_amf_write_number(&p, ++rt->nb_invokes); |
||
559 | ff_amf_write_null(&p); |
||
560 | ff_amf_write_string(&p, rt->playpath); |
||
561 | |||
562 | return rtmp_send_packet(rt, &pkt, 1); |
||
563 | } |
||
564 | |||
565 | /** |
||
566 | * Generate 'FCPublish' call and send it to the server. It should make |
||
567 | * the server preapare for receiving media streams. |
||
568 | */ |
||
569 | static int gen_fcpublish_stream(URLContext *s, RTMPContext *rt) |
||
570 | { |
||
571 | RTMPPacket pkt; |
||
572 | uint8_t *p; |
||
573 | int ret; |
||
574 | |||
575 | if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SYSTEM_CHANNEL, RTMP_PT_INVOKE, |
||
576 | 0, 25 + strlen(rt->playpath))) < 0) |
||
577 | return ret; |
||
578 | |||
579 | av_log(s, AV_LOG_DEBUG, "FCPublish stream...\n"); |
||
580 | p = pkt.data; |
||
581 | ff_amf_write_string(&p, "FCPublish"); |
||
582 | ff_amf_write_number(&p, ++rt->nb_invokes); |
||
583 | ff_amf_write_null(&p); |
||
584 | ff_amf_write_string(&p, rt->playpath); |
||
585 | |||
586 | return rtmp_send_packet(rt, &pkt, 1); |
||
587 | } |
||
588 | |||
589 | /** |
||
590 | * Generate 'FCUnpublish' call and send it to the server. It should make |
||
591 | * the server destroy stream. |
||
592 | */ |
||
593 | static int gen_fcunpublish_stream(URLContext *s, RTMPContext *rt) |
||
594 | { |
||
595 | RTMPPacket pkt; |
||
596 | uint8_t *p; |
||
597 | int ret; |
||
598 | |||
599 | if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SYSTEM_CHANNEL, RTMP_PT_INVOKE, |
||
600 | 0, 27 + strlen(rt->playpath))) < 0) |
||
601 | return ret; |
||
602 | |||
603 | av_log(s, AV_LOG_DEBUG, "UnPublishing stream...\n"); |
||
604 | p = pkt.data; |
||
605 | ff_amf_write_string(&p, "FCUnpublish"); |
||
606 | ff_amf_write_number(&p, ++rt->nb_invokes); |
||
607 | ff_amf_write_null(&p); |
||
608 | ff_amf_write_string(&p, rt->playpath); |
||
609 | |||
610 | return rtmp_send_packet(rt, &pkt, 0); |
||
611 | } |
||
612 | |||
613 | /** |
||
614 | * Generate 'createStream' call and send it to the server. It should make |
||
615 | * the server allocate some channel for media streams. |
||
616 | */ |
||
617 | static int gen_create_stream(URLContext *s, RTMPContext *rt) |
||
618 | { |
||
619 | RTMPPacket pkt; |
||
620 | uint8_t *p; |
||
621 | int ret; |
||
622 | |||
623 | av_log(s, AV_LOG_DEBUG, "Creating stream...\n"); |
||
624 | |||
625 | if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SYSTEM_CHANNEL, RTMP_PT_INVOKE, |
||
626 | 0, 25)) < 0) |
||
627 | return ret; |
||
628 | |||
629 | p = pkt.data; |
||
630 | ff_amf_write_string(&p, "createStream"); |
||
631 | ff_amf_write_number(&p, ++rt->nb_invokes); |
||
632 | ff_amf_write_null(&p); |
||
633 | |||
634 | return rtmp_send_packet(rt, &pkt, 1); |
||
635 | } |
||
636 | |||
637 | |||
638 | /** |
||
639 | * Generate 'deleteStream' call and send it to the server. It should make |
||
640 | * the server remove some channel for media streams. |
||
641 | */ |
||
642 | static int gen_delete_stream(URLContext *s, RTMPContext *rt) |
||
643 | { |
||
644 | RTMPPacket pkt; |
||
645 | uint8_t *p; |
||
646 | int ret; |
||
647 | |||
648 | av_log(s, AV_LOG_DEBUG, "Deleting stream...\n"); |
||
649 | |||
650 | if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SYSTEM_CHANNEL, RTMP_PT_INVOKE, |
||
651 | 0, 34)) < 0) |
||
652 | return ret; |
||
653 | |||
654 | p = pkt.data; |
||
655 | ff_amf_write_string(&p, "deleteStream"); |
||
656 | ff_amf_write_number(&p, ++rt->nb_invokes); |
||
657 | ff_amf_write_null(&p); |
||
658 | ff_amf_write_number(&p, rt->stream_id); |
||
659 | |||
660 | return rtmp_send_packet(rt, &pkt, 0); |
||
661 | } |
||
662 | |||
663 | /** |
||
664 | * Generate client buffer time and send it to the server. |
||
665 | */ |
||
666 | static int gen_buffer_time(URLContext *s, RTMPContext *rt) |
||
667 | { |
||
668 | RTMPPacket pkt; |
||
669 | uint8_t *p; |
||
670 | int ret; |
||
671 | |||
672 | if ((ret = ff_rtmp_packet_create(&pkt, RTMP_NETWORK_CHANNEL, RTMP_PT_PING, |
||
673 | 1, 10)) < 0) |
||
674 | return ret; |
||
675 | |||
676 | p = pkt.data; |
||
677 | bytestream_put_be16(&p, 3); |
||
678 | bytestream_put_be32(&p, rt->stream_id); |
||
679 | bytestream_put_be32(&p, rt->client_buffer_time); |
||
680 | |||
681 | return rtmp_send_packet(rt, &pkt, 0); |
||
682 | } |
||
683 | |||
684 | /** |
||
685 | * Generate 'play' call and send it to the server, then ping the server |
||
686 | * to start actual playing. |
||
687 | */ |
||
688 | static int gen_play(URLContext *s, RTMPContext *rt) |
||
689 | { |
||
690 | RTMPPacket pkt; |
||
691 | uint8_t *p; |
||
692 | int ret; |
||
693 | |||
694 | av_log(s, AV_LOG_DEBUG, "Sending play command for '%s'\n", rt->playpath); |
||
695 | |||
696 | if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SOURCE_CHANNEL, RTMP_PT_INVOKE, |
||
697 | 0, 29 + strlen(rt->playpath))) < 0) |
||
698 | return ret; |
||
699 | |||
700 | pkt.extra = rt->stream_id; |
||
701 | |||
702 | p = pkt.data; |
||
703 | ff_amf_write_string(&p, "play"); |
||
704 | ff_amf_write_number(&p, ++rt->nb_invokes); |
||
705 | ff_amf_write_null(&p); |
||
706 | ff_amf_write_string(&p, rt->playpath); |
||
707 | ff_amf_write_number(&p, rt->live * 1000); |
||
708 | |||
709 | return rtmp_send_packet(rt, &pkt, 1); |
||
710 | } |
||
711 | |||
712 | static int gen_seek(URLContext *s, RTMPContext *rt, int64_t timestamp) |
||
713 | { |
||
714 | RTMPPacket pkt; |
||
715 | uint8_t *p; |
||
716 | int ret; |
||
717 | |||
718 | av_log(s, AV_LOG_DEBUG, "Sending seek command for timestamp %"PRId64"\n", |
||
719 | timestamp); |
||
720 | |||
721 | if ((ret = ff_rtmp_packet_create(&pkt, 3, RTMP_PT_INVOKE, 0, 26)) < 0) |
||
722 | return ret; |
||
723 | |||
724 | pkt.extra = rt->stream_id; |
||
725 | |||
726 | p = pkt.data; |
||
727 | ff_amf_write_string(&p, "seek"); |
||
728 | ff_amf_write_number(&p, 0); //no tracking back responses |
||
729 | ff_amf_write_null(&p); //as usual, the first null param |
||
730 | ff_amf_write_number(&p, timestamp); //where we want to jump |
||
731 | |||
732 | return rtmp_send_packet(rt, &pkt, 1); |
||
733 | } |
||
734 | |||
735 | /** |
||
736 | * Generate 'publish' call and send it to the server. |
||
737 | */ |
||
738 | static int gen_publish(URLContext *s, RTMPContext *rt) |
||
739 | { |
||
740 | RTMPPacket pkt; |
||
741 | uint8_t *p; |
||
742 | int ret; |
||
743 | |||
744 | av_log(s, AV_LOG_DEBUG, "Sending publish command for '%s'\n", rt->playpath); |
||
745 | |||
746 | if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SOURCE_CHANNEL, RTMP_PT_INVOKE, |
||
747 | 0, 30 + strlen(rt->playpath))) < 0) |
||
748 | return ret; |
||
749 | |||
750 | pkt.extra = rt->stream_id; |
||
751 | |||
752 | p = pkt.data; |
||
753 | ff_amf_write_string(&p, "publish"); |
||
754 | ff_amf_write_number(&p, ++rt->nb_invokes); |
||
755 | ff_amf_write_null(&p); |
||
756 | ff_amf_write_string(&p, rt->playpath); |
||
757 | ff_amf_write_string(&p, "live"); |
||
758 | |||
759 | return rtmp_send_packet(rt, &pkt, 1); |
||
760 | } |
||
761 | |||
762 | /** |
||
763 | * Generate ping reply and send it to the server. |
||
764 | */ |
||
765 | static int gen_pong(URLContext *s, RTMPContext *rt, RTMPPacket *ppkt) |
||
766 | { |
||
767 | RTMPPacket pkt; |
||
768 | uint8_t *p; |
||
769 | int ret; |
||
770 | |||
771 | if (ppkt->size < 6) { |
||
772 | av_log(s, AV_LOG_ERROR, "Too short ping packet (%d)\n", |
||
773 | ppkt->size); |
||
774 | return AVERROR_INVALIDDATA; |
||
775 | } |
||
776 | |||
777 | if ((ret = ff_rtmp_packet_create(&pkt, RTMP_NETWORK_CHANNEL, RTMP_PT_PING, |
||
778 | ppkt->timestamp + 1, 6)) < 0) |
||
779 | return ret; |
||
780 | |||
781 | p = pkt.data; |
||
782 | bytestream_put_be16(&p, 7); |
||
783 | bytestream_put_be32(&p, AV_RB32(ppkt->data+2)); |
||
784 | |||
785 | return rtmp_send_packet(rt, &pkt, 0); |
||
786 | } |
||
787 | |||
788 | /** |
||
789 | * Generate SWF verification message and send it to the server. |
||
790 | */ |
||
791 | static int gen_swf_verification(URLContext *s, RTMPContext *rt) |
||
792 | { |
||
793 | RTMPPacket pkt; |
||
794 | uint8_t *p; |
||
795 | int ret; |
||
796 | |||
797 | av_log(s, AV_LOG_DEBUG, "Sending SWF verification...\n"); |
||
798 | if ((ret = ff_rtmp_packet_create(&pkt, RTMP_NETWORK_CHANNEL, RTMP_PT_PING, |
||
799 | 0, 44)) < 0) |
||
800 | return ret; |
||
801 | |||
802 | p = pkt.data; |
||
803 | bytestream_put_be16(&p, 27); |
||
804 | memcpy(p, rt->swfverification, 42); |
||
805 | |||
806 | return rtmp_send_packet(rt, &pkt, 0); |
||
807 | } |
||
808 | |||
809 | /** |
||
810 | * Generate server bandwidth message and send it to the server. |
||
811 | */ |
||
812 | static int gen_server_bw(URLContext *s, RTMPContext *rt) |
||
813 | { |
||
814 | RTMPPacket pkt; |
||
815 | uint8_t *p; |
||
816 | int ret; |
||
817 | |||
818 | if ((ret = ff_rtmp_packet_create(&pkt, RTMP_NETWORK_CHANNEL, RTMP_PT_SERVER_BW, |
||
819 | 0, 4)) < 0) |
||
820 | return ret; |
||
821 | |||
822 | p = pkt.data; |
||
823 | bytestream_put_be32(&p, rt->server_bw); |
||
824 | |||
825 | return rtmp_send_packet(rt, &pkt, 0); |
||
826 | } |
||
827 | |||
828 | /** |
||
829 | * Generate check bandwidth message and send it to the server. |
||
830 | */ |
||
831 | static int gen_check_bw(URLContext *s, RTMPContext *rt) |
||
832 | { |
||
833 | RTMPPacket pkt; |
||
834 | uint8_t *p; |
||
835 | int ret; |
||
836 | |||
837 | if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SYSTEM_CHANNEL, RTMP_PT_INVOKE, |
||
838 | 0, 21)) < 0) |
||
839 | return ret; |
||
840 | |||
841 | p = pkt.data; |
||
842 | ff_amf_write_string(&p, "_checkbw"); |
||
843 | ff_amf_write_number(&p, ++rt->nb_invokes); |
||
844 | ff_amf_write_null(&p); |
||
845 | |||
846 | return rtmp_send_packet(rt, &pkt, 1); |
||
847 | } |
||
848 | |||
849 | /** |
||
850 | * Generate report on bytes read so far and send it to the server. |
||
851 | */ |
||
852 | static int gen_bytes_read(URLContext *s, RTMPContext *rt, uint32_t ts) |
||
853 | { |
||
854 | RTMPPacket pkt; |
||
855 | uint8_t *p; |
||
856 | int ret; |
||
857 | |||
858 | if ((ret = ff_rtmp_packet_create(&pkt, RTMP_NETWORK_CHANNEL, RTMP_PT_BYTES_READ, |
||
859 | ts, 4)) < 0) |
||
860 | return ret; |
||
861 | |||
862 | p = pkt.data; |
||
863 | bytestream_put_be32(&p, rt->bytes_read); |
||
864 | |||
865 | return rtmp_send_packet(rt, &pkt, 0); |
||
866 | } |
||
867 | |||
868 | static int gen_fcsubscribe_stream(URLContext *s, RTMPContext *rt, |
||
869 | const char *subscribe) |
||
870 | { |
||
871 | RTMPPacket pkt; |
||
872 | uint8_t *p; |
||
873 | int ret; |
||
874 | |||
875 | if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SYSTEM_CHANNEL, RTMP_PT_INVOKE, |
||
876 | 0, 27 + strlen(subscribe))) < 0) |
||
877 | return ret; |
||
878 | |||
879 | p = pkt.data; |
||
880 | ff_amf_write_string(&p, "FCSubscribe"); |
||
881 | ff_amf_write_number(&p, ++rt->nb_invokes); |
||
882 | ff_amf_write_null(&p); |
||
883 | ff_amf_write_string(&p, subscribe); |
||
884 | |||
885 | return rtmp_send_packet(rt, &pkt, 1); |
||
886 | } |
||
887 | |||
888 | int ff_rtmp_calc_digest(const uint8_t *src, int len, int gap, |
||
889 | const uint8_t *key, int keylen, uint8_t *dst) |
||
890 | { |
||
891 | struct AVSHA *sha; |
||
892 | uint8_t hmac_buf[64+32] = {0}; |
||
893 | int i; |
||
894 | |||
895 | sha = av_sha_alloc(); |
||
896 | if (!sha) |
||
897 | return AVERROR(ENOMEM); |
||
898 | |||
899 | if (keylen < 64) { |
||
900 | memcpy(hmac_buf, key, keylen); |
||
901 | } else { |
||
902 | av_sha_init(sha, 256); |
||
903 | av_sha_update(sha,key, keylen); |
||
904 | av_sha_final(sha, hmac_buf); |
||
905 | } |
||
906 | for (i = 0; i < 64; i++) |
||
907 | hmac_buf[i] ^= HMAC_IPAD_VAL; |
||
908 | |||
909 | av_sha_init(sha, 256); |
||
910 | av_sha_update(sha, hmac_buf, 64); |
||
911 | if (gap <= 0) { |
||
912 | av_sha_update(sha, src, len); |
||
913 | } else { //skip 32 bytes used for storing digest |
||
914 | av_sha_update(sha, src, gap); |
||
915 | av_sha_update(sha, src + gap + 32, len - gap - 32); |
||
916 | } |
||
917 | av_sha_final(sha, hmac_buf + 64); |
||
918 | |||
919 | for (i = 0; i < 64; i++) |
||
920 | hmac_buf[i] ^= HMAC_IPAD_VAL ^ HMAC_OPAD_VAL; //reuse XORed key for opad |
||
921 | av_sha_init(sha, 256); |
||
922 | av_sha_update(sha, hmac_buf, 64+32); |
||
923 | av_sha_final(sha, dst); |
||
924 | |||
925 | av_free(sha); |
||
926 | |||
927 | return 0; |
||
928 | } |
||
929 | |||
930 | int ff_rtmp_calc_digest_pos(const uint8_t *buf, int off, int mod_val, |
||
931 | int add_val) |
||
932 | { |
||
933 | int i, digest_pos = 0; |
||
934 | |||
935 | for (i = 0; i < 4; i++) |
||
936 | digest_pos += buf[i + off]; |
||
937 | digest_pos = digest_pos % mod_val + add_val; |
||
938 | |||
939 | return digest_pos; |
||
940 | } |
||
941 | |||
942 | /** |
||
943 | * Put HMAC-SHA2 digest of packet data (except for the bytes where this digest |
||
944 | * will be stored) into that packet. |
||
945 | * |
||
946 | * @param buf handshake data (1536 bytes) |
||
947 | * @param encrypted use an encrypted connection (RTMPE) |
||
948 | * @return offset to the digest inside input data |
||
949 | */ |
||
950 | static int rtmp_handshake_imprint_with_digest(uint8_t *buf, int encrypted) |
||
951 | { |
||
952 | int ret, digest_pos; |
||
953 | |||
954 | if (encrypted) |
||
955 | digest_pos = ff_rtmp_calc_digest_pos(buf, 772, 728, 776); |
||
956 | else |
||
957 | digest_pos = ff_rtmp_calc_digest_pos(buf, 8, 728, 12); |
||
958 | |||
959 | ret = ff_rtmp_calc_digest(buf, RTMP_HANDSHAKE_PACKET_SIZE, digest_pos, |
||
960 | rtmp_player_key, PLAYER_KEY_OPEN_PART_LEN, |
||
961 | buf + digest_pos); |
||
962 | if (ret < 0) |
||
963 | return ret; |
||
964 | |||
965 | return digest_pos; |
||
966 | } |
||
967 | |||
968 | /** |
||
969 | * Verify that the received server response has the expected digest value. |
||
970 | * |
||
971 | * @param buf handshake data received from the server (1536 bytes) |
||
972 | * @param off position to search digest offset from |
||
973 | * @return 0 if digest is valid, digest position otherwise |
||
974 | */ |
||
975 | static int rtmp_validate_digest(uint8_t *buf, int off) |
||
976 | { |
||
977 | uint8_t digest[32]; |
||
978 | int ret, digest_pos; |
||
979 | |||
980 | digest_pos = ff_rtmp_calc_digest_pos(buf, off, 728, off + 4); |
||
981 | |||
982 | ret = ff_rtmp_calc_digest(buf, RTMP_HANDSHAKE_PACKET_SIZE, digest_pos, |
||
983 | rtmp_server_key, SERVER_KEY_OPEN_PART_LEN, |
||
984 | digest); |
||
985 | if (ret < 0) |
||
986 | return ret; |
||
987 | |||
988 | if (!memcmp(digest, buf + digest_pos, 32)) |
||
989 | return digest_pos; |
||
990 | return 0; |
||
991 | } |
||
992 | |||
993 | static int rtmp_calc_swf_verification(URLContext *s, RTMPContext *rt, |
||
994 | uint8_t *buf) |
||
995 | { |
||
996 | uint8_t *p; |
||
997 | int ret; |
||
998 | |||
999 | if (rt->swfhash_len != 32) { |
||
1000 | av_log(s, AV_LOG_ERROR, |
||
1001 | "Hash of the decompressed SWF file is not 32 bytes long.\n"); |
||
1002 | return AVERROR(EINVAL); |
||
1003 | } |
||
1004 | |||
1005 | p = &rt->swfverification[0]; |
||
1006 | bytestream_put_byte(&p, 1); |
||
1007 | bytestream_put_byte(&p, 1); |
||
1008 | bytestream_put_be32(&p, rt->swfsize); |
||
1009 | bytestream_put_be32(&p, rt->swfsize); |
||
1010 | |||
1011 | if ((ret = ff_rtmp_calc_digest(rt->swfhash, 32, 0, buf, 32, p)) < 0) |
||
1012 | return ret; |
||
1013 | |||
1014 | return 0; |
||
1015 | } |
||
1016 | |||
1017 | #if CONFIG_ZLIB |
||
1018 | static int rtmp_uncompress_swfplayer(uint8_t *in_data, int64_t in_size, |
||
1019 | uint8_t **out_data, int64_t *out_size) |
||
1020 | { |
||
1021 | z_stream zs = { 0 }; |
||
1022 | void *ptr; |
||
1023 | int size; |
||
1024 | int ret = 0; |
||
1025 | |||
1026 | zs.avail_in = in_size; |
||
1027 | zs.next_in = in_data; |
||
1028 | ret = inflateInit(&zs); |
||
1029 | if (ret != Z_OK) |
||
1030 | return AVERROR_UNKNOWN; |
||
1031 | |||
1032 | do { |
||
1033 | uint8_t tmp_buf[16384]; |
||
1034 | |||
1035 | zs.avail_out = sizeof(tmp_buf); |
||
1036 | zs.next_out = tmp_buf; |
||
1037 | |||
1038 | ret = inflate(&zs, Z_NO_FLUSH); |
||
1039 | if (ret != Z_OK && ret != Z_STREAM_END) { |
||
1040 | ret = AVERROR_UNKNOWN; |
||
1041 | goto fail; |
||
1042 | } |
||
1043 | |||
1044 | size = sizeof(tmp_buf) - zs.avail_out; |
||
1045 | if (!(ptr = av_realloc(*out_data, *out_size + size))) { |
||
1046 | ret = AVERROR(ENOMEM); |
||
1047 | goto fail; |
||
1048 | } |
||
1049 | *out_data = ptr; |
||
1050 | |||
1051 | memcpy(*out_data + *out_size, tmp_buf, size); |
||
1052 | *out_size += size; |
||
1053 | } while (zs.avail_out == 0); |
||
1054 | |||
1055 | fail: |
||
1056 | inflateEnd(&zs); |
||
1057 | return ret; |
||
1058 | } |
||
1059 | #endif |
||
1060 | |||
1061 | static int rtmp_calc_swfhash(URLContext *s) |
||
1062 | { |
||
1063 | RTMPContext *rt = s->priv_data; |
||
1064 | uint8_t *in_data = NULL, *out_data = NULL, *swfdata; |
||
1065 | int64_t in_size, out_size; |
||
1066 | URLContext *stream; |
||
1067 | char swfhash[32]; |
||
1068 | int swfsize; |
||
1069 | int ret = 0; |
||
1070 | |||
1071 | /* Get the SWF player file. */ |
||
1072 | if ((ret = ffurl_open(&stream, rt->swfverify, AVIO_FLAG_READ, |
||
1073 | &s->interrupt_callback, NULL)) < 0) { |
||
1074 | av_log(s, AV_LOG_ERROR, "Cannot open connection %s.\n", rt->swfverify); |
||
1075 | goto fail; |
||
1076 | } |
||
1077 | |||
1078 | if ((in_size = ffurl_seek(stream, 0, AVSEEK_SIZE)) < 0) { |
||
1079 | ret = AVERROR(EIO); |
||
1080 | goto fail; |
||
1081 | } |
||
1082 | |||
1083 | if (!(in_data = av_malloc(in_size))) { |
||
1084 | ret = AVERROR(ENOMEM); |
||
1085 | goto fail; |
||
1086 | } |
||
1087 | |||
1088 | if ((ret = ffurl_read_complete(stream, in_data, in_size)) < 0) |
||
1089 | goto fail; |
||
1090 | |||
1091 | if (in_size < 3) { |
||
1092 | ret = AVERROR_INVALIDDATA; |
||
1093 | goto fail; |
||
1094 | } |
||
1095 | |||
1096 | if (!memcmp(in_data, "CWS", 3)) { |
||
1097 | /* Decompress the SWF player file using Zlib. */ |
||
1098 | if (!(out_data = av_malloc(8))) { |
||
1099 | ret = AVERROR(ENOMEM); |
||
1100 | goto fail; |
||
1101 | } |
||
1102 | *in_data = 'F'; // magic stuff |
||
1103 | memcpy(out_data, in_data, 8); |
||
1104 | out_size = 8; |
||
1105 | |||
1106 | #if CONFIG_ZLIB |
||
1107 | if ((ret = rtmp_uncompress_swfplayer(in_data + 8, in_size - 8, |
||
1108 | &out_data, &out_size)) < 0) |
||
1109 | goto fail; |
||
1110 | #else |
||
1111 | av_log(s, AV_LOG_ERROR, |
||
1112 | "Zlib is required for decompressing the SWF player file.\n"); |
||
1113 | ret = AVERROR(EINVAL); |
||
1114 | goto fail; |
||
1115 | #endif |
||
1116 | swfsize = out_size; |
||
1117 | swfdata = out_data; |
||
1118 | } else { |
||
1119 | swfsize = in_size; |
||
1120 | swfdata = in_data; |
||
1121 | } |
||
1122 | |||
1123 | /* Compute the SHA256 hash of the SWF player file. */ |
||
1124 | if ((ret = ff_rtmp_calc_digest(swfdata, swfsize, 0, |
||
1125 | "Genuine Adobe Flash Player 001", 30, |
||
1126 | swfhash)) < 0) |
||
1127 | goto fail; |
||
1128 | |||
1129 | /* Set SWFVerification parameters. */ |
||
1130 | av_opt_set_bin(rt, "rtmp_swfhash", swfhash, 32, 0); |
||
1131 | rt->swfsize = swfsize; |
||
1132 | |||
1133 | fail: |
||
1134 | av_freep(&in_data); |
||
1135 | av_freep(&out_data); |
||
1136 | ffurl_close(stream); |
||
1137 | return ret; |
||
1138 | } |
||
1139 | |||
1140 | /** |
||
1141 | * Perform handshake with the server by means of exchanging pseudorandom data |
||
1142 | * signed with HMAC-SHA2 digest. |
||
1143 | * |
||
1144 | * @return 0 if handshake succeeds, negative value otherwise |
||
1145 | */ |
||
1146 | static int rtmp_handshake(URLContext *s, RTMPContext *rt) |
||
1147 | { |
||
1148 | AVLFG rnd; |
||
1149 | uint8_t tosend [RTMP_HANDSHAKE_PACKET_SIZE+1] = { |
||
1150 | 3, // unencrypted data |
||
1151 | 0, 0, 0, 0, // client uptime |
||
1152 | RTMP_CLIENT_VER1, |
||
1153 | RTMP_CLIENT_VER2, |
||
1154 | RTMP_CLIENT_VER3, |
||
1155 | RTMP_CLIENT_VER4, |
||
1156 | }; |
||
1157 | uint8_t clientdata[RTMP_HANDSHAKE_PACKET_SIZE]; |
||
1158 | uint8_t serverdata[RTMP_HANDSHAKE_PACKET_SIZE+1]; |
||
1159 | int i; |
||
1160 | int server_pos, client_pos; |
||
1161 | uint8_t digest[32], signature[32]; |
||
1162 | int ret, type = 0; |
||
1163 | |||
1164 | av_log(s, AV_LOG_DEBUG, "Handshaking...\n"); |
||
1165 | |||
1166 | av_lfg_init(&rnd, 0xDEADC0DE); |
||
1167 | // generate handshake packet - 1536 bytes of pseudorandom data |
||
1168 | for (i = 9; i <= RTMP_HANDSHAKE_PACKET_SIZE; i++) |
||
1169 | tosend[i] = av_lfg_get(&rnd) >> 24; |
||
1170 | |||
1171 | if (CONFIG_FFRTMPCRYPT_PROTOCOL && rt->encrypted) { |
||
1172 | /* When the client wants to use RTMPE, we have to change the command |
||
1173 | * byte to 0x06 which means to use encrypted data and we have to set |
||
1174 | * the flash version to at least 9.0.115.0. */ |
||
1175 | tosend[0] = 6; |
||
1176 | tosend[5] = 128; |
||
1177 | tosend[6] = 0; |
||
1178 | tosend[7] = 3; |
||
1179 | tosend[8] = 2; |
||
1180 | |||
1181 | /* Initialize the Diffie-Hellmann context and generate the public key |
||
1182 | * to send to the server. */ |
||
1183 | if ((ret = ff_rtmpe_gen_pub_key(rt->stream, tosend + 1)) < 0) |
||
1184 | return ret; |
||
1185 | } |
||
1186 | |||
1187 | client_pos = rtmp_handshake_imprint_with_digest(tosend + 1, rt->encrypted); |
||
1188 | if (client_pos < 0) |
||
1189 | return client_pos; |
||
1190 | |||
1191 | if ((ret = ffurl_write(rt->stream, tosend, |
||
1192 | RTMP_HANDSHAKE_PACKET_SIZE + 1)) < 0) { |
||
1193 | av_log(s, AV_LOG_ERROR, "Cannot write RTMP handshake request\n"); |
||
1194 | return ret; |
||
1195 | } |
||
1196 | |||
1197 | if ((ret = ffurl_read_complete(rt->stream, serverdata, |
||
1198 | RTMP_HANDSHAKE_PACKET_SIZE + 1)) < 0) { |
||
1199 | av_log(s, AV_LOG_ERROR, "Cannot read RTMP handshake response\n"); |
||
1200 | return ret; |
||
1201 | } |
||
1202 | |||
1203 | if ((ret = ffurl_read_complete(rt->stream, clientdata, |
||
1204 | RTMP_HANDSHAKE_PACKET_SIZE)) < 0) { |
||
1205 | av_log(s, AV_LOG_ERROR, "Cannot read RTMP handshake response\n"); |
||
1206 | return ret; |
||
1207 | } |
||
1208 | |||
1209 | av_log(s, AV_LOG_DEBUG, "Type answer %d\n", serverdata[0]); |
||
1210 | av_log(s, AV_LOG_DEBUG, "Server version %d.%d.%d.%d\n", |
||
1211 | serverdata[5], serverdata[6], serverdata[7], serverdata[8]); |
||
1212 | |||
1213 | if (rt->is_input && serverdata[5] >= 3) { |
||
1214 | server_pos = rtmp_validate_digest(serverdata + 1, 772); |
||
1215 | if (server_pos < 0) |
||
1216 | return server_pos; |
||
1217 | |||
1218 | if (!server_pos) { |
||
1219 | type = 1; |
||
1220 | server_pos = rtmp_validate_digest(serverdata + 1, 8); |
||
1221 | if (server_pos < 0) |
||
1222 | return server_pos; |
||
1223 | |||
1224 | if (!server_pos) { |
||
1225 | av_log(s, AV_LOG_ERROR, "Server response validating failed\n"); |
||
1226 | return AVERROR(EIO); |
||
1227 | } |
||
1228 | } |
||
1229 | |||
1230 | /* Generate SWFVerification token (SHA256 HMAC hash of decompressed SWF, |
||
1231 | * key are the last 32 bytes of the server handshake. */ |
||
1232 | if (rt->swfsize) { |
||
1233 | if ((ret = rtmp_calc_swf_verification(s, rt, serverdata + 1 + |
||
1234 | RTMP_HANDSHAKE_PACKET_SIZE - 32)) < 0) |
||
1235 | return ret; |
||
1236 | } |
||
1237 | |||
1238 | ret = ff_rtmp_calc_digest(tosend + 1 + client_pos, 32, 0, |
||
1239 | rtmp_server_key, sizeof(rtmp_server_key), |
||
1240 | digest); |
||
1241 | if (ret < 0) |
||
1242 | return ret; |
||
1243 | |||
1244 | ret = ff_rtmp_calc_digest(clientdata, RTMP_HANDSHAKE_PACKET_SIZE - 32, |
||
1245 | 0, digest, 32, signature); |
||
1246 | if (ret < 0) |
||
1247 | return ret; |
||
1248 | |||
1249 | if (CONFIG_FFRTMPCRYPT_PROTOCOL && rt->encrypted) { |
||
1250 | /* Compute the shared secret key sent by the server and initialize |
||
1251 | * the RC4 encryption. */ |
||
1252 | if ((ret = ff_rtmpe_compute_secret_key(rt->stream, serverdata + 1, |
||
1253 | tosend + 1, type)) < 0) |
||
1254 | return ret; |
||
1255 | |||
1256 | /* Encrypt the signature received by the server. */ |
||
1257 | ff_rtmpe_encrypt_sig(rt->stream, signature, digest, serverdata[0]); |
||
1258 | } |
||
1259 | |||
1260 | if (memcmp(signature, clientdata + RTMP_HANDSHAKE_PACKET_SIZE - 32, 32)) { |
||
1261 | av_log(s, AV_LOG_ERROR, "Signature mismatch\n"); |
||
1262 | return AVERROR(EIO); |
||
1263 | } |
||
1264 | |||
1265 | for (i = 0; i < RTMP_HANDSHAKE_PACKET_SIZE; i++) |
||
1266 | tosend[i] = av_lfg_get(&rnd) >> 24; |
||
1267 | ret = ff_rtmp_calc_digest(serverdata + 1 + server_pos, 32, 0, |
||
1268 | rtmp_player_key, sizeof(rtmp_player_key), |
||
1269 | digest); |
||
1270 | if (ret < 0) |
||
1271 | return ret; |
||
1272 | |||
1273 | ret = ff_rtmp_calc_digest(tosend, RTMP_HANDSHAKE_PACKET_SIZE - 32, 0, |
||
1274 | digest, 32, |
||
1275 | tosend + RTMP_HANDSHAKE_PACKET_SIZE - 32); |
||
1276 | if (ret < 0) |
||
1277 | return ret; |
||
1278 | |||
1279 | if (CONFIG_FFRTMPCRYPT_PROTOCOL && rt->encrypted) { |
||
1280 | /* Encrypt the signature to be send to the server. */ |
||
1281 | ff_rtmpe_encrypt_sig(rt->stream, tosend + |
||
1282 | RTMP_HANDSHAKE_PACKET_SIZE - 32, digest, |
||
1283 | serverdata[0]); |
||
1284 | } |
||
1285 | |||
1286 | // write reply back to the server |
||
1287 | if ((ret = ffurl_write(rt->stream, tosend, |
||
1288 | RTMP_HANDSHAKE_PACKET_SIZE)) < 0) |
||
1289 | return ret; |
||
1290 | |||
1291 | if (CONFIG_FFRTMPCRYPT_PROTOCOL && rt->encrypted) { |
||
1292 | /* Set RC4 keys for encryption and update the keystreams. */ |
||
1293 | if ((ret = ff_rtmpe_update_keystream(rt->stream)) < 0) |
||
1294 | return ret; |
||
1295 | } |
||
1296 | } else { |
||
1297 | if (CONFIG_FFRTMPCRYPT_PROTOCOL && rt->encrypted) { |
||
1298 | /* Compute the shared secret key sent by the server and initialize |
||
1299 | * the RC4 encryption. */ |
||
1300 | if ((ret = ff_rtmpe_compute_secret_key(rt->stream, serverdata + 1, |
||
1301 | tosend + 1, 1)) < 0) |
||
1302 | return ret; |
||
1303 | |||
1304 | if (serverdata[0] == 9) { |
||
1305 | /* Encrypt the signature received by the server. */ |
||
1306 | ff_rtmpe_encrypt_sig(rt->stream, signature, digest, |
||
1307 | serverdata[0]); |
||
1308 | } |
||
1309 | } |
||
1310 | |||
1311 | if ((ret = ffurl_write(rt->stream, serverdata + 1, |
||
1312 | RTMP_HANDSHAKE_PACKET_SIZE)) < 0) |
||
1313 | return ret; |
||
1314 | |||
1315 | if (CONFIG_FFRTMPCRYPT_PROTOCOL && rt->encrypted) { |
||
1316 | /* Set RC4 keys for encryption and update the keystreams. */ |
||
1317 | if ((ret = ff_rtmpe_update_keystream(rt->stream)) < 0) |
||
1318 | return ret; |
||
1319 | } |
||
1320 | } |
||
1321 | |||
1322 | return 0; |
||
1323 | } |
||
1324 | |||
1325 | static int rtmp_receive_hs_packet(RTMPContext* rt, uint32_t *first_int, |
||
1326 | uint32_t *second_int, char *arraydata, |
||
1327 | int size) |
||
1328 | { |
||
1329 | int inoutsize; |
||
1330 | |||
1331 | inoutsize = ffurl_read_complete(rt->stream, arraydata, |
||
1332 | RTMP_HANDSHAKE_PACKET_SIZE); |
||
1333 | if (inoutsize <= 0) |
||
1334 | return AVERROR(EIO); |
||
1335 | if (inoutsize != RTMP_HANDSHAKE_PACKET_SIZE) { |
||
1336 | av_log(rt, AV_LOG_ERROR, "Erroneous Message size %d" |
||
1337 | " not following standard\n", (int)inoutsize); |
||
1338 | return AVERROR(EINVAL); |
||
1339 | } |
||
1340 | |||
1341 | *first_int = AV_RB32(arraydata); |
||
1342 | *second_int = AV_RB32(arraydata + 4); |
||
1343 | return 0; |
||
1344 | } |
||
1345 | |||
1346 | static int rtmp_send_hs_packet(RTMPContext* rt, uint32_t first_int, |
||
1347 | uint32_t second_int, char *arraydata, int size) |
||
1348 | { |
||
1349 | int inoutsize; |
||
1350 | |||
1351 | AV_WB32(arraydata, first_int); |
||
1352 | AV_WB32(arraydata + 4, second_int); |
||
1353 | inoutsize = ffurl_write(rt->stream, arraydata, |
||
1354 | RTMP_HANDSHAKE_PACKET_SIZE); |
||
1355 | if (inoutsize != RTMP_HANDSHAKE_PACKET_SIZE) { |
||
1356 | av_log(rt, AV_LOG_ERROR, "Unable to write answer\n"); |
||
1357 | return AVERROR(EIO); |
||
1358 | } |
||
1359 | |||
1360 | return 0; |
||
1361 | } |
||
1362 | |||
1363 | /** |
||
1364 | * rtmp handshake server side |
||
1365 | */ |
||
1366 | static int rtmp_server_handshake(URLContext *s, RTMPContext *rt) |
||
1367 | { |
||
1368 | uint8_t buffer[RTMP_HANDSHAKE_PACKET_SIZE]; |
||
1369 | uint32_t hs_epoch; |
||
1370 | uint32_t hs_my_epoch; |
||
1371 | uint8_t hs_c1[RTMP_HANDSHAKE_PACKET_SIZE]; |
||
1372 | uint8_t hs_s1[RTMP_HANDSHAKE_PACKET_SIZE]; |
||
1373 | uint32_t zeroes; |
||
1374 | uint32_t temp = 0; |
||
1375 | int randomidx = 0; |
||
1376 | int inoutsize = 0; |
||
1377 | int ret; |
||
1378 | |||
1379 | inoutsize = ffurl_read_complete(rt->stream, buffer, 1); // Receive C0 |
||
1380 | if (inoutsize <= 0) { |
||
1381 | av_log(s, AV_LOG_ERROR, "Unable to read handshake\n"); |
||
1382 | return AVERROR(EIO); |
||
1383 | } |
||
1384 | // Check Version |
||
1385 | if (buffer[0] != 3) { |
||
1386 | av_log(s, AV_LOG_ERROR, "RTMP protocol version mismatch\n"); |
||
1387 | return AVERROR(EIO); |
||
1388 | } |
||
1389 | if (ffurl_write(rt->stream, buffer, 1) <= 0) { // Send S0 |
||
1390 | av_log(s, AV_LOG_ERROR, |
||
1391 | "Unable to write answer - RTMP S0\n"); |
||
1392 | return AVERROR(EIO); |
||
1393 | } |
||
1394 | /* Receive C1 */ |
||
1395 | ret = rtmp_receive_hs_packet(rt, &hs_epoch, &zeroes, hs_c1, |
||
1396 | RTMP_HANDSHAKE_PACKET_SIZE); |
||
1397 | if (ret) { |
||
1398 | av_log(s, AV_LOG_ERROR, "RTMP Handshake C1 Error\n"); |
||
1399 | return ret; |
||
1400 | } |
||
1401 | /* Send S1 */ |
||
1402 | /* By now same epoch will be sent */ |
||
1403 | hs_my_epoch = hs_epoch; |
||
1404 | /* Generate random */ |
||
1405 | for (randomidx = 8; randomidx < (RTMP_HANDSHAKE_PACKET_SIZE); |
||
1406 | randomidx += 4) |
||
1407 | AV_WB32(hs_s1 + randomidx, av_get_random_seed()); |
||
1408 | |||
1409 | ret = rtmp_send_hs_packet(rt, hs_my_epoch, 0, hs_s1, |
||
1410 | RTMP_HANDSHAKE_PACKET_SIZE); |
||
1411 | if (ret) { |
||
1412 | av_log(s, AV_LOG_ERROR, "RTMP Handshake S1 Error\n"); |
||
1413 | return ret; |
||
1414 | } |
||
1415 | /* Send S2 */ |
||
1416 | ret = rtmp_send_hs_packet(rt, hs_epoch, 0, hs_c1, |
||
1417 | RTMP_HANDSHAKE_PACKET_SIZE); |
||
1418 | if (ret) { |
||
1419 | av_log(s, AV_LOG_ERROR, "RTMP Handshake S2 Error\n"); |
||
1420 | return ret; |
||
1421 | } |
||
1422 | /* Receive C2 */ |
||
1423 | ret = rtmp_receive_hs_packet(rt, &temp, &zeroes, buffer, |
||
1424 | RTMP_HANDSHAKE_PACKET_SIZE); |
||
1425 | if (ret) { |
||
1426 | av_log(s, AV_LOG_ERROR, "RTMP Handshake C2 Error\n"); |
||
1427 | return ret; |
||
1428 | } |
||
1429 | if (temp != hs_my_epoch) |
||
1430 | av_log(s, AV_LOG_WARNING, |
||
1431 | "Erroneous C2 Message epoch does not match up with C1 epoch\n"); |
||
1432 | if (memcmp(buffer + 8, hs_s1 + 8, |
||
1433 | RTMP_HANDSHAKE_PACKET_SIZE - 8)) |
||
1434 | av_log(s, AV_LOG_WARNING, |
||
1435 | "Erroneous C2 Message random does not match up\n"); |
||
1436 | |||
1437 | return 0; |
||
1438 | } |
||
1439 | |||
1440 | static int handle_chunk_size(URLContext *s, RTMPPacket *pkt) |
||
1441 | { |
||
1442 | RTMPContext *rt = s->priv_data; |
||
1443 | int ret; |
||
1444 | |||
1445 | if (pkt->size < 4) { |
||
1446 | av_log(s, AV_LOG_ERROR, |
||
1447 | "Too short chunk size change packet (%d)\n", |
||
1448 | pkt->size); |
||
1449 | return AVERROR_INVALIDDATA; |
||
1450 | } |
||
1451 | |||
1452 | if (!rt->is_input) { |
||
1453 | /* Send the same chunk size change packet back to the server, |
||
1454 | * setting the outgoing chunk size to the same as the incoming one. */ |
||
1455 | if ((ret = ff_rtmp_packet_write(rt->stream, pkt, rt->out_chunk_size, |
||
1456 | &rt->prev_pkt[1], &rt->nb_prev_pkt[1])) < 0) |
||
1457 | return ret; |
||
1458 | rt->out_chunk_size = AV_RB32(pkt->data); |
||
1459 | } |
||
1460 | |||
1461 | rt->in_chunk_size = AV_RB32(pkt->data); |
||
1462 | if (rt->in_chunk_size <= 0) { |
||
1463 | av_log(s, AV_LOG_ERROR, "Incorrect chunk size %d\n", |
||
1464 | rt->in_chunk_size); |
||
1465 | return AVERROR_INVALIDDATA; |
||
1466 | } |
||
1467 | av_log(s, AV_LOG_DEBUG, "New incoming chunk size = %d\n", |
||
1468 | rt->in_chunk_size); |
||
1469 | |||
1470 | return 0; |
||
1471 | } |
||
1472 | |||
1473 | static int handle_ping(URLContext *s, RTMPPacket *pkt) |
||
1474 | { |
||
1475 | RTMPContext *rt = s->priv_data; |
||
1476 | int t, ret; |
||
1477 | |||
1478 | if (pkt->size < 2) { |
||
1479 | av_log(s, AV_LOG_ERROR, "Too short ping packet (%d)\n", |
||
1480 | pkt->size); |
||
1481 | return AVERROR_INVALIDDATA; |
||
1482 | } |
||
1483 | |||
1484 | t = AV_RB16(pkt->data); |
||
1485 | if (t == 6) { |
||
1486 | if ((ret = gen_pong(s, rt, pkt)) < 0) |
||
1487 | return ret; |
||
1488 | } else if (t == 26) { |
||
1489 | if (rt->swfsize) { |
||
1490 | if ((ret = gen_swf_verification(s, rt)) < 0) |
||
1491 | return ret; |
||
1492 | } else { |
||
1493 | av_log(s, AV_LOG_WARNING, "Ignoring SWFVerification request.\n"); |
||
1494 | } |
||
1495 | } |
||
1496 | |||
1497 | return 0; |
||
1498 | } |
||
1499 | |||
1500 | static int handle_client_bw(URLContext *s, RTMPPacket *pkt) |
||
1501 | { |
||
1502 | RTMPContext *rt = s->priv_data; |
||
1503 | |||
1504 | if (pkt->size < 4) { |
||
1505 | av_log(s, AV_LOG_ERROR, |
||
1506 | "Client bandwidth report packet is less than 4 bytes long (%d)\n", |
||
1507 | pkt->size); |
||
1508 | return AVERROR_INVALIDDATA; |
||
1509 | } |
||
1510 | |||
1511 | rt->client_report_size = AV_RB32(pkt->data); |
||
1512 | if (rt->client_report_size <= 0) { |
||
1513 | av_log(s, AV_LOG_ERROR, "Incorrect client bandwidth %d\n", |
||
1514 | rt->client_report_size); |
||
1515 | return AVERROR_INVALIDDATA; |
||
1516 | |||
1517 | } |
||
1518 | av_log(s, AV_LOG_DEBUG, "Client bandwidth = %d\n", rt->client_report_size); |
||
1519 | rt->client_report_size >>= 1; |
||
1520 | |||
1521 | return 0; |
||
1522 | } |
||
1523 | |||
1524 | static int handle_server_bw(URLContext *s, RTMPPacket *pkt) |
||
1525 | { |
||
1526 | RTMPContext *rt = s->priv_data; |
||
1527 | |||
1528 | if (pkt->size < 4) { |
||
1529 | av_log(s, AV_LOG_ERROR, |
||
1530 | "Too short server bandwidth report packet (%d)\n", |
||
1531 | pkt->size); |
||
1532 | return AVERROR_INVALIDDATA; |
||
1533 | } |
||
1534 | |||
1535 | rt->server_bw = AV_RB32(pkt->data); |
||
1536 | if (rt->server_bw <= 0) { |
||
1537 | av_log(s, AV_LOG_ERROR, "Incorrect server bandwidth %d\n", |
||
1538 | rt->server_bw); |
||
1539 | return AVERROR_INVALIDDATA; |
||
1540 | } |
||
1541 | av_log(s, AV_LOG_DEBUG, "Server bandwidth = %d\n", rt->server_bw); |
||
1542 | |||
1543 | return 0; |
||
1544 | } |
||
1545 | |||
1546 | static int do_adobe_auth(RTMPContext *rt, const char *user, const char *salt, |
||
1547 | const char *opaque, const char *challenge) |
||
1548 | { |
||
1549 | uint8_t hash[16]; |
||
1550 | char hashstr[AV_BASE64_SIZE(sizeof(hash))], challenge2[10]; |
||
1551 | struct AVMD5 *md5 = av_md5_alloc(); |
||
1552 | if (!md5) |
||
1553 | return AVERROR(ENOMEM); |
||
1554 | |||
1555 | snprintf(challenge2, sizeof(challenge2), "%08x", av_get_random_seed()); |
||
1556 | |||
1557 | av_md5_init(md5); |
||
1558 | av_md5_update(md5, user, strlen(user)); |
||
1559 | av_md5_update(md5, salt, strlen(salt)); |
||
1560 | av_md5_update(md5, rt->password, strlen(rt->password)); |
||
1561 | av_md5_final(md5, hash); |
||
1562 | av_base64_encode(hashstr, sizeof(hashstr), hash, |
||
1563 | sizeof(hash)); |
||
1564 | av_md5_init(md5); |
||
1565 | av_md5_update(md5, hashstr, strlen(hashstr)); |
||
1566 | if (opaque) |
||
1567 | av_md5_update(md5, opaque, strlen(opaque)); |
||
1568 | else if (challenge) |
||
1569 | av_md5_update(md5, challenge, strlen(challenge)); |
||
1570 | av_md5_update(md5, challenge2, strlen(challenge2)); |
||
1571 | av_md5_final(md5, hash); |
||
1572 | av_base64_encode(hashstr, sizeof(hashstr), hash, |
||
1573 | sizeof(hash)); |
||
1574 | snprintf(rt->auth_params, sizeof(rt->auth_params), |
||
1575 | "?authmod=%s&user=%s&challenge=%s&response=%s", |
||
1576 | "adobe", user, challenge2, hashstr); |
||
1577 | if (opaque) |
||
1578 | av_strlcatf(rt->auth_params, sizeof(rt->auth_params), |
||
1579 | "&opaque=%s", opaque); |
||
1580 | |||
1581 | av_free(md5); |
||
1582 | return 0; |
||
1583 | } |
||
1584 | |||
1585 | static int do_llnw_auth(RTMPContext *rt, const char *user, const char *nonce) |
||
1586 | { |
||
1587 | uint8_t hash[16]; |
||
1588 | char hashstr1[33], hashstr2[33]; |
||
1589 | const char *realm = "live"; |
||
1590 | const char *method = "publish"; |
||
1591 | const char *qop = "auth"; |
||
1592 | const char *nc = "00000001"; |
||
1593 | char cnonce[10]; |
||
1594 | struct AVMD5 *md5 = av_md5_alloc(); |
||
1595 | if (!md5) |
||
1596 | return AVERROR(ENOMEM); |
||
1597 | |||
1598 | snprintf(cnonce, sizeof(cnonce), "%08x", av_get_random_seed()); |
||
1599 | |||
1600 | av_md5_init(md5); |
||
1601 | av_md5_update(md5, user, strlen(user)); |
||
1602 | av_md5_update(md5, ":", 1); |
||
1603 | av_md5_update(md5, realm, strlen(realm)); |
||
1604 | av_md5_update(md5, ":", 1); |
||
1605 | av_md5_update(md5, rt->password, strlen(rt->password)); |
||
1606 | av_md5_final(md5, hash); |
||
1607 | ff_data_to_hex(hashstr1, hash, 16, 1); |
||
1608 | hashstr1[32] = '\0'; |
||
1609 | |||
1610 | av_md5_init(md5); |
||
1611 | av_md5_update(md5, method, strlen(method)); |
||
1612 | av_md5_update(md5, ":/", 2); |
||
1613 | av_md5_update(md5, rt->app, strlen(rt->app)); |
||
1614 | if (!strchr(rt->app, '/')) |
||
1615 | av_md5_update(md5, "/_definst_", strlen("/_definst_")); |
||
1616 | av_md5_final(md5, hash); |
||
1617 | ff_data_to_hex(hashstr2, hash, 16, 1); |
||
1618 | hashstr2[32] = '\0'; |
||
1619 | |||
1620 | av_md5_init(md5); |
||
1621 | av_md5_update(md5, hashstr1, strlen(hashstr1)); |
||
1622 | av_md5_update(md5, ":", 1); |
||
1623 | if (nonce) |
||
1624 | av_md5_update(md5, nonce, strlen(nonce)); |
||
1625 | av_md5_update(md5, ":", 1); |
||
1626 | av_md5_update(md5, nc, strlen(nc)); |
||
1627 | av_md5_update(md5, ":", 1); |
||
1628 | av_md5_update(md5, cnonce, strlen(cnonce)); |
||
1629 | av_md5_update(md5, ":", 1); |
||
1630 | av_md5_update(md5, qop, strlen(qop)); |
||
1631 | av_md5_update(md5, ":", 1); |
||
1632 | av_md5_update(md5, hashstr2, strlen(hashstr2)); |
||
1633 | av_md5_final(md5, hash); |
||
1634 | ff_data_to_hex(hashstr1, hash, 16, 1); |
||
1635 | |||
1636 | snprintf(rt->auth_params, sizeof(rt->auth_params), |
||
1637 | "?authmod=%s&user=%s&nonce=%s&cnonce=%s&nc=%s&response=%s", |
||
1638 | "llnw", user, nonce, cnonce, nc, hashstr1); |
||
1639 | |||
1640 | av_free(md5); |
||
1641 | return 0; |
||
1642 | } |
||
1643 | |||
1644 | static int handle_connect_error(URLContext *s, const char *desc) |
||
1645 | { |
||
1646 | RTMPContext *rt = s->priv_data; |
||
1647 | char buf[300], *ptr, authmod[15]; |
||
1648 | int i = 0, ret = 0; |
||
1649 | const char *user = "", *salt = "", *opaque = NULL, |
||
1650 | *challenge = NULL, *cptr = NULL, *nonce = NULL; |
||
1651 | |||
1652 | if (!(cptr = strstr(desc, "authmod=adobe")) && |
||
1653 | !(cptr = strstr(desc, "authmod=llnw"))) { |
||
1654 | av_log(s, AV_LOG_ERROR, |
||
1655 | "Unknown connect error (unsupported authentication method?)\n"); |
||
1656 | return AVERROR_UNKNOWN; |
||
1657 | } |
||
1658 | cptr += strlen("authmod="); |
||
1659 | while (*cptr && *cptr != ' ' && i < sizeof(authmod) - 1) |
||
1660 | authmod[i++] = *cptr++; |
||
1661 | authmod[i] = '\0'; |
||
1662 | |||
1663 | if (!rt->username[0] || !rt->password[0]) { |
||
1664 | av_log(s, AV_LOG_ERROR, "No credentials set\n"); |
||
1665 | return AVERROR_UNKNOWN; |
||
1666 | } |
||
1667 | |||
1668 | if (strstr(desc, "?reason=authfailed")) { |
||
1669 | av_log(s, AV_LOG_ERROR, "Incorrect username/password\n"); |
||
1670 | return AVERROR_UNKNOWN; |
||
1671 | } else if (strstr(desc, "?reason=nosuchuser")) { |
||
1672 | av_log(s, AV_LOG_ERROR, "Incorrect username\n"); |
||
1673 | return AVERROR_UNKNOWN; |
||
1674 | } |
||
1675 | |||
1676 | if (rt->auth_tried) { |
||
1677 | av_log(s, AV_LOG_ERROR, "Authentication failed\n"); |
||
1678 | return AVERROR_UNKNOWN; |
||
1679 | } |
||
1680 | |||
1681 | rt->auth_params[0] = '\0'; |
||
1682 | |||
1683 | if (strstr(desc, "code=403 need auth")) { |
||
1684 | snprintf(rt->auth_params, sizeof(rt->auth_params), |
||
1685 | "?authmod=%s&user=%s", authmod, rt->username); |
||
1686 | return 0; |
||
1687 | } |
||
1688 | |||
1689 | if (!(cptr = strstr(desc, "?reason=needauth"))) { |
||
1690 | av_log(s, AV_LOG_ERROR, "No auth parameters found\n"); |
||
1691 | return AVERROR_UNKNOWN; |
||
1692 | } |
||
1693 | |||
1694 | av_strlcpy(buf, cptr + 1, sizeof(buf)); |
||
1695 | ptr = buf; |
||
1696 | |||
1697 | while (ptr) { |
||
1698 | char *next = strchr(ptr, '&'); |
||
1699 | char *value = strchr(ptr, '='); |
||
1700 | if (next) |
||
1701 | *next++ = '\0'; |
||
1702 | if (value) |
||
1703 | *value++ = '\0'; |
||
1704 | if (!strcmp(ptr, "user")) { |
||
1705 | user = value; |
||
1706 | } else if (!strcmp(ptr, "salt")) { |
||
1707 | salt = value; |
||
1708 | } else if (!strcmp(ptr, "opaque")) { |
||
1709 | opaque = value; |
||
1710 | } else if (!strcmp(ptr, "challenge")) { |
||
1711 | challenge = value; |
||
1712 | } else if (!strcmp(ptr, "nonce")) { |
||
1713 | nonce = value; |
||
1714 | } |
||
1715 | ptr = next; |
||
1716 | } |
||
1717 | |||
1718 | if (!strcmp(authmod, "adobe")) { |
||
1719 | if ((ret = do_adobe_auth(rt, user, salt, opaque, challenge)) < 0) |
||
1720 | return ret; |
||
1721 | } else { |
||
1722 | if ((ret = do_llnw_auth(rt, user, nonce)) < 0) |
||
1723 | return ret; |
||
1724 | } |
||
1725 | |||
1726 | rt->auth_tried = 1; |
||
1727 | return 0; |
||
1728 | } |
||
1729 | |||
1730 | static int handle_invoke_error(URLContext *s, RTMPPacket *pkt) |
||
1731 | { |
||
1732 | RTMPContext *rt = s->priv_data; |
||
1733 | const uint8_t *data_end = pkt->data + pkt->size; |
||
1734 | char *tracked_method = NULL; |
||
1735 | int level = AV_LOG_ERROR; |
||
1736 | uint8_t tmpstr[256]; |
||
1737 | int ret; |
||
1738 | |||
1739 | if ((ret = find_tracked_method(s, pkt, 9, &tracked_method)) < 0) |
||
1740 | return ret; |
||
1741 | |||
1742 | if (!ff_amf_get_field_value(pkt->data + 9, data_end, |
||
1743 | "description", tmpstr, sizeof(tmpstr))) { |
||
1744 | if (tracked_method && (!strcmp(tracked_method, "_checkbw") || |
||
1745 | !strcmp(tracked_method, "releaseStream") || |
||
1746 | !strcmp(tracked_method, "FCSubscribe") || |
||
1747 | !strcmp(tracked_method, "FCPublish"))) { |
||
1748 | /* Gracefully ignore Adobe-specific historical artifact errors. */ |
||
1749 | level = AV_LOG_WARNING; |
||
1750 | ret = 0; |
||
1751 | } else if (tracked_method && !strcmp(tracked_method, "connect")) { |
||
1752 | ret = handle_connect_error(s, tmpstr); |
||
1753 | if (!ret) { |
||
1754 | rt->do_reconnect = 1; |
||
1755 | level = AV_LOG_VERBOSE; |
||
1756 | } |
||
1757 | } else |
||
1758 | ret = AVERROR_UNKNOWN; |
||
1759 | av_log(s, level, "Server error: %s\n", tmpstr); |
||
1760 | } |
||
1761 | |||
1762 | av_free(tracked_method); |
||
1763 | return ret; |
||
1764 | } |
||
1765 | |||
1766 | static int write_begin(URLContext *s) |
||
1767 | { |
||
1768 | RTMPContext *rt = s->priv_data; |
||
1769 | PutByteContext pbc; |
||
1770 | RTMPPacket spkt = { 0 }; |
||
1771 | int ret; |
||
1772 | |||
1773 | // Send Stream Begin 1 |
||
1774 | if ((ret = ff_rtmp_packet_create(&spkt, RTMP_NETWORK_CHANNEL, |
||
1775 | RTMP_PT_PING, 0, 6)) < 0) { |
||
1776 | av_log(s, AV_LOG_ERROR, "Unable to create response packet\n"); |
||
1777 | return ret; |
||
1778 | } |
||
1779 | |||
1780 | bytestream2_init_writer(&pbc, spkt.data, spkt.size); |
||
1781 | bytestream2_put_be16(&pbc, 0); // 0 -> Stream Begin |
||
1782 | bytestream2_put_be32(&pbc, rt->nb_streamid); |
||
1783 | |||
1784 | ret = ff_rtmp_packet_write(rt->stream, &spkt, rt->out_chunk_size, |
||
1785 | &rt->prev_pkt[1], &rt->nb_prev_pkt[1]); |
||
1786 | |||
1787 | ff_rtmp_packet_destroy(&spkt); |
||
1788 | |||
1789 | return ret; |
||
1790 | } |
||
1791 | |||
1792 | static int write_status(URLContext *s, RTMPPacket *pkt, |
||
1793 | const char *status, const char *filename) |
||
1794 | { |
||
1795 | RTMPContext *rt = s->priv_data; |
||
1796 | RTMPPacket spkt = { 0 }; |
||
1797 | char statusmsg[128]; |
||
1798 | uint8_t *pp; |
||
1799 | int ret; |
||
1800 | |||
1801 | if ((ret = ff_rtmp_packet_create(&spkt, RTMP_SYSTEM_CHANNEL, |
||
1802 | RTMP_PT_INVOKE, 0, |
||
1803 | RTMP_PKTDATA_DEFAULT_SIZE)) < 0) { |
||
1804 | av_log(s, AV_LOG_ERROR, "Unable to create response packet\n"); |
||
1805 | return ret; |
||
1806 | } |
||
1807 | |||
1808 | pp = spkt.data; |
||
1809 | spkt.extra = pkt->extra; |
||
1810 | ff_amf_write_string(&pp, "onStatus"); |
||
1811 | ff_amf_write_number(&pp, 0); |
||
1812 | ff_amf_write_null(&pp); |
||
1813 | |||
1814 | ff_amf_write_object_start(&pp); |
||
1815 | ff_amf_write_field_name(&pp, "level"); |
||
1816 | ff_amf_write_string(&pp, "status"); |
||
1817 | ff_amf_write_field_name(&pp, "code"); |
||
1818 | ff_amf_write_string(&pp, status); |
||
1819 | ff_amf_write_field_name(&pp, "description"); |
||
1820 | snprintf(statusmsg, sizeof(statusmsg), |
||
1821 | "%s is now published", filename); |
||
1822 | ff_amf_write_string(&pp, statusmsg); |
||
1823 | ff_amf_write_field_name(&pp, "details"); |
||
1824 | ff_amf_write_string(&pp, filename); |
||
1825 | ff_amf_write_field_name(&pp, "clientid"); |
||
1826 | snprintf(statusmsg, sizeof(statusmsg), "%s", LIBAVFORMAT_IDENT); |
||
1827 | ff_amf_write_string(&pp, statusmsg); |
||
1828 | ff_amf_write_object_end(&pp); |
||
1829 | |||
1830 | spkt.size = pp - spkt.data; |
||
1831 | ret = ff_rtmp_packet_write(rt->stream, &spkt, rt->out_chunk_size, |
||
1832 | &rt->prev_pkt[1], &rt->nb_prev_pkt[1]); |
||
1833 | ff_rtmp_packet_destroy(&spkt); |
||
1834 | |||
1835 | return ret; |
||
1836 | } |
||
1837 | |||
1838 | static int send_invoke_response(URLContext *s, RTMPPacket *pkt) |
||
1839 | { |
||
1840 | RTMPContext *rt = s->priv_data; |
||
1841 | double seqnum; |
||
1842 | char filename[64]; |
||
1843 | char command[64]; |
||
1844 | int stringlen; |
||
1845 | char *pchar; |
||
1846 | const uint8_t *p = pkt->data; |
||
1847 | uint8_t *pp = NULL; |
||
1848 | RTMPPacket spkt = { 0 }; |
||
1849 | GetByteContext gbc; |
||
1850 | int ret; |
||
1851 | |||
1852 | bytestream2_init(&gbc, p, pkt->size); |
||
1853 | if (ff_amf_read_string(&gbc, command, sizeof(command), |
||
1854 | &stringlen)) { |
||
1855 | av_log(s, AV_LOG_ERROR, "Error in PT_INVOKE\n"); |
||
1856 | return AVERROR_INVALIDDATA; |
||
1857 | } |
||
1858 | |||
1859 | ret = ff_amf_read_number(&gbc, &seqnum); |
||
1860 | if (ret) |
||
1861 | return ret; |
||
1862 | ret = ff_amf_read_null(&gbc); |
||
1863 | if (ret) |
||
1864 | return ret; |
||
1865 | if (!strcmp(command, "FCPublish") || |
||
1866 | !strcmp(command, "publish")) { |
||
1867 | ret = ff_amf_read_string(&gbc, filename, |
||
1868 | sizeof(filename), &stringlen); |
||
1869 | // check with url |
||
1870 | if (s->filename) { |
||
1871 | pchar = strrchr(s->filename, '/'); |
||
1872 | if (!pchar) { |
||
1873 | av_log(s, AV_LOG_WARNING, |
||
1874 | "Unable to find / in url %s, bad format\n", |
||
1875 | s->filename); |
||
1876 | pchar = s->filename; |
||
1877 | } |
||
1878 | pchar++; |
||
1879 | if (strcmp(pchar, filename)) |
||
1880 | av_log(s, AV_LOG_WARNING, "Unexpected stream %s, expecting" |
||
1881 | " %s\n", filename, pchar); |
||
1882 | } |
||
1883 | rt->state = STATE_RECEIVING; |
||
1884 | } |
||
1885 | |||
1886 | if (!strcmp(command, "FCPublish")) { |
||
1887 | if ((ret = ff_rtmp_packet_create(&spkt, RTMP_SYSTEM_CHANNEL, |
||
1888 | RTMP_PT_INVOKE, 0, |
||
1889 | RTMP_PKTDATA_DEFAULT_SIZE)) < 0) { |
||
1890 | av_log(s, AV_LOG_ERROR, "Unable to create response packet\n"); |
||
1891 | return ret; |
||
1892 | } |
||
1893 | pp = spkt.data; |
||
1894 | ff_amf_write_string(&pp, "onFCPublish"); |
||
1895 | } else if (!strcmp(command, "publish")) { |
||
1896 | ret = write_begin(s); |
||
1897 | if (ret < 0) |
||
1898 | return ret; |
||
1899 | |||
1900 | // Send onStatus(NetStream.Publish.Start) |
||
1901 | return write_status(s, pkt, "NetStream.Publish.Start", |
||
1902 | filename); |
||
1903 | } else if (!strcmp(command, "play")) { |
||
1904 | ret = write_begin(s); |
||
1905 | if (ret < 0) |
||
1906 | return ret; |
||
1907 | rt->state = STATE_SENDING; |
||
1908 | return write_status(s, pkt, "NetStream.Play.Start", |
||
1909 | filename); |
||
1910 | } else { |
||
1911 | if ((ret = ff_rtmp_packet_create(&spkt, RTMP_SYSTEM_CHANNEL, |
||
1912 | RTMP_PT_INVOKE, 0, |
||
1913 | RTMP_PKTDATA_DEFAULT_SIZE)) < 0) { |
||
1914 | av_log(s, AV_LOG_ERROR, "Unable to create response packet\n"); |
||
1915 | return ret; |
||
1916 | } |
||
1917 | pp = spkt.data; |
||
1918 | ff_amf_write_string(&pp, "_result"); |
||
1919 | ff_amf_write_number(&pp, seqnum); |
||
1920 | ff_amf_write_null(&pp); |
||
1921 | if (!strcmp(command, "createStream")) { |
||
1922 | rt->nb_streamid++; |
||
1923 | if (rt->nb_streamid == 0 || rt->nb_streamid == 2) |
||
1924 | rt->nb_streamid++; /* Values 0 and 2 are reserved */ |
||
1925 | ff_amf_write_number(&pp, rt->nb_streamid); |
||
1926 | /* By now we don't control which streams are removed in |
||
1927 | * deleteStream. There is no stream creation control |
||
1928 | * if a client creates more than 2^32 - 2 streams. */ |
||
1929 | } |
||
1930 | } |
||
1931 | spkt.size = pp - spkt.data; |
||
1932 | ret = ff_rtmp_packet_write(rt->stream, &spkt, rt->out_chunk_size, |
||
1933 | &rt->prev_pkt[1], &rt->nb_prev_pkt[1]); |
||
1934 | ff_rtmp_packet_destroy(&spkt); |
||
1935 | return ret; |
||
1936 | } |
||
1937 | |||
1938 | static int handle_invoke_result(URLContext *s, RTMPPacket *pkt) |
||
1939 | { |
||
1940 | RTMPContext *rt = s->priv_data; |
||
1941 | char *tracked_method = NULL; |
||
1942 | int ret = 0; |
||
1943 | |||
1944 | if ((ret = find_tracked_method(s, pkt, 10, &tracked_method)) < 0) |
||
1945 | return ret; |
||
1946 | |||
1947 | if (!tracked_method) { |
||
1948 | /* Ignore this reply when the current method is not tracked. */ |
||
1949 | return ret; |
||
1950 | } |
||
1951 | |||
1952 | if (!strcmp(tracked_method, "connect")) { |
||
1953 | if (!rt->is_input) { |
||
1954 | if ((ret = gen_release_stream(s, rt)) < 0) |
||
1955 | goto fail; |
||
1956 | |||
1957 | if ((ret = gen_fcpublish_stream(s, rt)) < 0) |
||
1958 | goto fail; |
||
1959 | } else { |
||
1960 | if ((ret = gen_server_bw(s, rt)) < 0) |
||
1961 | goto fail; |
||
1962 | } |
||
1963 | |||
1964 | if ((ret = gen_create_stream(s, rt)) < 0) |
||
1965 | goto fail; |
||
1966 | |||
1967 | if (rt->is_input) { |
||
1968 | /* Send the FCSubscribe command when the name of live |
||
1969 | * stream is defined by the user or if it's a live stream. */ |
||
1970 | if (rt->subscribe) { |
||
1971 | if ((ret = gen_fcsubscribe_stream(s, rt, rt->subscribe)) < 0) |
||
1972 | goto fail; |
||
1973 | } else if (rt->live == -1) { |
||
1974 | if ((ret = gen_fcsubscribe_stream(s, rt, rt->playpath)) < 0) |
||
1975 | goto fail; |
||
1976 | } |
||
1977 | } |
||
1978 | } else if (!strcmp(tracked_method, "createStream")) { |
||
1979 | //extract a number from the result |
||
1980 | if (pkt->data[10] || pkt->data[19] != 5 || pkt->data[20]) { |
||
1981 | av_log(s, AV_LOG_WARNING, "Unexpected reply on connect()\n"); |
||
1982 | } else { |
||
1983 | rt->stream_id = av_int2double(AV_RB64(pkt->data + 21)); |
||
1984 | } |
||
1985 | |||
1986 | if (!rt->is_input) { |
||
1987 | if ((ret = gen_publish(s, rt)) < 0) |
||
1988 | goto fail; |
||
1989 | } else { |
||
1990 | if ((ret = gen_play(s, rt)) < 0) |
||
1991 | goto fail; |
||
1992 | if ((ret = gen_buffer_time(s, rt)) < 0) |
||
1993 | goto fail; |
||
1994 | } |
||
1995 | } |
||
1996 | |||
1997 | fail: |
||
1998 | av_free(tracked_method); |
||
1999 | return ret; |
||
2000 | } |
||
2001 | |||
2002 | static int handle_invoke_status(URLContext *s, RTMPPacket *pkt) |
||
2003 | { |
||
2004 | RTMPContext *rt = s->priv_data; |
||
2005 | const uint8_t *data_end = pkt->data + pkt->size; |
||
2006 | const uint8_t *ptr = pkt->data + RTMP_HEADER; |
||
2007 | uint8_t tmpstr[256]; |
||
2008 | int i, t; |
||
2009 | |||
2010 | for (i = 0; i < 2; i++) { |
||
2011 | t = ff_amf_tag_size(ptr, data_end); |
||
2012 | if (t < 0) |
||
2013 | return 1; |
||
2014 | ptr += t; |
||
2015 | } |
||
2016 | |||
2017 | t = ff_amf_get_field_value(ptr, data_end, "level", tmpstr, sizeof(tmpstr)); |
||
2018 | if (!t && !strcmp(tmpstr, "error")) { |
||
2019 | t = ff_amf_get_field_value(ptr, data_end, |
||
2020 | "description", tmpstr, sizeof(tmpstr)); |
||
2021 | if (t || !tmpstr[0]) |
||
2022 | t = ff_amf_get_field_value(ptr, data_end, "code", |
||
2023 | tmpstr, sizeof(tmpstr)); |
||
2024 | if (!t) |
||
2025 | av_log(s, AV_LOG_ERROR, "Server error: %s\n", tmpstr); |
||
2026 | return -1; |
||
2027 | } |
||
2028 | |||
2029 | t = ff_amf_get_field_value(ptr, data_end, "code", tmpstr, sizeof(tmpstr)); |
||
2030 | if (!t && !strcmp(tmpstr, "NetStream.Play.Start")) rt->state = STATE_PLAYING; |
||
2031 | if (!t && !strcmp(tmpstr, "NetStream.Play.Stop")) rt->state = STATE_STOPPED; |
||
2032 | if (!t && !strcmp(tmpstr, "NetStream.Play.UnpublishNotify")) rt->state = STATE_STOPPED; |
||
2033 | if (!t && !strcmp(tmpstr, "NetStream.Publish.Start")) rt->state = STATE_PUBLISHING; |
||
2034 | if (!t && !strcmp(tmpstr, "NetStream.Seek.Notify")) rt->state = STATE_PLAYING; |
||
2035 | |||
2036 | return 0; |
||
2037 | } |
||
2038 | |||
2039 | static int handle_invoke(URLContext *s, RTMPPacket *pkt) |
||
2040 | { |
||
2041 | RTMPContext *rt = s->priv_data; |
||
2042 | int ret = 0; |
||
2043 | |||
2044 | //TODO: check for the messages sent for wrong state? |
||
2045 | if (ff_amf_match_string(pkt->data, pkt->size, "_error")) { |
||
2046 | if ((ret = handle_invoke_error(s, pkt)) < 0) |
||
2047 | return ret; |
||
2048 | } else if (ff_amf_match_string(pkt->data, pkt->size, "_result")) { |
||
2049 | if ((ret = handle_invoke_result(s, pkt)) < 0) |
||
2050 | return ret; |
||
2051 | } else if (ff_amf_match_string(pkt->data, pkt->size, "onStatus")) { |
||
2052 | if ((ret = handle_invoke_status(s, pkt)) < 0) |
||
2053 | return ret; |
||
2054 | } else if (ff_amf_match_string(pkt->data, pkt->size, "onBWDone")) { |
||
2055 | if ((ret = gen_check_bw(s, rt)) < 0) |
||
2056 | return ret; |
||
2057 | } else if (ff_amf_match_string(pkt->data, pkt->size, "releaseStream") || |
||
2058 | ff_amf_match_string(pkt->data, pkt->size, "FCPublish") || |
||
2059 | ff_amf_match_string(pkt->data, pkt->size, "publish") || |
||
2060 | ff_amf_match_string(pkt->data, pkt->size, "play") || |
||
2061 | ff_amf_match_string(pkt->data, pkt->size, "_checkbw") || |
||
2062 | ff_amf_match_string(pkt->data, pkt->size, "createStream")) { |
||
2063 | if ((ret = send_invoke_response(s, pkt)) < 0) |
||
2064 | return ret; |
||
2065 | } |
||
2066 | |||
2067 | return ret; |
||
2068 | } |
||
2069 | |||
2070 | static int update_offset(RTMPContext *rt, int size) |
||
2071 | { |
||
2072 | int old_flv_size; |
||
2073 | |||
2074 | // generate packet header and put data into buffer for FLV demuxer |
||
2075 | if (rt->flv_off < rt->flv_size) { |
||
2076 | // There is old unread data in the buffer, thus append at the end |
||
2077 | old_flv_size = rt->flv_size; |
||
2078 | rt->flv_size += size; |
||
2079 | } else { |
||
2080 | // All data has been read, write the new data at the start of the buffer |
||
2081 | old_flv_size = 0; |
||
2082 | rt->flv_size = size; |
||
2083 | rt->flv_off = 0; |
||
2084 | } |
||
2085 | |||
2086 | return old_flv_size; |
||
2087 | } |
||
2088 | |||
2089 | static int append_flv_data(RTMPContext *rt, RTMPPacket *pkt, int skip) |
||
2090 | { |
||
2091 | int old_flv_size, ret; |
||
2092 | PutByteContext pbc; |
||
2093 | const uint8_t *data = pkt->data + skip; |
||
2094 | const int size = pkt->size - skip; |
||
2095 | uint32_t ts = pkt->timestamp; |
||
2096 | |||
2097 | old_flv_size = update_offset(rt, size + 15); |
||
2098 | |||
2099 | if ((ret = av_reallocp(&rt->flv_data, rt->flv_size)) < 0) { |
||
2100 | rt->flv_size = rt->flv_off = 0; |
||
2101 | return ret; |
||
2102 | } |
||
2103 | bytestream2_init_writer(&pbc, rt->flv_data, rt->flv_size); |
||
2104 | bytestream2_skip_p(&pbc, old_flv_size); |
||
2105 | bytestream2_put_byte(&pbc, pkt->type); |
||
2106 | bytestream2_put_be24(&pbc, size); |
||
2107 | bytestream2_put_be24(&pbc, ts); |
||
2108 | bytestream2_put_byte(&pbc, ts >> 24); |
||
2109 | bytestream2_put_be24(&pbc, 0); |
||
2110 | bytestream2_put_buffer(&pbc, data, size); |
||
2111 | bytestream2_put_be32(&pbc, 0); |
||
2112 | |||
2113 | return 0; |
||
2114 | } |
||
2115 | |||
2116 | static int handle_notify(URLContext *s, RTMPPacket *pkt) |
||
2117 | { |
||
2118 | RTMPContext *rt = s->priv_data; |
||
2119 | uint8_t commandbuffer[64]; |
||
2120 | char statusmsg[128]; |
||
2121 | int stringlen, ret, skip = 0; |
||
2122 | GetByteContext gbc; |
||
2123 | |||
2124 | bytestream2_init(&gbc, pkt->data, pkt->size); |
||
2125 | if (ff_amf_read_string(&gbc, commandbuffer, sizeof(commandbuffer), |
||
2126 | &stringlen)) |
||
2127 | return AVERROR_INVALIDDATA; |
||
2128 | |||
2129 | // Skip the @setDataFrame string and validate it is a notification |
||
2130 | if (!strcmp(commandbuffer, "@setDataFrame")) { |
||
2131 | skip = gbc.buffer - pkt->data; |
||
2132 | ret = ff_amf_read_string(&gbc, statusmsg, |
||
2133 | sizeof(statusmsg), &stringlen); |
||
2134 | if (ret < 0) |
||
2135 | return AVERROR_INVALIDDATA; |
||
2136 | } |
||
2137 | |||
2138 | return append_flv_data(rt, pkt, skip); |
||
2139 | } |
||
2140 | |||
2141 | /** |
||
2142 | * Parse received packet and possibly perform some action depending on |
||
2143 | * the packet contents. |
||
2144 | * @return 0 for no errors, negative values for serious errors which prevent |
||
2145 | * further communications, positive values for uncritical errors |
||
2146 | */ |
||
2147 | static int rtmp_parse_result(URLContext *s, RTMPContext *rt, RTMPPacket *pkt) |
||
2148 | { |
||
2149 | int ret; |
||
2150 | |||
2151 | #ifdef DEBUG |
||
2152 | ff_rtmp_packet_dump(s, pkt); |
||
2153 | #endif |
||
2154 | |||
2155 | switch (pkt->type) { |
||
2156 | case RTMP_PT_BYTES_READ: |
||
2157 | av_dlog(s, "received bytes read report\n"); |
||
2158 | break; |
||
2159 | case RTMP_PT_CHUNK_SIZE: |
||
2160 | if ((ret = handle_chunk_size(s, pkt)) < 0) |
||
2161 | return ret; |
||
2162 | break; |
||
2163 | case RTMP_PT_PING: |
||
2164 | if ((ret = handle_ping(s, pkt)) < 0) |
||
2165 | return ret; |
||
2166 | break; |
||
2167 | case RTMP_PT_CLIENT_BW: |
||
2168 | if ((ret = handle_client_bw(s, pkt)) < 0) |
||
2169 | return ret; |
||
2170 | break; |
||
2171 | case RTMP_PT_SERVER_BW: |
||
2172 | if ((ret = handle_server_bw(s, pkt)) < 0) |
||
2173 | return ret; |
||
2174 | break; |
||
2175 | case RTMP_PT_INVOKE: |
||
2176 | if ((ret = handle_invoke(s, pkt)) < 0) |
||
2177 | return ret; |
||
2178 | break; |
||
2179 | case RTMP_PT_VIDEO: |
||
2180 | case RTMP_PT_AUDIO: |
||
2181 | case RTMP_PT_METADATA: |
||
2182 | case RTMP_PT_NOTIFY: |
||
2183 | /* Audio, Video and Metadata packets are parsed in get_packet() */ |
||
2184 | break; |
||
2185 | default: |
||
2186 | av_log(s, AV_LOG_VERBOSE, "Unknown packet type received 0x%02X\n", pkt->type); |
||
2187 | break; |
||
2188 | } |
||
2189 | return 0; |
||
2190 | } |
||
2191 | |||
2192 | static int handle_metadata(RTMPContext *rt, RTMPPacket *pkt) |
||
2193 | { |
||
2194 | int ret, old_flv_size, type; |
||
2195 | const uint8_t *next; |
||
2196 | uint8_t *p; |
||
2197 | uint32_t size; |
||
2198 | uint32_t ts, cts, pts = 0; |
||
2199 | |||
2200 | old_flv_size = update_offset(rt, pkt->size); |
||
2201 | |||
2202 | if ((ret = av_reallocp(&rt->flv_data, rt->flv_size)) < 0) { |
||
2203 | rt->flv_size = rt->flv_off = 0; |
||
2204 | return ret; |
||
2205 | } |
||
2206 | |||
2207 | next = pkt->data; |
||
2208 | p = rt->flv_data + old_flv_size; |
||
2209 | |||
2210 | /* copy data while rewriting timestamps */ |
||
2211 | ts = pkt->timestamp; |
||
2212 | |||
2213 | while (next - pkt->data < pkt->size - RTMP_HEADER) { |
||
2214 | type = bytestream_get_byte(&next); |
||
2215 | size = bytestream_get_be24(&next); |
||
2216 | cts = bytestream_get_be24(&next); |
||
2217 | cts |= bytestream_get_byte(&next) << 24; |
||
2218 | if (!pts) |
||
2219 | pts = cts; |
||
2220 | ts += cts - pts; |
||
2221 | pts = cts; |
||
2222 | if (size + 3 + 4 > pkt->data + pkt->size - next) |
||
2223 | break; |
||
2224 | bytestream_put_byte(&p, type); |
||
2225 | bytestream_put_be24(&p, size); |
||
2226 | bytestream_put_be24(&p, ts); |
||
2227 | bytestream_put_byte(&p, ts >> 24); |
||
2228 | memcpy(p, next, size + 3 + 4); |
||
2229 | next += size + 3 + 4; |
||
2230 | p += size + 3 + 4; |
||
2231 | } |
||
2232 | if (p != rt->flv_data + rt->flv_size) { |
||
2233 | av_log(NULL, AV_LOG_WARNING, "Incomplete flv packets in " |
||
2234 | "RTMP_PT_METADATA packet\n"); |
||
2235 | rt->flv_size = p - rt->flv_data; |
||
2236 | } |
||
2237 | |||
2238 | return 0; |
||
2239 | } |
||
2240 | |||
2241 | /** |
||
2242 | * Interact with the server by receiving and sending RTMP packets until |
||
2243 | * there is some significant data (media data or expected status notification). |
||
2244 | * |
||
2245 | * @param s reading context |
||
2246 | * @param for_header non-zero value tells function to work until it |
||
2247 | * gets notification from the server that playing has been started, |
||
2248 | * otherwise function will work until some media data is received (or |
||
2249 | * an error happens) |
||
2250 | * @return 0 for successful operation, negative value in case of error |
||
2251 | */ |
||
2252 | static int get_packet(URLContext *s, int for_header) |
||
2253 | { |
||
2254 | RTMPContext *rt = s->priv_data; |
||
2255 | int ret; |
||
2256 | |||
2257 | if (rt->state == STATE_STOPPED) |
||
2258 | return AVERROR_EOF; |
||
2259 | |||
2260 | for (;;) { |
||
2261 | RTMPPacket rpkt = { 0 }; |
||
2262 | if ((ret = ff_rtmp_packet_read(rt->stream, &rpkt, |
||
2263 | rt->in_chunk_size, &rt->prev_pkt[0], |
||
2264 | &rt->nb_prev_pkt[0])) <= 0) { |
||
2265 | if (ret == 0) { |
||
2266 | return AVERROR(EAGAIN); |
||
2267 | } else { |
||
2268 | return AVERROR(EIO); |
||
2269 | } |
||
2270 | } |
||
2271 | rt->bytes_read += ret; |
||
2272 | if (rt->bytes_read - rt->last_bytes_read > rt->client_report_size) { |
||
2273 | av_log(s, AV_LOG_DEBUG, "Sending bytes read report\n"); |
||
2274 | if ((ret = gen_bytes_read(s, rt, rpkt.timestamp + 1)) < 0) |
||
2275 | return ret; |
||
2276 | rt->last_bytes_read = rt->bytes_read; |
||
2277 | } |
||
2278 | |||
2279 | ret = rtmp_parse_result(s, rt, &rpkt); |
||
2280 | |||
2281 | // At this point we must check if we are in the seek state and continue |
||
2282 | // with the next packet. handle_invoke will get us out of this state |
||
2283 | // when the right message is encountered |
||
2284 | if (rt->state == STATE_SEEKING) { |
||
2285 | ff_rtmp_packet_destroy(&rpkt); |
||
2286 | // We continue, let the natural flow of things happen: |
||
2287 | // AVERROR(EAGAIN) or handle_invoke gets us out of here |
||
2288 | continue; |
||
2289 | } |
||
2290 | |||
2291 | if (ret < 0) {//serious error in current packet |
||
2292 | ff_rtmp_packet_destroy(&rpkt); |
||
2293 | return ret; |
||
2294 | } |
||
2295 | if (rt->do_reconnect && for_header) { |
||
2296 | ff_rtmp_packet_destroy(&rpkt); |
||
2297 | return 0; |
||
2298 | } |
||
2299 | if (rt->state == STATE_STOPPED) { |
||
2300 | ff_rtmp_packet_destroy(&rpkt); |
||
2301 | return AVERROR_EOF; |
||
2302 | } |
||
2303 | if (for_header && (rt->state == STATE_PLAYING || |
||
2304 | rt->state == STATE_PUBLISHING || |
||
2305 | rt->state == STATE_SENDING || |
||
2306 | rt->state == STATE_RECEIVING)) { |
||
2307 | ff_rtmp_packet_destroy(&rpkt); |
||
2308 | return 0; |
||
2309 | } |
||
2310 | if (!rpkt.size || !rt->is_input) { |
||
2311 | ff_rtmp_packet_destroy(&rpkt); |
||
2312 | continue; |
||
2313 | } |
||
2314 | if (rpkt.type == RTMP_PT_VIDEO || rpkt.type == RTMP_PT_AUDIO) { |
||
2315 | ret = append_flv_data(rt, &rpkt, 0); |
||
2316 | ff_rtmp_packet_destroy(&rpkt); |
||
2317 | return ret; |
||
2318 | } else if (rpkt.type == RTMP_PT_NOTIFY) { |
||
2319 | ret = handle_notify(s, &rpkt); |
||
2320 | ff_rtmp_packet_destroy(&rpkt); |
||
2321 | return ret; |
||
2322 | } else if (rpkt.type == RTMP_PT_METADATA) { |
||
2323 | ret = handle_metadata(rt, &rpkt); |
||
2324 | ff_rtmp_packet_destroy(&rpkt); |
||
2325 | return 0; |
||
2326 | } |
||
2327 | ff_rtmp_packet_destroy(&rpkt); |
||
2328 | } |
||
2329 | } |
||
2330 | |||
2331 | static int rtmp_close(URLContext *h) |
||
2332 | { |
||
2333 | RTMPContext *rt = h->priv_data; |
||
2334 | int ret = 0, i, j; |
||
2335 | |||
2336 | if (!rt->is_input) { |
||
2337 | rt->flv_data = NULL; |
||
2338 | if (rt->out_pkt.size) |
||
2339 | ff_rtmp_packet_destroy(&rt->out_pkt); |
||
2340 | if (rt->state > STATE_FCPUBLISH) |
||
2341 | ret = gen_fcunpublish_stream(h, rt); |
||
2342 | } |
||
2343 | if (rt->state > STATE_HANDSHAKED) |
||
2344 | ret = gen_delete_stream(h, rt); |
||
2345 | for (i = 0; i < 2; i++) { |
||
2346 | for (j = 0; j < rt->nb_prev_pkt[i]; j++) |
||
2347 | ff_rtmp_packet_destroy(&rt->prev_pkt[i][j]); |
||
2348 | av_freep(&rt->prev_pkt[i]); |
||
2349 | } |
||
2350 | |||
2351 | free_tracked_methods(rt); |
||
2352 | av_freep(&rt->flv_data); |
||
2353 | ffurl_close(rt->stream); |
||
2354 | return ret; |
||
2355 | } |
||
2356 | |||
2357 | /** |
||
2358 | * Open RTMP connection and verify that the stream can be played. |
||
2359 | * |
||
2360 | * URL syntax: rtmp://server[:port][/app][/playpath] |
||
2361 | * where 'app' is first one or two directories in the path |
||
2362 | * (e.g. /ondemand/, /flash/live/, etc.) |
||
2363 | * and 'playpath' is a file name (the rest of the path, |
||
2364 | * may be prefixed with "mp4:") |
||
2365 | */ |
||
2366 | static int rtmp_open(URLContext *s, const char *uri, int flags) |
||
2367 | { |
||
2368 | RTMPContext *rt = s->priv_data; |
||
2369 | char proto[8], hostname[256], path[1024], auth[100], *fname; |
||
2370 | char *old_app; |
||
2371 | uint8_t buf[2048]; |
||
2372 | int port; |
||
2373 | AVDictionary *opts = NULL; |
||
2374 | int ret; |
||
2375 | |||
2376 | if (rt->listen_timeout > 0) |
||
2377 | rt->listen = 1; |
||
2378 | |||
2379 | rt->is_input = !(flags & AVIO_FLAG_WRITE); |
||
2380 | |||
2381 | av_url_split(proto, sizeof(proto), auth, sizeof(auth), |
||
2382 | hostname, sizeof(hostname), &port, |
||
2383 | path, sizeof(path), s->filename); |
||
2384 | |||
2385 | if (strchr(path, ' ')) { |
||
2386 | av_log(s, AV_LOG_WARNING, |
||
2387 | "Detected librtmp style URL parameters, these aren't supported " |
||
2388 | "by the libavformat internal RTMP handler currently enabled. " |
||
2389 | "See the documentation for the correct way to pass parameters.\n"); |
||
2390 | } |
||
2391 | |||
2392 | if (auth[0]) { |
||
2393 | char *ptr = strchr(auth, ':'); |
||
2394 | if (ptr) { |
||
2395 | *ptr = '\0'; |
||
2396 | av_strlcpy(rt->username, auth, sizeof(rt->username)); |
||
2397 | av_strlcpy(rt->password, ptr + 1, sizeof(rt->password)); |
||
2398 | } |
||
2399 | } |
||
2400 | |||
2401 | if (rt->listen && strcmp(proto, "rtmp")) { |
||
2402 | av_log(s, AV_LOG_ERROR, "rtmp_listen not available for %s\n", |
||
2403 | proto); |
||
2404 | return AVERROR(EINVAL); |
||
2405 | } |
||
2406 | if (!strcmp(proto, "rtmpt") || !strcmp(proto, "rtmpts")) { |
||
2407 | if (!strcmp(proto, "rtmpts")) |
||
2408 | av_dict_set(&opts, "ffrtmphttp_tls", "1", 1); |
||
2409 | |||
2410 | /* open the http tunneling connection */ |
||
2411 | ff_url_join(buf, sizeof(buf), "ffrtmphttp", NULL, hostname, port, NULL); |
||
2412 | } else if (!strcmp(proto, "rtmps")) { |
||
2413 | /* open the tls connection */ |
||
2414 | if (port < 0) |
||
2415 | port = RTMPS_DEFAULT_PORT; |
||
2416 | ff_url_join(buf, sizeof(buf), "tls", NULL, hostname, port, NULL); |
||
2417 | } else if (!strcmp(proto, "rtmpe") || (!strcmp(proto, "rtmpte"))) { |
||
2418 | if (!strcmp(proto, "rtmpte")) |
||
2419 | av_dict_set(&opts, "ffrtmpcrypt_tunneling", "1", 1); |
||
2420 | |||
2421 | /* open the encrypted connection */ |
||
2422 | ff_url_join(buf, sizeof(buf), "ffrtmpcrypt", NULL, hostname, port, NULL); |
||
2423 | rt->encrypted = 1; |
||
2424 | } else { |
||
2425 | /* open the tcp connection */ |
||
2426 | if (port < 0) |
||
2427 | port = RTMP_DEFAULT_PORT; |
||
2428 | if (rt->listen) |
||
2429 | ff_url_join(buf, sizeof(buf), "tcp", NULL, hostname, port, |
||
2430 | "?listen&listen_timeout=%d", |
||
2431 | rt->listen_timeout * 1000); |
||
2432 | else |
||
2433 | ff_url_join(buf, sizeof(buf), "tcp", NULL, hostname, port, NULL); |
||
2434 | } |
||
2435 | |||
2436 | reconnect: |
||
2437 | if ((ret = ffurl_open(&rt->stream, buf, AVIO_FLAG_READ_WRITE, |
||
2438 | &s->interrupt_callback, &opts)) < 0) { |
||
2439 | av_log(s , AV_LOG_ERROR, "Cannot open connection %s\n", buf); |
||
2440 | goto fail; |
||
2441 | } |
||
2442 | |||
2443 | if (rt->swfverify) { |
||
2444 | if ((ret = rtmp_calc_swfhash(s)) < 0) |
||
2445 | goto fail; |
||
2446 | } |
||
2447 | |||
2448 | rt->state = STATE_START; |
||
2449 | if (!rt->listen && (ret = rtmp_handshake(s, rt)) < 0) |
||
2450 | goto fail; |
||
2451 | if (rt->listen && (ret = rtmp_server_handshake(s, rt)) < 0) |
||
2452 | goto fail; |
||
2453 | |||
2454 | rt->out_chunk_size = 128; |
||
2455 | rt->in_chunk_size = 128; // Probably overwritten later |
||
2456 | rt->state = STATE_HANDSHAKED; |
||
2457 | |||
2458 | // Keep the application name when it has been defined by the user. |
||
2459 | old_app = rt->app; |
||
2460 | |||
2461 | rt->app = av_malloc(APP_MAX_LENGTH); |
||
2462 | if (!rt->app) { |
||
2463 | ret = AVERROR(ENOMEM); |
||
2464 | goto fail; |
||
2465 | } |
||
2466 | |||
2467 | //extract "app" part from path |
||
2468 | if (!strncmp(path, "/ondemand/", 10)) { |
||
2469 | fname = path + 10; |
||
2470 | memcpy(rt->app, "ondemand", 9); |
||
2471 | } else { |
||
2472 | char *next = *path ? path + 1 : path; |
||
2473 | char *p = strchr(next, '/'); |
||
2474 | if (!p) { |
||
2475 | fname = next; |
||
2476 | rt->app[0] = '\0'; |
||
2477 | } else { |
||
2478 | // make sure we do not mismatch a playpath for an application instance |
||
2479 | char *c = strchr(p + 1, ':'); |
||
2480 | fname = strchr(p + 1, '/'); |
||
2481 | if (!fname || (c && c < fname)) { |
||
2482 | fname = p + 1; |
||
2483 | av_strlcpy(rt->app, path + 1, FFMIN(p - path, APP_MAX_LENGTH)); |
||
2484 | } else { |
||
2485 | fname++; |
||
2486 | av_strlcpy(rt->app, path + 1, FFMIN(fname - path - 1, APP_MAX_LENGTH)); |
||
2487 | } |
||
2488 | } |
||
2489 | } |
||
2490 | |||
2491 | if (old_app) { |
||
2492 | // The name of application has been defined by the user, override it. |
||
2493 | if (strlen(old_app) >= APP_MAX_LENGTH) { |
||
2494 | ret = AVERROR(EINVAL); |
||
2495 | goto fail; |
||
2496 | } |
||
2497 | av_free(rt->app); |
||
2498 | rt->app = old_app; |
||
2499 | } |
||
2500 | |||
2501 | if (!rt->playpath) { |
||
2502 | int len = strlen(fname); |
||
2503 | |||
2504 | rt->playpath = av_malloc(PLAYPATH_MAX_LENGTH); |
||
2505 | if (!rt->playpath) { |
||
2506 | ret = AVERROR(ENOMEM); |
||
2507 | goto fail; |
||
2508 | } |
||
2509 | |||
2510 | if (!strchr(fname, ':') && len >= 4 && |
||
2511 | (!strcmp(fname + len - 4, ".f4v") || |
||
2512 | !strcmp(fname + len - 4, ".mp4"))) { |
||
2513 | memcpy(rt->playpath, "mp4:", 5); |
||
2514 | } else if (len >= 4 && !strcmp(fname + len - 4, ".flv")) { |
||
2515 | fname[len - 4] = '\0'; |
||
2516 | } else { |
||
2517 | rt->playpath[0] = 0; |
||
2518 | } |
||
2519 | av_strlcat(rt->playpath, fname, PLAYPATH_MAX_LENGTH); |
||
2520 | } |
||
2521 | |||
2522 | if (!rt->tcurl) { |
||
2523 | rt->tcurl = av_malloc(TCURL_MAX_LENGTH); |
||
2524 | if (!rt->tcurl) { |
||
2525 | ret = AVERROR(ENOMEM); |
||
2526 | goto fail; |
||
2527 | } |
||
2528 | ff_url_join(rt->tcurl, TCURL_MAX_LENGTH, proto, NULL, hostname, |
||
2529 | port, "/%s", rt->app); |
||
2530 | } |
||
2531 | |||
2532 | if (!rt->flashver) { |
||
2533 | rt->flashver = av_malloc(FLASHVER_MAX_LENGTH); |
||
2534 | if (!rt->flashver) { |
||
2535 | ret = AVERROR(ENOMEM); |
||
2536 | goto fail; |
||
2537 | } |
||
2538 | if (rt->is_input) { |
||
2539 | snprintf(rt->flashver, FLASHVER_MAX_LENGTH, "%s %d,%d,%d,%d", |
||
2540 | RTMP_CLIENT_PLATFORM, RTMP_CLIENT_VER1, RTMP_CLIENT_VER2, |
||
2541 | RTMP_CLIENT_VER3, RTMP_CLIENT_VER4); |
||
2542 | } else { |
||
2543 | snprintf(rt->flashver, FLASHVER_MAX_LENGTH, |
||
2544 | "FMLE/3.0 (compatible; %s)", LIBAVFORMAT_IDENT); |
||
2545 | } |
||
2546 | } |
||
2547 | |||
2548 | rt->client_report_size = 1048576; |
||
2549 | rt->bytes_read = 0; |
||
2550 | rt->last_bytes_read = 0; |
||
2551 | rt->server_bw = 2500000; |
||
2552 | |||
2553 | av_log(s, AV_LOG_DEBUG, "Proto = %s, path = %s, app = %s, fname = %s\n", |
||
2554 | proto, path, rt->app, rt->playpath); |
||
2555 | if (!rt->listen) { |
||
2556 | if ((ret = gen_connect(s, rt)) < 0) |
||
2557 | goto fail; |
||
2558 | } else { |
||
2559 | if (read_connect(s, s->priv_data) < 0) |
||
2560 | goto fail; |
||
2561 | } |
||
2562 | |||
2563 | do { |
||
2564 | ret = get_packet(s, 1); |
||
2565 | } while (ret == AVERROR(EAGAIN)); |
||
2566 | if (ret < 0) |
||
2567 | goto fail; |
||
2568 | |||
2569 | if (rt->do_reconnect) { |
||
2570 | int i; |
||
2571 | ffurl_close(rt->stream); |
||
2572 | rt->stream = NULL; |
||
2573 | rt->do_reconnect = 0; |
||
2574 | rt->nb_invokes = 0; |
||
2575 | for (i = 0; i < 2; i++) |
||
2576 | memset(rt->prev_pkt[i], 0, |
||
2577 | sizeof(**rt->prev_pkt) * rt->nb_prev_pkt[i]); |
||
2578 | free_tracked_methods(rt); |
||
2579 | goto reconnect; |
||
2580 | } |
||
2581 | |||
2582 | if (rt->is_input) { |
||
2583 | int err; |
||
2584 | // generate FLV header for demuxer |
||
2585 | rt->flv_size = 13; |
||
2586 | if ((err = av_reallocp(&rt->flv_data, rt->flv_size)) < 0) |
||
2587 | return err; |
||
2588 | rt->flv_off = 0; |
||
2589 | memcpy(rt->flv_data, "FLV\1\5\0\0\0\011\0\0\0\0", rt->flv_size); |
||
2590 | } else { |
||
2591 | rt->flv_size = 0; |
||
2592 | rt->flv_data = NULL; |
||
2593 | rt->flv_off = 0; |
||
2594 | rt->skip_bytes = 13; |
||
2595 | } |
||
2596 | |||
2597 | s->max_packet_size = rt->stream->max_packet_size; |
||
2598 | s->is_streamed = 1; |
||
2599 | return 0; |
||
2600 | |||
2601 | fail: |
||
2602 | av_dict_free(&opts); |
||
2603 | rtmp_close(s); |
||
2604 | return ret; |
||
2605 | } |
||
2606 | |||
2607 | static int rtmp_read(URLContext *s, uint8_t *buf, int size) |
||
2608 | { |
||
2609 | RTMPContext *rt = s->priv_data; |
||
2610 | int orig_size = size; |
||
2611 | int ret; |
||
2612 | |||
2613 | while (size > 0) { |
||
2614 | int data_left = rt->flv_size - rt->flv_off; |
||
2615 | |||
2616 | if (data_left >= size) { |
||
2617 | memcpy(buf, rt->flv_data + rt->flv_off, size); |
||
2618 | rt->flv_off += size; |
||
2619 | return orig_size; |
||
2620 | } |
||
2621 | if (data_left > 0) { |
||
2622 | memcpy(buf, rt->flv_data + rt->flv_off, data_left); |
||
2623 | buf += data_left; |
||
2624 | size -= data_left; |
||
2625 | rt->flv_off = rt->flv_size; |
||
2626 | return data_left; |
||
2627 | } |
||
2628 | if ((ret = get_packet(s, 0)) < 0) |
||
2629 | return ret; |
||
2630 | } |
||
2631 | return orig_size; |
||
2632 | } |
||
2633 | |||
2634 | static int64_t rtmp_seek(URLContext *s, int stream_index, int64_t timestamp, |
||
2635 | int flags) |
||
2636 | { |
||
2637 | RTMPContext *rt = s->priv_data; |
||
2638 | int ret; |
||
2639 | av_log(s, AV_LOG_DEBUG, |
||
2640 | "Seek on stream index %d at timestamp %"PRId64" with flags %08x\n", |
||
2641 | stream_index, timestamp, flags); |
||
2642 | if ((ret = gen_seek(s, rt, timestamp)) < 0) { |
||
2643 | av_log(s, AV_LOG_ERROR, |
||
2644 | "Unable to send seek command on stream index %d at timestamp " |
||
2645 | "%"PRId64" with flags %08x\n", |
||
2646 | stream_index, timestamp, flags); |
||
2647 | return ret; |
||
2648 | } |
||
2649 | rt->flv_off = rt->flv_size; |
||
2650 | rt->state = STATE_SEEKING; |
||
2651 | return timestamp; |
||
2652 | } |
||
2653 | |||
2654 | static int rtmp_write(URLContext *s, const uint8_t *buf, int size) |
||
2655 | { |
||
2656 | RTMPContext *rt = s->priv_data; |
||
2657 | int size_temp = size; |
||
2658 | int pktsize, pkttype; |
||
2659 | uint32_t ts; |
||
2660 | const uint8_t *buf_temp = buf; |
||
2661 | uint8_t c; |
||
2662 | int ret; |
||
2663 | |||
2664 | do { |
||
2665 | if (rt->skip_bytes) { |
||
2666 | int skip = FFMIN(rt->skip_bytes, size_temp); |
||
2667 | buf_temp += skip; |
||
2668 | size_temp -= skip; |
||
2669 | rt->skip_bytes -= skip; |
||
2670 | continue; |
||
2671 | } |
||
2672 | |||
2673 | if (rt->flv_header_bytes < RTMP_HEADER) { |
||
2674 | const uint8_t *header = rt->flv_header; |
||
2675 | int copy = FFMIN(RTMP_HEADER - rt->flv_header_bytes, size_temp); |
||
2676 | int channel = RTMP_AUDIO_CHANNEL; |
||
2677 | bytestream_get_buffer(&buf_temp, rt->flv_header + rt->flv_header_bytes, copy); |
||
2678 | rt->flv_header_bytes += copy; |
||
2679 | size_temp -= copy; |
||
2680 | if (rt->flv_header_bytes < RTMP_HEADER) |
||
2681 | break; |
||
2682 | |||
2683 | pkttype = bytestream_get_byte(&header); |
||
2684 | pktsize = bytestream_get_be24(&header); |
||
2685 | ts = bytestream_get_be24(&header); |
||
2686 | ts |= bytestream_get_byte(&header) << 24; |
||
2687 | bytestream_get_be24(&header); |
||
2688 | rt->flv_size = pktsize; |
||
2689 | |||
2690 | if (pkttype == RTMP_PT_VIDEO) |
||
2691 | channel = RTMP_VIDEO_CHANNEL; |
||
2692 | |||
2693 | //force 12bytes header |
||
2694 | if (((pkttype == RTMP_PT_VIDEO || pkttype == RTMP_PT_AUDIO) && ts == 0) || |
||
2695 | pkttype == RTMP_PT_NOTIFY) { |
||
2696 | if (pkttype == RTMP_PT_NOTIFY) |
||
2697 | pktsize += 16; |
||
2698 | if ((ret = ff_rtmp_check_alloc_array(&rt->prev_pkt[1], |
||
2699 | &rt->nb_prev_pkt[1], |
||
2700 | channel)) < 0) |
||
2701 | return ret; |
||
2702 | rt->prev_pkt[1][channel].channel_id = 0; |
||
2703 | } |
||
2704 | |||
2705 | //this can be a big packet, it's better to send it right here |
||
2706 | if ((ret = ff_rtmp_packet_create(&rt->out_pkt, channel, |
||
2707 | pkttype, ts, pktsize)) < 0) |
||
2708 | return ret; |
||
2709 | |||
2710 | rt->out_pkt.extra = rt->stream_id; |
||
2711 | rt->flv_data = rt->out_pkt.data; |
||
2712 | |||
2713 | if (pkttype == RTMP_PT_NOTIFY) |
||
2714 | ff_amf_write_string(&rt->flv_data, "@setDataFrame"); |
||
2715 | } |
||
2716 | |||
2717 | if (rt->flv_size - rt->flv_off > size_temp) { |
||
2718 | bytestream_get_buffer(&buf_temp, rt->flv_data + rt->flv_off, size_temp); |
||
2719 | rt->flv_off += size_temp; |
||
2720 | size_temp = 0; |
||
2721 | } else { |
||
2722 | bytestream_get_buffer(&buf_temp, rt->flv_data + rt->flv_off, rt->flv_size - rt->flv_off); |
||
2723 | size_temp -= rt->flv_size - rt->flv_off; |
||
2724 | rt->flv_off += rt->flv_size - rt->flv_off; |
||
2725 | } |
||
2726 | |||
2727 | if (rt->flv_off == rt->flv_size) { |
||
2728 | rt->skip_bytes = 4; |
||
2729 | |||
2730 | if ((ret = rtmp_send_packet(rt, &rt->out_pkt, 0)) < 0) |
||
2731 | return ret; |
||
2732 | rt->flv_size = 0; |
||
2733 | rt->flv_off = 0; |
||
2734 | rt->flv_header_bytes = 0; |
||
2735 | rt->flv_nb_packets++; |
||
2736 | } |
||
2737 | } while (buf_temp - buf < size); |
||
2738 | |||
2739 | if (rt->flv_nb_packets < rt->flush_interval) |
||
2740 | return size; |
||
2741 | rt->flv_nb_packets = 0; |
||
2742 | |||
2743 | /* set stream into nonblocking mode */ |
||
2744 | rt->stream->flags |= AVIO_FLAG_NONBLOCK; |
||
2745 | |||
2746 | /* try to read one byte from the stream */ |
||
2747 | ret = ffurl_read(rt->stream, &c, 1); |
||
2748 | |||
2749 | /* switch the stream back into blocking mode */ |
||
2750 | rt->stream->flags &= ~AVIO_FLAG_NONBLOCK; |
||
2751 | |||
2752 | if (ret == AVERROR(EAGAIN)) { |
||
2753 | /* no incoming data to handle */ |
||
2754 | return size; |
||
2755 | } else if (ret < 0) { |
||
2756 | return ret; |
||
2757 | } else if (ret == 1) { |
||
2758 | RTMPPacket rpkt = { 0 }; |
||
2759 | |||
2760 | if ((ret = ff_rtmp_packet_read_internal(rt->stream, &rpkt, |
||
2761 | rt->in_chunk_size, |
||
2762 | &rt->prev_pkt[0], |
||
2763 | &rt->nb_prev_pkt[0], c)) <= 0) |
||
2764 | return ret; |
||
2765 | |||
2766 | if ((ret = rtmp_parse_result(s, rt, &rpkt)) < 0) |
||
2767 | return ret; |
||
2768 | |||
2769 | ff_rtmp_packet_destroy(&rpkt); |
||
2770 | } |
||
2771 | |||
2772 | return size; |
||
2773 | } |
||
2774 | |||
2775 | #define OFFSET(x) offsetof(RTMPContext, x) |
||
2776 | #define DEC AV_OPT_FLAG_DECODING_PARAM |
||
2777 | #define ENC AV_OPT_FLAG_ENCODING_PARAM |
||
2778 | |||
2779 | static const AVOption rtmp_options[] = { |
||
2780 | {"rtmp_app", "Name of application to connect to on the RTMP server", OFFSET(app), AV_OPT_TYPE_STRING, {.str = NULL }, 0, 0, DEC|ENC}, |
||
2781 | {"rtmp_buffer", "Set buffer time in milliseconds. The default is 3000.", OFFSET(client_buffer_time), AV_OPT_TYPE_INT, {.i64 = 3000}, 0, INT_MAX, DEC|ENC}, |
||
2782 | {"rtmp_conn", "Append arbitrary AMF data to the Connect message", OFFSET(conn), AV_OPT_TYPE_STRING, {.str = NULL }, 0, 0, DEC|ENC}, |
||
2783 | {"rtmp_flashver", "Version of the Flash plugin used to run the SWF player.", OFFSET(flashver), AV_OPT_TYPE_STRING, {.str = NULL }, 0, 0, DEC|ENC}, |
||
2784 | {"rtmp_flush_interval", "Number of packets flushed in the same request (RTMPT only).", OFFSET(flush_interval), AV_OPT_TYPE_INT, {.i64 = 10}, 0, INT_MAX, ENC}, |
||
2785 | {"rtmp_live", "Specify that the media is a live stream.", OFFSET(live), AV_OPT_TYPE_INT, {.i64 = -2}, INT_MIN, INT_MAX, DEC, "rtmp_live"}, |
||
2786 | {"any", "both", 0, AV_OPT_TYPE_CONST, {.i64 = -2}, 0, 0, DEC, "rtmp_live"}, |
||
2787 | {"live", "live stream", 0, AV_OPT_TYPE_CONST, {.i64 = -1}, 0, 0, DEC, "rtmp_live"}, |
||
2788 | {"recorded", "recorded stream", 0, AV_OPT_TYPE_CONST, {.i64 = 0}, 0, 0, DEC, "rtmp_live"}, |
||
2789 | {"rtmp_pageurl", "URL of the web page in which the media was embedded. By default no value will be sent.", OFFSET(pageurl), AV_OPT_TYPE_STRING, {.str = NULL }, 0, 0, DEC}, |
||
2790 | {"rtmp_playpath", "Stream identifier to play or to publish", OFFSET(playpath), AV_OPT_TYPE_STRING, {.str = NULL }, 0, 0, DEC|ENC}, |
||
2791 | {"rtmp_subscribe", "Name of live stream to subscribe to. Defaults to rtmp_playpath.", OFFSET(subscribe), AV_OPT_TYPE_STRING, {.str = NULL }, 0, 0, DEC}, |
||
2792 | {"rtmp_swfhash", "SHA256 hash of the decompressed SWF file (32 bytes).", OFFSET(swfhash), AV_OPT_TYPE_BINARY, .flags = DEC}, |
||
2793 | {"rtmp_swfsize", "Size of the decompressed SWF file, required for SWFVerification.", OFFSET(swfsize), AV_OPT_TYPE_INT, {.i64 = 0}, 0, INT_MAX, DEC}, |
||
2794 | {"rtmp_swfurl", "URL of the SWF player. By default no value will be sent", OFFSET(swfurl), AV_OPT_TYPE_STRING, {.str = NULL }, 0, 0, DEC|ENC}, |
||
2795 | {"rtmp_swfverify", "URL to player swf file, compute hash/size automatically.", OFFSET(swfverify), AV_OPT_TYPE_STRING, {.str = NULL }, 0, 0, DEC}, |
||
2796 | {"rtmp_tcurl", "URL of the target stream. Defaults to proto://host[:port]/app.", OFFSET(tcurl), AV_OPT_TYPE_STRING, {.str = NULL }, 0, 0, DEC|ENC}, |
||
2797 | {"rtmp_listen", "Listen for incoming rtmp connections", OFFSET(listen), AV_OPT_TYPE_INT, {.i64 = 0}, INT_MIN, INT_MAX, DEC, "rtmp_listen" }, |
||
2798 | {"listen", "Listen for incoming rtmp connections", OFFSET(listen), AV_OPT_TYPE_INT, {.i64 = 0}, INT_MIN, INT_MAX, DEC, "rtmp_listen" }, |
||
2799 | {"timeout", "Maximum timeout (in seconds) to wait for incoming connections. -1 is infinite. Implies -rtmp_listen 1", OFFSET(listen_timeout), AV_OPT_TYPE_INT, {.i64 = -1}, INT_MIN, INT_MAX, DEC, "rtmp_listen" }, |
||
2800 | { NULL }, |
||
2801 | }; |
||
2802 | |||
2803 | #define RTMP_PROTOCOL(flavor) \ |
||
2804 | static const AVClass flavor##_class = { \ |
||
2805 | .class_name = #flavor, \ |
||
2806 | .item_name = av_default_item_name, \ |
||
2807 | .option = rtmp_options, \ |
||
2808 | .version = LIBAVUTIL_VERSION_INT, \ |
||
2809 | }; \ |
||
2810 | \ |
||
2811 | URLProtocol ff_##flavor##_protocol = { \ |
||
2812 | .name = #flavor, \ |
||
2813 | .url_open = rtmp_open, \ |
||
2814 | .url_read = rtmp_read, \ |
||
2815 | .url_read_seek = rtmp_seek, \ |
||
2816 | .url_write = rtmp_write, \ |
||
2817 | .url_close = rtmp_close, \ |
||
2818 | .priv_data_size = sizeof(RTMPContext), \ |
||
2819 | .flags = URL_PROTOCOL_FLAG_NETWORK, \ |
||
2820 | .priv_data_class= &flavor##_class, \ |
||
2821 | }; |
||
2822 | |||
2823 | |||
2824 | RTMP_PROTOCOL(rtmp) |
||
2825 | RTMP_PROTOCOL(rtmpe) |
||
2826 | RTMP_PROTOCOL(rtmps) |
||
2827 | RTMP_PROTOCOL(rtmpt) |
||
2828 | RTMP_PROTOCOL(rtmpte) |
||
2829 | RTMP_PROTOCOL(rtmpts)>=>>>>>>>><>>>>>>>>>>>>>>>>>>>>>=>><>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>=>>=>>>>>=>>>>=>=>=>>>>>>>>>>>>>>>>>>>>=>>>>>>>>>>>>=>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> |