diff --git a/res/media_service_files/CreateProfile.xml b/res/media_service_files/CreateProfile.xml new file mode 100644 index 0000000..8f21c03 --- /dev/null +++ b/res/media_service_files/CreateProfile.xml @@ -0,0 +1,13 @@ + + + + + + SynoProfile + + + + diff --git a/src/events_service.c b/src/events_service.c index 5ca08f3..4ef5813 100644 --- a/src/events_service.c +++ b/src/events_service.c @@ -511,9 +511,10 @@ int events_pull_messages() if (service_ctx.events[i].topic != NULL && strcmp("tns1:Device/Trigger/Relay", service_ctx.events[i].topic) == 0) { strcpy(data_name, "LogicalState"); } else if (service_ctx.events[i].topic != NULL - && (strstr(service_ctx.events[i].topic, "VideoSource/MotionAlarm") - || strstr(service_ctx.events[i].topic, "CellMotionDetector/Motion"))) { - // Use IsMotion for motion topics to match common client expectations + && strstr(service_ctx.events[i].topic, "VideoSource/MotionAlarm")) { + strcpy(data_name, "State"); + } else if (service_ctx.events[i].topic != NULL + && strstr(service_ctx.events[i].topic, "CellMotionDetector/Motion")) { strcpy(data_name, "IsMotion"); } else { strcpy(data_name, "State"); @@ -523,6 +524,10 @@ int events_pull_messages() const char *safe_source_name = service_ctx.events[i].source_name ? service_ctx.events[i].source_name : "Unknown"; const char *safe_source_value = service_ctx.events[i].source_value ? service_ctx.events[i].source_value : "Unknown"; + if (strstr(service_ctx.events[i].topic, "VideoSource/MotionAlarm")) { + safe_source_name = "Source"; + } + size = cat(dest, "events_service_files/PullMessages_2.xml", 14, @@ -1019,8 +1024,18 @@ int events_get_event_properties() /* walk through other tokens */ for (j = 0; j < 3; j++) { - sprintf(topic_ls[j], "<%s", token); - sprintf(topic_le[j], "", token); + /* + * Topic paths commonly specify the namespace only on the + * first component, such as tns1:VideoSource/MotionAlarm. + * Keep every generated XML element in the topic namespace. + */ + if (strchr(token, ':') != NULL) { + snprintf(topic_ls[j], sizeof(topic_ls[j]), "<%s", token); + snprintf(topic_le[j], sizeof(topic_le[j]), "", token); + } else { + snprintf(topic_ls[j], sizeof(topic_ls[j]), "", token); + } token = strtok(NULL, "/"); if (token == NULL) { @@ -1040,9 +1055,15 @@ int events_get_event_properties() strcpy(data_name, "LogicalState"); strcpy(data_type, "tt:RelayLogicalState"); } else if (service_ctx.events[i].topic != NULL - && (strstr(service_ctx.events[i].topic, "VideoSource/MotionAlarm") - || strstr(service_ctx.events[i].topic, "CellMotionDetector/Motion"))) { - // Advertise IsMotion for motion topics + && strstr(service_ctx.events[i].topic, "VideoSource/MotionAlarm")) { + /* + * Advertise the standard property schema expected by + * Synology for VideoSource/MotionAlarm. + */ + strcpy(data_name, "State"); + strcpy(data_type, "xsd:boolean"); + } else if (service_ctx.events[i].topic != NULL + && strstr(service_ctx.events[i].topic, "CellMotionDetector/Motion")) { strcpy(data_name, "IsMotion"); strcpy(data_type, "xsd:boolean"); } else { @@ -1054,6 +1075,11 @@ int events_get_event_properties() const char *safe_source_name = service_ctx.events[i].source_name ? service_ctx.events[i].source_name : "Unknown"; const char *safe_source_type = service_ctx.events[i].source_type ? service_ctx.events[i].source_type : "Unknown"; + if (strstr(service_ctx.events[i].topic, "VideoSource/MotionAlarm")) { + safe_source_name = "Source"; + safe_source_type = "tt:ReferenceToken"; + } + size = cat(dest, "events_service_files/GetEventProperties_2.xml", 20, diff --git a/src/media_service.c b/src/media_service.c index ea17a2c..7cf8d18 100644 --- a/src/media_service.c +++ b/src/media_service.c @@ -2252,7 +2252,17 @@ int media_set_audio_output_configuration() int media_delete_profile() { - // All profiles are fixed — deletion is not permitted + const char *token = get_element("ProfileToken", "Body"); + + if ((service_ctx.adv_synology_nvr == 1) + && (token != NULL) + && (strcasecmp(token, "SynoProfileToken") == 0)) { + long size = cat(NULL, "media_service_files/DeleteProfile.xml", 0); + output_http_headers(size); + return cat("stdout", "media_service_files/DeleteProfile.xml", 0); + } + + // All real profiles are fixed — deletion is not permitted send_fault("media_service", "Sender", "ter:InvalidArgVal", diff --git a/src/onvif_simple_server.c b/src/onvif_simple_server.c index 8b6ee0b..3b92b47 100644 --- a/src/onvif_simple_server.c +++ b/src/onvif_simple_server.c @@ -510,7 +510,8 @@ int main(int argc, char **argv) pre_auth = 1; } if ((strcasecmp("events_service", prog_name) == 0) - && (strcasecmp("GetServiceCapabilities", method) == 0 || strcasecmp("CreatePullPointSubscription", method) == 0 + && (strcasecmp("GetServiceCapabilities", method) == 0 || strcasecmp("GetEventProperties", method) == 0 + || strcasecmp("CreatePullPointSubscription", method) == 0 || strcasecmp("PullMessages", method) == 0 || strcasecmp("Renew", method) == 0 || strcasecmp("Unsubscribe", method) == 0 || strcasecmp("SetSynchronizationPoint", method) == 0)) { /* @@ -551,11 +552,12 @@ int main(int argc, char **argv) /* Events (tev): allow common subscription lifecycle methods as PRE_AUTH * when the service is running in anonymous mode (no username configured). - * This covers CreatePullPointSubscription, PullMessages, Renew, + * This covers CreatePullPointSubscription, PullMessages, Renew, GetEventProperties, * Unsubscribe, SetSynchronizationPoint and GetServiceCapabilities. */ if ((strcasecmp("events_service", prog_name) == 0) - && (strcasecmp("GetServiceCapabilities", method) == 0 || strcasecmp("CreatePullPointSubscription", method) == 0 + && (strcasecmp("GetServiceCapabilities", method) == 0 || strcasecmp("GetEventProperties", method) == 0 + || strcasecmp("CreatePullPointSubscription", method) == 0 || strcasecmp("PullMessages", method) == 0 || strcasecmp("Renew", method) == 0 || strcasecmp("Unsubscribe", method) == 0 || strcasecmp("SetSynchronizationPoint", method) == 0)) { auth_error = 0; @@ -591,13 +593,12 @@ int main(int argc, char **argv) // Special case: Synology NVR CreateProfile bypass (before authentication) if ((service_ctx.adv_synology_nvr == 1) && (strcasecmp("media_service", prog_name) == 0) && (strcasecmp("CreateProfile", method) == 0)) { - log_debug("Synology NVR mode: bypassing authentication for CreateProfile"); - send_fault("media_service", - "Receiver", - "ter:Action", - "ter:MaxNVTProfiles", - "Max profile number reached", - "The maximum number of supported profiles supported by the device has been reached"); + log_debug("Synology NVR mode: returning synthetic CreateProfile response"); + + long size = cat(NULL, "media_service_files/CreateProfile.xml", 0); + output_http_headers(size); + cat("stdout", "media_service_files/CreateProfile.xml", 0); + close_xml(); return 0; }