2 * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/
4 * Copyright (c) 2006-2008, Thomas Bernard
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions are met:
9 * * Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * * Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * * The name of the author may not be used to endorse or promote products
15 * derived from this software without specific prior written permission.
17 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
21 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 * POSSIBILITY OF SUCH DAMAGE.
35 #include "getifaddr.h"
36 #include "upnpdescgen.h"
37 #include "minidlnapath.h"
38 #include "upnpglobalvars.h"
42 static const char * const upnptypes
[] =
54 static const char * const upnpdefaultvalues
[] =
60 static const char * const upnpallowedvalues
[] =
76 "Unconfigured", /* 14 */
80 "Unconfigured", /* 18 */
87 "ERROR_NONE", /* 25 */
90 "ContentFormatMismatch",
91 "InsufficientBandwidth",
98 "BrowseMetadata", /* 36 */
99 "BrowseDirectChildren",
101 "COMPLETED", /* 39 */
106 RESOURCE_PROTOCOL_INFO_VALUES
, /* 44 */
114 static const char xmlver
[] =
115 "<?xml version=\"1.0\"?>\r\n";
116 static const char root_service
[] =
117 "scpd xmlns=\"urn:schemas-upnp-org:service-1-0\"";
118 static const char root_device
[] =
119 "root xmlns=\"urn:schemas-upnp-org:device-1-0\"";
121 /* root Description of the UPnP Device */
122 static const struct XMLElt rootDesc
[] =
124 {root_device
, INITHELPER(1,2)},
125 {"specVersion", INITHELPER(3,2)},
126 {"device", INITHELPER(5,(14))},
129 {"/deviceType", "urn:schemas-upnp-org:device:MediaServer:1"},
130 {"/friendlyName", friendly_name
}, /* required */
131 {"/manufacturer", ROOTDEV_MANUFACTURER
}, /* required */
132 {"/manufacturerURL", ROOTDEV_MANUFACTURERURL
}, /* optional */
133 {"/modelDescription", ROOTDEV_MODELDESCRIPTION
}, /* recommended */
134 {"/modelName", modelname
}, /* required */
135 {"/modelNumber", modelnumber
},
136 {"/modelURL", ROOTDEV_MODELURL
},
137 {"/serialNumber", serialnumber
},
138 {"/UDN", uuidvalue
}, /* required */
139 {"/dlna:X_DLNADOC xmlns:dlna=\"urn:schemas-dlna-org:device-1-0\"", "DMS-1.50"},
140 {"/presentationURL", presentationurl
}, /* recommended */
141 {"iconList", INITHELPER((19),4)},
142 {"serviceList", INITHELPER((43),3)},
143 {"icon", INITHELPER((23),5)},
144 {"icon", INITHELPER((28),5)},
145 {"icon", INITHELPER((33),5)},
146 {"icon", INITHELPER((38),5)},
147 {"/mimetype", "image/png"},
151 {"/url", "/icons/sm.png"},
152 {"/mimetype", "image/png"},
156 {"/url", "/icons/lrg.png"},
157 {"/mimetype", "image/jpeg"},
161 {"/url", "/icons/sm.jpg"},
162 {"/mimetype", "image/jpeg"},
166 {"/url", "/icons/lrg.jpg"},
167 {"service", INITHELPER((46),5)},
168 {"service", INITHELPER((51),5)},
169 {"service", INITHELPER((56),5)},
170 {"/serviceType", "urn:schemas-upnp-org:service:ContentDirectory:1"},
171 {"/serviceId", "urn:upnp-org:serviceId:ContentDirectory"},
172 {"/controlURL", CONTENTDIRECTORY_CONTROLURL
},
173 {"/eventSubURL", CONTENTDIRECTORY_EVENTURL
},
174 {"/SCPDURL", CONTENTDIRECTORY_PATH
},
175 {"/serviceType", "urn:schemas-upnp-org:service:ConnectionManager:1"},
176 {"/serviceId", "urn:upnp-org:serviceId:ConnectionManager"},
177 {"/controlURL", CONNECTIONMGR_CONTROLURL
},
178 {"/eventSubURL", CONNECTIONMGR_EVENTURL
},
179 {"/SCPDURL", CONNECTIONMGR_PATH
},
180 {"/serviceType", "urn:microsoft.com:service:X_MS_MediaReceiverRegistrar:1"},
181 {"/serviceId", "urn:microsoft.com:serviceId:X_MS_MediaReceiverRegistrar"},
182 {"/controlURL", X_MS_MEDIARECEIVERREGISTRAR_CONTROLURL
},
183 {"/eventSubURL", X_MS_MEDIARECEIVERREGISTRAR_EVENTURL
},
184 {"/SCPDURL", X_MS_MEDIARECEIVERREGISTRAR_PATH
},
188 /* For ConnectionManager */
189 static const struct argument GetProtocolInfoArgs
[] =
196 static const struct argument GetCurrentConnectionIDsArgs
[] =
198 {"ConnectionIDs", 2, 2},
202 static const struct argument GetCurrentConnectionInfoArgs
[] =
204 {"ConnectionID", 1, 7},
206 {"AVTransportID", 2, 8},
207 {"ProtocolInfo", 2, 6},
208 {"PeerConnectionManager", 2, 4},
209 {"PeerConnectionID", 2, 7},
215 static const struct action ConnectionManagerActions
[] =
217 {"GetProtocolInfo", GetProtocolInfoArgs
}, /* R */
218 {"GetCurrentConnectionIDs", GetCurrentConnectionIDsArgs
}, /* R */
219 {"GetCurrentConnectionInfo", GetCurrentConnectionInfoArgs
}, /* R */
223 static const struct stateVar ConnectionManagerVars
[] =
225 {"SourceProtocolInfo", 0|EVENTED
, 0, 0, 44}, /* required */
226 {"SinkProtocolInfo", 0|EVENTED
, 0, 0, 48}, /* required */
227 {"CurrentConnectionIDs", 0|EVENTED
, 0, 0, 46}, /* required */
228 {"A_ARG_TYPE_ConnectionStatus", 0, 0, 27}, /* required */
229 {"A_ARG_TYPE_ConnectionManager", 0, 0}, /* required */
230 {"A_ARG_TYPE_Direction", 0, 0, 33}, /* required */
231 {"A_ARG_TYPE_ProtocolInfo", 0, 0}, /* required */
232 {"A_ARG_TYPE_ConnectionID", 4, 0}, /* required */
233 {"A_ARG_TYPE_AVTransportID", 4, 0}, /* required */
234 {"A_ARG_TYPE_RcsID", 4, 0}, /* required */
238 static const struct argument GetSearchCapabilitiesArgs
[] =
240 {"SearchCaps", 2, 11},
244 static const struct argument GetSortCapabilitiesArgs
[] =
250 static const struct argument GetSystemUpdateIDArgs
[] =
256 static const struct argument UpdateObjectArgs
[] =
259 {"CurrentTagValue", 1, 10},
260 {"NewTagValue", 1, 10},
264 static const struct argument BrowseArgs
[] =
267 {"BrowseFlag", 1, 4},
269 {"StartingIndex", 1, 7},
270 {"RequestedCount", 1, 8},
271 {"SortCriteria", 1, 6},
273 {"NumberReturned", 2, 8},
274 {"TotalMatches", 2, 8},
279 static const struct argument SearchArgs
[] =
281 {"ContainerID", 1, 1},
282 {"SearchCriteria", 1, 3},
284 {"StartingIndex", 1, 7},
285 {"RequestedCount", 1, 8},
286 {"SortCriteria", 1, 6},
288 {"NumberReturned", 2, 8},
289 {"TotalMatches", 2, 8},
294 static const struct action ContentDirectoryActions
[] =
296 {"GetSearchCapabilities", GetSearchCapabilitiesArgs
}, /* R */
297 {"GetSortCapabilities", GetSortCapabilitiesArgs
}, /* R */
298 {"GetSystemUpdateID", GetSystemUpdateIDArgs
}, /* R */
299 {"Browse", BrowseArgs
}, /* R */
300 {"Search", SearchArgs
}, /* O */
301 {"UpdateObject", UpdateObjectArgs
}, /* O */
302 #if 0 // Not implementing optional features yet...
303 {"CreateObject", CreateObjectArgs
}, /* O */
304 {"DestroyObject", DestroyObjectArgs
}, /* O */
305 {"ImportResource", ImportResourceArgs
}, /* O */
306 {"ExportResource", ExportResourceArgs
}, /* O */
307 {"StopTransferResource", StopTransferResourceArgs
}, /* O */
308 {"GetTransferProgress", GetTransferProgressArgs
}, /* O */
309 {"DeleteResource", DeleteResourceArgs
}, /* O */
310 {"CreateReference", CreateReferenceArgs
}, /* O */
315 static const struct stateVar ContentDirectoryVars
[] =
317 {"TransferIDs", 0|EVENTED
, 0, 0, 48}, /* 0 */
318 {"A_ARG_TYPE_ObjectID", 0, 0},
319 {"A_ARG_TYPE_Result", 0, 0},
320 {"A_ARG_TYPE_SearchCriteria", 0, 0},
321 {"A_ARG_TYPE_BrowseFlag", 0, 0, 36},
322 /* Allowed Values : BrowseMetadata / BrowseDirectChildren */
323 {"A_ARG_TYPE_Filter", 0, 0}, /* 5 */
324 {"A_ARG_TYPE_SortCriteria", 0, 0},
325 {"A_ARG_TYPE_Index", 3, 0},
326 {"A_ARG_TYPE_Count", 3, 0},
327 {"A_ARG_TYPE_UpdateID", 3, 0},
328 {"A_ARG_TYPE_TagValueList", 0, 0},
329 {"SearchCapabilities", 0, 0},
330 {"SortCapabilities", 0, 0},
331 {"SystemUpdateID", 3|EVENTED
, 0, 0, 255},
335 static const struct argument GetIsAuthorizedArgs
[] =
342 static const struct argument GetIsValidatedArgs
[] =
349 static const struct argument GetRegisterDeviceArgs
[] =
351 {"RegistrationReqMsg", 1, 1},
352 {"RegistrationRespMsg", 2, 2},
356 static const struct action X_MS_MediaReceiverRegistrarActions
[] =
358 {"IsAuthorized", GetIsAuthorizedArgs
}, /* R */
359 {"IsValidated", GetIsValidatedArgs
}, /* R */
360 {"RegisterDevice", GetRegisterDeviceArgs
},
364 static const struct stateVar X_MS_MediaReceiverRegistrarVars
[] =
366 {"A_ARG_TYPE_DeviceID", 0, 0},
367 {"A_ARG_TYPE_RegistrationReqMsg", 7, 0},
368 {"A_ARG_TYPE_RegistrationRespMsg", 7, 0},
369 {"A_ARG_TYPE_Result", 6, 0},
370 {"AuthorizationDeniedUpdateID", 3|EVENTED
, 0},
371 {"AuthorizationGrantedUpdateID", 3|EVENTED
, 0},
372 {"ValidationRevokedUpdateID", 3|EVENTED
, 0},
373 {"ValidationSucceededUpdateID", 3|EVENTED
, 0},
377 static const struct serviceDesc scpdContentDirectory
=
378 { ContentDirectoryActions
, ContentDirectoryVars
};
380 static const struct serviceDesc scpdConnectionManager
=
381 { ConnectionManagerActions
, ConnectionManagerVars
};
383 static const struct serviceDesc scpdX_MS_MediaReceiverRegistrar
=
384 { X_MS_MediaReceiverRegistrarActions
, X_MS_MediaReceiverRegistrarVars
};
387 * concatenate the string and use realloc to increase the
388 * memory buffer if needed. */
390 strcat_str(char * str
, int * len
, int * tmplen
, const char * s2
)
394 s2len
= (int)strlen(s2
);
395 if(*tmplen
<= (*len
+ s2len
))
400 *tmplen
+= s2len
+ 1;
401 p
= realloc(str
, *tmplen
);
407 *tmplen
-= s2len
+ 1;
413 /*strcpy(str + *len, s2); */
414 memcpy(str
+ *len
, s2
, s2len
+ 1);
420 * concatenate a character and use realloc to increase the
421 * size of the memory buffer if needed */
423 strcat_char(char * str
, int * len
, int * tmplen
, char c
)
426 if(*tmplen
<= (*len
+ 1))
429 p
= (char *)realloc(str
, *tmplen
);
443 /* iterative subroutine using a small stack
444 * This way, the progam stack usage is kept low */
446 genXML(char * str
, int * len
, int * tmplen
,
447 const struct XMLElt
* p
)
451 const char * eltname
, *s
;
457 const char * eltname
;
458 } pile
[16]; /* stack */
460 i
= 0; /* current node */
461 j
= 1; /* i + number of nodes*/
464 eltname
= p
[i
].eltname
;
467 if(eltname
[0] == '/')
470 printf("DBG: <%s>%s<%s>\n", eltname
+1, p
[i
].data
, eltname
);
472 str
= strcat_char(str
, len
, tmplen
, '<');
473 str
= strcat_str(str
, len
, tmplen
, eltname
+1);
474 str
= strcat_char(str
, len
, tmplen
, '>');
475 str
= strcat_str(str
, len
, tmplen
, p
[i
].data
);
476 str
= strcat_char(str
, len
, tmplen
, '<');
477 sscanf(eltname
, "%s", element
);
478 str
= strcat_str(str
, len
, tmplen
, element
);
479 str
= strcat_char(str
, len
, tmplen
, '>');
487 printf("DBG: pile[%d]\t%d %d\n", top
, i
, j
);
492 printf("DBG: i==j, </%s>\n", pile
[top
].eltname
);
494 str
= strcat_char(str
, len
, tmplen
, '<');
495 str
= strcat_char(str
, len
, tmplen
, '/');
496 s
= pile
[top
].eltname
;
497 for(c
= *s
; c
> ' '; c
= *(++s
))
498 str
= strcat_char(str
, len
, tmplen
, c
);
499 str
= strcat_char(str
, len
, tmplen
, '>');
509 printf("DBG: [%d] <%s>\n", i
, eltname
);
511 str
= strcat_char(str
, len
, tmplen
, '<');
512 str
= strcat_str(str
, len
, tmplen
, eltname
);
513 str
= strcat_char(str
, len
, tmplen
, '>');
516 /*j = i + p[k].nchild; */
517 i
= (unsigned long)p
[k
].data
& 0xffff;
518 j
= i
+ ((unsigned long)p
[k
].data
>> 16);
521 printf("DBG: +pile[%d]\t%d %d\n", top
, i
, j
);
525 pile
[top
].eltname
= eltname
;
531 * - Generate the root description of the UPnP device.
532 * - the len argument is used to return the length of
533 * the returned string.
534 * - tmp_uuid argument is used to build the uuid string */
536 genRootDesc(int * len
)
541 str
= (char *)malloc(tmplen
);
544 * len
= strlen(xmlver
);
545 memcpy(str
, xmlver
, *len
+ 1);
546 str
= genXML(str
, len
, &tmplen
, rootDesc
);
552 genRootDescSamsung(int * len
)
556 struct XMLElt samsungRootDesc
[sizeof(rootDesc
)/sizeof(struct XMLElt
)];
558 str
= (char *)malloc(tmplen
);
561 * len
= strlen(xmlver
);
562 memcpy(str
, xmlver
, *len
+ 1);
563 /* Replace the optional modelURL and manufacturerURL fields with Samsung foo */
564 memcpy(&samsungRootDesc
, &rootDesc
, sizeof(rootDesc
));
565 samsungRootDesc
[8].eltname
= "/sec:ProductCap";
566 samsungRootDesc
[8].data
= "smi,DCM10,getMediaInfo.sec,getCaptionInfo.sec";
567 samsungRootDesc
[12].eltname
= "/sec:X_ProductCap";
568 samsungRootDesc
[12].data
= "smi,DCM10,getMediaInfo.sec,getCaptionInfo.sec";
569 str
= genXML(str
, len
, &tmplen
, samsungRootDesc
);
574 /* genServiceDesc() :
575 * Generate service description with allowed methods and
576 * related variables. */
578 genServiceDesc(int * len
, const struct serviceDesc
* s
)
581 const struct action
* acts
;
582 const struct stateVar
* vars
;
583 const struct argument
* args
;
588 str
= (char *)malloc(tmplen
);
591 /*strcpy(str, xmlver); */
592 *len
= strlen(xmlver
);
593 memcpy(str
, xmlver
, *len
+ 1);
595 acts
= s
->actionList
;
596 vars
= s
->serviceStateTable
;
598 str
= strcat_char(str
, len
, &tmplen
, '<');
599 str
= strcat_str(str
, len
, &tmplen
, root_service
);
600 str
= strcat_char(str
, len
, &tmplen
, '>');
602 str
= strcat_str(str
, len
, &tmplen
,
603 "<specVersion><major>1</major><minor>0</minor></specVersion>");
606 str
= strcat_str(str
, len
, &tmplen
, "<actionList>");
609 str
= strcat_str(str
, len
, &tmplen
, "<action><name>");
610 str
= strcat_str(str
, len
, &tmplen
, acts
[i
].name
);
611 str
= strcat_str(str
, len
, &tmplen
, "</name>");
616 str
= strcat_str(str
, len
, &tmplen
, "<argumentList>");
620 str
= strcat_str(str
, len
, &tmplen
, "<argument><name>");
621 p
= vars
[args
[j
].relatedVar
].name
;
622 str
= strcat_str(str
, len
, &tmplen
, (args
[j
].name
? args
[j
].name
: p
));
623 str
= strcat_str(str
, len
, &tmplen
, "</name><direction>");
624 str
= strcat_str(str
, len
, &tmplen
, args
[j
].dir
==1?"in":"out");
625 str
= strcat_str(str
, len
, &tmplen
,
626 "</direction><relatedStateVariable>");
627 str
= strcat_str(str
, len
, &tmplen
, p
);
628 str
= strcat_str(str
, len
, &tmplen
,
629 "</relatedStateVariable></argument>");
632 str
= strcat_str(str
, len
, &tmplen
,"</argumentList>");
634 str
= strcat_str(str
, len
, &tmplen
, "</action>");
635 /*str = strcat_char(str, len, &tmplen, '\n'); // TEMP ! */
638 str
= strcat_str(str
, len
, &tmplen
, "</actionList><serviceStateTable>");
642 str
= strcat_str(str
, len
, &tmplen
,
643 "<stateVariable sendEvents=\"");
644 str
= strcat_str(str
, len
, &tmplen
, (vars
[i
].itype
& EVENTED
)?"yes":"no");
645 str
= strcat_str(str
, len
, &tmplen
, "\"><name>");
646 str
= strcat_str(str
, len
, &tmplen
, vars
[i
].name
);
647 str
= strcat_str(str
, len
, &tmplen
, "</name><dataType>");
648 str
= strcat_str(str
, len
, &tmplen
, upnptypes
[vars
[i
].itype
& 0x0f]);
649 str
= strcat_str(str
, len
, &tmplen
, "</dataType>");
650 if(vars
[i
].iallowedlist
)
652 str
= strcat_str(str
, len
, &tmplen
, "<allowedValueList>");
653 for(j
=vars
[i
].iallowedlist
; upnpallowedvalues
[j
]; j
++)
655 str
= strcat_str(str
, len
, &tmplen
, "<allowedValue>");
656 str
= strcat_str(str
, len
, &tmplen
, upnpallowedvalues
[j
]);
657 str
= strcat_str(str
, len
, &tmplen
, "</allowedValue>");
659 str
= strcat_str(str
, len
, &tmplen
, "</allowedValueList>");
661 /*if(vars[i].defaultValue) */
664 str
= strcat_str(str
, len
, &tmplen
, "<defaultValue>");
665 /*str = strcat_str(str, len, &tmplen, vars[i].defaultValue); */
666 str
= strcat_str(str
, len
, &tmplen
, upnpdefaultvalues
[vars
[i
].idefault
]);
667 str
= strcat_str(str
, len
, &tmplen
, "</defaultValue>");
669 str
= strcat_str(str
, len
, &tmplen
, "</stateVariable>");
670 /*str = strcat_char(str, len, &tmplen, '\n'); // TEMP ! */
673 str
= strcat_str(str
, len
, &tmplen
, "</serviceStateTable></scpd>");
678 /* genContentDirectory() :
679 * Generate the ContentDirectory xml description */
681 genContentDirectory(int * len
)
683 return genServiceDesc(len
, &scpdContentDirectory
);
686 /* genConnectionManager() :
687 * Generate the ConnectionManager xml description */
689 genConnectionManager(int * len
)
691 return genServiceDesc(len
, &scpdConnectionManager
);
694 /* genX_MS_MediaReceiverRegistrar() :
695 * Generate the X_MS_MediaReceiverRegistrar xml description */
697 genX_MS_MediaReceiverRegistrar(int * len
)
699 return genServiceDesc(len
, &scpdX_MS_MediaReceiverRegistrar
);
703 genEventVars(int * len
, const struct serviceDesc
* s
, const char * servns
)
705 const struct stateVar
* v
;
710 str
= (char *)malloc(tmplen
);
714 v
= s
->serviceStateTable
;
715 snprintf(buf
, sizeof(buf
), "<e:propertyset xmlns:e=\"urn:schemas-upnp-org:event-1-0\" xmlns:s=\"%s\">", servns
);
716 str
= strcat_str(str
, len
, &tmplen
, buf
);
718 if(v
->itype
& EVENTED
) {
719 snprintf(buf
, sizeof(buf
), "<e:property><%s>", v
->name
);
720 str
= strcat_str(str
, len
, &tmplen
, buf
);
721 //printf("<e:property><s:%s>", v->name);
722 switch(v
->ieventvalue
) {
725 case 255: /* Magical values should go around here */
726 if( strcmp(v
->name
, "SystemUpdateID") == 0 )
728 snprintf(buf
, sizeof(buf
), "%d", updateID
);
729 str
= strcat_str(str
, len
, &tmplen
, buf
);
733 str
= strcat_str(str
, len
, &tmplen
, upnpallowedvalues
[v
->ieventvalue
]);
734 //printf("%s", upnpallowedvalues[v->ieventvalue]);
736 snprintf(buf
, sizeof(buf
), "</%s></e:property>", v
->name
);
737 str
= strcat_str(str
, len
, &tmplen
, buf
);
738 //printf("</s:%s></e:property>\n", v->name);
742 str
= strcat_str(str
, len
, &tmplen
, "</e:propertyset>");
743 //printf("</e:propertyset>\n");
745 //printf("%d\n", tmplen);
751 getVarsContentDirectory(int * l
)
753 return genEventVars(l
,
754 &scpdContentDirectory
,
755 "urn:schemas-upnp-org:service:ContentDirectory:1");
759 getVarsConnectionManager(int * l
)
761 return genEventVars(l
,
762 &scpdConnectionManager
,
763 "urn:schemas-upnp-org:service:ConnectionManager:1");
767 getVarsX_MS_MediaReceiverRegistrar(int * l
)
769 return genEventVars(l
,
770 &scpdX_MS_MediaReceiverRegistrar
,
771 "urn:microsoft.com:service:X_MS_MediaReceiverRegistrar:1");