-
Notifications
You must be signed in to change notification settings - Fork 9
/
locateIgd.cpp
450 lines (400 loc) · 17.5 KB
/
locateIgd.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
// UPNP Internet Gateway Device locator - Library v1.0 - August 8th 2012
// Author: Deverick McIntyre
#include "locateIgd.h"
#include "Arduino.h"
int MSearchClass::begin()
{
reset();
return find_Igd();
}
void MSearchClass::reset()
{
// zero out or null all variables
_msearch_state = 0;
_msearchIgdIp = (0, 0, 0, 0);
_msearchIgdControlUrl = "";
_msearchIgdPort = 0;
}
IPAddress MSearchClass::igdIp()
{
return _msearchIgdIp;
}
uint16_t MSearchClass::igdPort()
{
return _msearchIgdPort;
}
String MSearchClass::controlUrl()
{
return _msearchIgdControlUrl;
}
int MSearchClass::find_Igd()
{
// Pick an initial transaction ID
_msearchTransactionId = random(1UL, 2000UL);
_msearchInitialTransactionId = _msearchTransactionId;
if (_msearchUdpSocket.begin(M_SEARCH_CLIENT_RCVE_PORT) == 0)
{
// Couldn't get a socket
return 0;
}
int result = 0;
unsigned long startTime = millis();
while(_msearch_state != STATE_MSEARCH_FOUND)
{
if (_msearch_state == STATE_MSEARCH_START)
{
if (send_MSEARCH_MESSAGE() == 1)
{
_msearchTransactionId++;
_msearch_state = STATE_MSEARCH_WAIT_RESPONSE;
}
}
if (_msearch_state == STATE_MSEARCH_WAIT_RESPONSE)
{
_msearchTransactionId++;
if (parseMSearchResponse() == 1)
{
_msearch_state == STATE_MSEARCH_FOUND;
result = 1;
}
break;
}
if(result != 1 && ((millis() - startTime) > IGD_LOCATE_TIMEOUT))
break;
}
// We're done with the socket now
_msearchUdpSocket.stop();
_msearchTransactionId++;
return result;
}
int MSearchClass::send_MSEARCH_MESSAGE()
{
static const IPAddress ms_bcast_addr( 239, 255, 255, 250 ); // M-SEARCH Broadcast address
if (-1 == _msearchUdpSocket.beginPacket(ms_bcast_addr, M_SEARCH_CLIENT_SEND_PORT))
{
return 0;
}
char * buffer = (char *) malloc (32);
memset(buffer, 0, 32);
strcpy_P(buffer, IGD_MSRCH_LINE1);
_msearchUdpSocket.print(buffer);
memset(buffer, 0, 32); // clear local buffer
strcpy_P(buffer, IGD_MSRCH_LINE2);
_msearchUdpSocket.print(buffer);
memset(buffer, 0, 32); // clear local buffer
strcpy_P(buffer, IGD_MSRCH_LINE3);
_msearchUdpSocket.print(buffer);
memset(buffer, 0, 32); // clear local buffer
strcpy_P(buffer, IGD_MSRCH_LINE4);
_msearchUdpSocket.print(buffer);
memset(buffer, 0, 32); // clear local buffer
strcpy_P(buffer, IGD_MSRCH_LINE5a);
_msearchUdpSocket.print(buffer);
memset(buffer, 0, 32); // clear local buffer
strcpy_P(buffer, IGD_MSRCH_LINE5b);
_msearchUdpSocket.print(buffer);
free(buffer);
int sendResult = _msearchUdpSocket.endPacket();
return sendResult;
}
// *** Due to differing implementations of discovery for so called upnp enabled devices, we look for two different packet types to verify
// *** a device is an IGD. So, strictly speaking we should only accept the NOTIFY datagram, but in this implementation we also accept the HTTP
// *** datagram with the correct payload.
// Below is an example structure for the UDP NOTIFY datagram from a root device which has the 'InternetGatewayDevice' notification type
// Following is the logic for parsing a datagram to confirm it is an IGD NOTIFY datagram
// first we check first six characters is 'NOTIFY'
// then we read through characters to each line feed character
// after each line feed character we look for a 'NT:' or 'Loc' string
// if 'NT:' string found then remove any trailing white space characters
// remaining string on that line should be 'urn:schemas-upnp-org:device:InternetGatewayDevice:1'
// if 'Loc' string found then check if 'ation:' string follows
// then remove any trailing white spaces after :
// then move the rest of the line into our string variable
//****************
// NOTIFY * HTTP/1.1
// Host:239.255.255.250:1900
// Cache-Control:max-age=180
// Location:http://192.168.0.1:52869/picsdesc.xml
// Server:OS 1.0 UPnP/1.0 Realtek/V1.3
// NT:urn:schemas-upnp-org:device:InternetGatewayDevice:1
// USN:uuid:12342409-1234-1234-5678-ee1234cc5678::urn:schemas-upnp-org:device:InternetGatewayDevice:1
// NTS:ssdp:alive
//*********************
//
// Below is an example structure for the HTTP response datagram from a root device which has the 'InternetGatewayDevice' notification type
// Following is the logic for parsing a datagram to confirm it is an IGD HTTP response datagram
// first we check first six characters is 'HTTP/1'
// then we read through characters to each line feed character
// after each line feed character we look for a 'ST:' string
// remove any space characters after the 'ST:' string
// remaining string on that line should be 'urn:schemas-upnp-org:device:InternetGatewayDevice:1'
//*********************
// HTTP/1.1 200 OK
// Cache-Control: max-age=180
// ST: urn:schemas-upnp-org:device:InternetGatewayDevice:1
// USN: uuid:12342409-1234-1234-5678-ee1234cc5678::urn:schemas-upnp-org:device:InternetGatewayDevice:1
// EXT:
// Server: OS 1.0 UPnP/1.0 Realtek/V1.3
// Location: http://192.168.0.1:52869/picsdesc.xml
//*********************
int MSearchClass::parseMSearchResponse()
{
// set variable intial states, and create local variables
char * buffer = (char *) malloc (32);
memset(buffer, 0, 32);
char c;
unsigned long responseStartTime = millis();
int type_flag = 0;
int result = 0;
_msearchIgdControlUrl = "";
_msearchIgdPort = 0;
while ((millis() - responseStartTime) < IGD_RESPONSE_TIMEOUT)
{
// loop will return to here after each packet to wait for next packet.
while(_msearchUdpSocket.parsePacket() <= 0)
{
if((millis() - responseStartTime) > IGD_RESPONSE_TIMEOUT)
{
//Serial.println(F("ran out of time!"));
_msearchUdpSocket.stop();
free(buffer);
return 0;
}
delay(40);
}
while (_msearchUdpSocket.available())
{
// first six characters of UPD frame should be 'NOTIFY' or 'HTTP/1'
memset(buffer, 0, 32);
_msearchUdpSocket.read(buffer, 6);
/*
NOTIFY and HTTP/1 DATAGRAM PROCESSING
*/
if ((strncmp_P(buffer, NOTIFY_PACKET_STRING, 6) == 0) || (strncmp_P(buffer, HTTP_PACKET_STRING, 6) == 0))
{
while (_msearchUdpSocket.available())
{
c = _msearchUdpSocket.read();
while (strncmp_P(&c, LINE_FEED, 1) != 0) // iterate through the characters until we hit a LF then drop out
{
c = _msearchUdpSocket.read();
}
memset(buffer, 0, 32);
_msearchUdpSocket.read(buffer, 3);
if ((strncmp_P(buffer, NT_TYPE_ID, 3) == 0) || (strncmp_P(buffer, ST_TYPE_ID, 3) == 0))// if new line starts with 'NT:' then continue checking
{
// first strip out any spaces
while (_msearchUdpSocket.peek() == ASCII_SPACE)
{
_msearchUdpSocket.read(); //discard white space character
}
memset(buffer, 0, 32);
_msearchUdpSocket.read(buffer, 31);
if (strncmp_P(buffer, IGD_STRING_1, 31) == 0) // check the first part of string is correct
{
memset(buffer, 0, 32);
_msearchUdpSocket.read(buffer, 20);
if (strncmp_P(buffer, IGD_STRING_2, 20) == 0) // check the second part of string is correct
{
type_flag = 1;
}
}
// if new line starts with 'Loc' or 'LOC' then continue checking
} else if ((strncmp_P(buffer, LOC_TYPE_ID, 3) == 0) || (strncmp_P(buffer, CAPS_LOC_TYPE_ID, 3) == 0))
{
memset(buffer, 0, 32);
_msearchUdpSocket.read(buffer, 6);
// if rest of word is 'ation:' or 'ATION:' then continue checking
if ((strncmp_P(buffer, LOC_TYPE_ID_2, 6) == 0) || (strncmp_P(buffer, CAPS_LOC_TYPE_ID_2, 6) == 0))
{
// first strip out any spaces
while (_msearchUdpSocket.peek() == ASCII_SPACE)
{
_msearchUdpSocket.read(); //discard white space character
}
c = _msearchUdpSocket.read(); //read the next character
while (strncmp_P(&c, CARRIAGE_RETURN, 1) != 0) // if we find a carriage return it means the URL and line have ended
{
_msearchLocationUrl += c;
c = _msearchUdpSocket.read(); //read the next character and append to the string
}
}
}
// if we have the location URL AND the packet is of the type we need then we can stop processing packets.
if (_msearchLocationUrl.length() && type_flag)
{
// if we are able to parse out the port number then try to parse out the controlURL
if (parseIgdPort(&_msearchLocationUrl)) // if we are able to find
{
_msearchIgdIp = _msearchUdpSocket.remoteIP();
// finally if we are able to parse out the control URL then return success
if (parseControlUrl(&_msearchLocationUrl, _msearchIgdIp, _msearchIgdPort)) {
_msearchUdpSocket.stop();
free(buffer);
return 1;
} else {
// unable to parse out the control URL. return error condition
_msearchUdpSocket.stop();
free(buffer);
return 0;
}
} else {
// unable to parse out the port number. return error condition
_msearchUdpSocket.stop();
free(buffer);
return 0;
}
}
}
} else {
// if we are here it is because this packet is not what we are looking for
// need to discard packet
// skip to the end of the packet
_msearchUdpSocket.flush();
//Serial.print(F("packet found but not notify or HTTP type"));
//while (_msearchUdpSocket.available())
//{
// char c = _msearchUdpSocket.read();
// Serial.print(c);
//}
}
// if we are here it is because a packet failed validation. need to reset variables and wait for next packet.
_msearchIgdControlUrl = "";
_msearchLocationUrl = "";
_msearchIgdPort = 0;
}
}
_msearchUdpSocket.stop();
free(buffer);
return 0;
}
int MSearchClass::parseIgdPort(String *locationURL)
{
String tempString = *locationURL;
int lastColon = tempString.lastIndexOf(':');
int firstSlash = tempString.indexOf('/', lastColon);
tempString = tempString.substring(lastColon+1, firstSlash);
char charArray[tempString.length()+1];
tempString.toCharArray(charArray, sizeof(charArray));
_msearchIgdPort = atof(charArray);
if (_msearchIgdPort) {
return 1;
} else {
return 0;
}
}
int MSearchClass::parseControlUrl(String *locationURL, IPAddress igdIp, uint16_t &igdPort)
{
String locationUrlString = *locationURL;
int lastColon = locationUrlString.lastIndexOf(':');
int firstSlash = locationUrlString.indexOf('/', lastColon);
locationUrlString = locationUrlString.substring(firstSlash);
char locationUrlArray[locationUrlString.length()+1];
locationUrlString.toCharArray(locationUrlArray, locationUrlString.length()+1);
if (_igdClient.connect(igdIp, igdPort)) {
char * buffer = (char *) malloc (32);
memset(buffer, 0, 32);
// Make a HTTP request:
strcpy_P(buffer, IGD_RQST_LINE1A);
_igdClient.print(buffer);
memset(buffer, 0, 32);
_igdClient.write((uint8_t *)locationUrlArray, locationUrlString.length());
strcpy_P(buffer, IGD_RQST_LINE1B);
_igdClient.print(buffer);
free(buffer);
return parseXmlResponse();
}
else {
// if you didn't get a connection to the server:
_igdClient.stop();
return 0;
}
}
int MSearchClass::parseXmlResponse()
{
// set variable intial states, and create local variables
char * buffer = (char *) malloc (32);
memset(buffer, 0, 32);
char c;
unsigned long responseStartTime = millis();
while (_igdClient.connected())
{
if (_igdClient.available())
{
// first strip out any spaces
while (_igdClient.peek() == ASCII_SPACE)
{
_igdClient.read(); //discard white space character
}
memset(buffer, 0, 32);
_igdClient.readBytes(buffer, 9);
if (strncmp_P(buffer, SERVICE_TYPE, 9) == 0) // if new line starts with '<serviceTy' then continue checking
{
memset(buffer, 0, 32);
_igdClient.readBytes(buffer, 31);
if (strncmp_P(buffer, SERVICE_STRING, 31) == 0) // if first part of line matches our string then continue checking
{
memset(buffer, 0, 32);
_igdClient.readBytes(buffer, 19);
if (strncmp_P(buffer, SERVICE_STRING_2, 19) == 0) // if second part of line matches our string then continue checking
{
// now we have found the service type we need setup a loop to iterate thru each line till we find control URL
// we only loop till we hit the the service tag end wrapper. </service> however we should not get this far before
// finding the control URL tag. not finding control URL tag and getting to </service> is an error condition
while (strncmp_P(buffer, END_SERVICE, 10) != 0)
{
c = _igdClient.read();
while (strncmp_P(&c, LINE_FEED, 1) != 0) // iterate through the characters until we hit a LF then drop out
{
c = _igdClient.read();
}
// strip out any spaces at start of new line
while (_igdClient.peek() == ASCII_SPACE)
{
_igdClient.read(); //discard white space character
}
memset(buffer, 0, 32);
_igdClient.readBytes(buffer, 12);
if (strncmp_P(buffer, CONTROL_URL, 12) == 0) // if new line starts with '<controlURL>' then continue checking
{
c = _igdClient.read(); //read the next character
while (strncmp_P(&c, START_XML_TAG, 1) != 0) // if we find an XML end tag it means we are at the end of the line
{
_msearchIgdControlUrl += c;
c = _igdClient.read(); //read the next character and append to the string
}
_igdClient.stop();
free(buffer);
return 1;
}
}
// We have reached the end service wrapper tag - error condition.
// If we reach here this is an error condition because we should have found
// the control URL tags for this service before getting this far.
_igdClient.stop();
free(buffer);
return 0;
}
}
}
// flush out the full line of characters
c = _igdClient.read();
while (strncmp(&c, LINE_FEED, 1) != 0) // iterate through the characters until we hit a LF then drop through
{
//Serial.print(c);
c = _igdClient.read();
}
} //end if (client.available())
if((millis() - responseStartTime) > IGD_RESPONSE_TIMEOUT)
{
//Serial.println(F("ran out of time!"));
_igdClient.stop();
free(buffer);
return 0;
}
} //end while (client.connected())
// if we are here it is because we couldnt find the URL in the XML. return an error condition
_igdClient.stop();
free(buffer);
return 0;
} //end function parseXmlResponse