1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* This file is part of the Ssdp project. |
4
|
|
|
* |
5
|
|
|
* @author Daniel Schröder <[email protected]> |
6
|
|
|
*/ |
7
|
|
|
|
8
|
|
|
namespace GravityMedia\Ssdp; |
9
|
|
|
|
10
|
|
|
use GravityMedia\Ssdp\Event\DiscoverEvent; |
11
|
|
|
use GravityMedia\Ssdp\Exception\DiscoverException; |
12
|
|
|
use GravityMedia\Ssdp\Multicast\Factory as MulticastFactory; |
13
|
|
|
use GravityMedia\Ssdp\Options\AliveOptions; |
14
|
|
|
use GravityMedia\Ssdp\Options\ByebyeOptions; |
15
|
|
|
use GravityMedia\Ssdp\Options\DiscoverOptions; |
16
|
|
|
use GravityMedia\Ssdp\Options\UpdateOptions; |
17
|
|
|
use GravityMedia\Ssdp\Request\Factory as RequestFactory; |
18
|
|
|
use Psr\Http\Message\RequestInterface; |
19
|
|
|
use Socket\Raw\Exception as SocketException; |
20
|
|
|
use Socket\Raw\Socket; |
21
|
|
|
use Symfony\Component\EventDispatcher\EventDispatcher; |
22
|
|
|
use Symfony\Component\EventDispatcher\EventDispatcherInterface; |
23
|
|
|
use Zend\Diactoros\Request\Serializer as RequestSerializer; |
24
|
|
|
use Zend\Diactoros\Response\Serializer as ResponseSerializer; |
25
|
|
|
use Zend\Diactoros\Uri; |
26
|
|
|
|
27
|
|
|
/** |
28
|
|
|
* Ssdp client class |
29
|
|
|
* |
30
|
|
|
* @package GravityMedia\Ssdp |
31
|
|
|
*/ |
32
|
|
|
class Client |
33
|
|
|
{ |
34
|
|
|
/** |
35
|
|
|
* The multicast address |
36
|
|
|
*/ |
37
|
|
|
const MULTICAST_ADDRESS = '239.255.255.250'; |
38
|
|
|
|
39
|
|
|
/** |
40
|
|
|
* The multicast port |
41
|
|
|
*/ |
42
|
|
|
const MULTICAST_PORT = 1900; |
43
|
|
|
|
44
|
|
|
/** |
45
|
|
|
* @var EventDispatcherInterface |
46
|
|
|
*/ |
47
|
|
|
protected $eventDispatcher; |
48
|
|
|
|
49
|
|
|
/** |
50
|
|
|
* @var MulticastFactory |
51
|
|
|
*/ |
52
|
|
|
protected $multicastFactory; |
53
|
|
|
|
54
|
|
|
/** |
55
|
|
|
* @var RequestFactory |
56
|
|
|
*/ |
57
|
|
|
protected $requestFactory; |
58
|
|
|
|
59
|
|
|
/** |
60
|
|
|
* Get event dispatcher |
61
|
|
|
* |
62
|
|
|
* @return EventDispatcherInterface |
63
|
|
|
*/ |
64
|
|
|
public function getEventDispatcher() |
65
|
|
|
{ |
66
|
|
|
if (null === $this->eventDispatcher) { |
67
|
|
|
$this->eventDispatcher = new EventDispatcher(); |
68
|
|
|
} |
69
|
|
|
|
70
|
|
|
return $this->eventDispatcher; |
71
|
|
|
} |
72
|
|
|
|
73
|
|
|
/** |
74
|
|
|
* Set event dispatcher |
75
|
|
|
* |
76
|
|
|
* @param EventDispatcherInterface $eventDispatcher |
77
|
|
|
* |
78
|
|
|
* @return $this |
79
|
|
|
*/ |
80
|
2 |
|
public function setEventDispatcher(EventDispatcherInterface $eventDispatcher) |
81
|
|
|
{ |
82
|
2 |
|
$this->eventDispatcher = $eventDispatcher; |
83
|
|
|
|
84
|
2 |
|
return $this; |
85
|
|
|
} |
86
|
|
|
|
87
|
|
|
/** |
88
|
|
|
* Get multicast factory |
89
|
|
|
* |
90
|
|
|
* @return MulticastFactory |
91
|
|
|
*/ |
92
|
2 |
|
public function getMulticastFactory() |
93
|
|
|
{ |
94
|
2 |
|
if (null === $this->multicastFactory) { |
95
|
|
|
$this->multicastFactory = new MulticastFactory(); |
96
|
|
|
} |
97
|
|
|
|
98
|
2 |
|
return $this->multicastFactory; |
99
|
|
|
} |
100
|
|
|
|
101
|
|
|
/** |
102
|
|
|
* Set multicast factory |
103
|
|
|
* |
104
|
|
|
* @param MulticastFactory $multicastFactory |
105
|
|
|
* |
106
|
|
|
* @return $this |
107
|
|
|
*/ |
108
|
2 |
|
public function setMulticastFactory(MulticastFactory $multicastFactory) |
109
|
|
|
{ |
110
|
2 |
|
$this->multicastFactory = $multicastFactory; |
111
|
|
|
|
112
|
2 |
|
return $this; |
113
|
|
|
} |
114
|
|
|
|
115
|
|
|
/** |
116
|
|
|
* Get request factory |
117
|
|
|
* |
118
|
|
|
* @return RequestFactory |
119
|
|
|
*/ |
120
|
2 |
|
public function getRequestFactory() |
121
|
|
|
{ |
122
|
2 |
|
if (null === $this->requestFactory) { |
123
|
|
|
$this->requestFactory = new RequestFactory(); |
124
|
|
|
} |
125
|
|
|
|
126
|
2 |
|
return $this->requestFactory; |
127
|
|
|
} |
128
|
|
|
|
129
|
|
|
/** |
130
|
|
|
* Set request factory |
131
|
|
|
* |
132
|
|
|
* @param RequestFactory $requestFactory |
133
|
|
|
* |
134
|
|
|
* @return $this |
135
|
|
|
*/ |
136
|
2 |
|
public function setRequestFactory(RequestFactory $requestFactory) |
137
|
|
|
{ |
138
|
2 |
|
$this->requestFactory = $requestFactory; |
139
|
|
|
|
140
|
2 |
|
return $this; |
141
|
|
|
} |
142
|
|
|
|
143
|
|
|
/** |
144
|
|
|
* Send request |
145
|
|
|
* |
146
|
|
|
* @param Socket $socket |
147
|
|
|
* @param RequestInterface $request |
148
|
|
|
* @param callable $onSuccess |
149
|
|
|
* @param callable $onFailure |
150
|
|
|
* |
151
|
|
|
* @return boolean |
152
|
|
|
*/ |
153
|
2 |
|
protected function sendRequest(Socket $socket, RequestInterface $request, $onSuccess, $onFailure) |
154
|
|
|
{ |
155
|
2 |
|
$data = trim(RequestSerializer::toString($request)) . "\r\n\r\n"; |
156
|
|
|
|
157
|
|
|
try { |
158
|
2 |
|
$bytes = $socket->sendTo($data, 0, sprintf('%s:%s', self::MULTICAST_ADDRESS, self::MULTICAST_PORT)); |
159
|
1 |
|
} catch (SocketException $exception) { |
160
|
|
|
return $onFailure($socket, $exception); |
161
|
|
|
} |
162
|
|
|
|
163
|
2 |
|
return $onSuccess($socket, $bytes); |
164
|
|
|
} |
165
|
|
|
|
166
|
|
|
/** |
167
|
|
|
* Receive response |
168
|
|
|
* |
169
|
|
|
* @param Socket $socket |
170
|
|
|
* @param callable $onSuccess |
171
|
|
|
* @param callable $onFailure |
172
|
|
|
* |
173
|
|
|
* @return boolean |
174
|
|
|
*/ |
175
|
2 |
|
protected function receiveResponse(Socket $socket, $onSuccess, $onFailure) |
176
|
|
|
{ |
177
|
|
|
try { |
178
|
2 |
|
$message = $socket->recvFrom(1024, MSG_WAITALL, $remote); |
179
|
1 |
|
} catch (SocketException $exception) { |
180
|
|
|
return $onFailure($socket, $exception); |
181
|
|
|
} |
182
|
|
|
|
183
|
2 |
|
return $onSuccess($socket, $message, $remote); |
184
|
|
|
} |
185
|
|
|
|
186
|
|
|
/** |
187
|
|
|
* Send alive request |
188
|
|
|
* |
189
|
|
|
* @param AliveOptions $options |
190
|
|
|
* |
191
|
|
|
* @return boolean |
192
|
|
|
*/ |
193
|
|
View Code Duplication |
public function alive(AliveOptions $options) |
|
|
|
|
194
|
|
|
{ |
195
|
|
|
return $this->sendRequest( |
196
|
|
|
$this->getMulticastFactory()->createSocket(self::MULTICAST_ADDRESS), |
197
|
|
|
$this->getRequestFactory()->createAliveRequest($options), |
198
|
|
|
function (Socket $socket) { |
199
|
|
|
$socket->close(); |
200
|
|
|
|
201
|
|
|
return true; |
202
|
|
|
}, |
203
|
|
|
function (Socket $socket, SocketException $exception) { |
204
|
|
|
$socket->close(); |
205
|
|
|
|
206
|
|
|
$event = new DiscoverEvent(); |
207
|
|
|
$event->setException(new DiscoverException('Error sending alive broadcast', 0, $exception)); |
208
|
|
|
$this->getEventDispatcher()->dispatch(DiscoverEvent::EVENT_DISCOVER_ERROR, $event); |
209
|
|
|
|
210
|
|
|
return false; |
211
|
|
|
} |
212
|
|
|
); |
213
|
|
|
} |
214
|
|
|
|
215
|
|
|
/** |
216
|
|
|
* Send byebye request |
217
|
|
|
* |
218
|
|
|
* @param ByebyeOptions $options |
219
|
|
|
* |
220
|
|
|
* @return boolean |
221
|
|
|
*/ |
222
|
|
View Code Duplication |
public function byebye(ByebyeOptions $options) |
|
|
|
|
223
|
|
|
{ |
224
|
|
|
return $this->sendRequest( |
225
|
|
|
$this->getMulticastFactory()->createSocket(self::MULTICAST_ADDRESS), |
226
|
|
|
$this->getRequestFactory()->createByebyeRequest($options), |
227
|
|
|
function (Socket $socket) { |
228
|
|
|
$socket->close(); |
229
|
|
|
|
230
|
|
|
return true; |
231
|
|
|
}, |
232
|
|
|
function (Socket $socket, SocketException $exception) { |
233
|
|
|
$socket->close(); |
234
|
|
|
|
235
|
|
|
$event = new DiscoverEvent(); |
236
|
|
|
$event->setException(new DiscoverException('Error sending byebye broadcast', 0, $exception)); |
237
|
|
|
$this->getEventDispatcher()->dispatch(DiscoverEvent::EVENT_DISCOVER_ERROR, $event); |
238
|
|
|
|
239
|
|
|
return false; |
240
|
|
|
} |
241
|
|
|
); |
242
|
|
|
} |
243
|
|
|
|
244
|
|
|
/** |
245
|
|
|
* Send discover request |
246
|
|
|
* |
247
|
|
|
* @param DiscoverOptions $options |
248
|
|
|
* @param int $timeout |
249
|
|
|
* |
250
|
|
|
* @return boolean |
251
|
|
|
*/ |
252
|
2 |
|
public function discover(DiscoverOptions $options, $timeout = 2) |
253
|
|
|
{ |
254
|
2 |
|
return $this->sendRequest( |
255
|
2 |
|
$this->getMulticastFactory()->createSocket(self::MULTICAST_ADDRESS, $timeout), |
256
|
2 |
|
$this->getRequestFactory()->createDiscoverRequest($options), |
257
|
|
|
function (Socket $socket) { |
258
|
|
|
do { |
259
|
2 |
|
$continue = $this->receiveResponse( |
260
|
1 |
|
$socket, |
261
|
|
|
function (Socket $socket, $message, $remote) { |
262
|
2 |
|
if (null === $message) { |
263
|
2 |
|
return false; |
264
|
|
|
} |
265
|
|
|
|
266
|
|
|
$event = $this->createDiscoverEvent($message)->setRemote($remote); |
267
|
|
|
$this->getEventDispatcher()->dispatch(DiscoverEvent::EVENT_DISCOVER, $event); |
268
|
|
|
|
269
|
|
|
return true; |
270
|
2 |
|
}, |
271
|
|
|
function (Socket $socket, SocketException $exception) { |
272
|
|
|
if (SOCKET_EAGAIN === $exception->getCode()) { |
273
|
|
|
return false; |
274
|
|
|
} |
275
|
|
|
|
276
|
|
|
$exception = new DiscoverException('Error receiving discover message', 0, $exception); |
277
|
|
|
$event = new DiscoverEvent(); |
278
|
|
|
$event->setException($exception); |
279
|
|
|
$this->getEventDispatcher()->dispatch(DiscoverEvent::EVENT_DISCOVER_ERROR, $event); |
280
|
|
|
|
281
|
|
|
return false; |
282
|
1 |
|
} |
283
|
1 |
|
); |
284
|
|
|
|
285
|
2 |
|
} while ($continue); |
286
|
|
|
|
287
|
2 |
|
$socket->close(); |
288
|
|
|
|
289
|
2 |
|
return true; |
290
|
2 |
|
}, |
291
|
|
|
function (Socket $socket, SocketException $exception) { |
292
|
|
|
$socket->close(); |
293
|
|
|
|
294
|
|
|
$event = new DiscoverEvent(); |
295
|
|
|
$event->setException(new DiscoverException('Error sending discover broadcast', 0, $exception)); |
296
|
|
|
$this->getEventDispatcher()->dispatch(DiscoverEvent::EVENT_DISCOVER_ERROR, $event); |
297
|
|
|
|
298
|
|
|
return false; |
299
|
1 |
|
} |
300
|
1 |
|
); |
301
|
|
|
} |
302
|
|
|
|
303
|
|
|
/** |
304
|
|
|
* Send update request |
305
|
|
|
* |
306
|
|
|
* @param UpdateOptions $options |
307
|
|
|
* |
308
|
|
|
* @return boolean |
309
|
|
|
*/ |
310
|
|
View Code Duplication |
public function update(UpdateOptions $options) |
|
|
|
|
311
|
|
|
{ |
312
|
|
|
return $this->sendRequest( |
313
|
|
|
$this->getMulticastFactory()->createSocket(self::MULTICAST_ADDRESS), |
314
|
|
|
$this->getRequestFactory()->createUpdateRequest($options), |
315
|
|
|
function (Socket $socket) { |
316
|
|
|
$socket->close(); |
317
|
|
|
|
318
|
|
|
return true; |
319
|
|
|
}, |
320
|
|
|
function (Socket $socket, SocketException $exception) { |
321
|
|
|
$socket->close(); |
322
|
|
|
|
323
|
|
|
$event = new DiscoverEvent(); |
324
|
|
|
$event->setException(new DiscoverException('Error sending byebye broadcast', 0, $exception)); |
325
|
|
|
$this->getEventDispatcher()->dispatch(DiscoverEvent::EVENT_DISCOVER_ERROR, $event); |
326
|
|
|
|
327
|
|
|
return false; |
328
|
|
|
} |
329
|
|
|
); |
330
|
|
|
} |
331
|
|
|
|
332
|
|
|
/** |
333
|
|
|
* Create discover event |
334
|
|
|
* |
335
|
|
|
* @param string $message |
336
|
|
|
* |
337
|
|
|
* @return DiscoverEvent |
338
|
|
|
*/ |
339
|
|
|
protected function createDiscoverEvent($message) |
340
|
|
|
{ |
341
|
|
|
$response = ResponseSerializer::fromString($message); |
342
|
|
|
$event = new DiscoverEvent(); |
343
|
|
|
|
344
|
|
|
if ($response->hasHeader('CACHE-CONTROL')) { |
345
|
|
|
$value = $response->getHeaderLine('CACHE-CONTROL'); |
346
|
|
|
$event->setLifetime(intval(substr($value, strpos($value, '=') + 1))); |
347
|
|
|
} |
348
|
|
|
|
349
|
|
|
if ($response->hasHeader('DATE')) { |
350
|
|
|
$event->setDate(new \DateTime($response->getHeaderLine('DATE'))); |
351
|
|
|
} |
352
|
|
|
|
353
|
|
|
if ($response->hasHeader('LOCATION')) { |
354
|
|
|
$event->setDescriptionUrl(new Uri($response->getHeaderLine('LOCATION'))); |
355
|
|
|
} |
356
|
|
|
|
357
|
|
|
if ($response->hasHeader('SERVER')) { |
358
|
|
|
$event->setServerString($response->getHeaderLine('SERVER')); |
359
|
|
|
} |
360
|
|
|
|
361
|
|
|
if ($response->hasHeader('ST')) { |
362
|
|
|
$event->setSearchTargetString($response->getHeaderLine('ST')); |
363
|
|
|
} |
364
|
|
|
|
365
|
|
|
if ($response->hasHeader('USN')) { |
366
|
|
|
$event->setUniqueServiceNameString($response->getHeaderLine('USN')); |
367
|
|
|
} |
368
|
|
|
|
369
|
|
|
return $event; |
370
|
|
|
} |
371
|
|
|
} |
372
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.