using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Rssdp.Infrastructure { /// /// Validates a object's properties meet the UPnP 1.0 specification. /// /// /// This is a best effort validation for known rules, it doesn't guarantee 100% compatibility with the specification. Reading the specification yourself is the best way to ensure compatibility. /// public class Upnp10DeviceValidator : IUpnpDeviceValidator { #region Public Methods /// /// Returns an enumerable set of strings, each one being a description of an invalid property on the specified root device. /// /// /// If no errors are found, an empty (but non-null) enumerable is returned. /// /// The to validate. /// Thrown if the argument is null. /// A non-null enumerable set of strings, empty if there are no validation errors, otherwise each string represents a discrete problem. public IEnumerable GetValidationErrors(SsdpRootDevice device) { if (device == null) throw new ArgumentNullException("device"); var retVal = GetValidationErrors((SsdpDevice)device) as IList; if (device.Location == null) retVal.Add("Location cannot be null."); else if (!device.Location.IsAbsoluteUri) retVal.Add("Location must be an absolute URL."); return retVal; } /// /// Returns an enumerable set of strings, each one being a description of an invalid property on the specified device. /// /// /// If no errors are found, an empty (but non-null) enumerable is returned. /// /// The to validate. /// Thrown if the argument is null. /// A non-null enumerable set of strings, empty if there are no validation errors, otherwise each string represents a discrete problem. public IEnumerable GetValidationErrors(SsdpDevice device) { if (device == null) throw new ArgumentNullException("device"); var retVal = new List(); if (String.IsNullOrEmpty(device.Uuid)) retVal.Add("Uuid is not set."); if (!String.IsNullOrEmpty(device.Upc)) ValidateUpc(device, retVal); if (String.IsNullOrEmpty(device.Udn)) retVal.Add("UDN is not set."); else ValidateUdn(device, retVal); if (String.IsNullOrEmpty(device.DeviceType)) retVal.Add("DeviceType is not set."); if (String.IsNullOrEmpty(device.DeviceTypeNamespace)) retVal.Add("DeviceTypeNamespace is not set."); else { if (IsOverLength(device.DeviceTypeNamespace, 64)) retVal.Add("DeviceTypeNamespace cannot be longer than 64 characters."); //if (device.DeviceTypeNamespace.Contains(".")) // retVal.Add("Period (.) characters in the DeviceTypeNamespace property must be replaced with hyphens (-)."); } if (device.DeviceVersion <= 0) retVal.Add("DeviceVersion must be 1 or greater."); if (IsOverLength(device.ModelName, 32)) retVal.Add("ModelName cannot be longer than 32 characters."); if (IsOverLength(device.ModelNumber, 32)) retVal.Add("ModelNumber cannot be longer than 32 characters."); if (IsOverLength(device.FriendlyName, 64)) retVal.Add("FriendlyName cannot be longer than 64 characters."); if (IsOverLength(device.Manufacturer, 64)) retVal.Add("Manufacturer cannot be longer than 64 characters."); if (IsOverLength(device.SerialNumber, 64)) retVal.Add("SerialNumber cannot be longer than 64 characters."); if (IsOverLength(device.ModelDescription, 128)) retVal.Add("ModelDescription cannot be longer than 128 characters."); if (String.IsNullOrEmpty(device.FriendlyName)) retVal.Add("FriendlyName is required."); if (String.IsNullOrEmpty(device.Manufacturer)) retVal.Add("Manufacturer is required."); if (String.IsNullOrEmpty(device.ModelName)) retVal.Add("ModelName is required."); if (device.Icons.Any()) ValidateIcons(device, retVal); ValidateChildDevices(device, retVal); return retVal; } /// /// Validates the specified device and throws an if there are any validation errors. /// /// The to validate. /// Thrown if the argument is null. /// Thrown if the device object does not pass validation. public void ThrowIfDeviceInvalid(SsdpDevice device) { var errors = this.GetValidationErrors(device); if (errors != null && errors.Any()) throw new InvalidOperationException("Invalid device settings : " + String.Join(Environment.NewLine, errors)); } #endregion #region Private Methods private static void ValidateUpc(SsdpDevice device, List retVal) { if (device.Upc.Length != 12) retVal.Add("Upc, if provided, should be 12 digits."); foreach (char c in device.Upc) { if (!Char.IsDigit(c)) { retVal.Add("Upc, if provided, should contain only digits (numeric characters)."); break; } } } private static void ValidateUdn(SsdpDevice device, List retVal) { if (!device.Udn.StartsWith("uuid:", StringComparison.OrdinalIgnoreCase)) retVal.Add("UDN must begin with uuid:. Correct format is uuid:"); else if (device.Udn.Substring(5).Trim() != device.Uuid) retVal.Add("UDN incorrect. Correct format is uuid:"); } private static void ValidateIcons(SsdpDevice device, List retVal) { if (device.Icons.Any((di) => di.Url == null)) retVal.Add("Device icon is missing URL."); if (device.Icons.Any((di) => String.IsNullOrEmpty(di.MimeType))) retVal.Add("Device icon is missing mime type."); if (device.Icons.Any((di) => di.Width <= 0 || di.Height <= 0)) retVal.Add("Device icon has zero (or negative) height, width or both."); if (device.Icons.Any((di) => di.ColorDepth <= 0)) retVal.Add("Device icon has zero (or negative) colordepth."); } private void ValidateChildDevices(SsdpDevice device, List retVal) { foreach (var childDevice in device.Devices) { foreach (var validationError in this.GetValidationErrors(childDevice)) { retVal.Add("Embedded Device : " + childDevice.Uuid + ": " + validationError); } } } private static bool IsOverLength(string value, int maxLength) { return !String.IsNullOrEmpty(value) && value.Length > maxLength; } #endregion } }