// Place the code and data below here into the LIBXDAAP section. #ifndef __GNUC__ #pragma code_seg( "LIBXDAAP_TEXT" ) #pragma data_seg( "LIBXDAAP_DATA" ) #pragma bss_seg( "LIBXDAAP_BSS" ) #pragma const_seg( "LIBXDAAP_RD" ) #endif /* daap * * core protocol * * Copyright (c) 2004 David Hammerton * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * */ #include "portability.h" #include #include #include #include "libXDAAP.h" #include "debug.h" #include "daap.h" #include "daap_contentcodes.h" #include "dmap_generics.h" #define DEFAULT_DEBUG_CHANNEL "daap" #include "daap_readtypes.h" #define UNHANDLED_CONTENT_CODE \ do { ERR("unhandled content code [%c%c%c%c]\n", \ SPLITFOURCC(code) \ ); } while(0) static int dmap_initilized = 0; #define IMPLEMENT_GENERICTOTYPE(_type, umember) \ dmap_DataTypes dmap_genericTo##_type(dmap_GenericType in, DMAP_##_type *out) \ { \ if (in.type != DMAP_DATATYPE_##_type) return in.type; \ *out = in.u.umember; \ return in.type; \ } IMPLEMENT_GENERICTOTYPE(INT8, int8) IMPLEMENT_GENERICTOTYPE(UINT8, uint8) IMPLEMENT_GENERICTOTYPE(INT16, int16) IMPLEMENT_GENERICTOTYPE(UINT16, uint16) IMPLEMENT_GENERICTOTYPE(INT32, int32) IMPLEMENT_GENERICTOTYPE(UINT32, uint32) IMPLEMENT_GENERICTOTYPE(INT64, int64) IMPLEMENT_GENERICTOTYPE(UINT64, uint64) IMPLEMENT_GENERICTOTYPE(STRING, string) IMPLEMENT_GENERICTOTYPE(TIME, time) IMPLEMENT_GENERICTOTYPE(VERSION, version) #undef IMPLEMENT_GENERICTOTYPE /* FIXME - replace with dictionary implementation */ dmap_ContentCode_table dmap_table = { "dmap", NULL }; dmap_ContentCode_table daap_table = { "daap", NULL }; dmap_ContentCode_table com_table = { "com", NULL }; const dmap_ContentCode *dmap_lookupCode(const dmap_ContentCode_table *table, const char *name) { dmap_ContentCodeContainer *cur = table->codes; while (cur) { if (strcmp(cur->c.cc_name, name) == 0) return &cur->c; cur = cur->next; } return NULL; } const dmap_ContentCode *dmap_lookupCodeFromFOURCC(const dmap_ContentCode_table *table, const dmap_contentCodeFOURCC fourcc) { dmap_ContentCodeContainer *cur = table->codes; while (cur) { if (cur->c.cc_number == fourcc) return &cur->c; cur = cur->next; } return NULL; } void dmap_addCode(dmap_ContentCode_table *table, const char *name, const dmap_contentCodeFOURCC code, const dmap_DataTypes type) { dmap_ContentCodeContainer *newItem; const dmap_ContentCode *existing = dmap_lookupCode(table, name); if (existing) { if (existing->cc_number != code) ERR("code for existing content code differs [%s] [%c%c%c%c vs %c%c%c%c]\n", name, SPLITFOURCC(existing->cc_number), SPLITFOURCC(code)); if (existing->cc_type != type) ERR("type for existing content code differs [%s] [%i vs %i]\n", name, existing->cc_type, type); return; } newItem = (dmap_ContentCodeContainer *) malloc(sizeof(dmap_ContentCodeContainer) + strlen(name) + 1); newItem->c.cc_number = code; newItem->c.cc_name = (char*)newItem + sizeof(dmap_ContentCodeContainer); strcpy(newItem->c.cc_name, name); newItem->c.cc_type = type; newItem->next = table->codes; table->codes = newItem; } static const char *getTypeString(dmap_DataTypes type) { switch (type) { case DMAP_DATATYPE_INVALID: return "DMAP_DATATYPE_INVALID\n"; case DMAP_DATATYPE_INT8: return "DMAP_DATATYPE_INT8"; case DMAP_DATATYPE_UINT8: return "DMAP_DATATYPE_UINT8"; case DMAP_DATATYPE_INT16: return "DMAP_DATATYPE_INT16"; case DMAP_DATATYPE_UINT16: return "DMAP_DATATYPE_UINT16"; case DMAP_DATATYPE_INT32: return "DMAP_DATATYPE_INT32"; case DMAP_DATATYPE_UINT32: return "DMAP_DATATYPE_UINT32"; case DMAP_DATATYPE_UINT64: return "DMAP_DATATYPE_UINT64"; case DMAP_DATATYPE_INT64: return "DMAP_DATATYPE_INT64"; case DMAP_DATATYPE_STRING: return "DMAP_DATATYPE_STRING"; case DMAP_DATATYPE_TIME: return "DMAP_DATATYPE_TIME"; case DMAP_DATATYPE_VERSION: return "DMAP_DATATYPE_VERSION"; case DMAP_DATATYPE_CONTAINER: return "DMAP_DATATYPE_CONTAINER"; } return "UNKNOWN_TYPE!\n"; } static void dumpContentCodes(dmap_ContentCode_table *table) { dmap_ContentCodeContainer *cur = table->codes; if (!TRACE_ON) return; while (cur) { DPRINTF("/* %c%c%c%c */\n", SPLITFOURCC(cur->c.cc_number)); DPRINTF("%s_add(\"%s\", ", table->prefix, cur->c.cc_name); DPRINTF("MAKEFOURCC('%c','%c','%c','%c'),\n", SPLITFOURCC(cur->c.cc_number)); DPRINTF(" %s);\n", getTypeString(cur->c.cc_type)); DPRINTF("\n"); cur = cur->next; } } /* END FIXME */ dmap_DataTypes dmap_isCC(const dmap_contentCodeFOURCC fourcc, const dmap_ContentCode *code) { if (!code) { ERR("unknown / unsupported content code\n"); return DMAP_DATATYPE_INVALID; } if (code->cc_number == fourcc) return code->cc_type; return DMAP_DATATYPE_INVALID; } /* utility functions */ int dmap_parseContainer(containerHandlerFunc pfnHandler, const int size, const char *buffer, void *scopeData) { int n = 0; while (n < size) { dmap_contentCodeFOURCC code; int codesize; /* we expect a content code first */ code = read_fourcc(&buffer[n], sizeof(code)); n+=4; /* then a code size */ codesize = readBigEndian_INT32(&buffer[n], sizeof(codesize)); n+=4; /* send it off to the container parser */ pfnHandler(code, codesize, &buffer[n], scopeData); n += codesize; } return 1; } /* container parsers */ /* content codes */ static void contentCodesDictionary(dmap_contentCodeFOURCC code, const int size, const char *buffer, void *scopeData) { scopeContentCodesDictionary *cd = (scopeContentCodesDictionary*)scopeData; if (dmap_isCC(code, dmap_l("contentcodesnumber")) == DMAP_DATATYPE_INT32) { dmap_contentCodeFOURCC fourcc = read_fourcc(buffer, size); cd->c.cc_number = fourcc; } else if (dmap_isCC(code, dmap_l("contentcodesname")) == DMAP_DATATYPE_STRING) { cd->c.cc_name = read_string_withalloc(buffer, size); /* freed by parent */ } else if (dmap_isCC(code, dmap_l("contentcodestype")) == DMAP_DATATYPE_INT16) { DMAP_INT16 type = readBigEndian_INT16(buffer, size); cd->c.cc_type = type; } else UNHANDLED_CONTENT_CODE; } static void contentCodesResponse(dmap_contentCodeFOURCC code, const int size, const char *buffer, void *scopeData) { if (dmap_isCC(code, dmap_l("status")) == DMAP_DATATYPE_INT32) { DMAP_INT32 status = readBigEndian_INT32(buffer, size); if (status != 200) FIXME("unknown status code %i\n", status); } else if (dmap_isCC(code, dmap_l("dictionary")) == DMAP_DATATYPE_CONTAINER) { scopeContentCodesDictionary sd; memset(&sd, 0, sizeof(sd)); dmap_parseContainer(contentCodesDictionary, size, buffer, (void*)&sd); if (sd.c.cc_name) { if (strncmp("dmap.", sd.c.cc_name, 5) == 0) { const char *name = sd.c.cc_name + 5; dmap_add(name, sd.c.cc_number, sd.c.cc_type); } else if (strncmp("daap.", sd.c.cc_name, 5) == 0) { const char *name = sd.c.cc_name + 5; daap_add(name, sd.c.cc_number, sd.c.cc_type); } else if (strncmp("com.", sd.c.cc_name, 4) == 0) { const char *name = sd.c.cc_name + 4; com_add(name, sd.c.cc_number, sd.c.cc_type); } else ERR("unknown class for content code: %s\n", sd.c.cc_name); free(sd.c.cc_name); } } else UNHANDLED_CONTENT_CODE; } /* server info container */ static void serverInfoResponse(dmap_contentCodeFOURCC code, const int size, const char *buffer, void *scopeData) { protoParseResult_serverinfo *sd = (protoParseResult_serverinfo*)scopeData; if (dmap_isCC(code, dmap_l("status")) == DMAP_DATATYPE_INT32) { DMAP_INT32 status = readBigEndian_INT32(buffer, size); if (status != 200) FIXME("unknown status code %i\n", status); } else if (dmap_isCC(code, dmap_l("protocolversion")) == DMAP_DATATYPE_VERSION) { if (sd) sd->dmap_version = read_version(buffer, size); } else if (dmap_isCC(code, daap_l("protocolversion")) == DMAP_DATATYPE_VERSION) { if (sd) sd->daap_version = read_version(buffer, size); } else if (dmap_isCC(code, dmap_l("itemname")) == DMAP_DATATYPE_STRING) { if (sd) sd->hostname = read_string_withalloc(buffer, size); } else if (dmap_isCC(code, dmap_l("authenticationmethod")) == DMAP_DATATYPE_INT8) { if (readBigEndian_INT8(buffer, size)) TRACE("requires a login\n"); } else if (dmap_isCC(code, dmap_l("loginrequired")) == DMAP_DATATYPE_INT8) { if (readBigEndian_INT8(buffer, size)) TRACE("requires a login\n"); } else if (dmap_isCC(code, dmap_l("timeoutinterval")) == DMAP_DATATYPE_INT32) { TRACE("timeout interval: %i\n", readBigEndian_INT32(buffer, size)); } else if (dmap_isCC(code, dmap_l("supportsautologout")) == DMAP_DATATYPE_INT8) { } else if (dmap_isCC(code, dmap_l("supportsupdate")) == DMAP_DATATYPE_INT8) { } else if (dmap_isCC(code, dmap_l("supportspersistentids")) == DMAP_DATATYPE_INT8) { } else if (dmap_isCC(code, dmap_l("supportsextensions")) == DMAP_DATATYPE_INT8) { } else if (dmap_isCC(code, dmap_l("supportsbrowse")) == DMAP_DATATYPE_INT8) { } else if (dmap_isCC(code, dmap_l("supportsquery")) == DMAP_DATATYPE_INT8) { } else if (dmap_isCC(code, dmap_l("supportsindex")) == DMAP_DATATYPE_INT8) { } else if (dmap_isCC(code, dmap_l("supportsresolve")) == DMAP_DATATYPE_INT8) { } else if (dmap_isCC(code, dmap_l("databasescount")) == DMAP_DATATYPE_INT32) { if (sd) sd->databasescount = readBigEndian_INT32(buffer, size); } else UNHANDLED_CONTENT_CODE; } /* login container */ static void loginResponse(dmap_contentCodeFOURCC code, const int size, const char *buffer, void *scopeData) { protoParseResult_login *sd = (protoParseResult_login*)scopeData; if (dmap_isCC(code, dmap_l("status")) == DMAP_DATATYPE_INT32) { DMAP_INT32 status = readBigEndian_INT32(buffer, size); if (status != 200) FIXME("unknown status code %i\n", status); } else if (dmap_isCC(code, dmap_l("sessionid")) == DMAP_DATATYPE_INT32) { sd->sessionid = readBigEndian_INT32(buffer, size); } else UNHANDLED_CONTENT_CODE; } /* update container */ static void updateResponse(dmap_contentCodeFOURCC code, const int size, const char *buffer, void *scopeData) { protoParseResult_update *sd = (protoParseResult_update*)scopeData; if (dmap_isCC(code, dmap_l("status")) == DMAP_DATATYPE_INT32) { DMAP_INT32 status = readBigEndian_INT32(buffer, size); if (status != 200) FIXME("unknown status code %i\n", status); } else if (dmap_isCC(code, dmap_l("serverrevision")) == DMAP_DATATYPE_INT32) { sd->serverrevision = readBigEndian_INT32(buffer, size); } else UNHANDLED_CONTENT_CODE; } /* main container */ static void toplevelResponse(dmap_contentCodeFOURCC code, const int size, const char *buffer, void *scopeData) { protoParseResult *sd = (protoParseResult*)scopeData; if (dmap_isCC(code, dmap_l("serverinforesponse")) == DMAP_DATATYPE_CONTAINER) { if (sd && sd->expecting == QUERY_SERVERINFORESPONSE) dmap_parseContainer(serverInfoResponse, size, buffer, sd); } else if (dmap_isCC(code, dmap_l("contentcodesresponse")) == DMAP_DATATYPE_CONTAINER) { dmap_parseContainer(contentCodesResponse, size, buffer, NULL); #if 1 dumpContentCodes(&dmap_table); dumpContentCodes(&daap_table); dumpContentCodes(&com_table); #endif } else if (dmap_isCC(code, dmap_l("loginresponse")) == DMAP_DATATYPE_CONTAINER) { if (sd && sd->expecting == QUERY_LOGINRESPONSE) dmap_parseContainer(loginResponse, size, buffer, sd); } else if (dmap_isCC(code, dmap_l("updateresponse")) == DMAP_DATATYPE_CONTAINER) { if (sd && sd->expecting == QUERY_UPDATERESPONSE) dmap_parseContainer(updateResponse, size, buffer, sd); } else if (dmap_isCC(code, daap_l("serverdatabases")) == DMAP_DATATYPE_CONTAINER) { if (sd && sd->expecting == QUERY_GENERICLISTING) dmap_parseContainer(preListingContainer, size, buffer, sd); } else if (dmap_isCC(code, daap_l("databasesongs")) == DMAP_DATATYPE_CONTAINER) { if (sd && sd->expecting == QUERY_GENERICLISTING) dmap_parseContainer(preListingContainer, size, buffer, sd); } else if (dmap_isCC(code, daap_l("databaseplaylists")) == DMAP_DATATYPE_CONTAINER) { if (sd && sd->expecting == QUERY_GENERICLISTING) dmap_parseContainer(preListingContainer, size, buffer, sd); } else if (dmap_isCC(code, daap_l("playlistsongs")) == DMAP_DATATYPE_CONTAINER) { if (sd && sd->expecting == QUERY_GENERICLISTING) dmap_parseContainer(preListingContainer, size, buffer, sd); } else UNHANDLED_CONTENT_CODE; } /* we load most of the content codes using the /content-codes * request. But to get there we must first be able to * pass the /server-info response and the /content-codes response * so this is the 'boot strapper' for that */ static void dmap_ContentCodesBootstrap() { /* common */ dmap_add("status", MAKEFOURCC('m','s','t','t'), DMAP_DATATYPE_INT32); dmap_add("dictionary", MAKEFOURCC('m','d','c','l'), DMAP_DATATYPE_CONTAINER); /* server info */ dmap_add("serverinforesponse", MAKEFOURCC('m','s','r','v'), DMAP_DATATYPE_CONTAINER); dmap_add("protocolversion", MAKEFOURCC('m','p','r','o'), DMAP_DATATYPE_VERSION); daap_add("protocolversion", MAKEFOURCC('a','p','r','o'), DMAP_DATATYPE_VERSION); dmap_add("itemname", MAKEFOURCC('m','i','n','m'), DMAP_DATATYPE_STRING); dmap_add("authenticationmethod", MAKEFOURCC('m','s','a','u'), DMAP_DATATYPE_INT8); dmap_add("loginrequired", MAKEFOURCC('m','s','l','r'), DMAP_DATATYPE_INT8); dmap_add("timeoutinterval", MAKEFOURCC('m','s','t','m'), DMAP_DATATYPE_INT32); dmap_add("supportsautologout", MAKEFOURCC('m','s','a','l'), DMAP_DATATYPE_INT8); dmap_add("supportsupdate", MAKEFOURCC('m','s','u','p'), DMAP_DATATYPE_INT8); dmap_add("supportspersistentids", MAKEFOURCC('m','s','p','i'), DMAP_DATATYPE_INT8); dmap_add("supportsextensions", MAKEFOURCC('m','s','e','x'), DMAP_DATATYPE_INT8); dmap_add("supportsbrowse", MAKEFOURCC('m','s','b','r'), DMAP_DATATYPE_INT8); dmap_add("supportsquery", MAKEFOURCC('m','s','q','y'), DMAP_DATATYPE_INT8); dmap_add("supportsindex", MAKEFOURCC('m','s','i','x'), DMAP_DATATYPE_INT8); dmap_add("supportsresolve", MAKEFOURCC('m','s','r','s'), DMAP_DATATYPE_INT8); dmap_add("databasescount", MAKEFOURCC('m','s','d','c'), DMAP_DATATYPE_INT32); dmap_add("contentcodesresponse", MAKEFOURCC('m','c','c','r'), DMAP_DATATYPE_CONTAINER); dmap_add("contentcodesnumber", MAKEFOURCC('m','c','n','m'), DMAP_DATATYPE_INT32); dmap_add("contentcodesname", MAKEFOURCC('m','c','n','a'), DMAP_DATATYPE_STRING); dmap_add("contentcodestype", MAKEFOURCC('m','c','t','y'), DMAP_DATATYPE_INT16); } /* main function called to parse the thing */ void dmap_init() { if (dmap_initilized) return; dmap_ContentCodesBootstrap(); dmap_initilized = 1; } void dmap_deinit() { if (dmap_initilized) { dmap_ContentCodeContainer *newItem; while(dmap_table.codes) { newItem = dmap_table.codes->next; free(dmap_table.codes); dmap_table.codes = newItem; } dmap_initilized = 0; } return; } void dmap_parseProtocolData(const int size, const char *buffer, protoParseResult *res) { if (!dmap_initilized) { ERR("dmap_init must be called first!\n"); return; } dmap_parseContainer(toplevelResponse, size, buffer, res); }