tls: remove tls_Recv() and tls_Send()
[vlc.git] / modules / stream_out / chromecast / chromecast_communication.cpp
1 /*****************************************************************************
2  * chromecast_communication.cpp: Handle chromecast protocol messages
3  *****************************************************************************
4  * Copyright © 2014-2017 VideoLAN
5  *
6  * Authors: Adrien Maglo <magsoft@videolan.org>
7  *          Jean-Baptiste Kempf <jb@videolan.org>
8  *          Steve Lhomme <robux4@videolabs.io>
9  *          Hugo Beauzée-Luyssen <hugo@beauzee.fr>
10  *
11  * This program is free software; you can redistribute it and/or modify it
12  * under the terms of the GNU Lesser General Public License as published by
13  * the Free Software Foundation; either version 2.1 of the License, or
14  * (at your option) any later version.
15  *
16  * This program is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19  * GNU Lesser General Public License for more details.
20  *
21  * You should have received a copy of the GNU Lesser General Public License
22  * along with this program; if not, write to the Free Software Foundation,
23  * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
24  *****************************************************************************/
25
26 #ifdef HAVE_CONFIG_H
27 # include "config.h"
28 #endif
29
30 #include "chromecast.h"
31 #ifdef HAVE_POLL
32 # include <poll.h>
33 #endif
34
35 /* deadline regarding pings sent from receiver */
36 #define PING_WAIT_TIME 6000
37
38 ChromecastCommunication::ChromecastCommunication( vlc_object_t* p_module, const char* targetIP, unsigned int devicePort )
39     : m_module( p_module )
40     , m_sock_fd( -1 )
41     , m_creds( NULL )
42     , m_tls( NULL )
43     , m_receiver_requestId( 0 )
44     , m_requestId( 0 )
45 {
46     if (devicePort == 0)
47         devicePort = CHROMECAST_CONTROL_PORT;
48     m_sock_fd = net_ConnectTCP( m_module, targetIP, devicePort);
49     if (m_sock_fd < 0)
50         throw std::runtime_error( "Failed to connect to the chromecast" );
51
52     char psz_localIP[NI_MAXNUMERICHOST];
53     if ( net_GetSockAddress( m_sock_fd, psz_localIP, NULL ) )
54         throw std::runtime_error( "Cannot get local IP address" );
55     m_serverIp = psz_localIP;
56
57     m_creds = vlc_tls_ClientCreate( m_module->obj.parent );
58     if (m_creds == NULL)
59     {
60         net_Close(m_sock_fd);
61         throw std::runtime_error( "Failed to create TLS client" );
62     }
63
64     m_tls = vlc_tls_ClientSessionCreateFD( m_creds, m_sock_fd, targetIP, "tcps", NULL, NULL );
65
66     if (m_tls == NULL)
67     {
68         net_Close(m_sock_fd);
69         vlc_tls_Delete(m_creds);
70         throw std::runtime_error( "Failed to create client session" );
71     }
72 }
73
74 ChromecastCommunication::~ChromecastCommunication()
75 {
76     disconnect();
77 }
78
79 void ChromecastCommunication::disconnect()
80 {
81     if ( m_tls != NULL )
82     {
83         vlc_tls_Close(m_tls);
84         vlc_tls_Delete(m_creds);
85         m_tls = NULL;
86     }
87 }
88
89 /**
90  * @brief Build a CastMessage to send to the Chromecast
91  * @param namespace_ the message namespace
92  * @param payloadType the payload type (CastMessage_PayloadType_STRING or
93  * CastMessage_PayloadType_BINARY
94  * @param payload the payload
95  * @param destinationId the destination idenifier
96  * @return the generated CastMessage
97  */
98 void ChromecastCommunication::buildMessage(const std::string & namespace_,
99                               const std::string & payload,
100                               const std::string & destinationId,
101                               castchannel::CastMessage_PayloadType payloadType)
102 {
103     castchannel::CastMessage msg;
104
105     msg.set_protocol_version(castchannel::CastMessage_ProtocolVersion_CASTV2_1_0);
106     msg.set_namespace_(namespace_);
107     msg.set_payload_type(payloadType);
108     msg.set_source_id("sender-vlc");
109     msg.set_destination_id(destinationId);
110     if (payloadType == castchannel::CastMessage_PayloadType_STRING)
111         msg.set_payload_utf8(payload);
112     else // CastMessage_PayloadType_BINARY
113         msg.set_payload_binary(payload);
114
115     sendMessage(msg);
116 }
117
118 /**
119  * @brief Receive a data packet from the Chromecast
120  * @param p_module the module to log with
121  * @param b_msgReceived returns true if a message has been entirely received else false
122  * @param i_payloadSize returns the payload size of the message received
123  * @return the number of bytes received of -1 on error
124  */
125 ssize_t ChromecastCommunication::recvPacket( uint8_t *p_data )
126 {
127     ssize_t i_payloadSize = -1;
128     struct pollfd ufd[1];
129     ufd[0].fd = m_sock_fd;
130     ufd[0].events = POLLIN;
131
132     /* The Chromecast normally sends a PING command every 5 seconds or so.
133      * If we do not receive one after 6 seconds, we send a PING.
134      * If after this PING, we do not receive a PONG, then we consider the
135      * connection as dead. */
136     ssize_t val = vlc_poll_i11e(ufd, 1, PING_WAIT_TIME);
137     if ( val == -1 )
138     {
139         if ( errno != EINTR )
140             return -1;
141         return 0;
142     }
143     if ( val == 0 )
144         return 0;
145
146     if ( ufd[0].revents & POLLIN )
147     {
148         /* we have received stuff */
149
150         /* Packet structure:
151          * +------------------------------------+------------------------------+
152          * | Payload size (uint32_t big endian) |         Payload data         |
153          * +------------------------------------+------------------------------+ */
154         // We receive the header.
155         ssize_t i_ret = vlc_tls_Read(m_tls, p_data, PACKET_HEADER_LEN, true);
156         if (i_ret < PACKET_HEADER_LEN)
157             return -1;
158
159         // We receive the payload.
160
161         // Get the size of the payload
162         i_payloadSize = U32_AT( p_data );
163         const uint32_t i_maxPayloadSize = PACKET_MAX_LEN - PACKET_HEADER_LEN;
164
165         if (i_payloadSize > i_maxPayloadSize)
166         {
167             // Error case: the packet sent by the Chromecast is too long: we drop it.
168             msg_Err( m_module, "Packet too long: dropping its data");
169
170             i_ret = vlc_tls_Read(m_tls, p_data + PACKET_HEADER_LEN,
171                                  i_maxPayloadSize, false);
172             return i_ret ? i_ret : -1;
173         }
174
175         // Normal case
176         i_ret = vlc_tls_Read(m_tls, p_data + PACKET_HEADER_LEN, i_payloadSize,
177                              false);
178         if (i_ret < i_payloadSize)
179             return -1;
180     }
181     return i_payloadSize;
182 }
183
184
185 /*****************************************************************************
186  * Message preparation
187  *****************************************************************************/
188 void ChromecastCommunication::msgAuth()
189 {
190     castchannel::DeviceAuthMessage authMessage;
191     authMessage.mutable_challenge();
192
193     buildMessage(NAMESPACE_DEVICEAUTH, authMessage.SerializeAsString(),
194                  DEFAULT_CHOMECAST_RECEIVER, castchannel::CastMessage_PayloadType_BINARY);
195 }
196
197
198 void ChromecastCommunication::msgPing()
199 {
200     std::string s("{\"type\":\"PING\"}");
201     buildMessage( NAMESPACE_HEARTBEAT, s, DEFAULT_CHOMECAST_RECEIVER );
202 }
203
204
205 void ChromecastCommunication::msgPong()
206 {
207     std::string s("{\"type\":\"PONG\"}");
208     buildMessage( NAMESPACE_HEARTBEAT, s, DEFAULT_CHOMECAST_RECEIVER );
209 }
210
211 void ChromecastCommunication::msgConnect( const std::string& destinationId )
212 {
213     std::string s("{\"type\":\"CONNECT\"}");
214     buildMessage( NAMESPACE_CONNECTION, s, destinationId );
215 }
216
217 void ChromecastCommunication::msgReceiverClose( const std::string& destinationId )
218 {
219     std::string s("{\"type\":\"CLOSE\"}");
220     buildMessage( NAMESPACE_CONNECTION, s, destinationId );
221 }
222
223 void ChromecastCommunication::msgReceiverGetStatus()
224 {
225     std::stringstream ss;
226     ss << "{\"type\":\"GET_STATUS\","
227        <<  "\"requestId\":" << m_receiver_requestId++ << "}";
228
229     buildMessage( NAMESPACE_RECEIVER, ss.str(), DEFAULT_CHOMECAST_RECEIVER );
230 }
231
232 void ChromecastCommunication::msgReceiverLaunchApp()
233 {
234     std::stringstream ss;
235     ss << "{\"type\":\"LAUNCH\","
236        <<  "\"appId\":\"" << APP_ID << "\","
237        <<  "\"requestId\":" << m_receiver_requestId++ << "}";
238
239     buildMessage( NAMESPACE_RECEIVER, ss.str(), DEFAULT_CHOMECAST_RECEIVER );
240 }
241
242 void ChromecastCommunication::msgPlayerGetStatus( const std::string& destinationId )
243 {
244     std::stringstream ss;
245     ss << "{\"type\":\"GET_STATUS\","
246        <<  "\"requestId\":" << m_requestId++
247        << "}";
248
249     pushMediaPlayerMessage( destinationId, ss );
250 }
251
252 std::string ChromecastCommunication::GetMedia( unsigned int i_port,
253                                                const std::string& title, const std::string& artwork,
254                                                const std::string& mime )
255 {
256     std::stringstream ss;
257
258     if ( title.size() )
259     {
260         ss << "\"metadata\":{"
261            << " \"metadataType\":0"
262            << ",\"title\":\"" << title << "\"";
263
264         if ( artwork.size() && !strncmp(artwork.c_str(), "http", 4))
265             ss << ",\"images\":[\"" << artwork << "\"]";
266
267         ss << "},";
268     }
269
270     std::stringstream chromecast_url;
271     chromecast_url << "http://" << m_serverIp << ":" << i_port << "/stream";
272
273     msg_Dbg( m_module, "s_chromecast_url: %s", chromecast_url.str().c_str());
274
275     ss << "\"contentId\":\"" << chromecast_url.str() << "\""
276        << ",\"streamType\":\"LIVE\""
277        << ",\"contentType\":\"" << mime << "\"";
278
279     return ss.str();
280 }
281
282 void ChromecastCommunication::msgPlayerLoad( const std::string& destinationId, unsigned int i_port,
283                                              const std::string& title, const std::string& artwork,
284                                              const std::string& mime )
285 {
286     std::stringstream ss;
287     ss << "{\"type\":\"LOAD\","
288        <<  "\"media\":{" << GetMedia( i_port, title, artwork, mime ) << "},"
289        <<  "\"autoplay\":\"false\","
290        <<  "\"requestId\":" << m_requestId++
291        << "}";
292
293     pushMediaPlayerMessage( destinationId, ss );
294 }
295
296 void ChromecastCommunication::msgPlayerPlay( const std::string& destinationId, const std::string& mediaSessionId )
297 {
298     assert(!mediaSessionId.empty());
299
300     std::stringstream ss;
301     ss << "{\"type\":\"PLAY\","
302        <<  "\"mediaSessionId\":" << mediaSessionId << ","
303        <<  "\"requestId\":" << m_requestId++
304        << "}";
305
306     pushMediaPlayerMessage( destinationId, ss );
307 }
308
309 void ChromecastCommunication::msgPlayerStop( const std::string& destinationId, const std::string& mediaSessionId )
310 {
311     assert(!mediaSessionId.empty());
312
313     std::stringstream ss;
314     ss << "{\"type\":\"STOP\","
315        <<  "\"mediaSessionId\":" << mediaSessionId << ","
316        <<  "\"requestId\":" << m_requestId++
317        << "}";
318
319     pushMediaPlayerMessage( destinationId, ss );
320 }
321
322 void ChromecastCommunication::msgPlayerPause( const std::string& destinationId, const std::string& mediaSessionId )
323 {
324     assert(!mediaSessionId.empty());
325
326     std::stringstream ss;
327     ss << "{\"type\":\"PAUSE\","
328        <<  "\"mediaSessionId\":" << mediaSessionId << ","
329        <<  "\"requestId\":" << m_requestId++
330        << "}";
331
332     pushMediaPlayerMessage( destinationId, ss );
333 }
334
335 void ChromecastCommunication::msgPlayerSetVolume( const std::string& destinationId, const std::string& mediaSessionId, float f_volume, bool b_mute )
336 {
337     assert(!mediaSessionId.empty());
338
339     if ( f_volume < 0.0 || f_volume > 1.0)
340         return;
341
342     std::stringstream ss;
343     ss << "{\"type\":\"SET_VOLUME\","
344        <<  "\"volume\":{\"level\":" << f_volume << ",\"muted\":" << ( b_mute ? "true" : "false" ) << "},"
345        <<  "\"mediaSessionId\":" << mediaSessionId << ","
346        <<  "\"requestId\":" << m_requestId++
347        << "}";
348
349     pushMediaPlayerMessage( destinationId, ss );
350 }
351
352 void ChromecastCommunication::msgPlayerSeek( const std::string& destinationId, const std::string& mediaSessionId, const std::string& currentTime )
353 {
354     assert(!mediaSessionId.empty());
355
356     std::stringstream ss;
357     ss << "{\"type\":\"SEEK\","
358        <<  "\"currentTime\":" << currentTime << ","
359        <<  "\"mediaSessionId\":" << mediaSessionId << ","
360        <<  "\"requestId\":" << m_requestId++
361        << "}";
362
363     pushMediaPlayerMessage( destinationId, ss );
364 }
365
366 /**
367  * @brief Send a message to the Chromecast
368  * @param msg the CastMessage to send
369  * @return vlc error code
370  */
371 int ChromecastCommunication::sendMessage( const castchannel::CastMessage &msg )
372 {
373     int i_size = msg.ByteSize();
374     uint8_t *p_data = new(std::nothrow) uint8_t[PACKET_HEADER_LEN + i_size];
375     if (p_data == NULL)
376         return VLC_ENOMEM;
377
378 #ifndef NDEBUG
379     msg_Dbg( m_module, "sendMessage: %s->%s %s", msg.namespace_().c_str(), msg.destination_id().c_str(), msg.payload_utf8().c_str());
380 #endif
381
382     SetDWBE(p_data, i_size);
383     msg.SerializeWithCachedSizesToArray(p_data + PACKET_HEADER_LEN);
384
385     int i_ret = vlc_tls_Write(m_tls, p_data, PACKET_HEADER_LEN + i_size);
386     delete[] p_data;
387     if (i_ret == PACKET_HEADER_LEN + i_size)
388         return VLC_SUCCESS;
389
390     msg_Warn( m_module, "failed to send message %s (%s)", msg.payload_utf8().c_str(), strerror( errno ) );
391
392     return VLC_EGENERIC;
393 }
394
395 void ChromecastCommunication::pushMediaPlayerMessage( const std::string& destinationId, const std::stringstream & payload )
396 {
397     assert(!destinationId.empty());
398     buildMessage( NAMESPACE_MEDIA, payload.str(), destinationId );
399 }