Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions res/media_service_files/CreateProfile.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope
xmlns:SOAP-ENV="http://www.w3.org/2003/05/soap-envelope"
xmlns:trt="http://www.onvif.org/ver10/media/wsdl"
xmlns:tt="http://www.onvif.org/ver10/schema">
<SOAP-ENV:Body>
<trt:CreateProfileResponse>
<trt:Profile token="SynoProfileToken" fixed="false">
<tt:Name>SynoProfile</tt:Name>
</trt:Profile>
</trt:CreateProfileResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
42 changes: 34 additions & 8 deletions src/events_service.c
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand All @@ -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,
Expand Down Expand Up @@ -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]), "<tns1:%s", token);
snprintf(topic_le[j], sizeof(topic_le[j]), "</tns1:%s>", token);
}

token = strtok(NULL, "/");
if (token == NULL) {
Expand All @@ -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 {
Expand All @@ -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,
Expand Down
12 changes: 11 additions & 1 deletion src/media_service.c
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
21 changes: 11 additions & 10 deletions src/onvif_simple_server.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
/*
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
Expand Down