Merge pull request #2329 from MediaBrowser/dev

Dev
This commit is contained in:
Luke 2016-12-04 17:02:21 -05:00 committed by GitHub
commit 06a8b8af88
27 changed files with 288 additions and 361 deletions

View file

@ -4,6 +4,7 @@ using System.Linq;
using System.Net; using System.Net;
using System.Net.Sockets; using System.Net.Sockets;
using System.Threading.Tasks; using System.Threading.Tasks;
using Emby.Common.Implementations.Networking;
using MediaBrowser.Model.Logging; using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Net; using MediaBrowser.Model.Net;
@ -18,11 +19,6 @@ namespace Emby.Common.Implementations.Net
// but that wasn't really the point so kept to YAGNI principal for now, even if the // but that wasn't really the point so kept to YAGNI principal for now, even if the
// interfaces are a bit ugly, specific and make assumptions. // interfaces are a bit ugly, specific and make assumptions.
/// <summary>
/// Used by RSSDP components to create implementations of the <see cref="IUdpSocket"/> interface, to perform platform agnostic socket communications.
/// </summary>
private IPAddress _LocalIP;
private readonly ILogger _logger; private readonly ILogger _logger;
public SocketFactory(ILogger logger) public SocketFactory(ILogger logger)
@ -33,7 +29,6 @@ namespace Emby.Common.Implementations.Net
} }
_logger = logger; _logger = logger;
_LocalIP = IPAddress.Any;
} }
public ISocket CreateSocket(IpAddressFamily family, MediaBrowser.Model.Net.SocketType socketType, MediaBrowser.Model.Net.ProtocolType protocolType, bool dualMode) public ISocket CreateSocket(IpAddressFamily family, MediaBrowser.Model.Net.SocketType socketType, MediaBrowser.Model.Net.ProtocolType protocolType, bool dualMode)
@ -66,7 +61,7 @@ namespace Emby.Common.Implementations.Net
try try
{ {
retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
return new UdpSocket(retVal, localPort, _LocalIP); return new UdpSocket(retVal, localPort, IPAddress.Any);
} }
catch catch
{ {
@ -80,9 +75,8 @@ namespace Emby.Common.Implementations.Net
/// <summary> /// <summary>
/// Creates a new UDP socket that is a member of the SSDP multicast local admin group and binds it to the specified local port. /// Creates a new UDP socket that is a member of the SSDP multicast local admin group and binds it to the specified local port.
/// </summary> /// </summary>
/// <param name="localPort">An integer specifying the local port to bind the socket to.</param>
/// <returns>An implementation of the <see cref="IUdpSocket"/> interface used by RSSDP components to perform socket operations.</returns> /// <returns>An implementation of the <see cref="IUdpSocket"/> interface used by RSSDP components to perform socket operations.</returns>
public IUdpSocket CreateSsdpUdpSocket(int localPort) public IUdpSocket CreateSsdpUdpSocket(IpAddressInfo localIpAddress, int localPort)
{ {
if (localPort < 0) throw new ArgumentException("localPort cannot be less than zero.", "localPort"); if (localPort < 0) throw new ArgumentException("localPort cannot be less than zero.", "localPort");
@ -91,8 +85,11 @@ namespace Emby.Common.Implementations.Net
{ {
retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
retVal.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastTimeToLive, 4); retVal.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastTimeToLive, 4);
retVal.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(IPAddress.Parse("239.255.255.250"), _LocalIP));
return new UdpSocket(retVal, localPort, _LocalIP); var localIp = NetworkManager.ToIPAddress(localIpAddress);
retVal.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(IPAddress.Parse("239.255.255.250"), localIp));
return new UdpSocket(retVal, localPort, localIp);
} }
catch catch
{ {
@ -134,10 +131,13 @@ namespace Emby.Common.Implementations.Net
//retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, true); //retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, true);
retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
retVal.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastTimeToLive, multicastTimeToLive); retVal.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastTimeToLive, multicastTimeToLive);
retVal.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(IPAddress.Parse(ipAddress), _LocalIP));
var localIp = IPAddress.Any;
retVal.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(IPAddress.Parse(ipAddress), localIp));
retVal.MulticastLoopback = true; retVal.MulticastLoopback = true;
return new UdpSocket(retVal, localPort, _LocalIP); return new UdpSocket(retVal, localPort, localIp);
} }
catch catch
{ {

View file

@ -20,7 +20,6 @@ namespace Emby.Common.Implementations.Net
private Socket _Socket; private Socket _Socket;
private int _LocalPort; private int _LocalPort;
#endregion #endregion
#region Constructors #region Constructors
@ -31,12 +30,19 @@ namespace Emby.Common.Implementations.Net
_Socket = socket; _Socket = socket;
_LocalPort = localPort; _LocalPort = localPort;
LocalIPAddress = NetworkManager.ToIpAddressInfo(ip);
_Socket.Bind(new IPEndPoint(ip, _LocalPort)); _Socket.Bind(new IPEndPoint(ip, _LocalPort));
} }
#endregion #endregion
public IpAddressInfo LocalIPAddress
{
get;
private set;
}
#region IUdpSocket Members #region IUdpSocket Members
public Task<SocketReceiveResult> ReceiveAsync() public Task<SocketReceiveResult> ReceiveAsync()
@ -50,18 +56,18 @@ namespace Emby.Common.Implementations.Net
state.TaskCompletionSource = tcs; state.TaskCompletionSource = tcs;
#if NETSTANDARD1_6 #if NETSTANDARD1_6
_Socket.ReceiveFromAsync(new ArraySegment<Byte>(state.Buffer),SocketFlags.None, state.EndPoint) _Socket.ReceiveFromAsync(new ArraySegment<Byte>(state.Buffer),SocketFlags.None, state.RemoteEndPoint)
.ContinueWith((task, asyncState) => .ContinueWith((task, asyncState) =>
{ {
if (task.Status != TaskStatus.Faulted) if (task.Status != TaskStatus.Faulted)
{ {
var receiveState = asyncState as AsyncReceiveState; var receiveState = asyncState as AsyncReceiveState;
receiveState.EndPoint = task.Result.RemoteEndPoint; receiveState.RemoteEndPoint = task.Result.RemoteEndPoint;
ProcessResponse(receiveState, () => task.Result.ReceivedBytes); ProcessResponse(receiveState, () => task.Result.ReceivedBytes, LocalIPAddress);
} }
}, state); }, state);
#else #else
_Socket.BeginReceiveFrom(state.Buffer, 0, state.Buffer.Length, SocketFlags.None, ref state.EndPoint, ProcessResponse, state); _Socket.BeginReceiveFrom(state.Buffer, 0, state.Buffer.Length, SocketFlags.None, ref state.RemoteEndPoint, ProcessResponse, state);
#endif #endif
return tcs.Task; return tcs.Task;
@ -74,6 +80,8 @@ namespace Emby.Common.Implementations.Net
if (buffer == null) throw new ArgumentNullException("messageData"); if (buffer == null) throw new ArgumentNullException("messageData");
if (endPoint == null) throw new ArgumentNullException("endPoint"); if (endPoint == null) throw new ArgumentNullException("endPoint");
var ipEndPoint = NetworkManager.ToIPEndPoint(endPoint);
#if NETSTANDARD1_6 #if NETSTANDARD1_6
if (size != buffer.Length) if (size != buffer.Length)
@ -83,14 +91,14 @@ namespace Emby.Common.Implementations.Net
buffer = copy; buffer = copy;
} }
_Socket.SendTo(buffer, new IPEndPoint(IPAddress.Parse(endPoint.IpAddress.ToString()), endPoint.Port)); _Socket.SendTo(buffer, ipEndPoint);
return Task.FromResult(true); return Task.FromResult(true);
#else #else
var taskSource = new TaskCompletionSource<bool>(); var taskSource = new TaskCompletionSource<bool>();
try try
{ {
_Socket.BeginSendTo(buffer, 0, size, SocketFlags.None, new System.Net.IPEndPoint(IPAddress.Parse(endPoint.IpAddress.ToString()), endPoint.Port), result => _Socket.BeginSendTo(buffer, 0, size, SocketFlags.None, ipEndPoint, result =>
{ {
try try
{ {
@ -109,7 +117,7 @@ namespace Emby.Common.Implementations.Net
taskSource.TrySetException(ex); taskSource.TrySetException(ex);
} }
//_Socket.SendTo(messageData, new System.Net.IPEndPoint(IPAddress.Parse(endPoint.IPAddress), endPoint.Port)); //_Socket.SendTo(messageData, new System.Net.IPEndPoint(IPAddress.Parse(RemoteEndPoint.IPAddress), RemoteEndPoint.Port));
return taskSource.Task; return taskSource.Task;
#endif #endif
@ -133,19 +141,20 @@ namespace Emby.Common.Implementations.Net
#region Private Methods #region Private Methods
private static void ProcessResponse(AsyncReceiveState state, Func<int> receiveData) private static void ProcessResponse(AsyncReceiveState state, Func<int> receiveData, IpAddressInfo localIpAddress)
{ {
try try
{ {
var bytesRead = receiveData(); var bytesRead = receiveData();
var ipEndPoint = state.EndPoint as IPEndPoint; var ipEndPoint = state.RemoteEndPoint as IPEndPoint;
state.TaskCompletionSource.SetResult( state.TaskCompletionSource.SetResult(
new SocketReceiveResult() new SocketReceiveResult
{ {
Buffer = state.Buffer, Buffer = state.Buffer,
ReceivedBytes = bytesRead, ReceivedBytes = bytesRead,
RemoteEndPoint = ToIpEndPointInfo(ipEndPoint) RemoteEndPoint = ToIpEndPointInfo(ipEndPoint),
LocalIPAddress = localIpAddress
} }
); );
} }
@ -182,15 +191,16 @@ namespace Emby.Common.Implementations.Net
var state = asyncResult.AsyncState as AsyncReceiveState; var state = asyncResult.AsyncState as AsyncReceiveState;
try try
{ {
var bytesRead = state.Socket.EndReceiveFrom(asyncResult, ref state.EndPoint); var bytesRead = state.Socket.EndReceiveFrom(asyncResult, ref state.RemoteEndPoint);
var ipEndPoint = state.EndPoint as IPEndPoint; var ipEndPoint = state.RemoteEndPoint as IPEndPoint;
state.TaskCompletionSource.SetResult( state.TaskCompletionSource.SetResult(
new SocketReceiveResult new SocketReceiveResult
{ {
Buffer = state.Buffer, Buffer = state.Buffer,
ReceivedBytes = bytesRead, ReceivedBytes = bytesRead,
RemoteEndPoint = ToIpEndPointInfo(ipEndPoint) RemoteEndPoint = ToIpEndPointInfo(ipEndPoint),
LocalIPAddress = LocalIPAddress
} }
); );
} }
@ -211,13 +221,13 @@ namespace Emby.Common.Implementations.Net
private class AsyncReceiveState private class AsyncReceiveState
{ {
public AsyncReceiveState(Socket socket, EndPoint endPoint) public AsyncReceiveState(Socket socket, EndPoint remoteEndPoint)
{ {
this.Socket = socket; this.Socket = socket;
this.EndPoint = endPoint; this.RemoteEndPoint = remoteEndPoint;
} }
public EndPoint EndPoint; public EndPoint RemoteEndPoint;
public byte[] Buffer = new byte[8192]; public byte[] Buffer = new byte[8192];
public Socket Socket { get; private set; } public Socket Socket { get; private set; }

View file

@ -27,7 +27,7 @@ namespace Emby.Common.Implementations.Networking
private List<IpAddressInfo> _localIpAddresses; private List<IpAddressInfo> _localIpAddresses;
private readonly object _localIpAddressSyncLock = new object(); private readonly object _localIpAddressSyncLock = new object();
public IEnumerable<IpAddressInfo> GetLocalIpAddresses() public List<IpAddressInfo> GetLocalIpAddresses()
{ {
const int cacheMinutes = 5; const int cacheMinutes = 5;

View file

@ -14,7 +14,7 @@ namespace Emby.Dlna.ConnectionManager
{ {
private readonly DeviceProfile _profile; private readonly DeviceProfile _profile;
protected override IEnumerable<KeyValuePair<string, string>> GetResult(string methodName, Headers methodParams) protected override IEnumerable<KeyValuePair<string, string>> GetResult(string methodName, IDictionary<string, string> methodParams)
{ {
if (string.Equals(methodName, "GetProtocolInfo", StringComparison.OrdinalIgnoreCase)) if (string.Equals(methodName, "GetProtocolInfo", StringComparison.OrdinalIgnoreCase))
{ {
@ -26,7 +26,7 @@ namespace Emby.Dlna.ConnectionManager
private IEnumerable<KeyValuePair<string, string>> HandleGetProtocolInfo() private IEnumerable<KeyValuePair<string, string>> HandleGetProtocolInfo()
{ {
return new Headers(true) return new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{ {
{ "Source", _profile.ProtocolInfo }, { "Source", _profile.ProtocolInfo },
{ "Sink", "" } { "Sink", "" }

View file

@ -65,7 +65,7 @@ namespace Emby.Dlna.ContentDirectory
_didlBuilder = new DidlBuilder(profile, user, imageProcessor, serverAddress, accessToken, userDataManager, localization, mediaSourceManager, Logger, libraryManager, mediaEncoder); _didlBuilder = new DidlBuilder(profile, user, imageProcessor, serverAddress, accessToken, userDataManager, localization, mediaSourceManager, Logger, libraryManager, mediaEncoder);
} }
protected override IEnumerable<KeyValuePair<string, string>> GetResult(string methodName, Headers methodParams) protected override IEnumerable<KeyValuePair<string, string>> GetResult(string methodName, IDictionary<string, string> methodParams)
{ {
var deviceId = "test"; var deviceId = "test";
@ -118,17 +118,20 @@ namespace Emby.Dlna.ContentDirectory
_userDataManager.SaveUserData(user.Id, item, userdata, UserDataSaveReason.TogglePlayed, _userDataManager.SaveUserData(user.Id, item, userdata, UserDataSaveReason.TogglePlayed,
CancellationToken.None); CancellationToken.None);
return new Headers(); return new Dictionary<string,string>(StringComparer.OrdinalIgnoreCase);
} }
private IEnumerable<KeyValuePair<string, string>> HandleGetSearchCapabilities() private IEnumerable<KeyValuePair<string, string>> HandleGetSearchCapabilities()
{ {
return new Headers(true) { { "SearchCaps", "res@resolution,res@size,res@duration,dc:title,dc:creator,upnp:actor,upnp:artist,upnp:genre,upnp:album,dc:date,upnp:class,@id,@refID,@protocolInfo,upnp:author,dc:description,pv:avKeywords" } }; return new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
{ "SearchCaps", "res@resolution,res@size,res@duration,dc:title,dc:creator,upnp:actor,upnp:artist,upnp:genre,upnp:album,dc:date,upnp:class,@id,@refID,@protocolInfo,upnp:author,dc:description,pv:avKeywords" }
};
} }
private IEnumerable<KeyValuePair<string, string>> HandleGetSortCapabilities() private IEnumerable<KeyValuePair<string, string>> HandleGetSortCapabilities()
{ {
return new Headers(true) return new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{ {
{ "SortCaps", "res@duration,res@size,res@bitrate,dc:date,dc:title,dc:size,upnp:album,upnp:artist,upnp:albumArtist,upnp:episodeNumber,upnp:genre,upnp:originalTrackNumber,upnp:rating" } { "SortCaps", "res@duration,res@size,res@bitrate,dc:date,dc:title,dc:size,upnp:album,upnp:artist,upnp:albumArtist,upnp:episodeNumber,upnp:genre,upnp:originalTrackNumber,upnp:rating" }
}; };
@ -136,7 +139,7 @@ namespace Emby.Dlna.ContentDirectory
private IEnumerable<KeyValuePair<string, string>> HandleGetSortExtensionCapabilities() private IEnumerable<KeyValuePair<string, string>> HandleGetSortExtensionCapabilities()
{ {
return new Headers(true) return new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{ {
{ "SortExtensionCaps", "res@duration,res@size,res@bitrate,dc:date,dc:title,dc:size,upnp:album,upnp:artist,upnp:albumArtist,upnp:episodeNumber,upnp:genre,upnp:originalTrackNumber,upnp:rating" } { "SortExtensionCaps", "res@duration,res@size,res@bitrate,dc:date,dc:title,dc:size,upnp:album,upnp:artist,upnp:albumArtist,upnp:episodeNumber,upnp:genre,upnp:originalTrackNumber,upnp:rating" }
}; };
@ -144,14 +147,14 @@ namespace Emby.Dlna.ContentDirectory
private IEnumerable<KeyValuePair<string, string>> HandleGetSystemUpdateID() private IEnumerable<KeyValuePair<string, string>> HandleGetSystemUpdateID()
{ {
var headers = new Headers(true); var headers = new Dictionary<string,string>(StringComparer.OrdinalIgnoreCase);
headers.Add("Id", _systemUpdateId.ToString(_usCulture)); headers.Add("Id", _systemUpdateId.ToString(_usCulture));
return headers; return headers;
} }
private IEnumerable<KeyValuePair<string, string>> HandleGetFeatureList() private IEnumerable<KeyValuePair<string, string>> HandleGetFeatureList()
{ {
return new Headers(true) return new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{ {
{ "FeatureList", GetFeatureListXml() } { "FeatureList", GetFeatureListXml() }
}; };
@ -159,7 +162,7 @@ namespace Emby.Dlna.ContentDirectory
private IEnumerable<KeyValuePair<string, string>> HandleXGetFeatureList() private IEnumerable<KeyValuePair<string, string>> HandleXGetFeatureList()
{ {
return new Headers(true) return new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{ {
{ "FeatureList", GetFeatureListXml() } { "FeatureList", GetFeatureListXml() }
}; };
@ -183,12 +186,24 @@ namespace Emby.Dlna.ContentDirectory
return builder.ToString(); return builder.ToString();
} }
private async Task<IEnumerable<KeyValuePair<string, string>>> HandleBrowse(Headers sparams, User user, string deviceId) public string GetValueOrDefault(IDictionary<string, string> sparams, string key, string defaultValue)
{
string val;
if (sparams.TryGetValue(key, out val))
{
return val;
}
return defaultValue;
}
private async Task<IEnumerable<KeyValuePair<string, string>>> HandleBrowse(IDictionary<string, string> sparams, User user, string deviceId)
{ {
var id = sparams["ObjectID"]; var id = sparams["ObjectID"];
var flag = sparams["BrowseFlag"]; var flag = sparams["BrowseFlag"];
var filter = new Filter(sparams.GetValueOrDefault("Filter", "*")); var filter = new Filter(GetValueOrDefault(sparams, "Filter", "*"));
var sortCriteria = new SortCriteria(sparams.GetValueOrDefault("SortCriteria", "")); var sortCriteria = new SortCriteria(GetValueOrDefault(sparams, "SortCriteria", ""));
var provided = 0; var provided = 0;
@ -294,11 +309,11 @@ namespace Emby.Dlna.ContentDirectory
}; };
} }
private async Task<IEnumerable<KeyValuePair<string, string>>> HandleSearch(Headers sparams, User user, string deviceId) private async Task<IEnumerable<KeyValuePair<string, string>>> HandleSearch(IDictionary<string, string> sparams, User user, string deviceId)
{ {
var searchCriteria = new SearchCriteria(sparams.GetValueOrDefault("SearchCriteria", "")); var searchCriteria = new SearchCriteria(GetValueOrDefault(sparams, "SearchCriteria", ""));
var sortCriteria = new SortCriteria(sparams.GetValueOrDefault("SortCriteria", "")); var sortCriteria = new SortCriteria(GetValueOrDefault(sparams, "SortCriteria", ""));
var filter = new Filter(sparams.GetValueOrDefault("Filter", "*")); var filter = new Filter(GetValueOrDefault(sparams, "Filter", "*"));
// sort example: dc:title, dc:date // sort example: dc:title, dc:date

View file

@ -152,7 +152,6 @@
<Compile Include="Profiles\XboxOneProfile.cs" /> <Compile Include="Profiles\XboxOneProfile.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Server\DescriptionXmlBuilder.cs" /> <Compile Include="Server\DescriptionXmlBuilder.cs" />
<Compile Include="Server\Headers.cs" />
<Compile Include="Server\UpnpDevice.cs" /> <Compile Include="Server\UpnpDevice.cs" />
<Compile Include="Service\BaseControlHandler.cs" /> <Compile Include="Service\BaseControlHandler.cs" />
<Compile Include="Service\BaseService.cs" /> <Compile Include="Service\BaseService.cs" />

View file

@ -54,6 +54,7 @@ namespace Emby.Dlna.Main
private readonly ITimerFactory _timerFactory; private readonly ITimerFactory _timerFactory;
private readonly ISocketFactory _socketFactory; private readonly ISocketFactory _socketFactory;
private readonly IEnvironmentInfo _environmentInfo; private readonly IEnvironmentInfo _environmentInfo;
private readonly INetworkManager _networkManager;
private ISsdpCommunicationsServer _communicationsServer; private ISsdpCommunicationsServer _communicationsServer;
@ -69,7 +70,7 @@ namespace Emby.Dlna.Main
IUserDataManager userDataManager, IUserDataManager userDataManager,
ILocalizationManager localization, ILocalizationManager localization,
IMediaSourceManager mediaSourceManager, IMediaSourceManager mediaSourceManager,
IDeviceDiscovery deviceDiscovery, IMediaEncoder mediaEncoder, ISocketFactory socketFactory, ITimerFactory timerFactory, IEnvironmentInfo environmentInfo) IDeviceDiscovery deviceDiscovery, IMediaEncoder mediaEncoder, ISocketFactory socketFactory, ITimerFactory timerFactory, IEnvironmentInfo environmentInfo, INetworkManager networkManager)
{ {
_config = config; _config = config;
_appHost = appHost; _appHost = appHost;
@ -87,6 +88,7 @@ namespace Emby.Dlna.Main
_socketFactory = socketFactory; _socketFactory = socketFactory;
_timerFactory = timerFactory; _timerFactory = timerFactory;
_environmentInfo = environmentInfo; _environmentInfo = environmentInfo;
_networkManager = networkManager;
_logger = logManager.GetLogger("Dlna"); _logger = logManager.GetLogger("Dlna");
} }
@ -156,7 +158,7 @@ namespace Emby.Dlna.Main
{ {
if (_communicationsServer == null) if (_communicationsServer == null)
{ {
_communicationsServer = new SsdpCommunicationsServer(_socketFactory) _communicationsServer = new SsdpCommunicationsServer(_socketFactory, _networkManager, _logger)
{ {
IsShared = true IsShared = true
}; };

View file

@ -11,7 +11,7 @@ namespace Emby.Dlna.MediaReceiverRegistrar
{ {
public class ControlHandler : BaseControlHandler public class ControlHandler : BaseControlHandler
{ {
protected override IEnumerable<KeyValuePair<string, string>> GetResult(string methodName, Headers methodParams) protected override IEnumerable<KeyValuePair<string, string>> GetResult(string methodName, IDictionary<string, string> methodParams)
{ {
if (string.Equals(methodName, "IsAuthorized", StringComparison.OrdinalIgnoreCase)) if (string.Equals(methodName, "IsAuthorized", StringComparison.OrdinalIgnoreCase))
return HandleIsAuthorized(); return HandleIsAuthorized();
@ -23,7 +23,7 @@ namespace Emby.Dlna.MediaReceiverRegistrar
private IEnumerable<KeyValuePair<string, string>> HandleIsAuthorized() private IEnumerable<KeyValuePair<string, string>> HandleIsAuthorized()
{ {
return new Headers(true) return new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{ {
{ "Result", "1" } { "Result", "1" }
}; };
@ -31,7 +31,7 @@ namespace Emby.Dlna.MediaReceiverRegistrar
private IEnumerable<KeyValuePair<string, string>> HandleIsValidated() private IEnumerable<KeyValuePair<string, string>> HandleIsValidated()
{ {
return new Headers(true) return new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{ {
{ "Result", "1" } { "Result", "1" }
}; };

View file

@ -1,169 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
namespace Emby.Dlna.Server
{
public class Headers : IDictionary<string, string>
{
private readonly bool _asIs = false;
private readonly Dictionary<string, string> _dict = new Dictionary<string, string>();
private readonly static Regex Validator = new Regex(@"^[a-z\d][a-z\d_.-]+$", RegexOptions.IgnoreCase);
public Headers(bool asIs)
{
_asIs = asIs;
}
public Headers()
: this(asIs: false)
{
}
public int Count
{
get
{
return _dict.Count;
}
}
public string HeaderBlock
{
get
{
var hb = new StringBuilder();
foreach (var h in this)
{
hb.AppendFormat("{0}: {1}\r\n", h.Key, h.Value);
}
return hb.ToString();
}
}
public bool IsReadOnly
{
get
{
return false;
}
}
public ICollection<string> Keys
{
get
{
return _dict.Keys;
}
}
public ICollection<string> Values
{
get
{
return _dict.Values;
}
}
public string this[string key]
{
get
{
return _dict[Normalize(key)];
}
set
{
_dict[Normalize(key)] = value;
}
}
private string Normalize(string header)
{
if (!_asIs)
{
header = header.ToLower();
}
header = header.Trim();
if (!Validator.IsMatch(header))
{
throw new ArgumentException("Invalid header: " + header);
}
return header;
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return _dict.GetEnumerator();
}
public void Add(KeyValuePair<string, string> item)
{
Add(item.Key, item.Value);
}
public void Add(string key, string value)
{
_dict.Add(Normalize(key), value);
}
public void Clear()
{
_dict.Clear();
}
public bool Contains(KeyValuePair<string, string> item)
{
var p = new KeyValuePair<string, string>(Normalize(item.Key), item.Value);
return _dict.Contains(p);
}
public bool ContainsKey(string key)
{
return _dict.ContainsKey(Normalize(key));
}
public void CopyTo(KeyValuePair<string, string>[] array, int arrayIndex)
{
throw new NotImplementedException();
}
public IEnumerator<KeyValuePair<string, string>> GetEnumerator()
{
return _dict.GetEnumerator();
}
public bool Remove(string key)
{
return _dict.Remove(Normalize(key));
}
public bool Remove(KeyValuePair<string, string> item)
{
return Remove(item.Key);
}
public override string ToString()
{
return string.Format("({0})", string.Join(", ", (from x in _dict
select string.Format("{0}={1}", x.Key, x.Value))));
}
public bool TryGetValue(string key, out string value)
{
return _dict.TryGetValue(Normalize(key), out value);
}
public string GetValueOrDefault(string key, string defaultValue)
{
string val;
if (TryGetValue(key, out val))
{
return val;
}
return defaultValue;
}
}
}

View file

@ -9,6 +9,7 @@ using System.Linq;
using System.Text; using System.Text;
using System.Xml; using System.Xml;
using Emby.Dlna.Didl; using Emby.Dlna.Didl;
using MediaBrowser.Controller.Extensions;
using MediaBrowser.Model.Xml; using MediaBrowser.Model.Xml;
namespace Emby.Dlna.Service namespace Emby.Dlna.Service
@ -185,8 +186,7 @@ namespace Emby.Dlna.Service
{ {
using (var subReader = reader.ReadSubtree()) using (var subReader = reader.ReadSubtree())
{ {
result.Headers = ParseFirstBodyChild(subReader); ParseFirstBodyChild(subReader, result.Headers);
return result; return result;
} }
} }
@ -204,10 +204,8 @@ namespace Emby.Dlna.Service
return result; return result;
} }
private Headers ParseFirstBodyChild(XmlReader reader) private void ParseFirstBodyChild(XmlReader reader, IDictionary<string,string> headers)
{ {
var result = new Headers();
reader.MoveToContent(); reader.MoveToContent();
reader.Read(); reader.Read();
@ -216,25 +214,24 @@ namespace Emby.Dlna.Service
{ {
if (reader.NodeType == XmlNodeType.Element) if (reader.NodeType == XmlNodeType.Element)
{ {
result.Add(reader.LocalName, reader.ReadElementContentAsString()); // TODO: Should we be doing this here, or should it be handled earlier when decoding the request?
headers[reader.LocalName.RemoveDiacritics()] = reader.ReadElementContentAsString();
} }
else else
{ {
reader.Read(); reader.Read();
} }
} }
return result;
} }
private class ControlRequestInfo private class ControlRequestInfo
{ {
public string LocalName; public string LocalName;
public string NamespaceURI; public string NamespaceURI;
public Headers Headers = new Headers(); public IDictionary<string, string> Headers = new Dictionary<string,string>(StringComparer.OrdinalIgnoreCase);
} }
protected abstract IEnumerable<KeyValuePair<string, string>> GetResult(string methodName, Headers methodParams); protected abstract IEnumerable<KeyValuePair<string, string>> GetResult(string methodName, IDictionary<string, string> methodParams);
private void LogRequest(ControlRequest request) private void LogRequest(ControlRequest request)
{ {

View file

@ -518,7 +518,7 @@ namespace Emby.Server.Implementations.HttpServer
return; return;
} }
var handler = HttpHandlerFactory.GetHandler(httpReq); var handler = HttpHandlerFactory.GetHandler(httpReq, _logger);
if (handler != null) if (handler != null)
{ {

View file

@ -299,7 +299,8 @@ namespace Emby.Server.Implementations.Library.Resolvers
return false; return false;
} }
return directoryService.GetFiles(fullPath).Any(i => string.Equals(i.Extension, ".m2ts", StringComparison.OrdinalIgnoreCase)); return directoryService.GetFiles(fullPath).Any(i => string.Equals(i.Extension, ".m2ts", StringComparison.OrdinalIgnoreCase) ||
string.Equals(i.Extension, ".mts", StringComparison.OrdinalIgnoreCase));
} }
} }
} }

View file

@ -77,7 +77,9 @@ namespace Emby.Server.Implementations.LiveTv.Listings
// It's going to come back gzipped regardless of this value // It's going to come back gzipped regardless of this value
// So we need to make sure the decompression method is set to gzip // So we need to make sure the decompression method is set to gzip
EnableHttpCompression = true EnableHttpCompression = true,
UserAgent = "Emby/3.0"
}).ConfigureAwait(false); }).ConfigureAwait(false);

View file

@ -47,7 +47,7 @@ namespace MediaBrowser.Common.Net
/// <returns><c>true</c> if [is in local network] [the specified endpoint]; otherwise, <c>false</c>.</returns> /// <returns><c>true</c> if [is in local network] [the specified endpoint]; otherwise, <c>false</c>.</returns>
bool IsInLocalNetwork(string endpoint); bool IsInLocalNetwork(string endpoint);
IEnumerable<IpAddressInfo> GetLocalIpAddresses(); List<IpAddressInfo> GetLocalIpAddresses();
IpAddressInfo ParseIpAddress(string ipAddress); IpAddressInfo ParseIpAddress(string ipAddress);

View file

@ -17,7 +17,7 @@ namespace MediaBrowser.Model.Net
/// <summary> /// <summary>
/// Createa a new unicast socket using the specified local port number. /// Createa a new unicast socket using the specified local port number.
/// </summary> /// </summary>
IUdpSocket CreateSsdpUdpSocket(int localPort); IUdpSocket CreateSsdpUdpSocket(IpAddressInfo localIp, int localPort);
/// <summary> /// <summary>
/// Createa a new multicast socket using the specified multicast IP address, multicast time to live and local port. /// Createa a new multicast socket using the specified multicast IP address, multicast time to live and local port.

View file

@ -10,16 +10,18 @@ namespace MediaBrowser.Model.Net
/// Provides a common interface across platforms for UDP sockets used by this SSDP implementation. /// Provides a common interface across platforms for UDP sockets used by this SSDP implementation.
/// </summary> /// </summary>
public interface IUdpSocket : IDisposable public interface IUdpSocket : IDisposable
{ {
/// <summary> IpAddressInfo LocalIPAddress { get; }
/// Waits for and returns the next UDP message sent to this socket (uni or multicast).
/// </summary> /// <summary>
/// <returns></returns> /// Waits for and returns the next UDP message sent to this socket (uni or multicast).
Task<SocketReceiveResult> ReceiveAsync(); /// </summary>
/// <returns></returns>
Task<SocketReceiveResult> ReceiveAsync();
/// <summary> /// <summary>
/// Sends a UDP message to a particular end point (uni or multicast). /// Sends a UDP message to a particular end point (uni or multicast).
/// </summary> /// </summary>
Task SendAsync(byte[] buffer, int bytes, IpEndPointInfo endPoint); Task SendAsync(byte[] buffer, int bytes, IpEndPointInfo endPoint);
} }
} }

View file

@ -20,5 +20,6 @@ namespace MediaBrowser.Model.Net
/// The <see cref="IpEndPointInfo"/> the data was received from. /// The <see cref="IpEndPointInfo"/> the data was received from.
/// </summary> /// </summary>
public IpEndPointInfo RemoteEndPoint { get; set; } public IpEndPointInfo RemoteEndPoint { get; set; }
} public IpAddressInfo LocalIPAddress { get; set; }
}
} }

View file

@ -46,7 +46,8 @@ namespace Rssdp.Infrastructure
/// </summary> /// </summary>
/// <param name="messageData">A byte array containing the data to send.</param> /// <param name="messageData">A byte array containing the data to send.</param>
/// <param name="destination">A <see cref="IpEndPointInfo"/> representing the destination address for the data. Can be either a multicast or unicast destination.</param> /// <param name="destination">A <see cref="IpEndPointInfo"/> representing the destination address for the data. Can be either a multicast or unicast destination.</param>
Task SendMessage(byte[] messageData, IpEndPointInfo destination); /// <param name="fromLocalIpAddress">A <see cref="IpEndPointInfo"/> The local ip address to send from, or .Any if sending from all available</param>
Task SendMessage(byte[] messageData, IpEndPointInfo destination, IpAddressInfo fromLocalIpAddress);
/// <summary> /// <summary>
/// Sends a message to the SSDP multicast address and port. /// Sends a message to the SSDP multicast address and port.

View file

@ -50,7 +50,6 @@
<Compile Include="ISsdpDevicePublisher.cs" /> <Compile Include="ISsdpDevicePublisher.cs" />
<Compile Include="IUPnPDeviceValidator.cs" /> <Compile Include="IUPnPDeviceValidator.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="ReadOnlyEnumerable.cs" />
<Compile Include="RequestReceivedEventArgs.cs" /> <Compile Include="RequestReceivedEventArgs.cs" />
<Compile Include="ResponseReceivedEventArgs.cs" /> <Compile Include="ResponseReceivedEventArgs.cs" />
<Compile Include="SsdpCommunicationsServer.cs" /> <Compile Include="SsdpCommunicationsServer.cs" />
@ -70,6 +69,10 @@
<Compile Include="UPnP10DeviceValidator.cs" /> <Compile Include="UPnP10DeviceValidator.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj">
<Project>{9142eefa-7570-41e1-bfcc-468bb571af2f}</Project>
<Name>MediaBrowser.Common</Name>
</ProjectReference>
<ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj"> <ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj">
<Project>{7eeeb4bb-f3e8-48fc-b4c5-70f0fff8329b}</Project> <Project>{7eeeb4bb-f3e8-48fc-b4c5-70f0fff8329b}</Project>
<Name>MediaBrowser.Model</Name> <Name>MediaBrowser.Model</Name>

View file

@ -1,46 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Rssdp
{
internal sealed class ReadOnlyEnumerable<T> : System.Collections.Generic.IEnumerable<T>
{
#region Fields
private IEnumerable<T> _Items;
#endregion
#region Constructors
public ReadOnlyEnumerable(IEnumerable<T> items)
{
if (items == null) throw new ArgumentNullException("items");
_Items = items;
}
#endregion
#region IEnumerable<T> Members
public IEnumerator<T> GetEnumerator()
{
return _Items.GetEnumerator();
}
#endregion
#region IEnumerable Members
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return _Items.GetEnumerator();
}
#endregion
}
}

View file

@ -22,17 +22,18 @@ namespace Rssdp.Infrastructure
#endregion #endregion
#region Constructors public IpAddressInfo LocalIpAddress { get; private set; }
#region Constructors
/// <summary> /// <summary>
/// Full constructor. /// Full constructor.
/// </summary> /// </summary>
/// <param name="message">The <see cref="HttpRequestMessage"/> that was received.</param> public RequestReceivedEventArgs(HttpRequestMessage message, IpEndPointInfo receivedFrom, IpAddressInfo localIpAddress)
/// <param name="receivedFrom">A <see cref="UdpEndPoint"/> representing the sender's address (sometimes used for replies).</param>
public RequestReceivedEventArgs(HttpRequestMessage message, IpEndPointInfo receivedFrom)
{ {
_Message = message; _Message = message;
_ReceivedFrom = receivedFrom; _ReceivedFrom = receivedFrom;
LocalIpAddress = localIpAddress;
} }
#endregion #endregion

View file

@ -5,6 +5,8 @@ using System.Net;
using System.Net.Http; using System.Net.Http;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Common.Net;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Net; using MediaBrowser.Model.Net;
namespace Rssdp.Infrastructure namespace Rssdp.Infrastructure
@ -38,12 +40,13 @@ namespace Rssdp.Infrastructure
private IUdpSocket _BroadcastListenSocket; private IUdpSocket _BroadcastListenSocket;
private object _SendSocketSynchroniser = new object(); private object _SendSocketSynchroniser = new object();
private IUdpSocket _SendSocket; private List<IUdpSocket> _sendSockets;
private HttpRequestParser _RequestParser; private HttpRequestParser _RequestParser;
private HttpResponseParser _ResponseParser; private HttpResponseParser _ResponseParser;
private readonly ILogger _logger;
private ISocketFactory _SocketFactory; private ISocketFactory _SocketFactory;
private readonly INetworkManager _networkManager;
private int _LocalPort; private int _LocalPort;
private int _MulticastTtl; private int _MulticastTtl;
@ -71,22 +74,18 @@ namespace Rssdp.Infrastructure
/// <summary> /// <summary>
/// Minimum constructor. /// Minimum constructor.
/// </summary> /// </summary>
/// <param name="socketFactory">An implementation of the <see cref="ISocketFactory"/> interface that can be used to make new unicast and multicast sockets. Cannot be null.</param>
/// <exception cref="System.ArgumentNullException">The <paramref name="socketFactory"/> argument is null.</exception> /// <exception cref="System.ArgumentNullException">The <paramref name="socketFactory"/> argument is null.</exception>
public SsdpCommunicationsServer(ISocketFactory socketFactory) public SsdpCommunicationsServer(ISocketFactory socketFactory, INetworkManager networkManager, ILogger logger)
: this(socketFactory, 0, SsdpConstants.SsdpDefaultMulticastTimeToLive) : this(socketFactory, 0, SsdpConstants.SsdpDefaultMulticastTimeToLive, networkManager, logger)
{ {
} }
/// <summary> /// <summary>
/// Full constructor. /// Full constructor.
/// </summary> /// </summary>
/// <param name="socketFactory">An implementation of the <see cref="ISocketFactory"/> interface that can be used to make new unicast and multicast sockets. Cannot be null.</param>
/// <param name="localPort">The specific local port to use for all sockets created by this instance. Specify zero to indicate the system should choose a free port itself.</param>
/// <param name="multicastTimeToLive">The multicast time to live value for multicast sockets. Technically this is a number of router hops, not a 'Time'. Must be greater than zero.</param>
/// <exception cref="System.ArgumentNullException">The <paramref name="socketFactory"/> argument is null.</exception> /// <exception cref="System.ArgumentNullException">The <paramref name="socketFactory"/> argument is null.</exception>
/// <exception cref="System.ArgumentOutOfRangeException">The <paramref name="multicastTimeToLive"/> argument is less than or equal to zero.</exception> /// <exception cref="System.ArgumentOutOfRangeException">The <paramref name="multicastTimeToLive"/> argument is less than or equal to zero.</exception>
public SsdpCommunicationsServer(ISocketFactory socketFactory, int localPort, int multicastTimeToLive) public SsdpCommunicationsServer(ISocketFactory socketFactory, int localPort, int multicastTimeToLive, INetworkManager networkManager, ILogger logger)
{ {
if (socketFactory == null) throw new ArgumentNullException("socketFactory"); if (socketFactory == null) throw new ArgumentNullException("socketFactory");
if (multicastTimeToLive <= 0) throw new ArgumentOutOfRangeException("multicastTimeToLive", "multicastTimeToLive must be greater than zero."); if (multicastTimeToLive <= 0) throw new ArgumentOutOfRangeException("multicastTimeToLive", "multicastTimeToLive must be greater than zero.");
@ -101,6 +100,8 @@ namespace Rssdp.Infrastructure
_ResponseParser = new HttpResponseParser(); _ResponseParser = new HttpResponseParser();
_MulticastTtl = multicastTimeToLive; _MulticastTtl = multicastTimeToLive;
_networkManager = networkManager;
_logger = logger;
} }
#endregion #endregion
@ -148,25 +149,72 @@ namespace Rssdp.Infrastructure
/// </summary> /// </summary>
/// <param name="messageData">A byte array containing the data to send.</param> /// <param name="messageData">A byte array containing the data to send.</param>
/// <param name="destination">A <see cref="IpEndPointInfo"/> representing the destination address for the data. Can be either a multicast or unicast destination.</param> /// <param name="destination">A <see cref="IpEndPointInfo"/> representing the destination address for the data. Can be either a multicast or unicast destination.</param>
/// <param name="fromLocalIpAddress">A <see cref="IpEndPointInfo"/> The local ip address to send from, or .Any if sending from all available</param>
/// <exception cref="System.ArgumentNullException">Thrown if the <paramref name="messageData"/> argument is null.</exception> /// <exception cref="System.ArgumentNullException">Thrown if the <paramref name="messageData"/> argument is null.</exception>
/// <exception cref="System.ObjectDisposedException">Thrown if the <see cref="DisposableManagedObjectBase.IsDisposed"/> property is true (because <seealso cref="DisposableManagedObjectBase.Dispose()" /> has been called previously).</exception> /// <exception cref="System.ObjectDisposedException">Thrown if the <see cref="DisposableManagedObjectBase.IsDisposed"/> property is true (because <seealso cref="DisposableManagedObjectBase.Dispose()" /> has been called previously).</exception>
public async Task SendMessage(byte[] messageData, IpEndPointInfo destination) public async Task SendMessage(byte[] messageData, IpEndPointInfo destination, IpAddressInfo fromLocalIpAddress)
{ {
if (messageData == null) throw new ArgumentNullException("messageData"); if (messageData == null) throw new ArgumentNullException("messageData");
ThrowIfDisposed(); ThrowIfDisposed();
EnsureSendSocketCreated(); var sockets = GetSendSockets(fromLocalIpAddress, destination);
if (sockets.Count == 0)
{
return;
}
// SSDP spec recommends sending messages multiple times (not more than 3) to account for possible packet loss over UDP. // SSDP spec recommends sending messages multiple times (not more than 3) to account for possible packet loss over UDP.
for (var i = 0; i < SsdpConstants.UdpResendCount; i++) for (var i = 0; i < SsdpConstants.UdpResendCount; i++)
{ {
await SendMessageIfSocketNotDisposed(messageData, destination).ConfigureAwait(false); var tasks = sockets.Select(s => SendFromSocket(s, messageData, destination)).ToArray();
await Task.WhenAll(tasks).ConfigureAwait(false);
await Task.Delay(100).ConfigureAwait(false); await Task.Delay(100).ConfigureAwait(false);
} }
} }
private async Task SendFromSocket(IUdpSocket socket, byte[] messageData, IpEndPointInfo destination)
{
try
{
await socket.SendAsync(messageData, messageData.Length, destination).ConfigureAwait(false);
}
catch (Exception ex)
{
_logger.ErrorException("Error sending socket message from {0} to {1}", ex, socket.LocalIPAddress.ToString(), destination.ToString());
}
}
private List<IUdpSocket> GetSendSockets(IpAddressInfo fromLocalIpAddress, IpEndPointInfo destination)
{
EnsureSendSocketCreated();
lock (_SendSocketSynchroniser)
{
var sockets = _sendSockets.Where(i => i.LocalIPAddress.AddressFamily == fromLocalIpAddress.AddressFamily);
// Send from the Any socket and the socket with the matching address
if (fromLocalIpAddress.AddressFamily == IpAddressFamily.InterNetwork)
{
sockets = sockets.Where(i => i.LocalIPAddress.Equals(IpAddressInfo.Any) || fromLocalIpAddress.Equals(i.LocalIPAddress));
}
else if (fromLocalIpAddress.AddressFamily == IpAddressFamily.InterNetworkV6)
{
sockets = sockets.Where(i => i.LocalIPAddress.Equals(IpAddressInfo.IPv6Any) || fromLocalIpAddress.Equals(i.LocalIPAddress));
}
// If sending to the loopback address, filter the socket list as well
if (destination.IpAddress.Equals(IpAddressInfo.Loopback))
{
sockets = sockets.Where(i => i.LocalIPAddress.Equals(IpAddressInfo.Any) || i.LocalIPAddress.Equals(IpAddressInfo.Loopback));
}
return sockets.ToList();
}
}
/// <summary> /// <summary>
/// Sends a message to the SSDP multicast address and port. /// Sends a message to the SSDP multicast address and port.
/// </summary> /// </summary>
@ -185,7 +233,10 @@ namespace Rssdp.Infrastructure
{ {
await SendMessageIfSocketNotDisposed(messageData, new IpEndPointInfo await SendMessageIfSocketNotDisposed(messageData, new IpEndPointInfo
{ {
IpAddress = new IpAddressInfo { Address = SsdpConstants.MulticastLocalAdminAddress }, IpAddress = new IpAddressInfo
{
Address = SsdpConstants.MulticastLocalAdminAddress
},
Port = SsdpConstants.MulticastPort Port = SsdpConstants.MulticastPort
}).ConfigureAwait(false); }).ConfigureAwait(false);
@ -204,10 +255,16 @@ namespace Rssdp.Infrastructure
lock (_SendSocketSynchroniser) lock (_SendSocketSynchroniser)
{ {
var socket = _SendSocket; if (_sendSockets != null)
_SendSocket = null; {
if (socket != null) var sockets = _sendSockets.ToList();
socket.Dispose(); _sendSockets = null;
foreach (var socket in sockets)
{
socket.Dispose();
}
}
} }
} }
@ -247,8 +304,16 @@ namespace Rssdp.Infrastructure
lock (_SendSocketSynchroniser) lock (_SendSocketSynchroniser)
{ {
if (_SendSocket != null) if (_sendSockets != null)
_SendSocket.Dispose(); {
var sockets = _sendSockets.ToList();
_sendSockets = null;
foreach (var socket in sockets)
{
socket.Dispose();
}
}
} }
} }
} }
@ -257,16 +322,20 @@ namespace Rssdp.Infrastructure
#region Private Methods #region Private Methods
private Task SendMessageIfSocketNotDisposed(byte[] messageData, IpEndPointInfo destination) private async Task SendMessageIfSocketNotDisposed(byte[] messageData, IpEndPointInfo destination)
{ {
var socket = _SendSocket; var sockets = _sendSockets;
if (socket != null) if (sockets != null)
{ {
return _SendSocket.SendAsync(messageData, messageData.Length, destination); sockets = sockets.ToList();
foreach (var socket in sockets)
{
await socket.SendAsync(messageData, messageData.Length, destination).ConfigureAwait(false);
}
} }
ThrowIfDisposed(); ThrowIfDisposed();
return Task.FromResult(true);
} }
private IUdpSocket ListenForBroadcastsAsync() private IUdpSocket ListenForBroadcastsAsync()
@ -278,13 +347,30 @@ namespace Rssdp.Infrastructure
return socket; return socket;
} }
private IUdpSocket CreateSocketAndListenForResponsesAsync() private List<IUdpSocket> CreateSocketAndListenForResponsesAsync()
{ {
_SendSocket = _SocketFactory.CreateSsdpUdpSocket(_LocalPort); var sockets = new List<IUdpSocket>();
ListenToSocket(_SendSocket); sockets.Add(_SocketFactory.CreateSsdpUdpSocket(IpAddressInfo.Any, _LocalPort));
return _SendSocket; foreach (var address in _networkManager.GetLocalIpAddresses().ToList())
{
try
{
sockets.Add(_SocketFactory.CreateSsdpUdpSocket(address, _LocalPort));
}
catch (Exception ex)
{
_logger.ErrorException("Error in CreateSsdpUdpSocket. IPAddress: {0}", ex, address);
}
}
foreach (var socket in sockets)
{
ListenToSocket(socket);
}
return sockets;
} }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1804:RemoveUnusedLocals", MessageId = "t", Justification = "Capturing task to local variable removes compiler warning, task is not otherwise required.")] [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1804:RemoveUnusedLocals", MessageId = "t", Justification = "Capturing task to local variable removes compiler warning, task is not otherwise required.")]
@ -305,7 +391,7 @@ namespace Rssdp.Infrastructure
// Strange cannot convert compiler error here if I don't explicitly // Strange cannot convert compiler error here if I don't explicitly
// assign or cast to Action first. Assignment is easier to read, // assign or cast to Action first. Assignment is easier to read,
// so went with that. // so went with that.
ProcessMessage(System.Text.UTF8Encoding.UTF8.GetString(result.Buffer, 0, result.ReceivedBytes), result.RemoteEndPoint); ProcessMessage(System.Text.UTF8Encoding.UTF8.GetString(result.Buffer, 0, result.ReceivedBytes), result.RemoteEndPoint, result.LocalIPAddress);
} }
} }
catch (ObjectDisposedException) catch (ObjectDisposedException)
@ -322,19 +408,19 @@ namespace Rssdp.Infrastructure
private void EnsureSendSocketCreated() private void EnsureSendSocketCreated()
{ {
if (_SendSocket == null) if (_sendSockets == null)
{ {
lock (_SendSocketSynchroniser) lock (_SendSocketSynchroniser)
{ {
if (_SendSocket == null) if (_sendSockets == null)
{ {
_SendSocket = CreateSocketAndListenForResponsesAsync(); _sendSockets = CreateSocketAndListenForResponsesAsync();
} }
} }
} }
} }
private void ProcessMessage(string data, IpEndPointInfo endPoint) private void ProcessMessage(string data, IpEndPointInfo endPoint, IpAddressInfo receivedOnLocalIpAddress)
{ {
//Responses start with the HTTP version, prefixed with HTTP/ while //Responses start with the HTTP version, prefixed with HTTP/ while
//requests start with a method which can vary and might be one we haven't //requests start with a method which can vary and might be one we haven't
@ -347,7 +433,10 @@ namespace Rssdp.Infrastructure
{ {
responseMessage = _ResponseParser.Parse(data); responseMessage = _ResponseParser.Parse(data);
} }
catch (ArgumentException) { } // Ignore invalid packets. catch (ArgumentException ex)
{
// Ignore invalid packets.
}
if (responseMessage != null) if (responseMessage != null)
OnResponseReceived(responseMessage, endPoint); OnResponseReceived(responseMessage, endPoint);
@ -359,23 +448,31 @@ namespace Rssdp.Infrastructure
{ {
requestMessage = _RequestParser.Parse(data); requestMessage = _RequestParser.Parse(data);
} }
catch (ArgumentException) { } // Ignore invalid packets. catch (ArgumentException ex)
{
// Ignore invalid packets.
}
if (requestMessage != null) if (requestMessage != null)
OnRequestReceived(requestMessage, endPoint); {
OnRequestReceived(requestMessage, endPoint, receivedOnLocalIpAddress);
}
} }
} }
private void OnRequestReceived(HttpRequestMessage data, IpEndPointInfo endPoint) private void OnRequestReceived(HttpRequestMessage data, IpEndPointInfo remoteEndPoint, IpAddressInfo receivedOnLocalIpAddress)
{ {
//SSDP specification says only * is currently used but other uri's might //SSDP specification says only * is currently used but other uri's might
//be implemented in the future and should be ignored unless understood. //be implemented in the future and should be ignored unless understood.
//Section 4.2 - http://tools.ietf.org/html/draft-cai-ssdp-v1-03#page-11 //Section 4.2 - http://tools.ietf.org/html/draft-cai-ssdp-v1-03#page-11
if (data.RequestUri.ToString() != "*") return; if (data.RequestUri.ToString() != "*")
{
return;
}
var handlers = this.RequestReceived; var handlers = this.RequestReceived;
if (handlers != null) if (handlers != null)
handlers(this, new RequestReceivedEventArgs(data, endPoint)); handlers(this, new RequestReceivedEventArgs(data, remoteEndPoint, receivedOnLocalIpAddress));
} }
private void OnResponseReceived(HttpResponseMessage data, IpEndPointInfo endPoint) private void OnResponseReceived(HttpResponseMessage data, IpEndPointInfo endPoint)

View file

@ -64,7 +64,7 @@ namespace Rssdp
this.Icons = new List<SsdpDeviceIcon>(); this.Icons = new List<SsdpDeviceIcon>();
_Devices = new List<SsdpDevice>(); _Devices = new List<SsdpDevice>();
this.Devices = new ReadOnlyEnumerable<SsdpDevice>(_Devices); this.Devices = new ReadOnlyCollection<SsdpDevice>(_Devices);
_CustomResponseHeaders = new CustomHttpHeadersCollection(); _CustomResponseHeaders = new CustomHttpHeadersCollection();
_CustomProperties = new SsdpDevicePropertiesCollection(); _CustomProperties = new SsdpDevicePropertiesCollection();
} }

View file

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq; using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Text; using System.Text;
@ -25,7 +26,7 @@ namespace Rssdp.Infrastructure
private bool _SupportPnpRootDevice; private bool _SupportPnpRootDevice;
private IList<SsdpRootDevice> _Devices; private IList<SsdpRootDevice> _Devices;
private ReadOnlyEnumerable<SsdpRootDevice> _ReadOnlyDevices; private IReadOnlyList<SsdpRootDevice> _ReadOnlyDevices;
private ITimer _RebroadcastAliveNotificationsTimer; private ITimer _RebroadcastAliveNotificationsTimer;
private ITimerFactory _timerFactory; private ITimerFactory _timerFactory;
@ -62,7 +63,7 @@ namespace Rssdp.Infrastructure
_SupportPnpRootDevice = true; _SupportPnpRootDevice = true;
_timerFactory = timerFactory; _timerFactory = timerFactory;
_Devices = new List<SsdpRootDevice>(); _Devices = new List<SsdpRootDevice>();
_ReadOnlyDevices = new ReadOnlyEnumerable<SsdpRootDevice>(_Devices); _ReadOnlyDevices = new ReadOnlyCollection<SsdpRootDevice>(_Devices);
_RecentSearchRequests = new Dictionary<string, SearchRequest>(StringComparer.OrdinalIgnoreCase); _RecentSearchRequests = new Dictionary<string, SearchRequest>(StringComparer.OrdinalIgnoreCase);
_Random = new Random(); _Random = new Random();
_DeviceValidator = new Upnp10DeviceValidator(); //Should probably inject this later, but for now we only support 1.0. _DeviceValidator = new Upnp10DeviceValidator(); //Should probably inject this later, but for now we only support 1.0.
@ -236,17 +237,17 @@ namespace Rssdp.Infrastructure
#region Search Related Methods #region Search Related Methods
private void ProcessSearchRequest(string mx, string searchTarget, IpEndPointInfo endPoint) private void ProcessSearchRequest(string mx, string searchTarget, IpEndPointInfo remoteEndPoint, IpAddressInfo receivedOnlocalIpAddress)
{ {
if (String.IsNullOrEmpty(searchTarget)) if (String.IsNullOrEmpty(searchTarget))
{ {
WriteTrace(String.Format("Invalid search request received From {0}, Target is null/empty.", endPoint.ToString())); WriteTrace(String.Format("Invalid search request received From {0}, Target is null/empty.", remoteEndPoint.ToString()));
return; return;
} }
WriteTrace(String.Format("Search Request Received From {0}, Target = {1}", endPoint.ToString(), searchTarget)); WriteTrace(String.Format("Search Request Received From {0}, Target = {1}", remoteEndPoint.ToString(), searchTarget));
if (IsDuplicateSearchRequest(searchTarget, endPoint)) if (IsDuplicateSearchRequest(searchTarget, remoteEndPoint))
{ {
WriteTrace("Search Request is Duplicate, ignoring."); WriteTrace("Search Request is Duplicate, ignoring.");
return; return;
@ -294,7 +295,7 @@ namespace Rssdp.Infrastructure
foreach (var device in deviceList) foreach (var device in deviceList)
{ {
SendDeviceSearchResponses(device, endPoint); SendDeviceSearchResponses(device, remoteEndPoint, receivedOnlocalIpAddress);
} }
} }
else else
@ -307,19 +308,19 @@ namespace Rssdp.Infrastructure
return _Devices.Union(_Devices.SelectManyRecursive<SsdpDevice>((d) => d.Devices)); return _Devices.Union(_Devices.SelectManyRecursive<SsdpDevice>((d) => d.Devices));
} }
private void SendDeviceSearchResponses(SsdpDevice device, IpEndPointInfo endPoint) private void SendDeviceSearchResponses(SsdpDevice device, IpEndPointInfo endPoint, IpAddressInfo receivedOnlocalIpAddress)
{ {
bool isRootDevice = (device as SsdpRootDevice) != null; bool isRootDevice = (device as SsdpRootDevice) != null;
if (isRootDevice) if (isRootDevice)
{ {
SendSearchResponse(SsdpConstants.UpnpDeviceTypeRootDevice, device, GetUsn(device.Udn, SsdpConstants.UpnpDeviceTypeRootDevice), endPoint); SendSearchResponse(SsdpConstants.UpnpDeviceTypeRootDevice, device, GetUsn(device.Udn, SsdpConstants.UpnpDeviceTypeRootDevice), endPoint, receivedOnlocalIpAddress);
if (this.SupportPnpRootDevice) if (this.SupportPnpRootDevice)
SendSearchResponse(SsdpConstants.PnpDeviceTypeRootDevice, device, GetUsn(device.Udn, SsdpConstants.PnpDeviceTypeRootDevice), endPoint); SendSearchResponse(SsdpConstants.PnpDeviceTypeRootDevice, device, GetUsn(device.Udn, SsdpConstants.PnpDeviceTypeRootDevice), endPoint, receivedOnlocalIpAddress);
} }
SendSearchResponse(device.Udn, device, device.Udn, endPoint); SendSearchResponse(device.Udn, device, device.Udn, endPoint, receivedOnlocalIpAddress);
SendSearchResponse(device.FullDeviceType, device, GetUsn(device.Udn, device.FullDeviceType), endPoint); SendSearchResponse(device.FullDeviceType, device, GetUsn(device.Udn, device.FullDeviceType), endPoint, receivedOnlocalIpAddress);
} }
private static string GetUsn(string udn, string fullDeviceType) private static string GetUsn(string udn, string fullDeviceType)
@ -327,7 +328,7 @@ namespace Rssdp.Infrastructure
return String.Format("{0}::{1}", udn, fullDeviceType); return String.Format("{0}::{1}", udn, fullDeviceType);
} }
private async void SendSearchResponse(string searchTarget, SsdpDevice device, string uniqueServiceName, IpEndPointInfo endPoint) private async void SendSearchResponse(string searchTarget, SsdpDevice device, string uniqueServiceName, IpEndPointInfo endPoint, IpAddressInfo receivedOnlocalIpAddress)
{ {
var rootDevice = device.ToRootDevice(); var rootDevice = device.ToRootDevice();
@ -349,7 +350,7 @@ namespace Rssdp.Infrastructure
try try
{ {
await _CommsServer.SendMessage(System.Text.Encoding.UTF8.GetBytes(message), endPoint).ConfigureAwait(false); await _CommsServer.SendMessage(System.Text.Encoding.UTF8.GetBytes(message), endPoint, receivedOnlocalIpAddress).ConfigureAwait(false);
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -674,7 +675,7 @@ namespace Rssdp.Infrastructure
//else if (!e.Message.Headers.Contains("MAN")) //else if (!e.Message.Headers.Contains("MAN"))
// WriteTrace("Ignoring search request - missing MAN header."); // WriteTrace("Ignoring search request - missing MAN header.");
//else //else
ProcessSearchRequest(GetFirstHeaderValue(e.Message.Headers, "MX"), GetFirstHeaderValue(e.Message.Headers, "ST"), e.ReceivedFrom); ProcessSearchRequest(GetFirstHeaderValue(e.Message.Headers, "MX"), GetFirstHeaderValue(e.Message.Headers, "ST"), e.ReceivedFrom, e.LocalIpAddress);
} }
} }

View file

@ -109,7 +109,7 @@ namespace ServiceStack.Host
this.Notes = notes; this.Notes = notes;
this.restPath = path; this.restPath = path;
this.allowsAllVerbs = verbs == null || verbs == WildCard; this.allowsAllVerbs = verbs == null || string.Equals(verbs, WildCard, StringComparison.OrdinalIgnoreCase);
if (!this.allowsAllVerbs) if (!this.allowsAllVerbs)
{ {
this.allowedVerbs = verbs.ToUpper(); this.allowedVerbs = verbs.ToUpper();
@ -123,7 +123,7 @@ namespace ServiceStack.Host
{ {
if (string.IsNullOrEmpty(component)) continue; if (string.IsNullOrEmpty(component)) continue;
if (component.Contains(VariablePrefix) if (StringContains(component, VariablePrefix)
&& component.IndexOf(ComponentSeperator) != -1) && component.IndexOf(ComponentSeperator) != -1)
{ {
hasSeparators.Add(true); hasSeparators.Add(true);
@ -240,12 +240,17 @@ namespace ServiceStack.Host
score += Math.Max((10 - VariableArgsCount), 1) * 100; score += Math.Max((10 - VariableArgsCount), 1) * 100;
//Exact verb match is better than ANY //Exact verb match is better than ANY
var exactVerb = httpMethod == AllowedVerbs; var exactVerb = string.Equals(httpMethod, AllowedVerbs, StringComparison.OrdinalIgnoreCase);
score += exactVerb ? 10 : 1; score += exactVerb ? 10 : 1;
return score; return score;
} }
private bool StringContains(string str1, string str2)
{
return str1.IndexOf(str2, StringComparison.OrdinalIgnoreCase) != -1;
}
/// <summary> /// <summary>
/// For performance withPathInfoParts should already be a lower case string /// For performance withPathInfoParts should already be a lower case string
/// to minimize redundant matching operations. /// to minimize redundant matching operations.
@ -259,7 +264,7 @@ namespace ServiceStack.Host
wildcardMatchCount = 0; wildcardMatchCount = 0;
if (withPathInfoParts.Length != this.PathComponentsCount && !this.IsWildCardPath) return false; if (withPathInfoParts.Length != this.PathComponentsCount && !this.IsWildCardPath) return false;
if (!this.allowsAllVerbs && !this.allowedVerbs.Contains(httpMethod.ToUpper())) return false; if (!this.allowsAllVerbs && !StringContains(this.allowedVerbs, httpMethod)) return false;
if (!ExplodeComponents(ref withPathInfoParts)) return false; if (!ExplodeComponents(ref withPathInfoParts)) return false;
if (this.TotalComponentsCount != withPathInfoParts.Length && !this.IsWildCardPath) return false; if (this.TotalComponentsCount != withPathInfoParts.Length && !this.IsWildCardPath) return false;

View file

@ -83,7 +83,7 @@ namespace ServiceStack.Host
} }
} }
public readonly Dictionary<string, List<RestPath>> RestPathMap = new Dictionary<string, List<RestPath>>(); public readonly Dictionary<string, List<RestPath>> RestPathMap = new Dictionary<string, List<RestPath>>(StringComparer.OrdinalIgnoreCase);
public void RegisterRestPaths(Type requestType) public void RegisterRestPaths(Type requestType)
{ {

View file

@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Services; using MediaBrowser.Model.Services;
using ServiceStack.Host; using ServiceStack.Host;
@ -9,12 +10,16 @@ namespace ServiceStack
public class HttpHandlerFactory public class HttpHandlerFactory
{ {
// Entry point for HttpListener // Entry point for HttpListener
public static RestHandler GetHandler(IHttpRequest httpReq) public static RestHandler GetHandler(IHttpRequest httpReq, ILogger logger)
{ {
var pathInfo = httpReq.PathInfo; var pathInfo = httpReq.PathInfo;
var pathParts = pathInfo.TrimStart('/').Split('/'); var pathParts = pathInfo.TrimStart('/').Split('/');
if (pathParts.Length == 0) return null; if (pathParts.Length == 0)
{
logger.Error("Path parts empty for PathInfo: {0}, Url: {1}", pathInfo, httpReq.RawUrl);
return null;
}
string contentType; string contentType;
var restPath = RestHandler.FindMatchingRestPath(httpReq.HttpMethod, pathInfo, out contentType); var restPath = RestHandler.FindMatchingRestPath(httpReq.HttpMethod, pathInfo, out contentType);