1/*
2 * TCP protocol
3 * Copyright (c) 2002 Fabrice Bellard
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#include "avformat.h"
22#include <unistd.h>
23#include "internal.h"
24#include "network.h"
25#include "os_support.h"
26#if HAVE_SYS_SELECT_H
27#include <sys/select.h>
28#endif
29#include <sys/time.h>
30
31typedef struct TCPContext {
32    int fd;
33} TCPContext;
34
35/* return non zero if error */
36static int tcp_open(URLContext *h, const char *uri, int flags)
37{
38    struct addrinfo hints, *ai, *cur_ai;
39    int port, fd = -1;
40    TCPContext *s = NULL;
41    fd_set wfds;
42    int fd_max, ret;
43    struct timeval tv;
44    socklen_t optlen;
45    char hostname[1024],proto[1024],path[1024];
46    char portstr[10];
47
48    ff_url_split(proto, sizeof(proto), NULL, 0, hostname, sizeof(hostname),
49        &port, path, sizeof(path), uri);
50    if (strcmp(proto,"tcp") || port <= 0 || port >= 65536)
51        return AVERROR(EINVAL);
52
53    memset(&hints, 0, sizeof(hints));
54    hints.ai_family = AF_UNSPEC;
55    hints.ai_socktype = SOCK_STREAM;
56    snprintf(portstr, sizeof(portstr), "%d", port);
57    if (getaddrinfo(hostname, portstr, &hints, &ai))
58        return AVERROR(EIO);
59
60    cur_ai = ai;
61
62 restart:
63    fd = socket(cur_ai->ai_family, cur_ai->ai_socktype, cur_ai->ai_protocol);
64    if (fd < 0)
65        goto fail;
66    ff_socket_nonblock(fd, 1);
67
68 redo:
69    ret = connect(fd, cur_ai->ai_addr, cur_ai->ai_addrlen);
70    if (ret < 0) {
71        if (ff_neterrno() == FF_NETERROR(EINTR))
72            goto redo;
73        if (ff_neterrno() != FF_NETERROR(EINPROGRESS) &&
74            ff_neterrno() != FF_NETERROR(EAGAIN))
75            goto fail;
76
77        /* wait until we are connected or until abort */
78        for(;;) {
79            if (url_interrupt_cb()) {
80                ret = AVERROR(EINTR);
81                goto fail1;
82            }
83            fd_max = fd;
84            FD_ZERO(&wfds);
85            FD_SET(fd, &wfds);
86            tv.tv_sec = 0;
87            tv.tv_usec = 100 * 1000;
88            ret = select(fd_max + 1, NULL, &wfds, NULL, &tv);
89            if (ret > 0 && FD_ISSET(fd, &wfds))
90                break;
91        }
92
93        /* test error */
94        optlen = sizeof(ret);
95        getsockopt (fd, SOL_SOCKET, SO_ERROR, &ret, &optlen);
96        if (ret != 0)
97            goto fail;
98    }
99    s = av_malloc(sizeof(TCPContext));
100    if (!s) {
101        freeaddrinfo(ai);
102        return AVERROR(ENOMEM);
103    }
104    h->priv_data = s;
105    h->is_streamed = 1;
106    s->fd = fd;
107    freeaddrinfo(ai);
108    return 0;
109
110 fail:
111    if (cur_ai->ai_next) {
112        /* Retry with the next sockaddr */
113        cur_ai = cur_ai->ai_next;
114        if (fd >= 0)
115            closesocket(fd);
116        goto restart;
117    }
118    ret = AVERROR(EIO);
119 fail1:
120    if (fd >= 0)
121        closesocket(fd);
122    freeaddrinfo(ai);
123    return ret;
124}
125
126static int tcp_read(URLContext *h, uint8_t *buf, int size)
127{
128    TCPContext *s = h->priv_data;
129    int len, fd_max, ret;
130    fd_set rfds;
131    struct timeval tv;
132
133    for (;;) {
134        if (url_interrupt_cb())
135            return AVERROR(EINTR);
136        fd_max = s->fd;
137        FD_ZERO(&rfds);
138        FD_SET(s->fd, &rfds);
139        tv.tv_sec = 0;
140        tv.tv_usec = 100 * 1000;
141        ret = select(fd_max + 1, &rfds, NULL, NULL, &tv);
142        if (ret > 0 && FD_ISSET(s->fd, &rfds)) {
143            len = recv(s->fd, buf, size, 0);
144            if (len < 0) {
145                if (ff_neterrno() != FF_NETERROR(EINTR) &&
146                    ff_neterrno() != FF_NETERROR(EAGAIN))
147                    return AVERROR(ff_neterrno());
148            } else return len;
149        } else if (ret < 0) {
150            if (ff_neterrno() == FF_NETERROR(EINTR))
151                continue;
152            return -1;
153        }
154    }
155}
156
157static int tcp_write(URLContext *h, uint8_t *buf, int size)
158{
159    TCPContext *s = h->priv_data;
160    int ret, size1, fd_max, len;
161    fd_set wfds;
162    struct timeval tv;
163
164    size1 = size;
165    while (size > 0) {
166        if (url_interrupt_cb())
167            return AVERROR(EINTR);
168        fd_max = s->fd;
169        FD_ZERO(&wfds);
170        FD_SET(s->fd, &wfds);
171        tv.tv_sec = 0;
172        tv.tv_usec = 100 * 1000;
173        ret = select(fd_max + 1, NULL, &wfds, NULL, &tv);
174        if (ret > 0 && FD_ISSET(s->fd, &wfds)) {
175            len = send(s->fd, buf, size, 0);
176            if (len < 0) {
177                if (ff_neterrno() != FF_NETERROR(EINTR) &&
178                    ff_neterrno() != FF_NETERROR(EAGAIN))
179                    return AVERROR(ff_neterrno());
180                continue;
181            }
182            size -= len;
183            buf += len;
184        } else if (ret < 0) {
185            if (ff_neterrno() == FF_NETERROR(EINTR))
186                continue;
187            return -1;
188        }
189    }
190    return size1 - size;
191}
192
193static int tcp_close(URLContext *h)
194{
195    TCPContext *s = h->priv_data;
196    closesocket(s->fd);
197    av_free(s);
198    return 0;
199}
200
201static int tcp_get_file_handle(URLContext *h)
202{
203    TCPContext *s = h->priv_data;
204    return s->fd;
205}
206
207URLProtocol tcp_protocol = {
208    "tcp",
209    tcp_open,
210    tcp_read,
211    tcp_write,
212    NULL, /* seek */
213    tcp_close,
214    .url_get_file_handle = tcp_get_file_handle,
215};
216