diff --git a/build/staging/version/BundleInfo.wxi b/build/staging/version/BundleInfo.wxi
index 4aeb86dca..cbcd36974 100644
--- a/build/staging/version/BundleInfo.wxi
+++ b/build/staging/version/BundleInfo.wxi
@@ -1,4 +1,4 @@
-
+
diff --git a/build/staging/version/WindowsMidiServicesVersion.cs b/build/staging/version/WindowsMidiServicesVersion.cs
index 18c9b039f..5d74cd150 100644
--- a/build/staging/version/WindowsMidiServicesVersion.cs
+++ b/build/staging/version/WindowsMidiServicesVersion.cs
@@ -6,12 +6,12 @@ public static class MidiBuildInformation
{
public const string Source = "GitHub Preview";
public const string Name = "Developer Preview 9";
- public const string BuildFullVersion = "1.0.2-preview-9.250106-1428";
+ public const string BuildFullVersion = "1.0.2-preview-9.250112-1659";
public const string VersionMajor = "1";
public const string VersionMinor = "0";
public const string VersionRevision = "2";
- public const string VersionDateNumber = "250106";
- public const string VersionTimeNumber = "1428";
+ public const string VersionDateNumber = "250112";
+ public const string VersionTimeNumber = "1659";
}
}
diff --git a/build/staging/version/WindowsMidiServicesVersion.h b/build/staging/version/WindowsMidiServicesVersion.h
index 5799c747f..66f3ed85f 100644
--- a/build/staging/version/WindowsMidiServicesVersion.h
+++ b/build/staging/version/WindowsMidiServicesVersion.h
@@ -5,12 +5,12 @@
#define WINDOWS_MIDI_SERVICES_BUILD_SOURCE L"GitHub Preview"
#define WINDOWS_MIDI_SERVICES_BUILD_VERSION_NAME L"Developer Preview 9"
-#define WINDOWS_MIDI_SERVICES_BUILD_VERSION_FULL L"1.0.2-preview-9.250106-1428"
+#define WINDOWS_MIDI_SERVICES_BUILD_VERSION_FULL L"1.0.2-preview-9.250112-1659"
#define WINDOWS_MIDI_SERVICES_BUILD_VERSION_MAJOR L"1"
#define WINDOWS_MIDI_SERVICES_BUILD_VERSION_MINOR L"0"
#define WINDOWS_MIDI_SERVICES_BUILD_VERSION_REVISION L"2"
-#define WINDOWS_MIDI_SERVICES_BUILD_VERSION_DATE_NUMBER L"250106"
-#define WINDOWS_MIDI_SERVICES_BUILD_VERSION_TIME_NUMBER L"1428"
+#define WINDOWS_MIDI_SERVICES_BUILD_VERSION_DATE_NUMBER L"250112"
+#define WINDOWS_MIDI_SERVICES_BUILD_VERSION_TIME_NUMBER L"1659"
#endif
diff --git a/diagnostics/trace-logging/MidiServices.wprp b/diagnostics/trace-logging/MidiServices.wprp
index 5a02a897f..88807371c 100644
--- a/diagnostics/trace-logging/MidiServices.wprp
+++ b/diagnostics/trace-logging/MidiServices.wprp
@@ -1,41 +1,12 @@
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
@@ -65,9 +36,7 @@
-
-
-
+
diff --git a/samples/cpp-winrt/basics/client-basics-cpp.vcxproj b/samples/cpp-winrt/basics/client-basics-cpp.vcxproj
index acd4c5e92..183d64bbf 100644
--- a/samples/cpp-winrt/basics/client-basics-cpp.vcxproj
+++ b/samples/cpp-winrt/basics/client-basics-cpp.vcxproj
@@ -2,7 +2,7 @@
- Microsoft.Windows.Devices.Midi2.1.0.2-preview-9.250106-1428
+ Microsoft.Windows.Devices.Midi2.1.0.2-preview-9.250112-1659
true
true
false
diff --git a/samples/cpp-winrt/basics/packages.config b/samples/cpp-winrt/basics/packages.config
index adcdab936..1c7a38d46 100644
--- a/samples/cpp-winrt/basics/packages.config
+++ b/samples/cpp-winrt/basics/packages.config
@@ -1,5 +1,5 @@
-
+
\ No newline at end of file
diff --git a/samples/cpp-winrt/loopback-endpoints/loopback-endpoints-cpp.vcxproj b/samples/cpp-winrt/loopback-endpoints/loopback-endpoints-cpp.vcxproj
index ed42dd4ec..c264f300c 100644
--- a/samples/cpp-winrt/loopback-endpoints/loopback-endpoints-cpp.vcxproj
+++ b/samples/cpp-winrt/loopback-endpoints/loopback-endpoints-cpp.vcxproj
@@ -2,7 +2,7 @@
- Microsoft.Windows.Devices.Midi2.1.0.2-preview-9.250106-1428
+ Microsoft.Windows.Devices.Midi2.1.0.2-preview-9.250112-1659
true
true
true
diff --git a/samples/cpp-winrt/loopback-endpoints/packages.config b/samples/cpp-winrt/loopback-endpoints/packages.config
index adcdab936..1c7a38d46 100644
--- a/samples/cpp-winrt/loopback-endpoints/packages.config
+++ b/samples/cpp-winrt/loopback-endpoints/packages.config
@@ -1,5 +1,5 @@
-
+
\ No newline at end of file
diff --git a/samples/cpp-winrt/send-speed/packages.config b/samples/cpp-winrt/send-speed/packages.config
index adcdab936..1c7a38d46 100644
--- a/samples/cpp-winrt/send-speed/packages.config
+++ b/samples/cpp-winrt/send-speed/packages.config
@@ -1,5 +1,5 @@
-
+
\ No newline at end of file
diff --git a/samples/cpp-winrt/send-speed/send-speed-cpp.vcxproj b/samples/cpp-winrt/send-speed/send-speed-cpp.vcxproj
index d817f8320..2e71022df 100644
--- a/samples/cpp-winrt/send-speed/send-speed-cpp.vcxproj
+++ b/samples/cpp-winrt/send-speed/send-speed-cpp.vcxproj
@@ -2,7 +2,7 @@
- Microsoft.Windows.Devices.Midi2.1.0.2-preview-9.250106-1428
+ Microsoft.Windows.Devices.Midi2.1.0.2-preview-9.250112-1659
true
true
true
diff --git a/samples/cpp-winrt/simple-app-to-app-midi/packages.config b/samples/cpp-winrt/simple-app-to-app-midi/packages.config
index adcdab936..1c7a38d46 100644
--- a/samples/cpp-winrt/simple-app-to-app-midi/packages.config
+++ b/samples/cpp-winrt/simple-app-to-app-midi/packages.config
@@ -1,5 +1,5 @@
-
+
\ No newline at end of file
diff --git a/samples/cpp-winrt/simple-app-to-app-midi/simple-app-to-app-cpp.vcxproj b/samples/cpp-winrt/simple-app-to-app-midi/simple-app-to-app-cpp.vcxproj
index fd4d1fa97..22df94d7a 100644
--- a/samples/cpp-winrt/simple-app-to-app-midi/simple-app-to-app-cpp.vcxproj
+++ b/samples/cpp-winrt/simple-app-to-app-midi/simple-app-to-app-cpp.vcxproj
@@ -2,7 +2,7 @@
- Microsoft.Windows.Devices.Midi2.1.0.2-preview-9.250106-1428
+ Microsoft.Windows.Devices.Midi2.1.0.2-preview-9.250112-1659
true
true
true
diff --git a/samples/cpp-winrt/static-enum-endpoints/packages.config b/samples/cpp-winrt/static-enum-endpoints/packages.config
index adcdab936..1c7a38d46 100644
--- a/samples/cpp-winrt/static-enum-endpoints/packages.config
+++ b/samples/cpp-winrt/static-enum-endpoints/packages.config
@@ -1,5 +1,5 @@
-
+
\ No newline at end of file
diff --git a/samples/cpp-winrt/static-enum-endpoints/static-enum-endpoints-cpp.vcxproj b/samples/cpp-winrt/static-enum-endpoints/static-enum-endpoints-cpp.vcxproj
index d19fa4b5f..8a48d94ab 100644
--- a/samples/cpp-winrt/static-enum-endpoints/static-enum-endpoints-cpp.vcxproj
+++ b/samples/cpp-winrt/static-enum-endpoints/static-enum-endpoints-cpp.vcxproj
@@ -2,7 +2,7 @@
- Microsoft.Windows.Devices.Midi2.1.0.2-preview-9.250106-1428
+ Microsoft.Windows.Devices.Midi2.1.0.2-preview-9.250112-1659
true
true
true
diff --git a/samples/cpp-winrt/watch-endpoints/packages.config b/samples/cpp-winrt/watch-endpoints/packages.config
index adcdab936..1c7a38d46 100644
--- a/samples/cpp-winrt/watch-endpoints/packages.config
+++ b/samples/cpp-winrt/watch-endpoints/packages.config
@@ -1,5 +1,5 @@
-
+
\ No newline at end of file
diff --git a/samples/cpp-winrt/watch-endpoints/watch-endpoints-cpp.vcxproj b/samples/cpp-winrt/watch-endpoints/watch-endpoints-cpp.vcxproj
index f6f339ae7..8b0664f0e 100644
--- a/samples/cpp-winrt/watch-endpoints/watch-endpoints-cpp.vcxproj
+++ b/samples/cpp-winrt/watch-endpoints/watch-endpoints-cpp.vcxproj
@@ -2,7 +2,7 @@
- Microsoft.Windows.Devices.Midi2.1.0.2-preview-9.250106-1428
+ Microsoft.Windows.Devices.Midi2.1.0.2-preview-9.250112-1659
true
true
true
diff --git a/src/api/Service/Exe/MidiDeviceManager.cpp b/src/api/Service/Exe/MidiDeviceManager.cpp
index 4fdbde58e..dcf4c6a69 100644
--- a/src/api/Service/Exe/MidiDeviceManager.cpp
+++ b/src/api/Service/Exe/MidiDeviceManager.cpp
@@ -2259,20 +2259,20 @@ CMidiDeviceManager::UseFallbackMidi1PortDefinition(
additionalProperties.Append(winrt::to_hstring(STRING_PKEY_MIDI_FunctionBlocksAreStatic));
auto deviceInfo = DeviceInformation::CreateFromIdAsync(umpDeviceInterfaceId, additionalProperties, winrt::Windows::Devices::Enumeration::DeviceInformationKind::DeviceInterface).get();
- auto prop = deviceInfo.Properties().Lookup(winrt::to_hstring(STRING_PKEY_MIDI_FunctionBlocksAreStatic));
- if (prop)
- {
- auto functionBlocksAreStatic = winrt::unbox_value>(prop);
- if (functionBlocksAreStatic)
- {
- TraceLoggingWrite(
- MidiSrvTelemetryProvider::Provider(),
- MIDI_TRACE_EVENT_INFO,
- TraceLoggingString(__FUNCTION__, MIDI_TRACE_EVENT_LOCATION_FIELD),
- TraceLoggingLevel(WINEVENT_LEVEL_INFO),
- TraceLoggingPointer(this, "this"),
- TraceLoggingWideString(L"Using static function blocks", MIDI_TRACE_EVENT_MESSAGE_FIELD)
- );
+ //auto prop = deviceInfo.Properties().Lookup(winrt::to_hstring(STRING_PKEY_MIDI_FunctionBlocksAreStatic));
+ //if (prop)
+ //{
+ //auto functionBlocksAreStatic = winrt::unbox_value>(prop);
+ //if (functionBlocksAreStatic)
+ //{
+ // TraceLoggingWrite(
+ // MidiSrvTelemetryProvider::Provider(),
+ // MIDI_TRACE_EVENT_INFO,
+ // TraceLoggingString(__FUNCTION__, MIDI_TRACE_EVENT_LOCATION_FIELD),
+ // TraceLoggingLevel(WINEVENT_LEVEL_INFO),
+ // TraceLoggingPointer(this, "this"),
+ // TraceLoggingWideString(L"Using static function blocks", MIDI_TRACE_EVENT_MESSAGE_FIELD)
+ // );
for (UINT flow = 0; flow < 2; flow++)
{
@@ -2284,8 +2284,8 @@ CMidiDeviceManager::UseFallbackMidi1PortDefinition(
portInfo[flow][groupIndex].InterfaceId = umpDeviceInterfaceId;
}
}
- }
- }
+ //}
+ //}
return S_OK;
}
diff --git a/src/api/Transport/UdpNetworkMidi2Transport/Midi2.NetworkMidiBidi.cpp b/src/api/Transport/UdpNetworkMidi2Transport/Midi2.NetworkMidiBidi.cpp
index c47d774eb..8f44b4a85 100644
--- a/src/api/Transport/UdpNetworkMidi2Transport/Midi2.NetworkMidiBidi.cpp
+++ b/src/api/Transport/UdpNetworkMidi2Transport/Midi2.NetworkMidiBidi.cpp
@@ -43,9 +43,16 @@ CMidi2NetworkMidiBiDi::Initialize(
// look up the endpointDeviceInterfaceId in our list of endpoints, and connect to it
m_connection = TransportState::Current().GetSessionConnection(m_endpointDeviceInterfaceId);
- RETURN_HR_IF_NULL(E_INVALIDARG, m_connection);
- m_connection->SetMidiCallback(this);
+ if (auto conn = m_connection.lock())
+ {
+ RETURN_IF_FAILED(conn->ConnectMidiCallback(this));
+ }
+ else
+ {
+ RETURN_IF_FAILED(E_INVALIDARG);
+ }
+
return S_OK;
}
@@ -62,15 +69,16 @@ CMidi2NetworkMidiBiDi::Shutdown()
TraceLoggingWideString(L"Enter", MIDI_TRACE_EVENT_MESSAGE_FIELD)
);
- m_callback = nullptr;
- m_context = 0;
+ // TODO: This causes the service to crash when the remote network endpoint disconnects. Need to look into this further.
- if (m_connection)
+ if (auto ptr = m_connection.lock())
{
- m_connection->RemoveMidiCallback();
+ ptr->DisconnectMidiCallback();
m_connection.reset();
}
-
+
+ m_callback = nullptr;
+ m_context = 0;
return S_OK;
}
@@ -88,9 +96,10 @@ CMidi2NetworkMidiBiDi::SendMidiMessage(
RETURN_HR_IF_NULL(E_INVALIDARG, message);
RETURN_HR_IF(E_INVALIDARG, size < sizeof(uint32_t));
- RETURN_HR_IF_NULL(E_UNEXPECTED, m_connection);
-
- RETURN_IF_FAILED(m_connection->SendMidiMessagesToNetwork(message, size));
+ if (auto conn = m_connection.lock())
+ {
+ RETURN_IF_FAILED(conn->SendMidiMessagesToNetwork(message, size));
+ }
return S_OK;
}
diff --git a/src/api/Transport/UdpNetworkMidi2Transport/Midi2.NetworkMidiBidi.h b/src/api/Transport/UdpNetworkMidi2Transport/Midi2.NetworkMidiBidi.h
index c3bcb6733..8b9dab1d7 100644
--- a/src/api/Transport/UdpNetworkMidi2Transport/Midi2.NetworkMidiBidi.h
+++ b/src/api/Transport/UdpNetworkMidi2Transport/Midi2.NetworkMidiBidi.h
@@ -22,12 +22,12 @@ class CMidi2NetworkMidiBiDi :
STDMETHOD(Shutdown)();
private:
- IMidiCallback* m_callback{ nullptr };
+ wil::com_ptr_nothrow m_callback{ nullptr };
LONGLONG m_context{ 0 };
std::wstring m_endpointDeviceInterfaceId{ };
- std::shared_ptr m_connection{ nullptr };
+ std::weak_ptr m_connection;
};
diff --git a/src/api/Transport/UdpNetworkMidi2Transport/Midi2.NetworkMidiConfigurationManager.cpp b/src/api/Transport/UdpNetworkMidi2Transport/Midi2.NetworkMidiConfigurationManager.cpp
index bf8a06ffa..9811715c5 100644
--- a/src/api/Transport/UdpNetworkMidi2Transport/Midi2.NetworkMidiConfigurationManager.cpp
+++ b/src/api/Transport/UdpNetworkMidi2Transport/Midi2.NetworkMidiConfigurationManager.cpp
@@ -298,7 +298,9 @@ CMidi2NetworkMidiConfigurationManager::UpdateConfiguration(
{
auto hostEntry = hostsSection.GetNamedObject(it.Current().Key());
- MidiNetworkHostDefinition definition{ };
+ auto definition = std::make_shared();
+ RETURN_IF_NULL_ALLOC(definition);
+
winrt::hstring validationErrorMessage{ };
// currently, UDP is the only allowed protocol
@@ -309,18 +311,20 @@ CMidi2NetworkMidiConfigurationManager::UpdateConfiguration(
validationErrorMessage = L"Invalid network protocol '" + protocol + L"' specified.";
}
- definition.EntryIdentifier = internal::TrimmedHStringCopy(it.Current().Key());
+ definition->EntryIdentifier = internal::TrimmedHStringCopy(it.Current().Key());
+
+ definition->Enabled = hostEntry.GetNamedBoolean(MIDI_CONFIG_JSON_NETWORK_MIDI_ENABLED_KEY, true);
+ definition->Advertise = hostEntry.GetNamedBoolean(MIDI_CONFIG_JSON_NETWORK_MIDI_MDNS_ADVERTISE_KEY, true);
- definition.Enabled = hostEntry.GetNamedBoolean(MIDI_CONFIG_JSON_NETWORK_MIDI_ENABLED_KEY, true);
- definition.Advertise = hostEntry.GetNamedBoolean(MIDI_CONFIG_JSON_NETWORK_MIDI_MDNS_ADVERTISE_KEY, true);
+ definition->CreateMidi1Ports = hostEntry.GetNamedBoolean(MIDI_CONFIG_JSON_NETWORK_MIDI_CREATE_MIDI1_PORTS_KEY, MIDI_NETWORK_MIDI_CREATE_MIDI1_PORTS_DEFAULT);
- definition.UmpEndpointName = internal::TrimmedHStringCopy(hostEntry.GetNamedString(MIDI_CONFIG_JSON_ENDPOINT_COMMON_NAME_PROPERTY, L""));
- definition.ProductInstanceId = internal::TrimmedHStringCopy(hostEntry.GetNamedString(MIDI_CONFIG_JSON_NETWORK_MIDI_PRODUCT_INSTANCE_ID_PROPERTY, L""));
+ definition->UmpEndpointName = internal::TrimmedHStringCopy(hostEntry.GetNamedString(MIDI_CONFIG_JSON_ENDPOINT_COMMON_NAME_PROPERTY, L""));
+ definition->ProductInstanceId = internal::TrimmedHStringCopy(hostEntry.GetNamedString(MIDI_CONFIG_JSON_NETWORK_MIDI_PRODUCT_INSTANCE_ID_PROPERTY, L""));
- definition.Port = internal::TrimmedHStringCopy(hostEntry.GetNamedString(MIDI_CONFIG_JSON_NETWORK_MIDI_NETWORK_PORT_KEY, MIDI_CONFIG_JSON_NETWORK_MIDI_NETWORK_PORT_VALUE_AUTO));
+ definition->Port = internal::TrimmedHStringCopy(hostEntry.GetNamedString(MIDI_CONFIG_JSON_NETWORK_MIDI_NETWORK_PORT_KEY, MIDI_CONFIG_JSON_NETWORK_MIDI_NETWORK_PORT_VALUE_AUTO));
- definition.Authentication = MidiNetworkHostAuthenticationFromJsonString(hostEntry.GetNamedString(MIDI_CONFIG_JSON_NETWORK_MIDI_HOST_AUTHENTICATION_KEY, MIDI_CONFIG_JSON_NETWORK_MIDI_HOST_AUTHENTICATION_VALUE_NONE));
- definition.ConnectionPolicy = MidiNetworkHostConnectionPolicyFromJsonString(hostEntry.GetNamedString(MIDI_CONFIG_JSON_NETWORK_MIDI_CONNECTION_POLICY_KEY, MIDI_CONFIG_JSON_NETWORK_MIDI_CONNECTION_POLICY_ALLOW_IPV4_VALUE_ANY));
+ definition->Authentication = MidiNetworkHostAuthenticationFromJsonString(hostEntry.GetNamedString(MIDI_CONFIG_JSON_NETWORK_MIDI_HOST_AUTHENTICATION_KEY, MIDI_CONFIG_JSON_NETWORK_MIDI_HOST_AUTHENTICATION_VALUE_NONE));
+ definition->ConnectionPolicy = MidiNetworkHostConnectionPolicyFromJsonString(hostEntry.GetNamedString(MIDI_CONFIG_JSON_NETWORK_MIDI_CONNECTION_POLICY_KEY, MIDI_CONFIG_JSON_NETWORK_MIDI_CONNECTION_POLICY_ALLOW_IPV4_VALUE_ANY));
// read the list of ip info
//if (definition.ConnectionPolicy != MidiNetworkHostConnectionPolicy::PolicyAllowAllConnections)
@@ -338,14 +342,14 @@ CMidi2NetworkMidiConfigurationManager::UpdateConfiguration(
//}
// read authentication information
- if (definition.Authentication != MidiNetworkHostAuthentication::NoAuthentication)
+ if (definition->Authentication != MidiNetworkHostAuthentication::NoAuthentication)
{
- if (definition.Authentication == MidiNetworkHostAuthentication::PasswordAuthentication)
+ if (definition->Authentication == MidiNetworkHostAuthentication::PasswordAuthentication)
{
// TODO: Read the password vault key
}
- else if (definition.Authentication == MidiNetworkHostAuthentication::UserAuthentication)
+ else if (definition->Authentication == MidiNetworkHostAuthentication::UserAuthentication)
{
// TODO: Read username/password vault key
}
@@ -376,7 +380,7 @@ CMidi2NetworkMidiConfigurationManager::UpdateConfiguration(
}
}
- definition.ServiceInstanceName = serviceInstanceNamePrefix;
+ definition->ServiceInstanceName = serviceInstanceNamePrefix;
// TODO: See if the serviceInstanceName is already in use. If so, add a disambiguation number. Keep trying until unused one is found
@@ -392,29 +396,29 @@ CMidi2NetworkMidiConfigurationManager::UpdateConfiguration(
if ((host.Type() == HostNameType::DomainName) &&
(host.RawName().ends_with(L".local")))
{
- definition.HostName = host.RawName();
+ definition->HostName = host.RawName();
break;
}
}
- if (definition.Port == MIDI_CONFIG_JSON_NETWORK_MIDI_NETWORK_PORT_VALUE_AUTO ||
- definition.Port == L"" ||
- definition.Port == L"0")
+ if (definition->Port == MIDI_CONFIG_JSON_NETWORK_MIDI_NETWORK_PORT_VALUE_AUTO ||
+ definition->Port == L"" ||
+ definition->Port == L"0")
{
// this will cause us to use an auto-generated free port
- definition.Port = L"";
- definition.UseAutomaticPortAllocation = true;
+ definition->Port = L"";
+ definition->UseAutomaticPortAllocation = true;
}
else
{
- definition.UseAutomaticPortAllocation = false;
+ definition->UseAutomaticPortAllocation = false;
}
// validate the entry
- if (SUCCEEDED(ValidateHostDefinition(definition, validationErrorMessage)))
+ if (SUCCEEDED(ValidateHostDefinition(*definition, validationErrorMessage)))
{
TraceLoggingWrite(
MidiNetworkMidiTransportTelemetryProvider::Provider(),
@@ -425,15 +429,14 @@ CMidi2NetworkMidiConfigurationManager::UpdateConfiguration(
TraceLoggingWideString(L"Host definition validated. Creating host", MIDI_TRACE_EVENT_MESSAGE_FIELD)
);
- // create the host
-
- auto host = std::make_shared();
-
- RETURN_HR_IF_NULL(E_POINTER, host);
- RETURN_IF_FAILED(host->Initialize(definition));
+ // create the host definition
// add to our collection of hosts
- TransportState::Current().AddHost(host);
+ TransportState::Current().AddPendingHostDefinition(definition);
+
+ responseObject.SetNamedValue(
+ MIDI_CONFIG_JSON_CONFIGURATION_RESPONSE_SUCCESS_PROPERTY_KEY,
+ jsonTrue);
}
else
{
@@ -448,7 +451,81 @@ CMidi2NetworkMidiConfigurationManager::UpdateConfiguration(
// device (or application)
if (clientsSection != nullptr && clientsSection.Size() > 0)
{
+ for (auto const& it = clientsSection.First(); it.HasCurrent(); it.MoveNext())
+ {
+ auto clientEntry = clientsSection.GetNamedObject(it.Current().Key());
+
+ auto definition = std::make_shared();
+ RETURN_IF_NULL_ALLOC(definition);
+
+ winrt::hstring validationErrorMessage{ };
+
+ // currently, UDP is the only allowed protocol
+ auto protocol = internal::ToLowerTrimmedHStringCopy(clientEntry.GetNamedString(MIDI_CONFIG_JSON_NETWORK_MIDI_NETWORK_PROTOCOL_KEY, MIDI_CONFIG_JSON_NETWORK_MIDI_NETWORK_PROTOCOL_VALUE_UDP));
+
+ if (protocol != MIDI_CONFIG_JSON_NETWORK_MIDI_NETWORK_PROTOCOL_VALUE_UDP)
+ {
+ validationErrorMessage = L"Invalid network protocol '" + protocol + L"' specified.";
+ }
+ else
+ {
+ definition->EntryIdentifier = internal::TrimmedHStringCopy(it.Current().Key());
+ definition->Enabled = clientEntry.GetNamedBoolean(MIDI_CONFIG_JSON_NETWORK_MIDI_ENABLED_KEY, true);
+
+ winrt::hstring localEndpointName{ };
+ winrt::hstring localProductInstanceId{ };
+
+ // TODO: Add ability for config file to specify the localEndpointName and localProductInstanceId
+ if (localEndpointName.empty())
+ {
+ std::wstring buffer{};
+ DWORD bufferSize = MAX_COMPUTERNAME_LENGTH + 1;
+ buffer.resize(bufferSize);
+
+ bool validName = GetComputerName(buffer.data(), &bufferSize);
+ if (validName)
+ {
+ localEndpointName = buffer;
+ }
+ }
+ // TODO: we may want to provide the local product instance id as a system-wide setting. Same with name
+ if (localProductInstanceId.empty())
+ {
+ localProductInstanceId = L"8675309-OU812";
+ }
+
+ definition->LocalEndpointName = localEndpointName;
+ definition->LocalProductInstanceId = localProductInstanceId;
+
+ auto matchSection = clientEntry.GetNamedObject(MIDI_CONFIG_JSON_NETWORK_MIDI_CLIENT_MATCH_OBJECT_KEY, nullptr);
+
+ if (matchSection)
+ {
+ // TODO: Match on IP/Port, etc.
+ // for the moment, we only match on the actual device id, so must be mdns-advertised
+
+ definition->MatchId = internal::TrimmedHStringCopy(matchSection.GetNamedString(MIDI_CONFIG_JSON_NETWORK_MIDI_CLIENT_MATCH_ID_KEY, L""));
+
+
+
+
+
+
+ TransportState::Current().AddPendingClientDefinition(definition);
+
+ responseObject.SetNamedValue(
+ MIDI_CONFIG_JSON_CONFIGURATION_RESPONSE_SUCCESS_PROPERTY_KEY,
+ jsonTrue);
+ }
+ else
+ {
+ // we have no way to match against endpoints, so this is a failure
+ validationErrorMessage = L"Missing match entry";
+ }
+ }
+
+ }
}
}
diff --git a/src/api/Transport/UdpNetworkMidi2Transport/Midi2.NetworkMidiEndpointManager.cpp b/src/api/Transport/UdpNetworkMidi2Transport/Midi2.NetworkMidiEndpointManager.cpp
index 6206ac8de..ae2141e3c 100644
--- a/src/api/Transport/UdpNetworkMidi2Transport/Midi2.NetworkMidiEndpointManager.cpp
+++ b/src/api/Transport/UdpNetworkMidi2Transport/Midi2.NetworkMidiEndpointManager.cpp
@@ -138,26 +138,28 @@ CMidi2NetworkMidiEndpointManager::OnDeviceWatcherAdded(enumeration::DeviceWatche
m_foundAdvertisedHosts.insert_or_assign(args.Id(), args);
- // TODO: do we need to signal the background thread to check the collection?
+ // signal the background thread to check the collection?
+ m_backgroundEndpointCreatorThreadWakeup.SetEvent();
return S_OK;
}
_Use_decl_annotations_
HRESULT
-CMidi2NetworkMidiEndpointManager::OnDeviceWatcherUpdated(enumeration::DeviceWatcher const&, enumeration::DeviceInformationUpdate const& args)
+CMidi2NetworkMidiEndpointManager::OnDeviceWatcherUpdated(enumeration::DeviceWatcher const&, enumeration::DeviceInformationUpdate const& /*args*/)
{
- TraceLoggingWrite(
- MidiNetworkMidiTransportTelemetryProvider::Provider(),
- MIDI_TRACE_EVENT_INFO,
- TraceLoggingString(__FUNCTION__, MIDI_TRACE_EVENT_LOCATION_FIELD),
- TraceLoggingLevel(WINEVENT_LEVEL_INFO),
- TraceLoggingPointer(this, "this"),
- TraceLoggingWideString(L"Enter", MIDI_TRACE_EVENT_MESSAGE_FIELD),
- TraceLoggingWideString(args.Id().c_str(), "id")
- );
-
- // nothing to do here. We don't care about updates.
+ //TraceLoggingWrite(
+ // MidiNetworkMidiTransportTelemetryProvider::Provider(),
+ // MIDI_TRACE_EVENT_INFO,
+ // TraceLoggingString(__FUNCTION__, MIDI_TRACE_EVENT_LOCATION_FIELD),
+ // TraceLoggingLevel(WINEVENT_LEVEL_INFO),
+ // TraceLoggingPointer(this, "this"),
+ // TraceLoggingWideString(L"Enter", MIDI_TRACE_EVENT_MESSAGE_FIELD),
+ // TraceLoggingWideString(args.Id().c_str(), "id")
+ //);
+
+ // nothing to do here. We don't care about updates. This gets really spammy because
+ // the endpoint updates maybe with each mdns ad broadcast or something
return S_OK;
}
@@ -232,17 +234,27 @@ CMidi2NetworkMidiEndpointManager::StartBackgroundEndpointCreator()
TraceLoggingWideString(L"Enter", MIDI_TRACE_EVENT_MESSAGE_FIELD)
);
- std::jthread workerThread(&CMidi2NetworkMidiEndpointManager::EndpointCreatorWorker, this);
+ std::jthread workerThread(std::bind_front(&CMidi2NetworkMidiEndpointManager::EndpointCreatorWorker, this));
m_backgroundEndpointCreatorThread = std::move(workerThread);
- m_backgroundEndpointCreatorThreadStopToken = m_backgroundEndpointCreatorThread.get_stop_token();
m_backgroundEndpointCreatorThread.detach();
+
+ TraceLoggingWrite(
+ MidiNetworkMidiTransportTelemetryProvider::Provider(),
+ MIDI_TRACE_EVENT_INFO,
+ TraceLoggingString(__FUNCTION__, MIDI_TRACE_EVENT_LOCATION_FIELD),
+ TraceLoggingLevel(WINEVENT_LEVEL_INFO),
+ TraceLoggingPointer(this, "this"),
+ TraceLoggingWideString(L"Exit", MIDI_TRACE_EVENT_MESSAGE_FIELD)
+ );
+
return S_OK;
}
+_Use_decl_annotations_
HRESULT
-CMidi2NetworkMidiEndpointManager::EndpointCreatorWorker()
+CMidi2NetworkMidiEndpointManager::EndpointCreatorWorker(std::stop_token stopToken)
{
TraceLoggingWrite(
MidiNetworkMidiTransportTelemetryProvider::Provider(),
@@ -253,12 +265,24 @@ CMidi2NetworkMidiEndpointManager::EndpointCreatorWorker()
TraceLoggingWideString(L"Enter", MIDI_TRACE_EVENT_MESSAGE_FIELD)
);
+// winrt::init_apartment();
+ //auto coInit = wil::CoInitializeEx(COINIT_MULTITHREADED);
+
// this is set up to run through one time before waiting for the wakeup
// this way we can process anything added before the EndpointManager has been
// initialized
- while (!m_backgroundEndpointCreatorThreadStopToken.stop_requested())
+ while (!stopToken.stop_requested())
{
+ TraceLoggingWrite(
+ MidiNetworkMidiTransportTelemetryProvider::Provider(),
+ MIDI_TRACE_EVENT_INFO,
+ TraceLoggingString(__FUNCTION__, MIDI_TRACE_EVENT_LOCATION_FIELD),
+ TraceLoggingLevel(WINEVENT_LEVEL_INFO),
+ TraceLoggingPointer(this, "this"),
+ TraceLoggingWideString(L"Background worker loop", MIDI_TRACE_EVENT_MESSAGE_FIELD)
+ );
+
if (m_backgroundEndpointCreatorThreadWakeup.is_signaled())
{
m_backgroundEndpointCreatorThreadWakeup.ResetEvent();
@@ -266,33 +290,212 @@ CMidi2NetworkMidiEndpointManager::EndpointCreatorWorker()
// run through host entries
- for (auto const& host : TransportState::Current().GetHosts())
+ for (auto& definition : TransportState::Current().GetPendingHostDefinitions())
{
- if (!host->HasStarted())
+ if (definition->Created)
{
- LOG_IF_FAILED(host->Start());
+ continue;
+ }
+
+ if (!definition->Enabled)
+ {
+ continue;
+ }
+
+ auto host = std::make_shared();
+ LOG_IF_NULL_ALLOC(host);
+
+ if (host != nullptr)
+ {
+ LOG_IF_FAILED(host->Initialize(*definition));
+
+ if (!host->HasStarted())
+ {
+ LOG_IF_FAILED(host->Start());
+ }
+
+ // Remove pending entry
+
+ definition->Created = true;
+
+ // this ensures the host doesn't disappear
+ TransportState::Current().AddHost(host);
}
}
// TODO: run through client definition entries. These aren't actual clients
// but are instead just parameters needed to create connections to hosts when
// they come online.
- //
for (auto const& clientDefinition : TransportState::Current().GetPendingClientDefinitions())
{
- UNREFERENCED_PARAMETER(clientDefinition);
+ if (clientDefinition->Created)
+ {
+ continue;
+ }
- // any requiring a match, see if we have a match in our deviceinformation collection
+ if (!clientDefinition->Enabled)
+ {
+ continue;
+ }
+ // any requiring a match, see if we have a match in our deviceinformation collection
// - any that are IP/port direct, try to connect, but don't keep trying the same ones if they fail
+ // check for id first, as this is fastest
+ // TODO: right now, this is case-sensitive. It needs clean-up.
+ if (auto advertised = m_foundAdvertisedHosts.find(clientDefinition->MatchId); advertised != m_foundAdvertisedHosts.end())
+ {
+ TraceLoggingWrite(
+ MidiNetworkMidiTransportTelemetryProvider::Provider(),
+ MIDI_TRACE_EVENT_INFO,
+ TraceLoggingString(__FUNCTION__, MIDI_TRACE_EVENT_LOCATION_FIELD),
+ TraceLoggingLevel(WINEVENT_LEVEL_INFO),
+ TraceLoggingPointer(this, "this"),
+ TraceLoggingWideString(L"Processing mdns entry", MIDI_TRACE_EVENT_MESSAGE_FIELD),
+ TraceLoggingWideString(advertised->second.Id().c_str(), "id")
+ );
+
+ auto client = std::make_shared();
+ LOG_IF_NULL_ALLOC(client);
+
+ auto initHr = client->Initialize(*clientDefinition, advertised->second);
+ LOG_IF_FAILED(initHr);
+
+ if (SUCCEEDED(initHr))
+ {
+ TransportState::Current().AddClient(client);
+
+ // todo: get the hostname and port, and then start the client
+
+ //deviceId = advertised.Id();
+ //deviceName = advertised.Name();
+
+ //host.HostName = internal::GetDeviceInfoProperty(entry.Properties(), L"System.Devices.Dnssd.HostName", L"");
+ //host.FullName = internal::GetDeviceInfoProperty(entry.Properties(), L"System.Devices.Dnssd.FullName", L"");
+ //host.ServiceInstanceName = internal::GetDeviceInfoProperty(entry.Properties(), L"System.Devices.Dnssd.InstanceName", L"");
+ //host.Port = internal::GetDeviceInfoProperty(entry.Properties(), L"System.Devices.Dnssd.PortNumber", 0);
+
+ winrt::hstring hostNameString{};
+ uint16_t port{ 0 };
+
+ const winrt::hstring hostNamePropertyKey = L"System.Devices.Dnssd.HostName";
+ //const winrt::hstring hostNamePropertyKey = L"System.Devices.Dnssd.FullName";
+ const winrt::hstring hostPortPropertyKey = L"System.Devices.Dnssd.PortNumber";
+ const winrt::hstring ipAddressPropertyKey = L"System.Devices.IpAddress";
+
+ // we use IP address first, as that is the most reliable
+ if (advertised->second.Properties().HasKey(ipAddressPropertyKey))
+ {
+ auto prop = advertised->second.Properties().Lookup(ipAddressPropertyKey).as>();
+ winrt::com_array array;
+ prop.GetStringArray(array);
+
+ // we only take the top one right now. We should take the others as well
+ if (array.size() > 0)
+ {
+ hostNameString = array.at(0);
+ }
+ }
+ // next we get the host name, but this relies on DNS being set up properly,
+ // which is often not the case on a network with just some devices and a laptop
+ else if (advertised->second.Properties().HasKey(hostNamePropertyKey))
+ {
+ auto prop = advertised->second.Properties().Lookup(hostNamePropertyKey);
+
+ if (prop)
+ {
+ hostNameString = winrt::unbox_value(prop);
+ }
+ }
+
+ if (advertised->second.Properties().HasKey(hostPortPropertyKey))
+ {
+ auto prop = advertised->second.Properties().Lookup(hostPortPropertyKey);
+
+ if (prop)
+ {
+ port = winrt::unbox_value(prop);
+ }
+ }
+
+ // the == 0 is hacky, but for midi, anything under 1024 is likely bogus
+ if (!hostNameString.empty() && port != 0)
+ {
+
+ HostName hostName(hostNameString);
+ winrt::hstring portNameString = winrt::to_hstring(port);
+
+ auto startHr = client->Start(hostName, portNameString);
+
+ LOG_IF_FAILED(startHr);
+
+ if (SUCCEEDED(startHr))
+ {
+ // Mark as created so we don't try to process it again
+ clientDefinition->Created = true;
+ }
+ else
+ {
+ TraceLoggingWrite(
+ MidiNetworkMidiTransportTelemetryProvider::Provider(),
+ MIDI_TRACE_EVENT_ERROR,
+ TraceLoggingString(__FUNCTION__, MIDI_TRACE_EVENT_LOCATION_FIELD),
+ TraceLoggingLevel(WINEVENT_LEVEL_ERROR),
+ TraceLoggingPointer(this, "this"),
+ TraceLoggingWideString(L"Failed to start client", MIDI_TRACE_EVENT_MESSAGE_FIELD),
+ TraceLoggingWideString(advertised->second.Id().c_str(), "id"),
+ TraceLoggingWideString(hostNameString.c_str(), "hostname string"),
+ TraceLoggingWideString(hostName.ToString().c_str(), "created hostname"),
+ TraceLoggingWideString(portNameString.c_str(), "port")
+ );
+ }
+ }
+ else
+ {
+ TraceLoggingWrite(
+ MidiNetworkMidiTransportTelemetryProvider::Provider(),
+ MIDI_TRACE_EVENT_ERROR,
+ TraceLoggingString(__FUNCTION__, MIDI_TRACE_EVENT_LOCATION_FIELD),
+ TraceLoggingLevel(WINEVENT_LEVEL_ERROR),
+ TraceLoggingPointer(this, "this"),
+ TraceLoggingWideString(L"Unable to resolve remote device", MIDI_TRACE_EVENT_MESSAGE_FIELD),
+ TraceLoggingWideString(advertised->second.Id().c_str(), "id")
+ );
+ }
+
+ }
+ else
+ {
+ TraceLoggingWrite(
+ MidiNetworkMidiTransportTelemetryProvider::Provider(),
+ MIDI_TRACE_EVENT_ERROR,
+ TraceLoggingString(__FUNCTION__, MIDI_TRACE_EVENT_LOCATION_FIELD),
+ TraceLoggingLevel(WINEVENT_LEVEL_ERROR),
+ TraceLoggingPointer(this, "this"),
+ TraceLoggingWideString(L"Failed to initialize client", MIDI_TRACE_EVENT_MESSAGE_FIELD),
+ TraceLoggingWideString(advertised->second.Id().c_str(), "id")
+ );
+
+ }
+
+ }
+
}
// TODO: wait for notification of new hosts online or new entries added via config
m_backgroundEndpointCreatorThreadWakeup.wait(10000);
}
+ TraceLoggingWrite(
+ MidiNetworkMidiTransportTelemetryProvider::Provider(),
+ MIDI_TRACE_EVENT_INFO,
+ TraceLoggingString(__FUNCTION__, MIDI_TRACE_EVENT_LOCATION_FIELD),
+ TraceLoggingLevel(WINEVENT_LEVEL_INFO),
+ TraceLoggingPointer(this, "this"),
+ TraceLoggingWideString(L"Exit", MIDI_TRACE_EVENT_MESSAGE_FIELD)
+ );
+
return S_OK;
}
@@ -427,6 +630,7 @@ CMidi2NetworkMidiEndpointManager::CreateNewEndpoint(
std::wstring const& remoteEndpointProductInstanceId,
winrt::Windows::Networking::HostName const& hostName,
std::wstring const& networkPort,
+ bool umpOnly,
std::wstring& createdNewDeviceInstanceId,
std::wstring& createdNewEndpointDeviceInterfaceId
)
@@ -527,7 +731,17 @@ CMidi2NetworkMidiEndpointManager::CreateNewEndpoint(
// TODO: Add custom properties for the network information
+ std::wstring endpointDescription{ L"Network MIDI 2.0 endpoint "};
+ switch (thisServiceRole)
+ {
+ case MidiNetworkConnectionRole::ConnectionWindowsIsHost:
+ endpointDescription += L"(This PC is the Network Host)";
+ break;
+ case MidiNetworkConnectionRole::ConnectionWindowsIsClient:
+ endpointDescription += L"(This PC is a Network Client)";
+ break;
+ }
MIDIENDPOINTCOMMONPROPERTIES commonProperties{};
commonProperties.TransportId = TRANSPORT_LAYER_GUID;
@@ -535,7 +749,7 @@ CMidi2NetworkMidiEndpointManager::CreateNewEndpoint(
commonProperties.FriendlyName = friendlyName.c_str();
commonProperties.TransportCode = transportCode.c_str();
commonProperties.EndpointName = endpointName.c_str();
- commonProperties.EndpointDescription = nullptr;
+ commonProperties.EndpointDescription = endpointDescription.c_str();
commonProperties.CustomEndpointName = nullptr;
commonProperties.CustomEndpointDescription = nullptr;
commonProperties.UniqueIdentifier = remoteEndpointProductInstanceId.c_str();
@@ -551,7 +765,7 @@ CMidi2NetworkMidiEndpointManager::CreateNewEndpoint(
RETURN_IF_FAILED(m_midiDeviceManager->ActivateEndpoint(
(PCWSTR)m_parentDeviceId.c_str(), // parent instance Id
- false, // UMP-only. When set to false, WinMM MIDI 1.0 ports are created
+ umpOnly, // UMP-only. When set to false, WinMM MIDI 1.0 ports are created
MidiFlow::MidiFlowBidirectional, // MIDI Flow
&commonProperties,
(ULONG)0, //interfaceDeviceProperties.size(),
diff --git a/src/api/Transport/UdpNetworkMidi2Transport/Midi2.NetworkMidiEndpointManager.h b/src/api/Transport/UdpNetworkMidi2Transport/Midi2.NetworkMidiEndpointManager.h
index 99fe22611..e68fae356 100644
--- a/src/api/Transport/UdpNetworkMidi2Transport/Midi2.NetworkMidiEndpointManager.h
+++ b/src/api/Transport/UdpNetworkMidi2Transport/Midi2.NetworkMidiEndpointManager.h
@@ -28,6 +28,7 @@ class CMidi2NetworkMidiEndpointManager :
_In_ std::wstring const& remoteEndpointProductInstanceId,
_In_ winrt::Windows::Networking::HostName const& hostName,
_In_ std::wstring const& networkPort,
+ _In_ bool umpOnly,
_Out_ std::wstring& createdNewDeviceInstanceId,
_Out_ std::wstring& createdNewEndpointDeviceInterfaceId
));
@@ -68,8 +69,8 @@ class CMidi2NetworkMidiEndpointManager :
std::jthread m_backgroundEndpointCreatorThread;
- std::stop_token m_backgroundEndpointCreatorThreadStopToken;
+ //std::stop_token m_backgroundEndpointCreatorThreadStopToken;
wil::slim_event_manual_reset m_backgroundEndpointCreatorThreadWakeup;
- HRESULT EndpointCreatorWorker();
+ HRESULT EndpointCreatorWorker(_In_ std::stop_token stopToken);
};
diff --git a/src/api/Transport/UdpNetworkMidi2Transport/MidiNetworkAdvertiser.cpp b/src/api/Transport/UdpNetworkMidi2Transport/MidiNetworkAdvertiser.cpp
index 78fa0a451..aaba5ba94 100644
--- a/src/api/Transport/UdpNetworkMidi2Transport/MidiNetworkAdvertiser.cpp
+++ b/src/api/Transport/UdpNetworkMidi2Transport/MidiNetworkAdvertiser.cpp
@@ -31,7 +31,7 @@ inline const winrt::hstring BuildFullServiceInstanceName(_In_ winrt::hstring con
}
-
+// TODO: Change this method to "Start" and have the parameters passed into Initialize instead of this.
_Use_decl_annotations_
HRESULT
@@ -56,18 +56,17 @@ MidiNetworkAdvertiser::Advertise(
auto fullServiceName = BuildFullServiceInstanceName(serviceInstanceNameWithoutSuffix);
-
- auto service = DnssdServiceInstance(
+ m_serviceInstance = DnssdServiceInstance(
fullServiceName,
hostName,
port);
// add the txt attributes per the spec
- service.TextAttributes().Insert(L"UMPEndpointName", midiEndpointName);
- service.TextAttributes().Insert(L"ProductInstanceId", midiProductInstanceId);
+ m_serviceInstance.TextAttributes().Insert(L"UMPEndpointName", midiEndpointName);
+ m_serviceInstance.TextAttributes().Insert(L"ProductInstanceId", midiProductInstanceId);
// register with the socket that's bound to the port
- auto registration = service.RegisterDatagramSocketAsync(boundSocket).get();
+ auto registration = m_serviceInstance.RegisterDatagramSocketAsync(boundSocket).get();
switch (registration.Status())
{
@@ -137,6 +136,15 @@ MidiNetworkAdvertiser::Advertise(
RETURN_IF_FAILED(E_FAIL);
}
+
+ TraceLoggingWrite(
+ MidiNetworkMidiTransportTelemetryProvider::Provider(),
+ MIDI_TRACE_EVENT_INFO,
+ TraceLoggingString(__FUNCTION__, MIDI_TRACE_EVENT_LOCATION_FIELD),
+ TraceLoggingLevel(WINEVENT_LEVEL_INFO),
+ TraceLoggingPointer(this, "this"),
+ TraceLoggingWideString(L"Exit", MIDI_TRACE_EVENT_MESSAGE_FIELD)
+ );
}
@@ -153,7 +161,7 @@ MidiNetworkAdvertiser::Shutdown()
TraceLoggingWideString(L"Enter", MIDI_TRACE_EVENT_MESSAGE_FIELD)
);
-
+ m_serviceInstance = nullptr;
return S_OK;
diff --git a/src/api/Transport/UdpNetworkMidi2Transport/MidiNetworkAdvertiser.h b/src/api/Transport/UdpNetworkMidi2Transport/MidiNetworkAdvertiser.h
index 4b51d3ca8..5e3819a1e 100644
--- a/src/api/Transport/UdpNetworkMidi2Transport/MidiNetworkAdvertiser.h
+++ b/src/api/Transport/UdpNetworkMidi2Transport/MidiNetworkAdvertiser.h
@@ -31,6 +31,6 @@ class MidiNetworkAdvertiser
HRESULT Shutdown();
private:
-
+ winrt::Windows::Networking::ServiceDiscovery::Dnssd::DnssdServiceInstance m_serviceInstance{ nullptr };
};
\ No newline at end of file
diff --git a/src/api/Transport/UdpNetworkMidi2Transport/MidiNetworkClient.cpp b/src/api/Transport/UdpNetworkMidi2Transport/MidiNetworkClient.cpp
index 580d3ae4e..bfb86c89c 100644
--- a/src/api/Transport/UdpNetworkMidi2Transport/MidiNetworkClient.cpp
+++ b/src/api/Transport/UdpNetworkMidi2Transport/MidiNetworkClient.cpp
@@ -11,7 +11,10 @@
_Use_decl_annotations_
HRESULT
-MidiNetworkClient::Initialize(MidiNetworkClientDefinition& clientDefinition)
+MidiNetworkClient::Initialize(
+ MidiNetworkClientDefinition& clientDefinition,
+ enumeration::DeviceInformation advertisedHost
+)
{
TraceLoggingWrite(
MidiNetworkMidiTransportTelemetryProvider::Provider(),
@@ -22,16 +25,226 @@ MidiNetworkClient::Initialize(MidiNetworkClientDefinition& clientDefinition)
TraceLoggingWideString(L"Enter", MIDI_TRACE_EVENT_MESSAGE_FIELD)
);
- UNREFERENCED_PARAMETER(clientDefinition);
+ UNREFERENCED_PARAMETER(advertisedHost);
+ m_createUmpEndpointsOnly = !clientDefinition.CreateMidi1Ports;
+ m_thisEndpointName = clientDefinition.LocalEndpointName;
+ m_thisProductInstanceId = clientDefinition.LocalProductInstanceId;
return S_OK;
}
+_Use_decl_annotations_
+void MidiNetworkClient::OnMessageReceived(
+ _In_ winrt::Windows::Networking::Sockets::DatagramSocket const& sender,
+ _In_ winrt::Windows::Networking::Sockets::DatagramSocketMessageReceivedEventArgs const& args)
+{
+ UNREFERENCED_PARAMETER(sender);
+
+ auto reader = args.GetDataReader();
+
+ if (reader != nullptr && reader.UnconsumedBufferLength() < MINIMUM_VALID_UDP_PACKET_SIZE)
+ {
+ // not a message we understand. Needs to be at least the size of the
+ // MIDI header plus a command packet header. Really it needs to be larger, but
+ // just trying to weed out blips
+
+ TraceLoggingWrite(
+ MidiNetworkMidiTransportTelemetryProvider::Provider(),
+ MIDI_TRACE_EVENT_WARNING,
+ TraceLoggingString(__FUNCTION__, MIDI_TRACE_EVENT_LOCATION_FIELD),
+ TraceLoggingLevel(WINEVENT_LEVEL_WARNING),
+ TraceLoggingPointer(this, "this"),
+ TraceLoggingWideString(L"Undersized packet", MIDI_TRACE_EVENT_MESSAGE_FIELD)
+ );
+
+ // todo: does the reader need to be cleared?
+
+ return;
+ }
+
+ uint32_t udpHeader = reader.ReadUInt32();
+
+ if (udpHeader != MIDI_UDP_PAYLOAD_HEADER)
+ {
+ // not a message we understand
+
+ return;
+ }
+ if (m_networkConnection)
+ {
+ m_networkConnection->ProcessIncomingMessage(reader);
+ }
+ else
+ {
+ TraceLoggingWrite(
+ MidiNetworkMidiTransportTelemetryProvider::Provider(),
+ MIDI_TRACE_EVENT_ERROR,
+ TraceLoggingString(__FUNCTION__, MIDI_TRACE_EVENT_LOCATION_FIELD),
+ TraceLoggingLevel(WINEVENT_LEVEL_ERROR),
+ TraceLoggingPointer(this, "this"),
+ TraceLoggingWideString(L"Message received from remote client, connection is nullptr", MIDI_TRACE_EVENT_MESSAGE_FIELD)
+ );
+ }
+
+
+ TraceLoggingWrite(
+ MidiNetworkMidiTransportTelemetryProvider::Provider(),
+ MIDI_TRACE_EVENT_INFO,
+ TraceLoggingString(__FUNCTION__, MIDI_TRACE_EVENT_LOCATION_FIELD),
+ TraceLoggingLevel(WINEVENT_LEVEL_INFO),
+ TraceLoggingPointer(this, "this"),
+ TraceLoggingWideString(L"Exit", MIDI_TRACE_EVENT_MESSAGE_FIELD)
+ );
+
+}
+
+
+_Use_decl_annotations_
+HRESULT
+MidiNetworkClient::Start(
+ HostName const& remoteHostName,
+ winrt::hstring const& remotePort
+)
+{
+ TraceLoggingWrite(
+ MidiNetworkMidiTransportTelemetryProvider::Provider(),
+ MIDI_TRACE_EVENT_INFO,
+ TraceLoggingString(__FUNCTION__, MIDI_TRACE_EVENT_LOCATION_FIELD),
+ TraceLoggingLevel(WINEVENT_LEVEL_INFO),
+ TraceLoggingPointer(this, "this"),
+ TraceLoggingWideString(L"Enter", MIDI_TRACE_EVENT_MESSAGE_FIELD),
+ TraceLoggingWideString(remoteHostName.ToString().c_str(), "remote hostname"),
+ TraceLoggingWideString(remotePort.c_str(), "remote port"));
+
+ auto conn = std::make_shared();
+ RETURN_IF_NULL_ALLOC(conn);
+
+ DatagramSocket socket;
+ m_socket = std::move(socket);
+
+ m_socket.Control().QualityOfService(SocketQualityOfService::LowLatency);
+ m_socket.Control().DontFragment(true);
+
+ auto messageReceivedHandler = winrt::Windows::Foundation::TypedEventHandler(this, &MidiNetworkClient::OnMessageReceived);
+ m_messageReceivedEventToken = m_socket.MessageReceived(messageReceivedHandler);
+
+ TraceLoggingWrite(
+ MidiNetworkMidiTransportTelemetryProvider::Provider(),
+ MIDI_TRACE_EVENT_INFO,
+ TraceLoggingString(__FUNCTION__, MIDI_TRACE_EVENT_LOCATION_FIELD),
+ TraceLoggingLevel(WINEVENT_LEVEL_INFO),
+ TraceLoggingPointer(this, "this"),
+ TraceLoggingWideString(L"Event handler hooked up. About to connect socket", MIDI_TRACE_EVENT_MESSAGE_FIELD),
+ TraceLoggingWideString(remoteHostName.ToString().c_str(), "remote hostname"),
+ TraceLoggingWideString(remotePort.c_str(), "remote port"));
+
+
+ //EndpointPair pair(
+ // nullptr,
+ // L"",
+ // remoteHostName,
+ // remotePort);
+
+ // establish the remote connection
+ try
+ {
+ // this throws if the address can't be resolved or if
+ // the connect otherwise fails
+
+ //m_socket.ConnectAsync(pair).get();
+ m_socket.ConnectAsync(remoteHostName, remotePort);
+ }
+ catch (winrt::hresult_error err)
+ {
+ TraceLoggingWrite(
+ MidiNetworkMidiTransportTelemetryProvider::Provider(),
+ MIDI_TRACE_EVENT_ERROR,
+ TraceLoggingString(__FUNCTION__, MIDI_TRACE_EVENT_LOCATION_FIELD),
+ TraceLoggingLevel(WINEVENT_LEVEL_ERROR),
+ TraceLoggingPointer(this, "this"),
+ TraceLoggingWideString(L"HRESULT Exception connecting to socket", MIDI_TRACE_EVENT_MESSAGE_FIELD),
+ TraceLoggingWideString(remoteHostName.ToString().c_str(), "remote hostname"),
+ TraceLoggingWideString(remotePort.c_str(), "remote port"),
+ TraceLoggingHResult(err.code(), "hresult"),
+ TraceLoggingWideString(err.message().c_str(), "error message")
+ );
+
+ RETURN_IF_FAILED(E_NOTFOUND);
+ }
+ catch (...)
+ {
+ TraceLoggingWrite(
+ MidiNetworkMidiTransportTelemetryProvider::Provider(),
+ MIDI_TRACE_EVENT_ERROR,
+ TraceLoggingString(__FUNCTION__, MIDI_TRACE_EVENT_LOCATION_FIELD),
+ TraceLoggingLevel(WINEVENT_LEVEL_ERROR),
+ TraceLoggingPointer(this, "this"),
+ TraceLoggingWideString(L"Exception connecting to socket", MIDI_TRACE_EVENT_MESSAGE_FIELD),
+ TraceLoggingWideString(remoteHostName.ToString().c_str(), "remote hostname"),
+ TraceLoggingWideString(remotePort.c_str(), "remote port"));
+
+ RETURN_IF_FAILED(E_NOTFOUND);
+ }
+
+
+ TraceLoggingWrite(
+ MidiNetworkMidiTransportTelemetryProvider::Provider(),
+ MIDI_TRACE_EVENT_INFO,
+ TraceLoggingString(__FUNCTION__, MIDI_TRACE_EVENT_LOCATION_FIELD),
+ TraceLoggingLevel(WINEVENT_LEVEL_INFO),
+ TraceLoggingPointer(this, "this"),
+ TraceLoggingWideString(L"About to initialize connection", MIDI_TRACE_EVENT_MESSAGE_FIELD),
+ TraceLoggingWideString(remoteHostName.ToString().c_str(), "remote hostname"),
+ TraceLoggingWideString(remotePort.c_str(), "remote port"));
+
+ RETURN_IF_FAILED(conn->Initialize(
+ MidiNetworkConnectionRole::ConnectionWindowsIsClient,
+ m_socket,
+ remoteHostName,
+ remotePort,
+ m_thisEndpointName,
+ m_thisProductInstanceId,
+ TransportState::Current().TransportSettings.RetransmitBufferMaxCommandPacketCount,
+ TransportState::Current().TransportSettings.ForwardErrorCorrectionMaxCommandPacketCount,
+ m_createUmpEndpointsOnly
+ ));
+
+ TransportState::Current().AddNetworkConnection(remoteHostName, remotePort, conn);
+
+ m_networkConnection = conn;
+
+ // try to establish connection in-protocol
+
+ // TODO: Need to wire up other security approaches here
+ // TODO: The invitation send should be in a loop so it's repeated if
+ // there's no response
+ RETURN_IF_FAILED(conn->SendInvitation());
+
+ TraceLoggingWrite(
+ MidiNetworkMidiTransportTelemetryProvider::Provider(),
+ MIDI_TRACE_EVENT_INFO,
+ TraceLoggingString(__FUNCTION__, MIDI_TRACE_EVENT_LOCATION_FIELD),
+ TraceLoggingLevel(WINEVENT_LEVEL_INFO),
+ TraceLoggingPointer(this, "this"),
+ TraceLoggingWideString(L"Invitation sent", MIDI_TRACE_EVENT_MESSAGE_FIELD),
+ TraceLoggingWideString(remoteHostName.ToString().c_str(), "remote hostname"),
+ TraceLoggingWideString(remotePort.c_str(), "remote port"));
+
+
+ // todo: associate connection with the endpoint id
+
+
+ // todo: initiate discovery
+
+
+ return S_OK;
+}
+
@@ -49,6 +262,13 @@ MidiNetworkClient::Shutdown()
);
+ if (m_socket)
+ {
+ m_socket.MessageReceived(m_messageReceivedEventToken);
+ m_socket.Close();
+ m_socket = nullptr;
+ }
+
return S_OK;
}
diff --git a/src/api/Transport/UdpNetworkMidi2Transport/MidiNetworkClient.h b/src/api/Transport/UdpNetworkMidi2Transport/MidiNetworkClient.h
index 3bb7d5aab..4b71c0699 100644
--- a/src/api/Transport/UdpNetworkMidi2Transport/MidiNetworkClient.h
+++ b/src/api/Transport/UdpNetworkMidi2Transport/MidiNetworkClient.h
@@ -10,13 +10,29 @@
struct MidiNetworkClientDefinition
{
+ bool Created{ false };
+
winrt::hstring EntryIdentifier; // internal
bool Enabled{ true };
+ winrt::hstring Name; // the name of the endpoint before we do discovery
+
+ winrt::hstring LocalEndpointName; // this is required by protocol
+ winrt::hstring LocalProductInstanceId; // also required by protocol
+
+
+ bool CreateMidi1Ports{ MIDI_NETWORK_MIDI_CREATE_MIDI1_PORTS_DEFAULT };
+
+
// protocol
// MidiNetworkHostProtocol NetworkProtocol{ MidiNetworkHostProtocol::ProtocolDefault };
+ // all match criteria follows
+
+ winrt::hstring MatchId{};
+
+
};
@@ -24,12 +40,34 @@ struct MidiNetworkClientDefinition
class MidiNetworkClient
{
public:
- HRESULT Initialize(_In_ MidiNetworkClientDefinition& clientDefinition);
+ // will need some different versions of Initialize for the different ways of connecting
+ HRESULT Initialize(
+ _In_ MidiNetworkClientDefinition& clientDefinition,
+ _In_ enumeration::DeviceInformation advertisedHost
+ );
+
+ HRESULT Start(
+ _In_ winrt::Windows::Networking::HostName const& remoteHostName,
+ _In_ winrt::hstring const& remotePort
+ );
HRESULT Shutdown();
private:
+ bool m_createUmpEndpointsOnly{ true };
+
+ std::shared_ptr m_networkConnection{ nullptr };
+ winrt::Windows::Networking::Sockets::DatagramSocket m_socket{ nullptr };
+
+ std::wstring m_thisEndpointName{ };
+ std::wstring m_thisProductInstanceId{ };
+
+
+ winrt::event_token m_messageReceivedEventToken;
+ void OnMessageReceived(
+ _In_ winrt::Windows::Networking::Sockets::DatagramSocket const& sender,
+ _In_ winrt::Windows::Networking::Sockets::DatagramSocketMessageReceivedEventArgs const& args);
};
\ No newline at end of file
diff --git a/src/api/Transport/UdpNetworkMidi2Transport/MidiNetworkConnection.cpp b/src/api/Transport/UdpNetworkMidi2Transport/MidiNetworkConnection.cpp
index 54500d452..996dd1649 100644
--- a/src/api/Transport/UdpNetworkMidi2Transport/MidiNetworkConnection.cpp
+++ b/src/api/Transport/UdpNetworkMidi2Transport/MidiNetworkConnection.cpp
@@ -29,7 +29,8 @@ MidiNetworkConnection::Initialize(
std::wstring const& thisEndpointName,
std::wstring const& thisProductInstanceId,
uint16_t const retransmitBufferMaxCommandPacketCount,
- uint8_t const maxForwardErrorCorrectionCommandPacketCount
+ uint8_t const maxForwardErrorCorrectionCommandPacketCount,
+ bool createUmpEndpointsOnly
)
{
TraceLoggingWrite(
@@ -48,6 +49,8 @@ MidiNetworkConnection::Initialize(
m_remoteHostName = hostName;
m_remotePort = port;
+ m_createUmpEndpointsOnly = createUmpEndpointsOnly;
+
m_thisEndpointName = thisEndpointName;
m_thisProductInstanceId = thisProductInstanceId;
@@ -66,8 +69,7 @@ MidiNetworkConnection::Initialize(
RETURN_IF_FAILED(E_OUTOFMEMORY);
}
- m_lastSentUmpCommandSequenceNumber = 0;
- m_lastSentUmpCommandSequenceNumber--; // init to this so first real one is zero
+ RETURN_IF_FAILED(ResetSequenceNumbers());
// create the data writer
m_writer = std::make_shared();
@@ -107,6 +109,15 @@ MidiNetworkConnection::StartOutboundProcessingThreads()
+
+
+
+
+
+
+
+
+
return S_OK;
}
@@ -115,20 +126,28 @@ MidiNetworkConnection::StartOutboundProcessingThreads()
_Use_decl_annotations_
HRESULT
-MidiNetworkConnection::SetMidiCallback(
- IMidiCallback* callback
+MidiNetworkConnection::ConnectMidiCallback(
+ wil::com_ptr_nothrow callback
)
{
+ RETURN_HR_IF_NULL(E_INVALIDARG, callback);
+
TraceLoggingWrite(
MidiNetworkMidiTransportTelemetryProvider::Provider(),
MIDI_TRACE_EVENT_INFO,
TraceLoggingString(__FUNCTION__, MIDI_TRACE_EVENT_LOCATION_FIELD),
TraceLoggingLevel(WINEVENT_LEVEL_INFO),
TraceLoggingPointer(this, "this"),
- TraceLoggingWideString(L"Enter", MIDI_TRACE_EVENT_MESSAGE_FIELD)
+ TraceLoggingWideString(L"Enter", MIDI_TRACE_EVENT_MESSAGE_FIELD),
+ TraceLoggingPointer(callback.get(), "callback")
);
- RETURN_HR_IF_NULL(E_INVALIDARG, callback);
+ // the previous callback wasn't disconnected. Something
+ // is not as it should be, so we'll fail.
+ if (m_callback != nullptr)
+ {
+ RETURN_IF_FAILED(E_UNEXPECTED);
+ }
m_callback = callback;
@@ -136,7 +155,7 @@ MidiNetworkConnection::SetMidiCallback(
}
HRESULT
-MidiNetworkConnection::RemoveMidiCallback()
+MidiNetworkConnection::DisconnectMidiCallback()
{
TraceLoggingWrite(
MidiNetworkMidiTransportTelemetryProvider::Provider(),
@@ -147,7 +166,10 @@ MidiNetworkConnection::RemoveMidiCallback()
TraceLoggingWideString(L"Enter", MIDI_TRACE_EVENT_MESSAGE_FIELD)
);
- m_callback = nullptr;
+ if (m_callback != nullptr)
+ {
+ m_callback = nullptr;
+ }
return S_OK;
}
@@ -170,7 +192,11 @@ MidiNetworkConnection::HandleIncomingUmpData(
TraceLoggingWideString(L"Enter", MIDI_TRACE_EVENT_MESSAGE_FIELD)
);
- if (m_callback != nullptr)
+
+ // empty UMP packets are a keep-alive approach
+ // callback can be null if there are no open connections
+ // from the client, but the remote device is sending messages
+ if (m_sessionActive && words.size() > 0 && m_callback != nullptr)
{
// this may have more than one message, so we need to tease it apart here
// and send the individual messages
@@ -194,10 +220,25 @@ MidiNetworkConnection::HandleIncomingUmpData(
return S_OK;
}
+HRESULT
+MidiNetworkConnection::ResetSequenceNumbers()
+{
+ // reset the last sent sequence number
+ m_lastSentUmpCommandSequenceNumber = 0;
+ m_lastSentUmpCommandSequenceNumber--; // prepare for next
+
+ // reset the last received sequence number.
+ m_lastReceivedUmpCommandSequenceNumber = 0;
+ m_lastReceivedUmpCommandSequenceNumber--;
+ // clear out retransmit buffer
+ m_retransmitBuffer.clear();
+
+ return S_OK;
+}
HRESULT
-MidiNetworkConnection::HandleIncomingBye()
+MidiNetworkConnection::EndActiveSession()
{
TraceLoggingWrite(
MidiNetworkMidiTransportTelemetryProvider::Provider(),
@@ -208,29 +249,188 @@ MidiNetworkConnection::HandleIncomingBye()
TraceLoggingWideString(L"Enter", MIDI_TRACE_EVENT_MESSAGE_FIELD)
);
+ m_sessionActive = false;
+
+ // clear the outbound queue
+ m_outgoingUmpMessages.clear();
+ ResetSequenceNumbers();
+
+ m_callback = nullptr;
+ RETURN_IF_FAILED(TransportState::Current().GetEndpointManager()->DeleteEndpoint(m_sessionDeviceInstanceId));
+ m_sessionDeviceInstanceId.clear();
// send bye reply
+
+ auto lock = m_socketWriterLock.lock();
RETURN_IF_FAILED(m_writer->WriteUdpPacketHeader());
RETURN_IF_FAILED(m_writer->WriteCommandByeReply());
RETURN_IF_FAILED(m_writer->Send());
+ // clear the association with the SWD
+ RETURN_IF_FAILED(TransportState::Current().DisassociateMidiEndpointFromConnection(m_sessionEndpointDeviceInterfaceId));
+ m_sessionEndpointDeviceInterfaceId.clear();
+
+
+ return S_OK;
+}
+
+
+HRESULT
+MidiNetworkConnection::HandleIncomingBye()
+{
+ TraceLoggingWrite(
+ MidiNetworkMidiTransportTelemetryProvider::Provider(),
+ MIDI_TRACE_EVENT_INFO,
+ TraceLoggingString(__FUNCTION__, MIDI_TRACE_EVENT_LOCATION_FIELD),
+ TraceLoggingLevel(WINEVENT_LEVEL_INFO),
+ TraceLoggingPointer(this, "this"),
+ TraceLoggingWideString(L"Enter", MIDI_TRACE_EVENT_MESSAGE_FIELD)
+ );
+
+ RETURN_HR_IF_NULL(E_UNEXPECTED, m_writer);
+ RETURN_HR_IF_NULL(E_UNEXPECTED, TransportState::Current().GetEndpointManager());
+
if (m_sessionActive)
{
- if (TransportState::Current().GetEndpointManager() != nullptr)
+ RETURN_IF_FAILED(EndActiveSession());
+ }
+ else
+ {
+ // not an active session. Nothing to clean up
+ // but we should NAK the Bye saying there's no active session
+
+ auto lock = m_socketWriterLock.lock();
+ RETURN_IF_FAILED(m_writer->WriteUdpPacketHeader());
+ RETURN_IF_FAILED(m_writer->WriteCommandNAK(0, MidiNetworkCommandNAKReason::CommandNAKReason_CommandNotExpected, L"BYE received when there's no active session."));
+ RETURN_IF_FAILED(m_writer->Send());
+ }
+
+ return S_OK;
+}
+
+
+_Use_decl_annotations_
+HRESULT
+MidiNetworkConnection::HandleIncomingInvitationReplyAccepted(
+ MidiNetworkCommandPacketHeader const& header,
+ std::wstring const& remoteHostUmpEndpointName,
+ std::wstring const& remoteHostProductInstanceId
+)
+{
+ TraceLoggingWrite(
+ MidiNetworkMidiTransportTelemetryProvider::Provider(),
+ MIDI_TRACE_EVENT_INFO,
+ TraceLoggingString(__FUNCTION__, MIDI_TRACE_EVENT_LOCATION_FIELD),
+ TraceLoggingLevel(WINEVENT_LEVEL_INFO),
+ TraceLoggingPointer(this, "this"),
+ TraceLoggingWideString(L"Enter", MIDI_TRACE_EVENT_MESSAGE_FIELD)
+ );
+
+ if (m_role == MidiNetworkConnectionRole::ConnectionWindowsIsClient)
+ {
+ if (m_sessionActive)
+ {
+ // per protocol, if we've already accepted this, then just ignore it
+ return S_OK;
+ }
+
+ // TODO: will we accept a session invitation from the specified hostname?
+ // TODO: Also need to check auth mechanism and follow instructions in 6.4 and send a Bye if not supported
+
+ // todo: see if we already have a session active for this remote. If so, use it.
+ // otherwise, we need to spin up a new session
+
+ std::wstring newDeviceInstanceId{ };
+ std::wstring newEndpointDeviceInterfaceId{ };
+
+ if (TransportState::Current().GetEndpointManager()->IsInitialized())
{
- RETURN_IF_FAILED(TransportState::Current().GetEndpointManager()->DeleteEndpoint(m_sessionDeviceInstanceId));
- RETURN_IF_FAILED(TransportState::Current().RemoveSessionConnection(m_sessionEndpointDeviceInterfaceId));
+ // Create the endpoint for Windows MIDI Services clients
+ HRESULT hr = S_OK;
+
+ hr = TransportState::Current().GetEndpointManager()->CreateNewEndpoint(
+ MidiNetworkConnectionRole::ConnectionWindowsIsClient,
+ remoteHostUmpEndpointName,
+ remoteHostProductInstanceId,
+ m_remoteHostName,
+ m_remotePort,
+ m_createUmpEndpointsOnly,
+ newDeviceInstanceId,
+ newEndpointDeviceInterfaceId
+ );
- m_sessionActive = false;
- m_sessionDeviceInstanceId.clear();
- m_sessionEndpointDeviceInterfaceId.clear();
+ if (SUCCEEDED(hr))
+ {
+ TraceLoggingWrite(
+ MidiNetworkMidiTransportTelemetryProvider::Provider(),
+ MIDI_TRACE_EVENT_INFO,
+ TraceLoggingString(__FUNCTION__, MIDI_TRACE_EVENT_LOCATION_FIELD),
+ TraceLoggingLevel(WINEVENT_LEVEL_INFO),
+ TraceLoggingPointer(this, "this"),
+ TraceLoggingWideString(L"Created MIDI endpoint", MIDI_TRACE_EVENT_MESSAGE_FIELD)
+ );
+
+ m_sessionEndpointDeviceInterfaceId = internal::NormalizeEndpointInterfaceIdWStringCopy(newEndpointDeviceInterfaceId);
+ m_sessionDeviceInstanceId = internal::NormalizeDeviceInstanceIdWStringCopy(newDeviceInstanceId);
+
+ // this is what the BiDi uses when it is created
+ RETURN_IF_FAILED(TransportState::Current().AssociateMidiEndpointWithConnection(m_sessionEndpointDeviceInterfaceId.c_str(), m_remoteHostName, m_remotePort.c_str()));
+
+ // protocol negotiation needs to happen here, not in the endpoint creation
+ // because we need to wire up the connection first. Bit of a race.
+
+ LOG_IF_FAILED(TransportState::Current().GetEndpointManager()->InitiateDiscoveryAndNegotiation(m_sessionEndpointDeviceInterfaceId));
+
+ m_sessionActive = true;
+ }
+ else
+ {
+ TraceLoggingWrite(
+ MidiNetworkMidiTransportTelemetryProvider::Provider(),
+ MIDI_TRACE_EVENT_ERROR,
+ TraceLoggingString(__FUNCTION__, MIDI_TRACE_EVENT_LOCATION_FIELD),
+ TraceLoggingLevel(WINEVENT_LEVEL_ERROR),
+ TraceLoggingPointer(this, "this"),
+ TraceLoggingWideString(L"Failed to create MIDI endpoint.", MIDI_TRACE_EVENT_MESSAGE_FIELD),
+ TraceLoggingHResult(hr, "hresult")
+ );
+
+ // let the other side know that we can't create the session
+
+ auto lock = m_socketWriterLock.lock();
+ RETURN_IF_FAILED(m_writer->WriteUdpPacketHeader());
+ // TODO: Move string to resources for localization
+ RETURN_IF_FAILED(m_writer->WriteCommandBye(MidiNetworkCommandByeReason::CommandByeReasonCommon_Undefined, L"Error attempting to create endpoint. Bad data?"));
+ RETURN_IF_FAILED(m_writer->Send());
+
+ // exit out of here, and log while we're at it
+ RETURN_IF_FAILED(hr);
+ }
}
+
}
else
{
- // not an active session. Nothing to clean up
+ TraceLoggingWrite(
+ MidiNetworkMidiTransportTelemetryProvider::Provider(),
+ MIDI_TRACE_EVENT_ERROR,
+ TraceLoggingString(__FUNCTION__, MIDI_TRACE_EVENT_LOCATION_FIELD),
+ TraceLoggingLevel(WINEVENT_LEVEL_ERROR),
+ TraceLoggingPointer(this, "this"),
+ TraceLoggingWideString(L"We are not in the client role, but received an invitation accept. Not normal.", MIDI_TRACE_EVENT_MESSAGE_FIELD)
+ );
+
+ // we are a host, not a client, so NAK this per spec 6.4
+ auto lock = m_socketWriterLock.lock();
+ RETURN_IF_FAILED(m_writer->WriteUdpPacketHeader());
+
+ // TODO: Move string to resources for localization
+ RETURN_IF_FAILED(m_writer->WriteCommandNAK(header.HeaderWord, MidiNetworkCommandNAKReason::CommandNAKReason_CommandNotExpected, L"Unexpected invitation accept sent to host."));
+ RETURN_IF_FAILED(m_writer->Send());
}
+
+
return S_OK;
}
@@ -252,17 +452,18 @@ MidiNetworkConnection::HandleIncomingInvitation(
TraceLoggingWideString(L"Enter", MIDI_TRACE_EVENT_MESSAGE_FIELD)
);
-
UNREFERENCED_PARAMETER(capabilities);
RETURN_HR_IF_NULL(E_UNEXPECTED, m_writer);
+ RETURN_HR_IF_NULL(E_UNEXPECTED, TransportState::Current().GetEndpointManager());
if (m_role == MidiNetworkConnectionRole::ConnectionWindowsIsHost)
{
if (m_sessionActive)
{
- // if the session is already active, we simply accept
+ // if the session is already active, we simply accept it again
+ auto lock = m_socketWriterLock.lock();
RETURN_IF_FAILED(m_writer->WriteUdpPacketHeader());
RETURN_IF_FAILED(m_writer->WriteCommandInvitationReplyAccepted(m_thisEndpointName, m_thisProductInstanceId));
RETURN_IF_FAILED(m_writer->Send());
@@ -281,50 +482,51 @@ MidiNetworkConnection::HandleIncomingInvitation(
if (TransportState::Current().GetEndpointManager()->IsInitialized())
{
-
// Create the endpoint for Windows MIDI Services clients
// This will also kick off discovery and protocol negotiation
HRESULT hr = S_OK;
- if (TransportState::Current().GetEndpointManager() == nullptr)
- {
- hr = E_UNEXPECTED;
- }
- else
- {
- hr = TransportState::Current().GetEndpointManager()->CreateNewEndpoint(
- MidiNetworkConnectionRole::ConnectionWindowsIsHost,
- clientUmpEndpointName,
- clientProductInstanceId,
- m_remoteHostName,
- m_remotePort,
- newDeviceInstanceId,
- newEndpointDeviceInterfaceId
- );
- }
+
+ //UNREFERENCED_PARAMETER(clientProductInstanceId);
+ //UNREFERENCED_PARAMETER(clientUmpEndpointName);
+ hr = TransportState::Current().GetEndpointManager()->CreateNewEndpoint(
+ MidiNetworkConnectionRole::ConnectionWindowsIsHost,
+ clientUmpEndpointName,
+ clientProductInstanceId,
+ m_remoteHostName,
+ m_remotePort,
+ m_createUmpEndpointsOnly,
+ newDeviceInstanceId,
+ newEndpointDeviceInterfaceId
+ );
if (SUCCEEDED(hr))
{
- m_sessionEndpointDeviceInterfaceId = newEndpointDeviceInterfaceId;
- m_sessionDeviceInstanceId = newDeviceInstanceId;
-
- std::shared_ptr connection;
- connection.reset(this);
+ m_sessionEndpointDeviceInterfaceId = internal::NormalizeEndpointInterfaceIdWStringCopy(newEndpointDeviceInterfaceId);
+ m_sessionDeviceInstanceId = internal::NormalizeDeviceInstanceIdWStringCopy(newDeviceInstanceId);
// this is what the BiDi uses when it is created
- TransportState::Current().AddSessionConnection(m_sessionEndpointDeviceInterfaceId, connection);
+ RETURN_IF_FAILED(TransportState::Current().AssociateMidiEndpointWithConnection(m_sessionEndpointDeviceInterfaceId.c_str(), m_remoteHostName, m_remotePort.c_str()));
// protocol negotiation needs to happen here, not in the endpoint creation
// because we need to wire up the connection first. Bit of a race.
LOG_IF_FAILED(TransportState::Current().GetEndpointManager()->InitiateDiscoveryAndNegotiation(m_sessionEndpointDeviceInterfaceId));
+
+ // Tell the remote endpoint we've accepted the invitation
+
+ auto lock = m_socketWriterLock.lock();
+ RETURN_IF_FAILED(m_writer->WriteUdpPacketHeader());
+ RETURN_IF_FAILED(m_writer->WriteCommandInvitationReplyAccepted(m_thisEndpointName, m_thisProductInstanceId));
+ RETURN_IF_FAILED(m_writer->Send());
+
+ m_sessionActive = true;
}
else
{
// let the other side know that we can't create the session
auto lock = m_socketWriterLock.lock();
-
RETURN_IF_FAILED(m_writer->WriteUdpPacketHeader());
// TODO: Move string to resources for localization
RETURN_IF_FAILED(m_writer->WriteCommandBye(MidiNetworkCommandByeReason::CommandByeReasonCommon_Undefined, L"Error attempting to create endpoint. Bad data?"));
@@ -333,23 +535,12 @@ MidiNetworkConnection::HandleIncomingInvitation(
// exit out of here, and log while we're at it
RETURN_IF_FAILED(hr);
}
-
- // Tell the remote endpoint we've accepted the invitation
-
- auto lock = m_socketWriterLock.lock();
-
- RETURN_IF_FAILED(m_writer->WriteUdpPacketHeader());
- RETURN_IF_FAILED(m_writer->WriteCommandInvitationReplyAccepted(m_thisEndpointName, m_thisProductInstanceId));
- RETURN_IF_FAILED(m_writer->Send());
-
- m_sessionActive = true;
}
else
{
// this shouldn't happen, but we handle it anyway
auto lock = m_socketWriterLock.lock();
-
RETURN_IF_FAILED(m_writer->WriteUdpPacketHeader());
// TODO: Move string to resources for localization
RETURN_IF_FAILED(m_writer->WriteCommandBye(MidiNetworkCommandByeReason::CommandByeReasonCommon_Undefined, L"Host is unable to accept invitations at this time."));
@@ -384,6 +575,8 @@ MidiNetworkConnection::HandleIncomingPing(uint32_t const pingId)
TraceLoggingWideString(L"Enter", MIDI_TRACE_EVENT_MESSAGE_FIELD)
);
+ RETURN_HR_IF_NULL(E_UNEXPECTED, m_writer);
+
auto lock = m_socketWriterLock.lock();
RETURN_IF_FAILED(m_writer->WriteUdpPacketHeader());
@@ -435,6 +628,23 @@ std::wstring MidiNetworkConnection::ReadUtf8String(
return ws;
}
+// TODO: This needs some checking. If we call this too many times, we
+// should close the connection. Also, if there's a retransmit request
+// already in progress, we shouldn't be asking for more if there's any
+// overlap at all.
+HRESULT
+MidiNetworkConnection::RequestMissingPackets()
+{
+ auto lock = m_socketWriterLock.lock();
+
+ // this requests all packets after the last one we received
+
+ RETURN_IF_FAILED(m_writer->WriteUdpPacketHeader());
+ RETURN_IF_FAILED(m_writer->WriteCommandRetransmitRequest(m_lastReceivedUmpCommandSequenceNumber + 1, 0));
+ RETURN_IF_FAILED(m_writer->Send());
+
+ return S_OK;
+}
_Use_decl_annotations_
HRESULT
@@ -499,13 +709,17 @@ MidiNetworkConnection::ProcessIncomingMessage(
case CommandClientToHost_InvitationWithAuthentication:
+ // TODO
break;
case CommandClientToHost_InvitationWithUserAuthentication:
+ // TODO
break;
case CommandCommon_UmpData:
{
+ // todo: If the session isn't active, we should reject any UMP data and NAK or BYE (see spec)
+
uint8_t numberOfWords = commandHeader.HeaderData.CommandPayloadLength;
MidiSequenceNumber sequenceNumber(commandHeader.HeaderData.CommandSpecificData.AsUInt16);
@@ -513,69 +727,64 @@ MidiNetworkConnection::ProcessIncomingMessage(
// TODO: a message can have zero MIDI words, but a valid sequence number. Need to handle that.
-
- // TODO: This logic doesn't handle wrap. Need to have a window
if (sequenceNumber <= m_lastReceivedUmpCommandSequenceNumber)
{
-
-
- // todo: skip all words
+ if (numberOfWords > 0)
+ {
+ // Skip all words in this command message because it is FEC.
+ for (uint8_t i = 0; i < numberOfWords && reader.UnconsumedBufferLength() >= sizeof(uint32_t); i++)
+ {
+ reader.ReadUInt32();
+ }
+ }
+ else
+ {
+ // empty UMP message. This is fine
+ }
}
else if (sequenceNumber == m_lastReceivedUmpCommandSequenceNumber + 1)
{
- // todo: process UMP data
-
+ // Process UMP data because this is the next expected sequence number
m_lastReceivedUmpCommandSequenceNumber = sequenceNumber;
- }
- else
- {
- // skipped data. Re-request missing packets
- }
-
-
-
-
-
-
-
- for (uint8_t i = 0; i < numberOfWords; i++)
- {
- if (reader.UnconsumedBufferLength() >= sizeof(uint32_t))
+ if (numberOfWords > 0)
{
- // TODO: We need to check this sequence number, and if there's any gap from the last received, drop this data and request a retransmit
-
- if (sequenceNumber > m_lastReceivedUmpCommandSequenceNumber)
+ for (uint8_t i = 0; i < numberOfWords; i++)
{
- // add to our vector
- words.push_back(reader.ReadUInt32());
-
- }
- else
- {
- // just read and discard
- reader.ReadUInt32();
+ if (reader.UnconsumedBufferLength() >= sizeof(uint32_t))
+ {
+ // add to our vector
+ words.push_back(reader.ReadUInt32());
+ }
+ else
+ {
+ // bad / incomplete packet
+
+ auto lock = m_socketWriterLock.lock();
+ RETURN_IF_FAILED(m_writer->WriteUdpPacketHeader());
+ RETURN_IF_FAILED(m_writer->WriteCommandNAK(commandHeader.HeaderWord, MidiNetworkCommandNAKReason::CommandNAKReason_CommandMalformed, L"Packet data incomplete"));
+ RETURN_IF_FAILED(m_writer->Send());
+
+ // TODO: We should request a retransmit of this packet
+
+ RETURN_IF_FAILED(E_FAIL);
+ }
}
}
else
{
- // bad / incomplete packet
-
- auto lock = m_socketWriterLock.lock();
- m_writer->WriteUdpPacketHeader();
- m_writer->WriteCommandNAK(commandHeader.HeaderWord, MidiNetworkCommandNAKReason::CommandNAKReason_CommandMalformed, L"Packet data incomplete");
- m_writer->Send();
-
- // TODO: We should request a retransmit of this packet
-
- return E_FAIL;
+ // empty UMP messages. That's fine.
}
}
-
- if (sequenceNumber > m_lastReceivedUmpCommandSequenceNumber)
+ else if (sequenceNumber > m_lastReceivedUmpCommandSequenceNumber + 1)
{
- m_lastReceivedUmpCommandSequenceNumber = sequenceNumber;
+ // skipped data. Re-request missing packets
+
+ // TODO: We should make sure we don't keep calling this for each
+ // UMP Data message within the same UDP packet.
+
+ RETURN_IF_FAILED(RequestMissingPackets());
}
if (words.size() > 0)
@@ -606,6 +815,31 @@ MidiNetworkConnection::ProcessIncomingMessage(
// TODO: handle session reset
break;
+ case CommandHostToClient_InvitationReplyAccepted:
+ {
+ uint16_t endpointNameLengthInBytes = commandHeader.HeaderData.CommandSpecificData.AsBytes.Byte1 * sizeof(uint32_t);
+ uint16_t productInstanceIdLengthInBytes = commandHeader.HeaderData.CommandPayloadLength * sizeof(uint32_t) - endpointNameLengthInBytes;
+
+ auto hostEndpointName = ReadUtf8String(reader, endpointNameLengthInBytes);
+ auto hostProductInstanceId = ReadUtf8String(reader, productInstanceIdLengthInBytes);
+
+ LOG_IF_FAILED(HandleIncomingInvitationReplyAccepted(commandHeader, hostEndpointName, hostProductInstanceId));
+ }
+ break;
+
+ case CommandHostToClient_InvitationReplyPending:
+ // TODO: not sure we need to do anything with this
+ break;
+
+ case CommandHostToClient_InvitationReplyAuthenticationRequired:
+ // TODO
+ break;
+
+ case CommandHostToClient_InvitationReplyUserAuthenticationRequired:
+ // TODO
+ break;
+
+
default:
// TODO: unexpected command code. Send NAK.
@@ -654,7 +888,8 @@ MidiNetworkConnection::HandleIncomingRetransmitRequest(uint16_t const startingSe
TraceLoggingWideString(L"Enter", MIDI_TRACE_EVENT_MESSAGE_FIELD)
);
- // TODO: A packet count of 0x0000 means to send all the data we have starting at the startingSequenceNumber
+ RETURN_HR_IF_NULL(E_UNEXPECTED, m_writer);
+
// find the starting sequence number in the circular buffer
@@ -686,6 +921,8 @@ MidiNetworkConnection::HandleIncomingRetransmitRequest(uint16_t const startingSe
auto endPacket = firstPacket + retransmitPacketCount;
+ // packet count of 0 means to send everything we've got
+
if (retransmitPacketCount == 0x0000)
{
endPacket = m_retransmitBuffer.end();
@@ -705,6 +942,30 @@ MidiNetworkConnection::HandleIncomingRetransmitRequest(uint16_t const startingSe
}
+HRESULT
+MidiNetworkConnection::SendInvitation()
+{
+ TraceLoggingWrite(
+ MidiNetworkMidiTransportTelemetryProvider::Provider(),
+ MIDI_TRACE_EVENT_INFO,
+ TraceLoggingString(__FUNCTION__, MIDI_TRACE_EVENT_LOCATION_FIELD),
+ TraceLoggingLevel(WINEVENT_LEVEL_INFO),
+ TraceLoggingPointer(this, "this"),
+ TraceLoggingWideString(L"Enter", MIDI_TRACE_EVENT_MESSAGE_FIELD)
+ );
+
+ auto lock = m_socketWriterLock.lock();
+
+ // TODO: When we support security, need to update the capabilities
+
+ RETURN_IF_FAILED(m_writer->WriteUdpPacketHeader());
+ RETURN_IF_FAILED(m_writer->WriteCommandInvitation(MidiNetworkCommandInvitationCapabilities::Capabilities_None, m_thisEndpointName, m_thisProductInstanceId));
+ RETURN_IF_FAILED(m_writer->Send());
+
+ return S_OK;
+}
+
+
_Use_decl_annotations_
HRESULT
MidiNetworkConnection::SendMidiMessagesToNetwork(std::vector const& words)
@@ -718,6 +979,8 @@ MidiNetworkConnection::SendMidiMessagesToNetwork(std::vector const& wo
TraceLoggingWideString(L"Enter", MIDI_TRACE_EVENT_MESSAGE_FIELD)
);
+ RETURN_HR_IF_NULL(E_UNEXPECTED, m_writer);
+
auto sequenceNumber = ++m_lastSentUmpCommandSequenceNumber;
auto lock = m_socketWriterLock.lock();
@@ -780,8 +1043,36 @@ MidiNetworkConnection::Shutdown()
TraceLoggingWideString(L"Enter", MIDI_TRACE_EVENT_MESSAGE_FIELD)
);
+ if (m_callback)
+ {
+ m_callback = nullptr;
+ }
+ if (m_sessionActive)
+ {
+ RETURN_IF_FAILED(TransportState::Current().DisassociateMidiEndpointFromConnection(m_sessionEndpointDeviceInterfaceId));
+ m_sessionActive = false;
+ }
+
+ if (m_writer != nullptr)
+ {
+ auto lock = m_socketWriterLock.lock();
+ RETURN_IF_FAILED(m_writer->WriteUdpPacketHeader());
+ RETURN_IF_FAILED(m_writer->WriteCommandBye(MidiNetworkCommandByeReason::CommandByeReasonCommon_PowerDown, L"Connection closing."));
+ RETURN_IF_FAILED(m_writer->Send());
+
+ LOG_IF_FAILED(m_writer->Shutdown());
+ m_writer = nullptr;
+ }
+ TraceLoggingWrite(
+ MidiNetworkMidiTransportTelemetryProvider::Provider(),
+ MIDI_TRACE_EVENT_INFO,
+ TraceLoggingString(__FUNCTION__, MIDI_TRACE_EVENT_LOCATION_FIELD),
+ TraceLoggingLevel(WINEVENT_LEVEL_INFO),
+ TraceLoggingPointer(this, "this"),
+ TraceLoggingWideString(L"Exit", MIDI_TRACE_EVENT_MESSAGE_FIELD)
+ );
return S_OK;
}
diff --git a/src/api/Transport/UdpNetworkMidi2Transport/MidiNetworkConnection.h b/src/api/Transport/UdpNetworkMidi2Transport/MidiNetworkConnection.h
index 0e9080683..18de33706 100644
--- a/src/api/Transport/UdpNetworkMidi2Transport/MidiNetworkConnection.h
+++ b/src/api/Transport/UdpNetworkMidi2Transport/MidiNetworkConnection.h
@@ -32,7 +32,8 @@ class MidiNetworkConnection
_In_ std::wstring const& thisEndpointName,
_In_ std::wstring const& thisProductInstanceId,
_In_ uint16_t const retransmitBufferMaxCommandPacketCount,
- _In_ uint8_t const maxForwardErrorCorrectionCommandPacketCount
+ _In_ uint8_t const maxForwardErrorCorrectionCommandPacketCount,
+ _In_ bool createUmpEndpointsOnly
);
HRESULT Shutdown();
@@ -48,12 +49,13 @@ class MidiNetworkConnection
_In_ PVOID const bytes,
_In_ UINT const byteCount);
+ HRESULT SendInvitation();
- HRESULT SetMidiCallback(
- _In_ IMidiCallback* callback
+ HRESULT ConnectMidiCallback(
+ _In_ wil::com_ptr_nothrow callback
);
- HRESULT RemoveMidiCallback();
+ HRESULT DisconnectMidiCallback();
// todo: session info, connection to bidi streams, etc.
@@ -61,8 +63,16 @@ class MidiNetworkConnection
HRESULT StartOutboundProcessingThreads();
+ HRESULT ResetSequenceNumbers();
+ HRESULT EndActiveSession();
+ HRESULT RequestMissingPackets();
+ bool m_createUmpEndpointsOnly{ true };
+
+
+ uint32_t m_emptyUmpIterationCounter{ 0 }; // this will increment over time
+ uint32_t m_emptyUmpIterationIntervalBeforeSending{ 10 }; // number of background thread iterations with no data before we send empty UMP packets. This changes over time.
MidiNetworkConnectionRole m_role{};
@@ -75,7 +85,7 @@ class MidiNetworkConnection
std::wstring m_sessionEndpointDeviceInterfaceId{}; // swd
std::wstring m_sessionDeviceInstanceId{}; // what we used to create/delete the device
bool m_sessionActive{ false };
- IMidiCallback* m_callback{ nullptr };
+ wil::com_ptr_nothrow m_callback{ nullptr };
winrt::Windows::Networking::HostName m_remoteHostName{ nullptr };
std::wstring m_remotePort{ };
@@ -96,6 +106,11 @@ class MidiNetworkConnection
_In_ std::wstring const& clientUmpEndpointName,
_In_ std::wstring const& clientProductInstanceId);
+ HRESULT HandleIncomingInvitationReplyAccepted(
+ _In_ MidiNetworkCommandPacketHeader const& header,
+ _In_ std::wstring const& remoteHostUmpEndpointName,
+ _In_ std::wstring const& remoteHostProductInstanceId);
+
HRESULT HandleIncomingBye();
// this buffer holds the last n ping messages used to calculate latency
diff --git a/src/api/Transport/UdpNetworkMidi2Transport/MidiNetworkDataWriter.cpp b/src/api/Transport/UdpNetworkMidi2Transport/MidiNetworkDataWriter.cpp
index 88e33f4ac..006192c96 100644
--- a/src/api/Transport/UdpNetworkMidi2Transport/MidiNetworkDataWriter.cpp
+++ b/src/api/Transport/UdpNetworkMidi2Transport/MidiNetworkDataWriter.cpp
@@ -12,6 +12,9 @@ HRESULT
MidiNetworkDataWriter::Send()
{
RETURN_HR_IF_NULL(E_UNEXPECTED, m_dataWriter);
+ RETURN_HR_IF_NULL(E_UNEXPECTED, m_stream);
+
+ auto lock = m_dataWriterLock.lock();
m_dataWriter.StoreAsync().get();
@@ -24,6 +27,9 @@ HRESULT
MidiNetworkDataWriter::WriteUdpPacketHeader()
{
RETURN_HR_IF_NULL(E_UNEXPECTED, m_dataWriter);
+ RETURN_HR_IF_NULL(E_UNEXPECTED, m_stream);
+
+ auto lock = m_dataWriterLock.lock();
m_dataWriter.WriteUInt32(MIDI_UDP_PAYLOAD_HEADER);
@@ -39,6 +45,9 @@ MidiNetworkDataWriter::InternalWriteCommandHeader(
)
{
RETURN_HR_IF_NULL(E_UNEXPECTED, m_dataWriter);
+ RETURN_HR_IF_NULL(E_UNEXPECTED, m_stream);
+
+ auto lock = m_dataWriterLock.lock();
m_dataWriter.WriteByte(commandCode);
m_dataWriter.WriteByte(payloadLengthIn32BitWords);
@@ -57,6 +66,10 @@ MidiNetworkDataWriter::InternalWriteCommandHeader(
)
{
RETURN_HR_IF_NULL(E_UNEXPECTED, m_dataWriter);
+ RETURN_HR_IF_NULL(E_UNEXPECTED, m_stream);
+
+ // don't lock here. the calling method will lock
+ //auto lock = m_dataWriterLock.lock();
m_dataWriter.WriteByte(commandCode);
m_dataWriter.WriteByte(payloadLengthIn32BitWords);
@@ -79,9 +92,10 @@ MidiNetworkDataWriter::Initialize(
winrt::Windows::Storage::Streams::DataWriter writer(m_stream);
- m_dataWriter = std::move(writer);
+ m_dataWriter = writer;
RETURN_HR_IF_NULL(E_UNEXPECTED, m_dataWriter);
+ // per Network MIDI 2.0 specification
m_dataWriter.ByteOrder(winrt::Windows::Storage::Streams::ByteOrder::BigEndian);
return S_OK;
@@ -92,11 +106,13 @@ HRESULT
MidiNetworkDataWriter::WriteCommandPing(uint32_t pingId)
{
RETURN_HR_IF_NULL(E_UNEXPECTED, m_dataWriter);
+ RETURN_HR_IF_NULL(E_UNEXPECTED, m_stream);
byte payloadLengthIn32BitWords{ 1 };
- RETURN_IF_FAILED(InternalWriteCommandHeader(MidiNetworkCommandCode::CommandCommon_Ping, payloadLengthIn32BitWords, 0));
+ auto lock = m_dataWriterLock.lock();
+ RETURN_IF_FAILED(InternalWriteCommandHeader(MidiNetworkCommandCode::CommandCommon_Ping, payloadLengthIn32BitWords, 0));
m_dataWriter.WriteUInt32(pingId);
return S_OK;
@@ -107,10 +123,13 @@ HRESULT
MidiNetworkDataWriter::WriteCommandPingReply(uint32_t pingId)
{
RETURN_HR_IF_NULL(E_UNEXPECTED, m_dataWriter);
+ RETURN_HR_IF_NULL(E_UNEXPECTED, m_stream);
+
byte payloadLengthIn32BitWords{ 1 };
- RETURN_IF_FAILED(InternalWriteCommandHeader(MidiNetworkCommandCode::CommandCommon_PingReply, payloadLengthIn32BitWords, 0));
+ auto lock = m_dataWriterLock.lock();
+ RETURN_IF_FAILED(InternalWriteCommandHeader(MidiNetworkCommandCode::CommandCommon_PingReply, payloadLengthIn32BitWords, 0));
m_dataWriter.WriteUInt32(pingId);
return S_OK;
@@ -128,6 +147,7 @@ MidiNetworkDataWriter::WriteCommandNAK(
)
{
RETURN_HR_IF_NULL(E_UNEXPECTED, m_dataWriter);
+ RETURN_HR_IF_NULL(E_UNEXPECTED, m_stream);
byte payloadLengthIn32BitWords{ 1 }; // 1 here to account for the original command header
@@ -140,6 +160,8 @@ MidiNetworkDataWriter::WriteCommandNAK(
payloadLengthIn32BitWords += CalculatePaddedStringSizeIn32BitWords(utf8, MIDI_MAX_NAK_MESSAGE_BYTE_COUNT);
+ auto lock = m_dataWriterLock.lock();
+
RETURN_IF_FAILED(InternalWriteCommandHeader(MidiNetworkCommandCode::CommandCommon_NAK, payloadLengthIn32BitWords, reason, 0));
m_dataWriter.WriteUInt32(originalCommandHeader);
@@ -159,6 +181,7 @@ MidiNetworkDataWriter::WriteCommandBye(
)
{
RETURN_HR_IF_NULL(E_UNEXPECTED, m_dataWriter);
+ RETURN_HR_IF_NULL(E_UNEXPECTED, m_stream);
byte payloadLengthIn32BitWords{ 0 }; // 1 here to account for the original command header
@@ -171,6 +194,8 @@ MidiNetworkDataWriter::WriteCommandBye(
payloadLengthIn32BitWords += CalculatePaddedStringSizeIn32BitWords(utf8, MIDI_MAX_BYE_MESSAGE_BYTE_COUNT);
+ auto lock = m_dataWriterLock.lock();
+
RETURN_IF_FAILED(InternalWriteCommandHeader(MidiNetworkCommandCode::CommandCommon_Bye, payloadLengthIn32BitWords, reason, 0));
// write the text
@@ -183,6 +208,9 @@ HRESULT
MidiNetworkDataWriter::WriteCommandByeReply()
{
RETURN_HR_IF_NULL(E_UNEXPECTED, m_dataWriter);
+ RETURN_HR_IF_NULL(E_UNEXPECTED, m_stream);
+
+ auto lock = m_dataWriterLock.lock();
RETURN_IF_FAILED(InternalWriteCommandHeader(MidiNetworkCommandCode::CommandCommon_ByeReply, MIDI_COMMAND_PAYLOAD_LENGTH_NO_PAYLOAD, 0));
@@ -193,6 +221,9 @@ HRESULT
MidiNetworkDataWriter::WriteCommandSessionReset()
{
RETURN_HR_IF_NULL(E_UNEXPECTED, m_dataWriter);
+ RETURN_HR_IF_NULL(E_UNEXPECTED, m_stream);
+
+ auto lock = m_dataWriterLock.lock();
RETURN_IF_FAILED(InternalWriteCommandHeader(MidiNetworkCommandCode::CommandCommon_SessionReset, MIDI_COMMAND_PAYLOAD_LENGTH_NO_PAYLOAD, 0));
@@ -203,12 +234,17 @@ HRESULT
MidiNetworkDataWriter::WriteCommandSessionResetReply()
{
RETURN_HR_IF_NULL(E_UNEXPECTED, m_dataWriter);
+ RETURN_HR_IF_NULL(E_UNEXPECTED, m_stream);
+
+ auto lock = m_dataWriterLock.lock();
RETURN_IF_FAILED(InternalWriteCommandHeader(MidiNetworkCommandCode::CommandCommon_SessionResetReply, MIDI_COMMAND_PAYLOAD_LENGTH_NO_PAYLOAD, 0));
return S_OK;
}
+#define MIDI_NETWORK_RESERVED_UINT16 ((uint16_t)0)
+
_Use_decl_annotations_
HRESULT
MidiNetworkDataWriter::WriteCommandRetransmitRequest(
@@ -217,13 +253,16 @@ MidiNetworkDataWriter::WriteCommandRetransmitRequest(
)
{
RETURN_HR_IF_NULL(E_UNEXPECTED, m_dataWriter);
+ RETURN_HR_IF_NULL(E_UNEXPECTED, m_stream);
byte payloadLengthIn32BitWords{ 1 }; // 1 here to account for number of UMP commands
+ auto lock = m_dataWriterLock.lock();
+
RETURN_IF_FAILED(InternalWriteCommandHeader(MidiNetworkCommandCode::CommandCommon_RetransmitRequest, payloadLengthIn32BitWords, sequenceNumber.Value()));
m_dataWriter.WriteUInt16(numberOfUmpCommands);
- m_dataWriter.WriteUInt16(0);
+ m_dataWriter.WriteUInt16(MIDI_NETWORK_RESERVED_UINT16);
return S_OK;
}
@@ -237,13 +276,16 @@ MidiNetworkDataWriter::WriteCommandRetransmitError(
)
{
RETURN_HR_IF_NULL(E_UNEXPECTED, m_dataWriter);
+ RETURN_HR_IF_NULL(E_UNEXPECTED, m_stream);
byte payloadLengthIn32BitWords{ 1 }; // 1 here to account for sequence number
+ auto lock = m_dataWriterLock.lock();
+
RETURN_IF_FAILED(InternalWriteCommandHeader(MidiNetworkCommandCode::CommandCommon_RetransmitError, payloadLengthIn32BitWords, errorReason, 0));
m_dataWriter.WriteUInt16(sequenceNumber.Value());
- m_dataWriter.WriteUInt16(0);
+ m_dataWriter.WriteUInt16(MIDI_NETWORK_RESERVED_UINT16);
return S_OK;
}
@@ -258,6 +300,7 @@ MidiNetworkDataWriter::WriteCommandInvitation(
)
{
RETURN_HR_IF_NULL(E_UNEXPECTED, m_dataWriter);
+ RETURN_HR_IF_NULL(E_UNEXPECTED, m_stream);
byte payloadLengthIn32BitWords{ 0 };
@@ -270,6 +313,9 @@ MidiNetworkDataWriter::WriteCommandInvitation(
payloadLengthIn32BitWords += umpEndpointNameLengthIn32BitWords;
payloadLengthIn32BitWords += productInstanceIdLengthIn32BitWords;
+
+ auto lock = m_dataWriterLock.lock();
+
RETURN_IF_FAILED(InternalWriteCommandHeader(MidiNetworkCommandCode::CommandClientToHost_Invitation, payloadLengthIn32BitWords, umpEndpointNameLengthIn32BitWords, capabilities));
WritePaddedString(endpointNameUtf8, MIDI_MAX_UMP_ENDPOINT_NAME_BYTE_COUNT);
@@ -286,10 +332,15 @@ MidiNetworkDataWriter::WriteCommandInvitationWithAuthentication(
)
{
RETURN_HR_IF_NULL(E_UNEXPECTED, m_dataWriter);
+ RETURN_HR_IF_NULL(E_UNEXPECTED, m_stream);
UNREFERENCED_PARAMETER(cryptoNonce);
UNREFERENCED_PARAMETER(sharedSecret);
+ auto lock = m_dataWriterLock.lock();
+
+ // todo: we should NAK this for now
+
return E_NOTIMPL;
}
@@ -302,11 +353,16 @@ MidiNetworkDataWriter::WriteCommandInvitationWithUserAuthentication(
)
{
RETURN_HR_IF_NULL(E_UNEXPECTED, m_dataWriter);
+ RETURN_HR_IF_NULL(E_UNEXPECTED, m_stream);
UNREFERENCED_PARAMETER(cryptoNonce);
UNREFERENCED_PARAMETER(userName);
UNREFERENCED_PARAMETER(password);
+ auto lock = m_dataWriterLock.lock();
+
+ // TODO: we should NAK this for now
+
return E_NOTIMPL;
}
@@ -318,6 +374,10 @@ MidiNetworkDataWriter::WriteCommandInvitationReplyAccepted(
)
{
RETURN_HR_IF_NULL(E_UNEXPECTED, m_dataWriter);
+ RETURN_HR_IF_NULL(E_UNEXPECTED, m_stream);
+
+ RETURN_HR_IF(E_INVALIDARG, hostUmpEndpointName.empty());
+ RETURN_HR_IF(E_INVALIDARG, hostProductInstanceId.empty());
byte payloadLengthIn32BitWords{ 0 };
@@ -330,6 +390,8 @@ MidiNetworkDataWriter::WriteCommandInvitationReplyAccepted(
payloadLengthIn32BitWords += productInstanceIdLengthIn32BitWords;
+ auto lock = m_dataWriterLock.lock();
+
RETURN_IF_FAILED(InternalWriteCommandHeader(MidiNetworkCommandCode::CommandHostToClient_InvitationReplyAccepted, payloadLengthIn32BitWords, umpEndpointNameLengthIn32BitWords, 0));
WritePaddedString(endpointNameUtf8, MIDI_MAX_UMP_ENDPOINT_NAME_BYTE_COUNT);
@@ -346,10 +408,15 @@ MidiNetworkDataWriter::WriteCommandInvitationReplyPending(
)
{
RETURN_HR_IF_NULL(E_UNEXPECTED, m_dataWriter);
+ RETURN_HR_IF_NULL(E_UNEXPECTED, m_stream);
UNREFERENCED_PARAMETER(hostUmpEndpointName);
UNREFERENCED_PARAMETER(hostProductInstanceId);
+ auto lock = m_dataWriterLock.lock();
+
+ // TODO
+
return E_NOTIMPL;
}
@@ -364,12 +431,17 @@ MidiNetworkDataWriter::WriteCommandInvitationReplyAuthenticationRequired(
)
{
RETURN_HR_IF_NULL(E_UNEXPECTED, m_dataWriter);
+ RETURN_HR_IF_NULL(E_UNEXPECTED, m_stream);
UNREFERENCED_PARAMETER(cryptoNonce);
UNREFERENCED_PARAMETER(authenticationState);
UNREFERENCED_PARAMETER(hostUmpEndpointName);
UNREFERENCED_PARAMETER(hostProductInstanceId);
+ auto lock = m_dataWriterLock.lock();
+
+ // TODO
+
return E_NOTIMPL;
}
@@ -384,12 +456,17 @@ MidiNetworkDataWriter::WriteCommandInvitationReplyUserAuthenticationRequired(
)
{
RETURN_HR_IF_NULL(E_UNEXPECTED, m_dataWriter);
+ RETURN_HR_IF_NULL(E_UNEXPECTED, m_stream);
UNREFERENCED_PARAMETER(cryptoNonce);
UNREFERENCED_PARAMETER(authenticationState);
UNREFERENCED_PARAMETER(hostUmpEndpointName);
UNREFERENCED_PARAMETER(hostProductInstanceId);
+ auto lock = m_dataWriterLock.lock();
+
+ // TODO
+
return E_NOTIMPL;
}
@@ -405,9 +482,13 @@ MidiNetworkDataWriter::WriteCommandUmpMessages(
)
{
RETURN_HR_IF_NULL(E_UNEXPECTED, m_dataWriter);
+ RETURN_HR_IF_NULL(E_UNEXPECTED, m_stream);
RETURN_HR_IF(E_INVALIDARG, words.size() > MIDI_MAX_UMP_WORDS_PER_PACKET);
+
+ auto lock = m_dataWriterLock.lock();
+
RETURN_IF_FAILED(InternalWriteCommandHeader(MidiNetworkCommandCode::CommandCommon_UmpData, static_cast(words.size()), sequenceNumber.Value()));
// we make the assumption that the calling code has already validated that the words
@@ -424,8 +505,13 @@ MidiNetworkDataWriter::WriteCommandUmpMessages(
HRESULT
MidiNetworkDataWriter::Shutdown()
{
- m_dataWriter.Close();
- m_dataWriter = nullptr;
+ auto lock = m_dataWriterLock.lock();
+
+ if (m_dataWriter != nullptr)
+ {
+ m_dataWriter.Close();
+ m_dataWriter = nullptr;
+ }
m_stream = nullptr;
diff --git a/src/api/Transport/UdpNetworkMidi2Transport/MidiNetworkDataWriter.h b/src/api/Transport/UdpNetworkMidi2Transport/MidiNetworkDataWriter.h
index 2e58efbc6..6d29bc3f3 100644
--- a/src/api/Transport/UdpNetworkMidi2Transport/MidiNetworkDataWriter.h
+++ b/src/api/Transport/UdpNetworkMidi2Transport/MidiNetworkDataWriter.h
@@ -68,6 +68,9 @@ class MidiNetworkDataWriter
inline void WritePaddedString(_In_ std::string s, _In_ size_t maxByteCount)
{
+ // don't lock here. Calling method will lock
+ //auto lock = m_dataWriterLock.lock();
+
size_t count{ 0 };
for (auto const& ch : s)
@@ -121,5 +124,6 @@ class MidiNetworkDataWriter
winrt::Windows::Storage::Streams::IOutputStream m_stream{ nullptr };
winrt::Windows::Storage::Streams::DataWriter m_dataWriter{ nullptr };
-
+ // data writer doesn't support concurrent writes and will throw exceptions if you try
+ wil::critical_section m_dataWriterLock;
};
\ No newline at end of file
diff --git a/src/api/Transport/UdpNetworkMidi2Transport/MidiNetworkHost.cpp b/src/api/Transport/UdpNetworkMidi2Transport/MidiNetworkHost.cpp
index aee557544..43e3f6ee7 100644
--- a/src/api/Transport/UdpNetworkMidi2Transport/MidiNetworkHost.cpp
+++ b/src/api/Transport/UdpNetworkMidi2Transport/MidiNetworkHost.cpp
@@ -36,6 +36,8 @@ MidiNetworkHost::Initialize(
m_started = false;
+ m_createUmpEndpointsOnly = !hostDefinition.CreateMidi1Ports;
+
m_hostEndpointName = hostDefinition.UmpEndpointName;
m_hostProductInstanceId = hostDefinition.ProductInstanceId;
@@ -46,6 +48,14 @@ MidiNetworkHost::Initialize(
m_hostDefinition = hostDefinition;
+ DatagramSocket socket;
+ m_socket = std::move(socket);
+
+ m_socket.Control().DontFragment(true);
+ //m_socket.Control().InboundBufferSizeInBytes(10000);
+ m_socket.Control().QualityOfService(SocketQualityOfService::LowLatency);
+
+
if (m_hostDefinition.Advertise)
{
m_advertiser = std::make_shared();
@@ -65,6 +75,36 @@ MidiNetworkHost::Initialize(
return S_OK;
}
+_Use_decl_annotations_
+HRESULT
+MidiNetworkHost::CreateNetworkConnection(HostName const& remoteHostName, winrt::hstring const& remotePort)
+{
+ auto conn = std::make_shared();
+
+ if (conn)
+ {
+ RETURN_IF_FAILED(conn->Initialize(
+ MidiNetworkConnectionRole::ConnectionWindowsIsHost,
+ m_socket,
+ remoteHostName,
+ remotePort,
+ m_hostEndpointName,
+ m_hostProductInstanceId,
+ TransportState::Current().TransportSettings.RetransmitBufferMaxCommandPacketCount,
+ TransportState::Current().TransportSettings.ForwardErrorCorrectionMaxCommandPacketCount,
+ m_createUmpEndpointsOnly
+ ));
+
+ TransportState::Current().AddNetworkConnection(remoteHostName, remotePort, conn);
+ }
+ else
+ {
+ // could not create the connection object.
+ }
+
+ return S_OK;
+}
+
HRESULT
MidiNetworkHost::Start()
{
@@ -81,9 +121,9 @@ MidiNetworkHost::Start()
// wire up to handle incoming events
auto messageReceivedHandler = winrt::Windows::Foundation::TypedEventHandler(this, &MidiNetworkHost::OnMessageReceived);
- m_messageReceivedRevoker = m_socket.MessageReceived(winrt::auto_revoke, messageReceivedHandler);
+ m_messageReceivedEventToken = m_socket.MessageReceived(messageReceivedHandler);
- // this should have error checking
+ // TODO: this should have error checking
m_socket.BindServiceNameAsync(winrt::to_hstring(m_hostDefinition.Port)).get();
auto boundPort = static_cast(std::stoi(winrt::to_string(m_socket.Information().LocalPort())));
@@ -101,6 +141,8 @@ MidiNetworkHost::Start()
));
}
+ m_started = true;
+
TraceLoggingWrite(
MidiNetworkMidiTransportTelemetryProvider::Provider(),
MIDI_TRACE_EVENT_INFO,
@@ -110,21 +152,15 @@ MidiNetworkHost::Start()
TraceLoggingWideString(L"Exit", MIDI_TRACE_EVENT_MESSAGE_FIELD)
);
- m_started = true;
-
return S_OK;
}
-// header sized plus a command packet header
-#define MINIMUM_VALID_UDP_PACKET_SIZE (sizeof(uint32_t) * 2)
-
// "message" here means UDP packet message, not a MIDI message
_Use_decl_annotations_
void MidiNetworkHost::OnMessageReceived(
DatagramSocket const& sender,
DatagramSocketMessageReceivedEventArgs const& args)
{
- // TEMP!!
TraceLoggingWrite(
MidiNetworkMidiTransportTelemetryProvider::Provider(),
MIDI_TRACE_EVENT_INFO,
@@ -134,11 +170,12 @@ void MidiNetworkHost::OnMessageReceived(
TraceLoggingWideString(L"Enter", MIDI_TRACE_EVENT_MESSAGE_FIELD)
);
+
UNREFERENCED_PARAMETER(sender);
auto reader = args.GetDataReader();
- if (reader.UnconsumedBufferLength() < MINIMUM_VALID_UDP_PACKET_SIZE)
+ if (reader != nullptr && reader.UnconsumedBufferLength() < MINIMUM_VALID_UDP_PACKET_SIZE)
{
// not a message we understand. Needs to be at least the size of the
// MIDI header plus a command packet header. Really it needs to be larger, but
@@ -167,7 +204,14 @@ void MidiNetworkHost::OnMessageReceived(
return;
}
- auto conn = GetOrCreateConnection(args.RemoteAddress(), args.RemotePort());
+ std::shared_ptr conn{ nullptr };
+
+ if (!TransportState::Current().NetworkConnectionExists(args.RemoteAddress(), args.RemotePort()))
+ {
+ LOG_IF_FAILED(CreateNetworkConnection(args.RemoteAddress(), args.RemotePort()));
+ }
+
+ conn = TransportState::Current().GetNetworkConnection(args.RemoteAddress(), args.RemotePort());
if (conn)
{
@@ -185,6 +229,16 @@ void MidiNetworkHost::OnMessageReceived(
);
}
+
+ TraceLoggingWrite(
+ MidiNetworkMidiTransportTelemetryProvider::Provider(),
+ MIDI_TRACE_EVENT_INFO,
+ TraceLoggingString(__FUNCTION__, MIDI_TRACE_EVENT_LOCATION_FIELD),
+ TraceLoggingLevel(WINEVENT_LEVEL_INFO),
+ TraceLoggingPointer(this, "this"),
+ TraceLoggingWideString(L"Exit", MIDI_TRACE_EVENT_MESSAGE_FIELD)
+ );
+
}
@@ -206,13 +260,39 @@ MidiNetworkHost::Shutdown()
}
// TODO: send "bye" to all sessions, and then unbind the socket
+ // but we need to restrict to this host, not every host/client
+
- // TODO: Stop packet processing thread
+ // TODO: Stop packet processing thread using the jthread stop token
+ m_keepProcessing = false;
+ if (m_socket)
+ {
+ m_socket.MessageReceived(m_messageReceivedEventToken);
+ m_socket.Close();
+ m_socket = nullptr;
+ }
+ //while (m_connections.size() > 0)
+ //{
+ // auto conn = m_connections.begin();
+ // LOG_IF_FAILED(conn->second->Shutdown());
- m_socket.Close();
+ // m_connections.erase(conn);
+ //}
+ TraceLoggingWrite(
+ MidiNetworkMidiTransportTelemetryProvider::Provider(),
+ MIDI_TRACE_EVENT_INFO,
+ TraceLoggingString(__FUNCTION__, MIDI_TRACE_EVENT_LOCATION_FIELD),
+ TraceLoggingLevel(WINEVENT_LEVEL_INFO),
+ TraceLoggingPointer(this, "this"),
+ TraceLoggingWideString(L"Exit", MIDI_TRACE_EVENT_MESSAGE_FIELD)
+ );
return S_OK;
-}
\ No newline at end of file
+}
+
+
+
+
diff --git a/src/api/Transport/UdpNetworkMidi2Transport/MidiNetworkHost.h b/src/api/Transport/UdpNetworkMidi2Transport/MidiNetworkHost.h
index 4c53281e0..397396b28 100644
--- a/src/api/Transport/UdpNetworkMidi2Transport/MidiNetworkHost.h
+++ b/src/api/Transport/UdpNetworkMidi2Transport/MidiNetworkHost.h
@@ -36,6 +36,8 @@ enum MidiNetworkHostProtocol
struct MidiNetworkHostDefinition
{
+ bool Created{ false };
+
winrt::hstring EntryIdentifier; // internal
bool UseAutomaticPortAllocation{ true };
@@ -48,6 +50,8 @@ struct MidiNetworkHostDefinition
bool Enabled{ true };
bool Advertise{ true };
+ bool CreateMidi1Ports{ MIDI_NETWORK_MIDI_CREATE_MIDI1_PORTS_DEFAULT };
+
// connection rules
MidiNetworkHostConnectionPolicy ConnectionPolicy{ MidiNetworkHostConnectionPolicy::PolicyAllowAllConnections };
@@ -75,27 +79,22 @@ class MidiNetworkHost
{
public:
HRESULT Initialize(_In_ MidiNetworkHostDefinition& hostDefinition);
-
HRESULT Start();
-
HRESULT Shutdown();
bool HasStarted() { return m_started; }
private:
bool m_started{ false };
+ bool m_createUmpEndpointsOnly{ true };
- //HRESULT EstablishNewSession();
std::wstring m_hostEndpointName{ };
std::wstring m_hostProductInstanceId{ };
std::atomic m_keepProcessing{ true };
- // todo: these probably aren't necessary. The only queues we need are for midi messages
-// std::queue m_incomingOutOfBandCommands;
-// std::queue m_outgoingOutOfBandCommands;
+ winrt::event_token m_messageReceivedEventToken;
- winrt::impl::consume_Windows_Networking_Sockets_IDatagramSocket::MessageReceived_revoker m_messageReceivedRevoker;
void OnMessageReceived(
_In_ DatagramSocket const& sender,
_In_ DatagramSocketMessageReceivedEventArgs const& args);
@@ -107,68 +106,6 @@ class MidiNetworkHost
DatagramSocket m_socket{ nullptr };
- // Map of MidiNetworkHostSessions and their related remote client addresses
- // the keys for these two maps are the same values created with CreateSessionMapKey
- std::map> m_connections{ };
-
- inline std::string CreateConnectionMapKey(_In_ HostName const& hostName, _In_ winrt::hstring const& port)
- {
- return winrt::to_string(hostName.CanonicalName() + L":" + port);
- }
-
- inline bool ConnectionExists(_In_ HostName const& hostName, _In_ winrt::hstring const& port)
- {
- auto key = CreateConnectionMapKey(hostName, port);
-
- return m_connections.find(key) != m_connections.end();
- }
-
-
- inline void RemoveConnection(_In_ HostName const& hostName, _In_ winrt::hstring const& port)
- {
- if (ConnectionExists(hostName, port))
- {
- auto entry = m_connections.find(CreateConnectionMapKey(hostName, port));
-
- LOG_IF_FAILED(entry->second->Shutdown());
-
- m_connections.erase(CreateConnectionMapKey(hostName, port));
- }
- }
-
- inline std::shared_ptr GetOrCreateConnection(_In_ HostName const& hostName, _In_ winrt::hstring const& port)
- {
- auto key = CreateConnectionMapKey(hostName, port);
-
- if (!ConnectionExists(hostName, port))
- {
- // need to create it and then add one
-
- auto conn = std::make_shared();
-
- if (conn)
- {
- conn->Initialize(
- MidiNetworkConnectionRole::ConnectionWindowsIsHost,
- m_socket,
- hostName,
- port,
- m_hostEndpointName,
- m_hostProductInstanceId,
- TransportState::Current().TransportSettings.RetransmitBufferMaxCommandPacketCount,
- TransportState::Current().TransportSettings.ForwardErrorCorrectionMaxCommandPacketCount
- );
-
- m_connections.insert_or_assign(key, conn);
- }
- else
- {
- // could not create the connection object.
- }
- }
-
- return m_connections.find(key)->second;
- }
-
+ HRESULT CreateNetworkConnection(_In_ winrt::Windows::Networking::HostName const& remoteHostName, _In_ winrt::hstring const& remotePort);
};
diff --git a/src/api/Transport/UdpNetworkMidi2Transport/MidiNetworkMessages.h b/src/api/Transport/UdpNetworkMidi2Transport/MidiNetworkMessages.h
index 40e14e27d..82162e05c 100644
--- a/src/api/Transport/UdpNetworkMidi2Transport/MidiNetworkMessages.h
+++ b/src/api/Transport/UdpNetworkMidi2Transport/MidiNetworkMessages.h
@@ -80,6 +80,7 @@ enum MidiNetworkCommandRetransmitErrorReason : byte
enum MidiNetworkCommandInvitationCapabilities : byte
{
+ Capabilities_None = 0x00,
Capabilities_ClientSupportsInvitationWithAuthentication = 0x01,
Capabilities_ClientSupportsInvitationWithUserAuthentication = 0x02,
};
diff --git a/src/api/Transport/UdpNetworkMidi2Transport/TransportState.cpp b/src/api/Transport/UdpNetworkMidi2Transport/TransportState.cpp
index 0debd2f28..71ee3552e 100644
--- a/src/api/Transport/UdpNetworkMidi2Transport/TransportState.cpp
+++ b/src/api/Transport/UdpNetworkMidi2Transport/TransportState.cpp
@@ -22,7 +22,6 @@ TransportState& TransportState::Current()
}
-
HRESULT
TransportState::ConstructEndpointManager()
{
@@ -31,7 +30,6 @@ TransportState::ConstructEndpointManager()
return S_OK;
}
-
HRESULT
TransportState::ConstructConfigurationManager()
{
@@ -41,9 +39,12 @@ TransportState::ConstructConfigurationManager()
}
+
+
_Use_decl_annotations_
HRESULT
-TransportState::AddHost(std::shared_ptr host)
+TransportState::AddHost(
+ std::shared_ptr host)
{
RETURN_HR_IF_NULL(E_INVALIDARG, host);
@@ -52,10 +53,35 @@ TransportState::AddHost(std::shared_ptr host)
return S_OK;
}
+_Use_decl_annotations_
+HRESULT
+TransportState::AddPendingHostDefinition(
+ std::shared_ptr hostDefinition)
+{
+ RETURN_HR_IF_NULL(E_INVALIDARG, hostDefinition);
+
+ m_pendingHostDefinitions.push_back(hostDefinition);
+
+ return S_OK;
+}
_Use_decl_annotations_
HRESULT
-TransportState::AddPendingClientDefinition(std::shared_ptr clientDefinition)
+TransportState::AddClient(
+ std::shared_ptr client)
+{
+ RETURN_HR_IF_NULL(E_INVALIDARG, client);
+
+ m_clients.push_back(client);
+
+ return S_OK;
+}
+
+
+_Use_decl_annotations_
+HRESULT
+TransportState::AddPendingClientDefinition(
+ std::shared_ptr clientDefinition)
{
RETURN_HR_IF_NULL(E_INVALIDARG, clientDefinition);
@@ -65,33 +91,58 @@ TransportState::AddPendingClientDefinition(std::shared_ptr connection)
+TransportState::AssociateMidiEndpointWithConnection(
+ _In_ std::wstring endpointDeviceInterfaceId,
+ _In_ winrt::Windows::Networking::HostName const& remoteHostName,
+ _In_ winrt::hstring const& remotePort)
{
auto cleanId = internal::NormalizeEndpointInterfaceIdWStringCopy(endpointDeviceInterfaceId);
- m_sessionConnections.insert_or_assign(cleanId, connection);
+ // find the connection and then associate it here
+
+ auto connection = GetNetworkConnection(remoteHostName, remotePort);
+
+ if (connection != nullptr)
+ {
+ m_sessionConnections.insert_or_assign(cleanId, connection);
+
+ return S_OK;
+ }
+ else
+ {
+ return E_FAIL;
+ }
- return S_OK;
}
_Use_decl_annotations_
HRESULT
-TransportState::RemoveSessionConnection(_In_ std::wstring endpointDeviceInterfaceId)
+TransportState::DisassociateMidiEndpointFromConnection(
+ std::wstring endpointDeviceInterfaceId)
{
+ RETURN_HR_IF(E_INVALIDARG, endpointDeviceInterfaceId.empty());
auto cleanId = internal::NormalizeEndpointInterfaceIdWStringCopy(endpointDeviceInterfaceId);
+ RETURN_HR_IF(E_INVALIDARG, cleanId.empty());
if (m_sessionConnections.find(cleanId) != m_sessionConnections.end())
{
+ //m_sessionConnections.find(cleanId)->second.reset();
m_sessionConnections.erase(cleanId);
}
+ else
+ {
+ RETURN_IF_FAILED(E_NOTFOUND);
+ }
return S_OK;
}
_Use_decl_annotations_
-std::shared_ptr TransportState::GetSessionConnection(_In_ std::wstring endpointDeviceInterfaceId)
+std::shared_ptr
+TransportState::GetSessionConnection(_In_ std::wstring endpointDeviceInterfaceId)
{
auto cleanId = internal::NormalizeEndpointInterfaceIdWStringCopy(endpointDeviceInterfaceId);
@@ -103,3 +154,68 @@ std::shared_ptr TransportState::GetSessionConnection(_In_
return nullptr;
}
+
+
+
+
+_Use_decl_annotations_
+bool
+TransportState::NetworkConnectionExists(
+ winrt::Windows::Networking::HostName const& remoteHostName,
+ winrt::hstring const& port)
+{
+ auto key = CreateNetworkConnectionMapKey(remoteHostName, port);
+
+ return m_networkConnections.find(key) != m_networkConnections.end();
+}
+
+
+_Use_decl_annotations_
+HRESULT
+TransportState::RemoveNetworkConnection(
+ winrt::Windows::Networking::HostName const& remoteHostName,
+ winrt::hstring const& remotePort)
+{
+ if (NetworkConnectionExists(remoteHostName, remotePort))
+ {
+ auto entry = m_networkConnections.find(CreateNetworkConnectionMapKey(remoteHostName, remotePort));
+
+ LOG_IF_FAILED(entry->second->Shutdown());
+
+ m_networkConnections.erase(CreateNetworkConnectionMapKey(remoteHostName, remotePort));
+ }
+
+ return S_OK;
+}
+
+_Use_decl_annotations_
+std::shared_ptr
+TransportState::GetNetworkConnection(
+ winrt::Windows::Networking::HostName const& remoteHostName,
+ winrt::hstring const& remotePort)
+{
+ auto key = CreateNetworkConnectionMapKey(remoteHostName, remotePort);
+
+ if (NetworkConnectionExists(remoteHostName, remotePort))
+ {
+ return m_networkConnections.find(key)->second;
+ }
+
+ return nullptr;
+}
+
+
+_Use_decl_annotations_
+HRESULT
+TransportState::AddNetworkConnection(
+ winrt::Windows::Networking::HostName const& remoteHostName,
+ winrt::hstring const& remotePort,
+ std::shared_ptr connection
+)
+{
+ auto key = CreateNetworkConnectionMapKey(remoteHostName, remotePort);
+
+ m_networkConnections.insert_or_assign(key, connection);
+
+ return S_OK;
+}
diff --git a/src/api/Transport/UdpNetworkMidi2Transport/TransportState.h b/src/api/Transport/UdpNetworkMidi2Transport/TransportState.h
index d34db4020..b02a6d479 100644
--- a/src/api/Transport/UdpNetworkMidi2Transport/TransportState.h
+++ b/src/api/Transport/UdpNetworkMidi2Transport/TransportState.h
@@ -48,7 +48,7 @@ class TransportState
//}
- HRESULT Cleanup()
+ HRESULT Shutdown()
{
m_endpointManager.reset();
m_configurationManager.reset();
@@ -61,17 +61,55 @@ class TransportState
HRESULT ConstructEndpointManager();
HRESULT ConstructConfigurationManager();
- HRESULT AddHost(_In_ std::shared_ptr);
+ HRESULT AddHost(
+ _In_ std::shared_ptr);
std::vector> GetHosts() { return m_hosts; }
- HRESULT AddPendingClientDefinition(_In_ std::shared_ptr);
+ HRESULT AddPendingHostDefinition(
+ _In_ std::shared_ptr);
+ std::vector> GetPendingHostDefinitions() { return m_pendingHostDefinitions; }
+
+ HRESULT AddClient(
+ _In_ std::shared_ptr);
+ std::vector> GetClients() { return m_clients; }
+
+ HRESULT AddPendingClientDefinition(
+ _In_ std::shared_ptr);
std::vector> GetPendingClientDefinitions() { return m_pendingClientDefinitions; }
- HRESULT AddSessionConnection(_In_ std::wstring endpointDeviceInterfaceId, _In_ std::shared_ptr connection);
- HRESULT RemoveSessionConnection(_In_ std::wstring endpointDeviceInterfaceId);
- std::shared_ptr GetSessionConnection(_In_ std::wstring endpointDeviceInterfaceId);
+ // these two sets of functions, and their related maps, work with the same
+ // connection objects, just in different states
+
+ // these are for when the connection is associated with a UMP endpoint
+ HRESULT AssociateMidiEndpointWithConnection(
+ _In_ std::wstring endpointDeviceInterfaceId,
+ _In_ winrt::Windows::Networking::HostName const& remoteHostName,
+ _In_ winrt::hstring const& remotePort);
+
+ HRESULT DisassociateMidiEndpointFromConnection(
+ _In_ std::wstring endpointDeviceInterfaceId);
+
+ std::shared_ptr GetSessionConnection(
+ _In_ std::wstring endpointDeviceInterfaceId);
+
+ // these are for when the connection is first created. They also live through when they become UMP endpoints
+ bool NetworkConnectionExists(
+ _In_ winrt::Windows::Networking::HostName const& remoteHostName,
+ _In_ winrt::hstring const& remotePort);
+
+ std::shared_ptr GetNetworkConnection(
+ _In_ winrt::Windows::Networking::HostName const& remoteHostName,
+ _In_ winrt::hstring const& remotePort);
+ HRESULT AddNetworkConnection(
+ _In_ winrt::Windows::Networking::HostName const& remoteHostName,
+ _In_ winrt::hstring const& remotePort,
+ _In_ std::shared_ptr connection);
+
+ HRESULT RemoveNetworkConnection(
+ _In_ winrt::Windows::Networking::HostName const& remoteHostName,
+ _In_ winrt::hstring const& remotePort);
private:
TransportState();
@@ -81,10 +119,21 @@ class TransportState
wil::com_ptr m_endpointManager;
wil::com_ptr m_configurationManager;
- // key is the host identifier
std::vector> m_hosts{ };
+ std::vector> m_clients{ };
+
+ std::vector> m_pendingHostDefinitions{ };
std::vector> m_pendingClientDefinitions{ };
std::map> m_sessionConnections{ };
+ // Map of MidiNetworkConnections and their related remote client addresses
+ // the keys for these two maps are the values created with CreateConnectionMapKey
+ std::map> m_networkConnections{ };
+
+ inline std::string CreateNetworkConnectionMapKey(_In_ winrt::Windows::Networking::HostName const& remoteHostName, _In_ winrt::hstring const& remotePort)
+ {
+ return winrt::to_string(remoteHostName.CanonicalName() + L":" + remotePort);
+ }
+
};
\ No newline at end of file
diff --git a/src/api/Transport/UdpNetworkMidi2Transport/pch.h b/src/api/Transport/UdpNetworkMidi2Transport/pch.h
index 293a2aa53..781497cc4 100644
--- a/src/api/Transport/UdpNetworkMidi2Transport/pch.h
+++ b/src/api/Transport/UdpNetworkMidi2Transport/pch.h
@@ -7,7 +7,7 @@
#endif
#include
-
+#include // mostly for E_NOTFOUND
#include
#include
#include
@@ -110,6 +110,7 @@ class MidiNetworkHost;
class MidiNetworkClient;
class MidiNetworkConnection;
struct MidiNetworkClientDefinition;
+struct MidiNetworkHostDefinition;
//#include "MidiNetworkEndpointDefinition.h"
diff --git a/src/api/Transport/UdpNetworkMidi2Transport/transport_defs.h b/src/api/Transport/UdpNetworkMidi2Transport/transport_defs.h
index 581c2b5bb..03a90a4f8 100644
--- a/src/api/Transport/UdpNetworkMidi2Transport/transport_defs.h
+++ b/src/api/Transport/UdpNetworkMidi2Transport/transport_defs.h
@@ -53,9 +53,14 @@
#define MIDI_NETWORK_OUTBOUND_PING_INTERVAL_UPPER_BOUND 120000
#define MIDI_NETWORK_OUTBOUND_PING_INTERVAL_LOWER_BOUND 250
+#define MIDI_NETWORK_STARTING_OUTBOUND_UMP_QUEUE_CAPACITY 50
+
+#define MIDI_NETWORK_MIDI_CREATE_MIDI1_PORTS_DEFAULT false
+
+// header sized plus a command packet header
+#define MINIMUM_VALID_UDP_PACKET_SIZE (sizeof(uint32_t) * 2)
-#define MIDI_NETWORK_STARTING_OUTBOUND_UMP_QUEUE_CAPACITY 50
// JSON keys. Can move to json_defs when in-box
@@ -78,6 +83,8 @@
#define MIDI_CONFIG_JSON_NETWORK_MIDI_MDNS_ADVERTISE_KEY L"advertise" // boolean
#define MIDI_CONFIG_JSON_NETWORK_MIDI_ENABLED_KEY L"enabled" // boolean
+#define MIDI_CONFIG_JSON_NETWORK_MIDI_CREATE_MIDI1_PORTS_KEY L"createMidi1Ports" // boolean - set to true to enable creating WinMM/WinRT 1.0 ports
+
#define MIDI_CONFIG_JSON_NETWORK_MIDI_SERVICE_INSTANCE_NAME_KEY L"serviceInstanceName" // just the first part (before the . ) of the host instance name. Defaults to machine name
@@ -104,13 +111,14 @@
#define MIDI_CONFIG_JSON_NETWORK_MIDI_PRODUCT_INSTANCE_ID_PROPERTY L"productInstanceId"
-#define MIDI_CONFIG_JSON_NETWORK_MIDI_CLIENT_MATCH_ID L"id" // Windows ID like: DnsSd#kb7C5D0A_1._midi2._udp.local#0
-#define MIDI_CONFIG_JSON_NETWORK_MIDI_CLIENT_MATCH_SERVICE_INSTANCE L"serviceInstance" // Like kb7C5D0A_1 or bomeboxdin-8q6d2z-1
-#define MIDI_CONFIG_JSON_NETWORK_MIDI_CLIENT_MATCH_IP L"ipAddress" // Like 192.168.1.253 (port is also required)
-#define MIDI_CONFIG_JSON_NETWORK_MIDI_CLIENT_MATCH_HOST_NAME L"hostName" // Like kissbox or BomeBox.local (port is also required)
-#define MIDI_CONFIG_JSON_NETWORK_MIDI_CLIENT_MATCH_PORT L"port" // Like 5004 (ip address or host name is also required)
-#define MIDI_CONFIG_JSON_NETWORK_MIDI_CLIENT_MATCH_UMP_ENDPOINT_NAME L"umpEndpointName" // Like UMP2TR @253 Port 1 or BomeBox
-#define MIDI_CONFIG_JSON_NETWORK_MIDI_CLIENT_MATCH_UMP_ENDPOINT_PID L"umpProductInstanceId" // Like kb7C5D0A_1 or CC851C0080257A96
+#define MIDI_CONFIG_JSON_NETWORK_MIDI_CLIENT_MATCH_OBJECT_KEY L"match" // object which contains match criteria
+#define MIDI_CONFIG_JSON_NETWORK_MIDI_CLIENT_MATCH_ID_KEY L"id" // Windows ID like: DnsSd#kb7C5D0A_1._midi2._udp.local#0
+#define MIDI_CONFIG_JSON_NETWORK_MIDI_CLIENT_MATCH_SERVICE_INSTANCE_KEY L"serviceInstance" // Like kb7C5D0A_1 or bomeboxdin-8q6d2z-1
+#define MIDI_CONFIG_JSON_NETWORK_MIDI_CLIENT_MATCH_IPV4_KEY L"ipv4Address" // Like 192.168.1.253 (port is also required)
+#define MIDI_CONFIG_JSON_NETWORK_MIDI_CLIENT_MATCH_HOST_NAME_KEY L"hostName" // Like kissbox or BomeBox.local (port is also required)
+#define MIDI_CONFIG_JSON_NETWORK_MIDI_CLIENT_MATCH_PORT_KEY L"port" // Like 5004 (ip address or host name is also required)
+#define MIDI_CONFIG_JSON_NETWORK_MIDI_CLIENT_MATCH_UMP_ENDPOINT_NAME_KEY L"umpEndpointName" // Like UMP2TR @253 Port 1 or BomeBox
+#define MIDI_CONFIG_JSON_NETWORK_MIDI_CLIENT_MATCH_UMP_ENDPOINT_PID_KEY L"umpProductInstanceId" // Like kb7C5D0A_1 or CC851C0080257A96
enum MidiNetworkConnectionRole
diff --git a/src/app-sdk/mididiag/color.hpp b/src/app-sdk/mididiag/color.hpp
new file mode 100644
index 000000000..cb080b6e5
--- /dev/null
+++ b/src/app-sdk/mididiag/color.hpp
@@ -0,0 +1,881 @@
+// source: https://github.com/aafulei/color-console/blob/master/include/color.hpp
+
+
+#ifndef COLOR_HPP
+#define COLOR_HPP
+
+#include
+#include
+#include
+#include