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\Options\AliveOptions; |
13
|
|
|
use GravityMedia\Ssdp\Options\ByebyeOptions; |
14
|
|
|
use GravityMedia\Ssdp\Options\DiscoverOptions; |
15
|
|
|
use GravityMedia\Ssdp\Options\UpdateOptions; |
16
|
|
|
use GravityMedia\Ssdp\Request\Factory as RequestFactory; |
17
|
|
|
use Socket\Raw\Exception as SocketException; |
18
|
|
|
use Socket\Raw\Factory as SocketFactory; |
19
|
|
|
use Symfony\Component\EventDispatcher\EventDispatcher; |
20
|
|
|
use Symfony\Component\EventDispatcher\EventDispatcherInterface; |
21
|
|
|
use Zend\Diactoros\Request\Serializer as RequestSerializer; |
22
|
|
|
use Zend\Diactoros\Response\Serializer as ResponseSerializer; |
23
|
|
|
use Zend\Diactoros\Uri; |
24
|
|
|
|
25
|
|
|
/** |
26
|
|
|
* Ssdp client class |
27
|
|
|
* |
28
|
|
|
* @package GravityMedia\Ssdp |
29
|
|
|
*/ |
30
|
|
|
class Client |
31
|
|
|
{ |
32
|
|
|
/** |
33
|
|
|
* The multicast address |
34
|
|
|
*/ |
35
|
|
|
const MULTICAST_ADDRESS = '239.255.255.250'; |
36
|
|
|
|
37
|
|
|
/** |
38
|
|
|
* The multicast port |
39
|
|
|
*/ |
40
|
|
|
const MULTICAST_PORT = 1900; |
41
|
|
|
|
42
|
|
|
/** |
43
|
|
|
* @var EventDispatcherInterface |
44
|
|
|
*/ |
45
|
|
|
protected $eventDispatcher; |
46
|
|
|
|
47
|
|
|
/** |
48
|
|
|
* @var SocketFactory |
49
|
|
|
*/ |
50
|
|
|
protected $socketFactory; |
51
|
|
|
|
52
|
|
|
/** |
53
|
|
|
* @var RequestFactory |
54
|
|
|
*/ |
55
|
|
|
protected $requestFactory; |
56
|
|
|
|
57
|
|
|
/** |
58
|
|
|
* Get event dispatcher |
59
|
|
|
* |
60
|
|
|
* @return EventDispatcherInterface |
61
|
|
|
*/ |
62
|
|
|
public function getEventDispatcher() |
63
|
|
|
{ |
64
|
|
|
if (null === $this->eventDispatcher) { |
65
|
|
|
$this->eventDispatcher = new EventDispatcher(); |
66
|
|
|
} |
67
|
|
|
|
68
|
|
|
return $this->eventDispatcher; |
69
|
|
|
} |
70
|
|
|
|
71
|
|
|
/** |
72
|
|
|
* Set event dispatcher |
73
|
|
|
* |
74
|
|
|
* @param EventDispatcherInterface $eventDispatcher |
75
|
|
|
* |
76
|
|
|
* @return $this |
77
|
|
|
*/ |
78
|
2 |
|
public function setEventDispatcher(EventDispatcherInterface $eventDispatcher) |
79
|
|
|
{ |
80
|
2 |
|
$this->eventDispatcher = $eventDispatcher; |
81
|
|
|
|
82
|
2 |
|
return $this; |
83
|
|
|
} |
84
|
|
|
|
85
|
|
|
/** |
86
|
|
|
* Get socket factory |
87
|
|
|
* |
88
|
|
|
* @return SocketFactory |
89
|
|
|
*/ |
90
|
2 |
|
public function getSocketFactory() |
91
|
|
|
{ |
92
|
2 |
|
if (null === $this->socketFactory) { |
93
|
|
|
$this->socketFactory = new SocketFactory(); |
94
|
|
|
} |
95
|
|
|
|
96
|
2 |
|
return $this->socketFactory; |
97
|
|
|
} |
98
|
|
|
|
99
|
|
|
/** |
100
|
|
|
* Set socket factory |
101
|
|
|
* |
102
|
|
|
* @param SocketFactory $socketFactory |
103
|
|
|
* |
104
|
|
|
* @return $this |
105
|
|
|
*/ |
106
|
2 |
|
public function setSocketFactory(SocketFactory $socketFactory) |
107
|
|
|
{ |
108
|
2 |
|
$this->socketFactory = $socketFactory; |
109
|
|
|
|
110
|
2 |
|
return $this; |
111
|
|
|
} |
112
|
|
|
|
113
|
|
|
/** |
114
|
|
|
* Get request factory |
115
|
|
|
* |
116
|
|
|
* @return RequestFactory |
117
|
|
|
*/ |
118
|
2 |
|
public function getRequestFactory() |
119
|
|
|
{ |
120
|
2 |
|
if (null === $this->requestFactory) { |
121
|
|
|
$this->requestFactory = new RequestFactory(); |
122
|
|
|
} |
123
|
|
|
|
124
|
2 |
|
return $this->requestFactory; |
125
|
|
|
} |
126
|
|
|
|
127
|
|
|
/** |
128
|
|
|
* Set request factory |
129
|
|
|
* |
130
|
|
|
* @param RequestFactory $requestFactory |
131
|
|
|
* |
132
|
|
|
* @return $this |
133
|
|
|
*/ |
134
|
2 |
|
public function setRequestFactory(RequestFactory $requestFactory) |
135
|
|
|
{ |
136
|
2 |
|
$this->requestFactory = $requestFactory; |
137
|
|
|
|
138
|
2 |
|
return $this; |
139
|
|
|
} |
140
|
|
|
|
141
|
|
|
/** |
142
|
|
|
* Send alive request |
143
|
|
|
* |
144
|
|
|
* @param AliveOptions $options |
145
|
|
|
* |
146
|
|
|
* @return PromiseInterface |
147
|
|
|
*/ |
148
|
|
View Code Duplication |
public function alive(AliveOptions $options) |
|
|
|
|
149
|
|
|
{ |
150
|
|
|
$request = $this->getRequestFactory()->createAliveRequest($options); |
151
|
|
|
$data = trim(RequestSerializer::toString($request)) . "\r\n\r\n"; |
152
|
|
|
|
153
|
|
|
$socket = $this->getSocketFactory()->createUdp4(); |
154
|
|
|
$socket->setOption(SOL_SOCKET, SO_BROADCAST, 1); |
155
|
|
|
$socket->setOption(IPPROTO_IP, IP_MULTICAST_IF, 0); |
156
|
|
|
$socket->setOption(IPPROTO_IP, IP_MULTICAST_LOOP, 0); |
157
|
|
|
$socket->setOption(IPPROTO_IP, IP_MULTICAST_TTL, 4); |
158
|
|
|
$socket->setOption(IPPROTO_IP, MCAST_JOIN_GROUP, ['group' => self::MULTICAST_ADDRESS, 'interface' => 0]); |
159
|
|
|
$socket->bind('0.0.0.0'); |
160
|
|
|
$socket->sendTo($data, 0, sprintf('%s:%s', self::MULTICAST_ADDRESS, self::MULTICAST_PORT)); |
161
|
|
|
|
162
|
|
|
return $this; |
163
|
|
|
} |
164
|
|
|
|
165
|
|
|
/** |
166
|
|
|
* Send byebye request |
167
|
|
|
* |
168
|
|
|
* @param ByebyeOptions $options |
169
|
|
|
* |
170
|
|
|
* @return PromiseInterface |
171
|
|
|
*/ |
172
|
|
View Code Duplication |
public function byebye(ByebyeOptions $options) |
|
|
|
|
173
|
|
|
{ |
174
|
|
|
$request = $this->getRequestFactory()->createByebyeRequest($options); |
175
|
|
|
$data = trim(RequestSerializer::toString($request)) . "\r\n\r\n"; |
176
|
|
|
|
177
|
|
|
$socket = $this->getSocketFactory()->createUdp4(); |
178
|
|
|
$socket->setOption(SOL_SOCKET, SO_BROADCAST, 1); |
179
|
|
|
$socket->setOption(IPPROTO_IP, IP_MULTICAST_IF, 0); |
180
|
|
|
$socket->setOption(IPPROTO_IP, IP_MULTICAST_LOOP, 0); |
181
|
|
|
$socket->setOption(IPPROTO_IP, IP_MULTICAST_TTL, 4); |
182
|
|
|
$socket->setOption(IPPROTO_IP, MCAST_JOIN_GROUP, ['group' => self::MULTICAST_ADDRESS, 'interface' => 0]); |
183
|
|
|
$socket->bind('0.0.0.0'); |
184
|
|
|
$socket->sendTo($data, 0, sprintf('%s:%s', self::MULTICAST_ADDRESS, self::MULTICAST_PORT)); |
185
|
|
|
|
186
|
|
|
return $this; |
187
|
|
|
} |
188
|
|
|
|
189
|
|
|
/** |
190
|
|
|
* Send discover request |
191
|
|
|
* |
192
|
|
|
* @param DiscoverOptions $options |
193
|
|
|
* @param int $timeout |
194
|
|
|
* |
195
|
|
|
* @return $this |
196
|
|
|
*/ |
197
|
2 |
|
public function discover(DiscoverOptions $options, $timeout = 2) |
198
|
|
|
{ |
199
|
2 |
|
$request = $this->getRequestFactory()->createDiscoverRequest($options); |
200
|
2 |
|
$data = trim(RequestSerializer::toString($request)) . "\r\n\r\n"; |
201
|
|
|
|
202
|
2 |
|
$socket = $this->getSocketFactory()->createUdp4(); |
203
|
2 |
|
$socket->setOption(SOL_SOCKET, SO_BROADCAST, 1); |
204
|
|
|
|
205
|
|
|
try { |
206
|
2 |
|
$socket->sendTo($data, 0, sprintf('%s:%s', self::MULTICAST_ADDRESS, self::MULTICAST_PORT)); |
207
|
1 |
|
} catch (SocketException $exception) { |
208
|
|
|
$socket->close(); |
209
|
|
|
|
210
|
|
|
$event = new DiscoverEvent(); |
211
|
|
|
$event->setException(new DiscoverException('Error sending discover broadcast', 0, $exception)); |
212
|
|
|
$this->getEventDispatcher()->dispatch(DiscoverEvent::EVENT_DISCOVER_ERROR, $event); |
213
|
|
|
|
214
|
|
|
return $this; |
215
|
|
|
} |
216
|
|
|
|
217
|
2 |
|
$socket->setOption(SOL_SOCKET, SO_RCVTIMEO, ['sec' => $timeout, 'usec' => 0]); |
218
|
|
|
do { |
219
|
|
|
try { |
220
|
2 |
|
$message = $socket->recvFrom(1024, MSG_WAITALL, $remote); |
221
|
1 |
|
} catch (SocketException $exception) { |
222
|
|
|
if (SOCKET_EAGAIN === $exception->getCode()) { |
223
|
|
|
break; |
224
|
|
|
} |
225
|
|
|
|
226
|
|
|
$socket->close(); |
227
|
|
|
|
228
|
|
|
$event = new DiscoverEvent(); |
229
|
|
|
$event->setException(new DiscoverException('Error receiving discover message', 0, $exception)); |
230
|
|
|
$this->getEventDispatcher()->dispatch(DiscoverEvent::EVENT_DISCOVER_ERROR, $event); |
231
|
|
|
|
232
|
|
|
return $this; |
233
|
|
|
} |
234
|
|
|
|
235
|
2 |
|
if (null !== $message) { |
236
|
|
|
$event = $this->createDiscoverEvent($message)->setRemote($remote); |
237
|
|
|
$this->getEventDispatcher()->dispatch(DiscoverEvent::EVENT_DISCOVER, $event); |
238
|
|
|
} |
239
|
2 |
|
} while (null !== $message); |
240
|
|
|
|
241
|
2 |
|
$socket->close(); |
242
|
|
|
|
243
|
2 |
|
return $this; |
244
|
|
|
} |
245
|
|
|
|
246
|
|
|
/** |
247
|
|
|
* Send update request |
248
|
|
|
* |
249
|
|
|
* @param UpdateOptions $options |
250
|
|
|
* |
251
|
|
|
* @return PromiseInterface |
252
|
|
|
*/ |
253
|
|
View Code Duplication |
public function update(UpdateOptions $options) |
|
|
|
|
254
|
|
|
{ |
255
|
|
|
$request = $this->getRequestFactory()->createUpdateRequest($options); |
256
|
|
|
$data = trim(RequestSerializer::toString($request)) . "\r\n\r\n"; |
257
|
|
|
|
258
|
|
|
$socket = $this->getSocketFactory()->createUdp4(); |
259
|
|
|
$socket->setOption(SOL_SOCKET, SO_BROADCAST, 1); |
260
|
|
|
$socket->setOption(IPPROTO_IP, IP_MULTICAST_IF, 0); |
261
|
|
|
$socket->setOption(IPPROTO_IP, IP_MULTICAST_LOOP, 0); |
262
|
|
|
$socket->setOption(IPPROTO_IP, IP_MULTICAST_TTL, 4); |
263
|
|
|
$socket->setOption(IPPROTO_IP, MCAST_JOIN_GROUP, ['group' => self::MULTICAST_ADDRESS, 'interface' => 0]); |
264
|
|
|
$socket->bind('0.0.0.0'); |
265
|
|
|
$socket->sendTo($data, 0, sprintf('%s:%s', self::MULTICAST_ADDRESS, self::MULTICAST_PORT)); |
266
|
|
|
|
267
|
|
|
return $this; |
268
|
|
|
} |
269
|
|
|
|
270
|
|
|
/** |
271
|
|
|
* Create discover event |
272
|
|
|
* |
273
|
|
|
* @param string $message |
274
|
|
|
* |
275
|
|
|
* @return DiscoverEvent |
276
|
|
|
*/ |
277
|
|
|
protected function createDiscoverEvent($message) |
278
|
|
|
{ |
279
|
|
|
$response = ResponseSerializer::fromString($message); |
280
|
|
|
$event = new DiscoverEvent(); |
281
|
|
|
|
282
|
|
|
if ($response->hasHeader('CACHE-CONTROL')) { |
283
|
|
|
$value = $response->getHeaderLine('CACHE-CONTROL'); |
284
|
|
|
$event->setLifetime(intval(substr($value, strpos($value, '=') + 1))); |
285
|
|
|
} |
286
|
|
|
|
287
|
|
|
if ($response->hasHeader('DATE')) { |
288
|
|
|
$event->setDate(new \DateTime($response->getHeaderLine('DATE'))); |
289
|
|
|
} |
290
|
|
|
|
291
|
|
|
if ($response->hasHeader('LOCATION')) { |
292
|
|
|
$event->setDescriptionUrl(new Uri($response->getHeaderLine('LOCATION'))); |
293
|
|
|
} |
294
|
|
|
|
295
|
|
|
if ($response->hasHeader('SERVER')) { |
296
|
|
|
$event->setServerString($response->getHeaderLine('SERVER')); |
297
|
|
|
} |
298
|
|
|
|
299
|
|
|
if ($response->hasHeader('ST')) { |
300
|
|
|
$event->setSearchTargetString($response->getHeaderLine('ST')); |
301
|
|
|
} |
302
|
|
|
|
303
|
|
|
if ($response->hasHeader('USN')) { |
304
|
|
|
$event->setUniqueServiceNameString($response->getHeaderLine('USN')); |
305
|
|
|
} |
306
|
|
|
|
307
|
|
|
return $event; |
308
|
|
|
} |
309
|
|
|
} |
310
|
|
|
|
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.