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], "%s>", 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]), "%s>", 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;
}