public void SendTo(byte[] messageData, UdpEndPoint endPoint) { if (messageData == null) throw new ArgumentNullException("messageData"); if (endPoint == null) throw new ArgumentNullException("endPoint"); ThrowIfDisposed(); var signal = new System.Threading.ManualResetEvent(false); try { _UdpClient.BeginSendToGroup(messageData, 0, messageData.Length, (asyncResult) => { _UdpClient.EndSendToGroup(asyncResult); signal.Set(); } , null ); signal.WaitOne(); } finally { signal.Dispose(); } }
public void SendTo(byte[] messageData, UdpEndPoint endPoint) { ThrowIfDisposed(); if (messageData == null) throw new ArgumentNullException("messageData"); if (endPoint == null) throw new ArgumentNullException("endPoint"); _Socket.SendTo(messageData, new System.Net.IPEndPoint(IPAddress.Parse(endPoint.IPAddress), endPoint.Port)); }
public void SendTo(byte[] messageData, UdpEndPoint endPoint) { if (messageData == null) throw new ArgumentNullException("messageData"); if (endPoint == null) throw new ArgumentNullException("endPoint"); ThrowIfDisposed(); var args = new SocketAsyncEventArgs(); try { args.SetBuffer(messageData, 0, messageData.Length); args.RemoteEndPoint = new System.Net.IPEndPoint(IPAddress.Parse(endPoint.IPAddress), endPoint.Port); var signal = new ManualResetEvent(false); try { args.Completed += (sender, e) => { signal.Set(); }; _Socket.SendToAsync(args); signal.WaitOne(); } finally { signal.Dispose(); } } catch { if (args != null) args.Dispose(); throw; } }
private void ProcessMessage(string data, UdpEndPoint endPoint) { //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 //seen/don't know. We'll check if this message is a request or a response //by checking for the static HTTP/ prefix on the start of the message. if (data.StartsWith("HTTP/", StringComparison.OrdinalIgnoreCase)) { HttpResponseMessage responseMessage = null; try { responseMessage = _ResponseParser.Parse(data); } catch (ArgumentException) { } // Ignore invalid packets. catch (FormatException) { } // Ignore invalid packets. if (responseMessage != null) { OnResponseReceived(responseMessage, endPoint); } } else { HttpRequestMessage requestMessage = null; try { requestMessage = _RequestParser.Parse(data); } catch (ArgumentException) { } // Ignore invalid packets. catch (FormatException) { } // Ignore invalid packets. if (requestMessage != null) { OnRequestReceived(requestMessage, endPoint); } } }
private void ProcessMessage(string data, UdpEndPoint endPoint) { //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 //seen/don't know. We'll check if this message is a request or a response //by checking for the static HTTP/ prefix on the start of the message. if (data.StartsWith("HTTP/", StringComparison.OrdinalIgnoreCase)) { HttpResponseMessage responseMessage = null; try { responseMessage = _ResponseParser.Parse(data); } catch (ArgumentException) { } // Ignore invalid packets. if (responseMessage != null) OnResponseReceived(responseMessage, endPoint); } else { HttpRequestMessage requestMessage = null; try { requestMessage = _RequestParser.Parse(data); } catch (ArgumentException) { } // Ignore invalid packets. if (requestMessage != null) OnRequestReceived(requestMessage, endPoint); } _MessageProcessedSignal.Set(); }
private void SendServiceSearchResponses(SsdpDevice device, string searchTarget, UdpEndPoint endPoint) { //uuid:device-UUID::urn:domain-name:service:serviceType:ver SendSearchResponse(searchTarget, device, device.Udn + "::" + searchTarget, endPoint); }
public void SendTo(byte[] messageData, UdpEndPoint endPoint) { using (var stream = (_Socket.GetOutputStreamAsync(new Windows.Networking.HostName(endPoint.IPAddress), endPoint.Port.ToString()).AsTask().Result)) { using (var outStream = stream.AsStreamForWrite()) { outStream.Write(messageData, 0, messageData.Length); outStream.Flush(); } } }
private void SendSearchResponse(string searchTarget, SsdpDevice device, string uniqueServiceName, UdpEndPoint endPoint) { var rootDevice = device.ToRootDevice(); var message = String.Format(DeviceSearchResponseMessageFormat, CacheControlHeaderFromTimeSpan(rootDevice), searchTarget, uniqueServiceName, rootDevice.Location, _OSName, _OSVersion, ServerVersion, DateTime.UtcNow.ToString("r") ); _CommsServer.SendMessage(System.Text.UTF8Encoding.UTF8.GetBytes(message), endPoint); WriteTrace(String.Format("Sent search response to " + endPoint.ToString()), device); }
public void CommsServer_SendMessageSendsOnUnicastSocket() { var socketFactory = new MockSocketFactory(); var server = new SsdpCommunicationsServer(socketFactory); string message = "Hello!"; UdpEndPoint destination = new UdpEndPoint() { IPAddress = "192.168.1.100", Port = 1701 }; server.SendMessage(System.Text.UTF8Encoding.UTF8.GetBytes(message), destination); Assert.IsNotNull(socketFactory.UnicastSocket); var mockSocket = socketFactory.UnicastSocket as MockSocket; Assert.AreEqual(message, System.Text.UTF8Encoding.UTF8.GetString(mockSocket.LastMessage)); Assert.AreEqual(destination.IPAddress, mockSocket.LastSentTo.IPAddress); Assert.AreEqual(destination.Port, mockSocket.LastSentTo.Port); }
/// <summary> /// Full constructor. /// </summary> /// <param name="message">The <see cref="HttpResponseMessage"/> that was received.</param> /// <param name="receivedFrom">A <see cref="UdpEndPoint"/> representing the sender's address (sometimes used for replies).</param> public ResponseReceivedEventArgs(HttpResponseMessage message, UdpEndPoint receivedFrom) { _Message = message; _ReceivedFrom = receivedFrom; }
private void ProcessSearchRequest(string mx, string searchTarget, UdpEndPoint endPoint) { if (String.IsNullOrEmpty(searchTarget)) { WriteTrace(String.Format("Invalid search request received From {0}, Target is null/empty.", endPoint.ToString())); return; } WriteTrace(String.Format("Search Request Received From {0}, Target = {1}", endPoint.ToString(), searchTarget)); if (IsDuplicateSearchRequest(searchTarget, endPoint)) { WriteTrace("Search Request is Duplicate, ignoring."); return; } //Wait on random interval up to MX, as per SSDP spec. //Also, as per UPnP 1.1/SSDP spec ignore missing/bank MX header. If over 120, assume random value between 0 and 120. //Using 16 as minimum as that's often the minimum system clock frequency anyway. int maxWaitInterval = 0; if (String.IsNullOrEmpty(mx)) { //Windows Explorer is poorly behaved and doesn't supply an MX header value. if (this.SupportPnpRootDevice) mx = "1"; else return; } if (!Int32.TryParse(mx, out maxWaitInterval) || maxWaitInterval <= 0) return; if (maxWaitInterval > 120) maxWaitInterval = _Random.Next(0, 120); //Do not block synchronously as that may tie up a threadpool thread for several seconds. TaskEx.Delay(_Random.Next(16, (maxWaitInterval * 1000))).ContinueWith((parentTask) => { //Copying devices to local array here to avoid threading issues/enumerator exceptions. IEnumerable<SsdpDevice> devices = null; lock (_Devices) { if (String.Compare(SsdpConstants.SsdpDiscoverAllSTHeader, searchTarget, StringComparison.OrdinalIgnoreCase) == 0) devices = GetAllDevicesAsFlatEnumerable().ToArray(); else if (String.Compare(SsdpConstants.UpnpDeviceTypeRootDevice, searchTarget, StringComparison.OrdinalIgnoreCase) == 0 || (this.SupportPnpRootDevice && String.Compare(SsdpConstants.PnpDeviceTypeRootDevice, searchTarget, StringComparison.OrdinalIgnoreCase) == 0)) devices = _Devices.ToArray(); else if (searchTarget.Trim().StartsWith("uuid:", StringComparison.OrdinalIgnoreCase)) devices = (from device in GetAllDevicesAsFlatEnumerable() where String.Compare(device.Uuid, searchTarget.Substring(5), StringComparison.OrdinalIgnoreCase) == 0 select device).ToArray(); else if (searchTarget.StartsWith("urn:", StringComparison.OrdinalIgnoreCase)) devices = (from device in GetAllDevicesAsFlatEnumerable() where String.Compare(device.FullDeviceType, searchTarget, StringComparison.OrdinalIgnoreCase) == 0 select device).ToArray(); } if (devices != null) { WriteTrace(String.Format("Sending {0} search responses", devices.Count())); foreach (var device in devices) { SendDeviceSearchResponses(device, endPoint); } } else WriteTrace(String.Format("Sending 0 search responses.")); }); }
public void SendTo(byte[] messageData, UdpEndPoint endPoint) { _LastSentTo = endPoint; _LastMessage = messageData; }
private void SendSearchResponse(string searchTarget, SsdpDevice device, string uniqueServiceName, UdpEndPoint endPoint) { var rootDevice = device.ToRootDevice(); var additionalheaders = FormatCustomHeadersForResponse(device); string message; if (device.Weemo) { message = String.Format(WeemoSearchResponseMessageFormat, String.Format("CACHE-CONTROL: max-age={0}", rootDevice.CacheLifetime.TotalSeconds), DateTime.UtcNow.ToString("r"), rootDevice.Location, rootDevice.SerialNumber, searchTarget, uniqueServiceName); } else { message = String.Format(DeviceSearchResponseMessageFormat, CacheControlHeaderFromTimeSpan(rootDevice), searchTarget, uniqueServiceName, rootDevice.Location, _OSName, _OSVersion, ServerVersion, DateTime.UtcNow.ToString("r"), additionalheaders); } _CommsServer.SendMessage(System.Text.UTF8Encoding.UTF8.GetBytes(message), endPoint); WriteTrace(String.Format("Sent search response to " + endPoint.ToString()), device); }
public void MockReceive(byte[] data, UdpEndPoint fromEndPoint) { _ReceiveQueue.Enqueue(new ReceivedUdpData() { Buffer = data, ReceivedFrom = fromEndPoint, ReceivedBytes = data.Length }); _DataAvailableSignal.Set(); }
public void CommsServer_StopListeningForResponsesDisposesUnicastSocket() { var socketFactory = new MockSocketFactory(); var server = new SsdpCommunicationsServer(socketFactory); string message = "Hello!"; UdpEndPoint destination = new UdpEndPoint() { IPAddress = "192.168.1.100", Port = 1701 }; server.SendMessage(System.Text.UTF8Encoding.UTF8.GetBytes(message), destination); var mockSocket = socketFactory.UnicastSocket as MockSocket; Assert.IsFalse(mockSocket.IsDisposed); server.StopListeningForResponses(); Assert.IsTrue(mockSocket.IsDisposed); }
public void CommsServer_SendNullMulticastMessageThrowsException() { var socketFactory = new MockSocketFactory(); var server = new SsdpCommunicationsServer(socketFactory); UdpEndPoint destination = new UdpEndPoint() { IPAddress = "192.168.1.100", Port = 1701 }; server.SendMulticastMessage(null); }
public void SendMessage(byte[] messageData, UdpEndPoint destination) { SentMessages.Enqueue(new ReceivedUdpData() { Buffer = messageData, ReceivedBytes = messageData.Length, ReceivedFrom = destination }); _SentMessageSignal.Set(); }
private bool IsDuplicateSearchRequest(string searchTarget, UdpEndPoint endPoint) { var isDuplicateRequest = false; var newRequest = new SearchRequest() { EndPoint = endPoint, SearchTarget = searchTarget, Received = DateTime.UtcNow }; lock (_RecentSearchRequests) { if (_RecentSearchRequests.ContainsKey(newRequest.Key)) { var lastRequest = _RecentSearchRequests[newRequest.Key]; if (lastRequest.IsOld()) _RecentSearchRequests[newRequest.Key] = newRequest; else isDuplicateRequest = true; } else { _RecentSearchRequests.Add(newRequest.Key, newRequest); if (_RecentSearchRequests.Count > 10) CleanUpRecentSearchRequestsAsync(); } } return isDuplicateRequest; }
private void ProcessSearchRequest(string mx, string searchTarget, UdpEndPoint endPoint) { if (String.IsNullOrEmpty(searchTarget)) { WriteTrace(String.Format("Invalid search request received From {0}, Target is null/empty.", endPoint.ToString())); return; } WriteTrace(String.Format("Search Request Received From {0}, Target = {1}", endPoint.ToString(), searchTarget)); if (IsDuplicateSearchRequest(searchTarget, endPoint)) { WriteTrace("Search Request is Duplicate, ignoring."); return; } //Wait on random interval up to MX, as per SSDP spec. //Also, as per UPnP 1.1/SSDP spec ignore missing/bank MX header. If over 120, assume random value between 0 and 120. //Using 16 as minimum as that's often the minimum system clock frequency anyway. int maxWaitInterval = 0; if (String.IsNullOrEmpty(mx)) { //Windows Explorer is poorly behaved and doesn't supply an MX header value. if (this.SupportPnpRootDevice) { mx = "1"; } else { return; } } if (!Int32.TryParse(mx, out maxWaitInterval) || maxWaitInterval <= 0) { return; } if (maxWaitInterval > 120) { maxWaitInterval = _Random.Next(0, 120); } //Do not block synchronously as that may tie up a threadpool thread for several seconds. TaskEx.Delay(_Random.Next(16, (maxWaitInterval * 1000))).ContinueWith((parentTask) => { //Copying devices to local array here to avoid threading issues/enumerator exceptions. IEnumerable <SsdpDevice> devices = null; lock (_Devices) { if (String.Compare(SsdpConstants.SsdpDiscoverAllSTHeader, searchTarget, StringComparison.OrdinalIgnoreCase) == 0) { devices = GetAllDevicesAsFlatEnumerable().ToArray(); } else if (String.Compare(SsdpConstants.UpnpDeviceTypeRootDevice, searchTarget, StringComparison.OrdinalIgnoreCase) == 0 || (this.SupportPnpRootDevice && String.Compare(SsdpConstants.PnpDeviceTypeRootDevice, searchTarget, StringComparison.OrdinalIgnoreCase) == 0)) { devices = _Devices.ToArray(); } else if (searchTarget.Trim().StartsWith("uuid:", StringComparison.OrdinalIgnoreCase)) { devices = (from device in GetAllDevicesAsFlatEnumerable() where String.Compare(device.Uuid, searchTarget.Substring(5), StringComparison.OrdinalIgnoreCase) == 0 select device).ToArray(); } else if (searchTarget.StartsWith("urn:", StringComparison.OrdinalIgnoreCase)) { devices = (from device in GetAllDevicesAsFlatEnumerable() where String.Compare(device.FullDeviceType, searchTarget, StringComparison.OrdinalIgnoreCase) == 0 select device).ToArray(); } } if (devices != null) { WriteTrace(String.Format("Sending {0} search responses", devices.Count())); foreach (var device in devices) { SendDeviceSearchResponses(device, endPoint); } } else { WriteTrace(String.Format("Sending 0 search responses.")); } }); }
private void SendDeviceSearchResponses(SsdpDevice device, UdpEndPoint endPoint) { bool isRootDevice = (device as SsdpRootDevice) != null; if (isRootDevice) { SendSearchResponse(SsdpConstants.UpnpDeviceTypeRootDevice, device, GetUsn(device.Udn, SsdpConstants.UpnpDeviceTypeRootDevice), endPoint); if (this.SupportPnpRootDevice) SendSearchResponse(SsdpConstants.PnpDeviceTypeRootDevice, device, GetUsn(device.Udn, SsdpConstants.PnpDeviceTypeRootDevice), endPoint); } SendSearchResponse(device.Udn, device, device.Udn, endPoint); SendSearchResponse(device.FullDeviceType, device, GetUsn(device.Udn, device.FullDeviceType), endPoint); }
private void ProcessSearchRequest(string mx, string searchTarget, UdpEndPoint endPoint) { if (String.IsNullOrEmpty(searchTarget)) { _Log.LogWarning(String.Format("Invalid search request received From {0}, Target is null/empty.", endPoint.ToString())); return; } _Log.LogInfo(String.Format("Search Request Received From {0}, Target = {1}", endPoint.ToString(), searchTarget)); if (IsDuplicateSearchRequest(searchTarget, endPoint)) { Log.LogWarning("Search Request is Duplicate, ignoring."); return; } //Wait on random interval up to MX, as per SSDP spec. //Also, as per UPnP 1.1/SSDP spec ignore missing/bank MX header (strict mode only). If over 120, assume random value between 0 and 120. //Using 16 as minimum as that's often the minimum system clock frequency anyway. int maxWaitInterval = 0; if (String.IsNullOrEmpty(mx)) { //Windows Explorer is poorly behaved and doesn't supply an MX header value. if (IsWindowsExplorerSupportEnabled) { mx = "1"; } else { _Log.LogWarning("Search Request ignored due to missing MX header. Set StandardsMode to relaxed to respond to these requests."); return; } } if (!Int32.TryParse(mx, out maxWaitInterval) || maxWaitInterval <= 0) { return; } if (maxWaitInterval > 120) { maxWaitInterval = _Random.Next(0, 120); } //Do not block synchronously as that may tie up a threadpool thread for several seconds. TaskEx.Delay(_Random.Next(16, (maxWaitInterval * 1000))).ContinueWith((parentTask) => { //Copying devices to local array here to avoid threading issues/enumerator exceptions. IEnumerable <SsdpDevice> devices = null; devices = GetDevicesMatchingSearchTarget(searchTarget, devices); if (devices != null) { SendSearchResponses(searchTarget, endPoint, devices); } else { _Log.LogWarning("Sending search responses for 0 devices (no matching targets)."); } }); }
/// <summary> /// Sends a message to a particular address (uni or multicast) and port. /// </summary> /// <param name="messageData">A byte array containing the data to send.</param> /// <param name="destination">A <see cref="UdpEndPoint"/> representing the destination address for the data. Can be either a multicast or unicast destination.</param> /// <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> public void SendMessage(byte[] messageData, UdpEndPoint destination) { if (messageData == null) throw new ArgumentNullException("messageData"); ThrowIfDisposed(); EnsureSendSocketCreated(); // SSDP spec recommends sending messages multiple times (not more than 3) to account for possible packet loss over UDP. Repeat(SsdpConstants.UdpResendCount, TimeSpan.FromMilliseconds(100), () => { SendMessageIfSocketNotDisposed(messageData, destination); }); }
/// <summary> /// Full constructor. /// </summary> /// <param name="message">The <see cref="HttpRequestMessage"/> that was received.</param> /// <param name="receivedFrom">A <see cref="UdpEndPoint"/> representing the sender's address (sometimes used for replies).</param> public RequestReceivedEventArgs(HttpRequestMessage message, UdpEndPoint receivedFrom) { _Message = message; _ReceivedFrom = receivedFrom; }
private void SendMessageIfSocketNotDisposed(byte[] messageData, UdpEndPoint destination) { var socket = _SendSocket; if (socket != null) { _SendSocket.SendTo(messageData, destination); } else { ThrowIfDisposed(); } }
private void SendDeviceSearchResponses(SsdpDevice device, string searchTarget, UdpEndPoint endPoint) { //http://www.upnp.org/specs/arch/UPnP-arch-DeviceArchitecture-v1.0-20080424.pdf - page 21 //For ssdp:all - Respond 3+2d+k times for a root device with d embedded devices and s embedded services but only k distinct service types //Root devices - Respond once (special handling when in related/Win Explorer support mode) //Udn (uuid) - Response once //Device type - response once //Service type - respond once per service type bool isRootDevice = (device as SsdpRootDevice) != null; bool sendAll = searchTarget == SsdpConstants.SsdpDiscoverAllSTHeader; bool sendRootDevices = searchTarget == SsdpConstants.UpnpDeviceTypeRootDevice || searchTarget == SsdpConstants.PnpDeviceTypeRootDevice; if (isRootDevice && (sendAll || sendRootDevices)) { SendSearchResponse(SsdpConstants.UpnpDeviceTypeRootDevice, device, GetUsn(device.Udn, SsdpConstants.UpnpDeviceTypeRootDevice), endPoint); if (IsWindowsExplorerSupportEnabled) { SendSearchResponse(SsdpConstants.PnpDeviceTypeRootDevice, device, GetUsn(device.Udn, SsdpConstants.PnpDeviceTypeRootDevice), endPoint); } } if (sendAll || searchTarget.StartsWith("uuid:", StringComparison.Ordinal)) { SendSearchResponse(device.Udn, device, device.Udn, endPoint); } if (sendAll || searchTarget.Contains(":device:")) { SendSearchResponse(device.FullDeviceType, device, GetUsn(device.Udn, device.FullDeviceType), endPoint); } if (searchTarget == SsdpConstants.SsdpDiscoverAllSTHeader) { //Send 1 search response for each unique service type for all devices found var serviceTypes = ( from s in device.Services select s.FullServiceType ).Distinct().ToArray(); foreach (var st in serviceTypes) { SendServiceSearchResponses(device, st, endPoint); } } }
private void SendSearchResponse(string searchTarget, SsdpDevice device, string uniqueServiceName, UdpEndPoint endPoint) { var rootDevice = device.ToRootDevice(); var additionalheaders = FormatCustomHeadersForResponse(device); var message = String.Format(DeviceSearchResponseMessageFormat, CacheControlHeaderFromTimeSpan(rootDevice), searchTarget, uniqueServiceName, rootDevice.Location, _OSName, _OSVersion, ServerVersion, DateTime.UtcNow.ToString("r"), additionalheaders ); _CommsServer.SendMessage(System.Text.UTF8Encoding.UTF8.GetBytes(message), endPoint); LogDeviceEventVerbose(String.Format("Sent search response ({0}) to {1}", uniqueServiceName, endPoint.ToString()), device); }
private void OnRequestReceived(HttpRequestMessage data, UdpEndPoint endPoint) { //SSDP specification says only * is currently used but other uri's might //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 if (data.RequestUri.ToString() != "*") return; var handlers = this.RequestReceived; if (handlers != null) handlers(this, new RequestReceivedEventArgs(data, endPoint)); }
private void OnResponseReceived(HttpResponseMessage data, UdpEndPoint endPoint) { var handlers = this.ResponseReceived; if (handlers != null) handlers(this, new ResponseReceivedEventArgs(data, endPoint)); }
private void SendSearchResponse(string searchTarget, SsdpDevice device, string uniqueServiceName, UdpEndPoint endPoint) { if (device is SsdpHueBridgeDevice) { var hueDevice = device as SsdpHueBridgeDevice; var messageHeader = String.Format(SsdpHueBridgeDevice.HUE_RESPONSE, hueDevice.HttpServerIpAddress, hueDevice.HttpServerPort, hueDevice.HttpServerOptionalSubFolder, hueDevice.HueBridgeId ); var message1 = messageHeader + String.Format(SsdpHueBridgeDevice.HUE_ST1, hueDevice.HueUuid); Debug.WriteLine("Sending packet 1: " + message1); var message2 = messageHeader + String.Format(SsdpHueBridgeDevice.HUE_ST2, hueDevice.HueUuid); Debug.WriteLine("Sending packet 2:" + message2); var message3 = messageHeader + String.Format(SsdpHueBridgeDevice.HUE_ST3, hueDevice.HueUuid); Debug.WriteLine("Sending packet 3: " + message3); _CommsServer.SendMessage(Encoding.UTF8.GetBytes(message1), endPoint); _CommsServer.SendMessage(Encoding.UTF8.GetBytes(message2), endPoint); _CommsServer.SendMessage(Encoding.UTF8.GetBytes(message3), endPoint); } else { var rootDevice = device.ToRootDevice(); var additionalheaders = FormatCustomHeadersForResponse(device); var message = String.Format(DeviceSearchResponseMessageFormat, CacheControlHeaderFromTimeSpan(rootDevice), searchTarget, uniqueServiceName, rootDevice.Location, _OSName, _OSVersion, ServerVersion, DateTime.UtcNow.ToString("r"), additionalheaders ); _CommsServer.SendMessage(System.Text.UTF8Encoding.UTF8.GetBytes(message), endPoint); } LogDeviceEventVerbose(String.Format("Sent search response ({0}) to {1}", uniqueServiceName, endPoint.ToString()), device); }