//------------------------------------------------------------------------------ // // Copyright (c) Microsoft Corporation. All rights reserved. // //------------------------------------------------------------------------------ namespace System.Net.PeerToPeer { using System; using System.Globalization; using System.Collections.Generic; using System.Text; using System.Threading; using System.Text.RegularExpressions; using System.Runtime.InteropServices; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Runtime.Serialization; using System.Security.Permissions; using Microsoft.Win32; /// /// The PeerName class represents the native PeerName construct. /// [Serializable] public class PeerName : ISerializable, IEquatable { //=================================================== //constants //=================================================== private const int PEER_MAX_CLASSIFIER_LENGTH = 149; private const int SECURE_AUTHORITY_LENGTH = 40; private const int INSECURE_AUTHORITY_LENGTH = 1; private const int PEER_MAX_PEERNAME_LENGTH = SECURE_AUTHORITY_LENGTH + 1 + PEER_MAX_CLASSIFIER_LENGTH; private const string PEERNAME_UNSECURED_AUTHORITY = "0"; //=================================================== //private member variables //=================================================== private string m_PeerName; private string m_Authority; private string m_Classifier; private string m_PeerHostName; // =================================================== // Constructors // =================================================== static PeerName() { //------------------------------------------------- //Check for the availability of the simpler PNRP APIs //------------------------------------------------- if (!PeerToPeerOSHelper.SupportsP2P) { throw new PlatformNotSupportedException(SR.GetString(SR.P2P_NotAvailable)); } } /// /// We use this constructor to create an PeerName /// for internal implementation /// private PeerName(string peerName, string authority, string classifier) { m_PeerName = peerName; m_Classifier = classifier; m_Authority = authority; } /// /// This constructor is to create a peername from an /// arbitrary string. The Identity is not necessarily /// the same as the current user /// /// string form of the peer name /// /// 1. We assume that the peername is already normalized according to the /// Unicode rules /// 2. The PeerName given has nothig to do with the default Identity /// It is just a way to create a PeerName instance given a string /// public PeerName(string remotePeerName) { //------------------------------------------------- //Check arguments //------------------------------------------------- if (remotePeerName == null) throw new ArgumentNullException("remotePeerName"); //------------------------------------------------- //Check PeerName format //NOTE: Currentlt thre is no native API to check //the format of the PeerName string. The //StrongParsePeerName implements the logic //------------------------------------------------- string authority; string classifier; if (!StrongParsePeerName(remotePeerName, out authority, out classifier)) { Logging.P2PTraceSource.TraceEvent(TraceEventType.Information, 0, "PeerName string {0} failed the check for a valid peer name", remotePeerName); throw new ArgumentException(SR.GetString(SR.Pnrp_InvalidPeerName), "remotePeerName"); } //authrority would have been lower cased //so we must ste the m_PeerName to authority + classifier if (classifier != null) m_PeerName = authority + "." + classifier; else m_PeerName = authority; m_Authority = authority; m_Classifier = classifier; Logging.P2PTraceSource.TraceEvent(TraceEventType.Information, 0, "PeerName instance created - PeerName {0} Authority {1} Classfier {2}", m_PeerName, m_Authority, ((m_Classifier == null)? "null" : m_Classifier)); } /// /// Given a classifier creates a secured or unsecured name /// /// /// // // // // // // // // // [System.Security.SecurityCritical] public PeerName(string classifier, PeerNameType peerNameType) { //------------------------------------------------- //Check arguments //------------------------------------------------- if ((classifier == null || classifier.Length == 0)&& peerNameType == PeerNameType.Unsecured) { throw new ArgumentNullException("classifier"); } if (classifier != null && classifier.Length > PEER_MAX_CLASSIFIER_LENGTH) { throw new ArgumentException(SR.GetString(SR.Pnrp_InvalidClassifier), "classifier"); } //-------------------------------------------------- //Normalize using NFC //-------------------------------------------------- if (classifier != null && classifier.Length > 0) { classifier = classifier.Normalize(NormalizationForm.FormC); } //------------------------------------------------- //call the helper to create the PeerName //------------------------------------------------- Int32 result; SafePeerData shNewPeerName = null; SafePeerData shDefaultIdentity = null; try { if (peerNameType == PeerNameType.Unsecured) { result = UnsafeP2PNativeMethods.PeerCreatePeerName((string)null, classifier, out shNewPeerName); if (result != 0) { throw PeerToPeerException.CreateFromHr(SR.GetString(SR.Pnrp_CouldNotCreateUnsecuredPeerName), result); } m_Authority = PEERNAME_UNSECURED_AUTHORITY; } else { result = UnsafeP2PNativeMethods.PeerIdentityGetDefault(out shDefaultIdentity); if (result != 0) { throw PeerToPeerException.CreateFromHr(SR.GetString(SR.Pnrp_CouldNotGetDefaultIdentity), result); } m_Authority = shDefaultIdentity.UnicodeString; //} result = UnsafeP2PNativeMethods.PeerCreatePeerName(m_Authority, classifier, out shNewPeerName); if (result != 0) { throw PeerToPeerException.CreateFromHr(SR.GetString(SR.Pnrp_CouldNotCreateSecuredPeerName), result); } } m_PeerName = shNewPeerName.UnicodeString; m_Classifier = classifier; } finally { if (shNewPeerName != null) shNewPeerName.Dispose(); if (shDefaultIdentity != null) shDefaultIdentity.Dispose(); } Logging.P2PTraceSource.TraceEvent(TraceEventType.Information, 0, "PeerName instance created - PeerName {0} Authority {1} Classfier {2}", m_PeerName, m_Authority, m_Classifier); } /// /// Given a peerHostName string [the dns safe version of the PeerName] /// Get the PeerName for it /// /// string form (dns safe form) of the peer name /// a PeerName instance // // // // // // // [System.Security.SecurityCritical] public static PeerName CreateFromPeerHostName(string peerHostName) { //------------------------------------------------- //Check arguments //------------------------------------------------- if (peerHostName == null) throw new ArgumentNullException("peerHostName"); if (peerHostName.Length == 0) throw new ArgumentException(SR.GetString(SR.Pnrp_InvalidPeerHostName), "peerHostName"); Int32 result; SafePeerData shPeerName = null; string peerName = null; try { result = UnsafeP2PNativeMethods.PeerHostNameToPeerName(peerHostName, out shPeerName); if (result != 0) { throw PeerToPeerException.CreateFromHr(SR.GetString(SR.Pnrp_CouldNotGetPeerNameFromPeerHostName), result); } peerName = shPeerName.UnicodeString; } finally { if (shPeerName != null) shPeerName.Dispose(); } string authority; string classifier; WeakParsePeerName(peerName, out authority, out classifier); PeerName p = new PeerName(peerName, authority, classifier); Logging.P2PTraceSource.TraceEvent(TraceEventType.Information, 0, "PeerName created from PeerHostName - PeerHostName {0} to PeerName PeerName {1} Authority {2} Classfier {3}", peerHostName, peerName, authority, classifier); return p; } /// /// Given a PeerName, change the classifier. /// This is simply replacing the classifier, but we will /// let the native APIs do the right thing since they /// might change the concept in future /// /// a PeerName instance /// a new classfier string /// // // // // // // // [System.Security.SecurityCritical] public static PeerName CreateRelativePeerName( PeerName peerName, string classifier) { //------------------------------------------------- //Check arguments //------------------------------------------------- if (peerName == null) { throw new ArgumentNullException("peerName", SR.GetString(SR.Pnrp_PeerNameCantBeNull)); } if (!peerName.IsSecured && (classifier == null || classifier.Length == 0)) { throw new ArgumentException(SR.GetString(SR.Pnrp_InvalidClassifier), "classifier"); } if (classifier != null && classifier.Length > PEER_MAX_CLASSIFIER_LENGTH) { throw new ArgumentException(SR.GetString(SR.Pnrp_InvalidClassifier), "classifier"); } //-------------------------------------------------- //Normalize using NFC //-------------------------------------------------- if (classifier != null && classifier.Length > 0) { classifier = classifier.Normalize(NormalizationForm.FormC); } Int32 result; SafePeerData shNewPeerName = null; string newPeerName = null; try { //Here there is change made on the native side //when passing secured peer names, it takes string of the form [40hexdigits].claasisifer and a newclassifier //returns [40hexdigits.newclassifier] //But for unsecured peer names it does not take 0.clasfier and newclassfier to return 0.newclassfier. //It expects NULL as the first param. To satisfy this broken finctionality, we are passing null if the //peer name is unsecured. result = UnsafeP2PNativeMethods.PeerCreatePeerName(peerName.IsSecured? peerName.m_PeerName : null, classifier, out shNewPeerName); if (result != 0) { throw PeerToPeerException.CreateFromHr(SR.GetString(SR.Pnrp_CouldNotCreateRelativePeerName), result); } newPeerName = shNewPeerName.UnicodeString; } finally { if (shNewPeerName != null) shNewPeerName.Dispose(); } string authority; string newClassifier; WeakParsePeerName(newPeerName, out authority, out newClassifier); PeerName p = new PeerName(newPeerName, authority, newClassifier); Logging.P2PTraceSource.TraceEvent(TraceEventType.Information, 0, "A new PeerName created from existing PeerName with a new classfier. Existing PeerName {0} Classifier {1} New PeerName {2}", peerName, classifier, p); return p; } /// /// Friendly string /// /// public override string ToString() { return m_PeerName; } /// /// Notionb of equals is based on same peer name string content /// /// /// public bool Equals(PeerName other) { if(other == null) return false; return string.Compare(other.m_PeerName, m_PeerName, StringComparison.Ordinal) == 0; } public override bool Equals(object obj) { if (obj == null) return false; PeerName other = obj as PeerName; if (other == null) return false; return Equals(other); } /// /// Hash code comes from m_PeerName since the others are just components of this /// /// public override int GetHashCode() { return m_PeerName.GetHashCode(); } /// /// Authroity /// public string Authority { get { return m_Authority; } } /// /// Classifier /// public string Classifier { get { return m_Classifier; } } /// /// A DNS Safe version of the Peer Name /// public string PeerHostName { // // // // // // // [System.Security.SecurityCritical] get { if (m_PeerHostName == null) { Int32 result; SafePeerData shPeerHostName = null; try { //This API gives HRESULT > 0 for success instead of S_OK == 0 //WINDOWS OS result = UnsafeP2PNativeMethods.PeerNameToPeerHostName( m_PeerName, out shPeerHostName); if (result < 0) { throw PeerToPeerException.CreateFromHr(SR.GetString(SR.Pnrp_CouldNotGetPeerHostNameFromPeerName), result); } m_PeerHostName = shPeerHostName.UnicodeString; } finally { if (shPeerHostName != null) shPeerHostName.Dispose(); } } Logging.P2PTraceSource.TraceEvent(TraceEventType.Information, 0, "PeerHostName created for PeerName PeerHostName {0} PeerName {1}", m_PeerHostName, this); return m_PeerHostName; } } /// /// Basically if the authority == 0 or not [unsecured or secured respectively] /// public bool IsSecured { get { return m_Authority != PEERNAME_UNSECURED_AUTHORITY; } } // =================================================== // Private methods // =================================================== /// /// I have considered using regular expressions. However, the regular expressions offer /// poor performance and or startup cost. Really there is no substiture for custom /// parsing logic. I decided to write this piece of code to parse the peername for now /// - Microsoft 6/6/2005 /// /// /// /// /// private static bool StrongParsePeerName(string peerName, out string authority, out string classifier) { authority = null; classifier = null; //Rule 0. The max length must not be exeeded if (peerName == null || peerName.Length == 0 || peerName.Length > PEER_MAX_PEERNAME_LENGTH) return false; //Rule 1. The length of the string must be at least 3 //as in 0.C for unsecured PeerName with classifier "C" if (peerName.Length < 3) return false; string tempAuthority = null; int IndexOfPeriod = peerName.IndexOf('.'); if (IndexOfPeriod == 0) return false; if (IndexOfPeriod < 0) { //Rule 2. Secure PeerNames can have just the authority //or Authority. or Authority.Classifier //if there is no period, we have to treat the entire string as the authority tempAuthority = peerName; } else if (IndexOfPeriod == peerName.Length - 1) { //May be this is of the form Authority. tempAuthority = peerName.Substring(0, peerName.Length - 1); } else { //Rule 2B. Unsecure peer names must have a classifier //There must be a period separating the authority and classifier //and must not be the first character and it must not be the last character tempAuthority = peerName.Substring(0, IndexOfPeriod); } //Rule 3. Authority is either SECURE_AUTHORITY_LENGTH hex characters or "0" if (tempAuthority.Length != SECURE_AUTHORITY_LENGTH && tempAuthority.Length != INSECURE_AUTHORITY_LENGTH) return false; //Rule 4. If it is length 1 it must be 0 if (tempAuthority.Length == INSECURE_AUTHORITY_LENGTH && tempAuthority != PEERNAME_UNSECURED_AUTHORITY) return false; //Rule 5. the authority must be 40 hex characters if(tempAuthority.Length == SECURE_AUTHORITY_LENGTH) { foreach (char c in tempAuthority) { if (!IsHexDigit(c)) return false; } } //Rule 6. The maximum length of the classfier is PEER_MAX_CLASSIFIER_LENGTH string tempClassifier = null; if (IndexOfPeriod != peerName.Length - 1 && IndexOfPeriod > 0) { tempClassifier = peerName.Substring(IndexOfPeriod + 1); } if (tempClassifier != null && tempClassifier.Length > PEER_MAX_CLASSIFIER_LENGTH) return false; //Finish authority = tempAuthority.ToLower(CultureInfo.InvariantCulture); //Safe for Hex digits classifier = tempClassifier; return true; } private static bool IsHexDigit(char character) { return ((character >= '0') && (character <= '9')) || ((character >= 'A') && (character <= 'F')) || ((character >= 'a') && (character <= 'f')); } /// /// WARNING: Don't call this unless you are sure that /// the PeerName is a valid string. This is invoked only when /// we are sure that the peername is valid and we need to components /// /// in: peerName to parse /// out: parsed authority value /// out: parsed classfier value /// none private static void WeakParsePeerName(string peerName, out string authority, out string classifier) { authority = null; classifier = null; int indexOfPeriod = peerName.IndexOf('.'); if (indexOfPeriod < 0) { authority = peerName; } else if (indexOfPeriod == peerName.Length - 1) { //May be this is of the form Authority. authority = peerName.Substring(0, peerName.Length - 1); } else { authority = peerName.Substring(0, indexOfPeriod); } if (indexOfPeriod != peerName.Length - 1 && indexOfPeriod > 0) { classifier = peerName.Substring(indexOfPeriod + 1); } authority = authority.ToLower(CultureInfo.InvariantCulture); //safe for hex strings } /// /// Constructor to enable serialization /// /// /// [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.SerializationFormatter)] protected PeerName(SerializationInfo info, StreamingContext context) { if (info == null) { throw new ArgumentNullException("info"); } m_PeerName = info.GetString("_PeerName"); m_Authority = info.GetString("_Authority"); m_Classifier = info.GetString("_Classifier"); } // // // [SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase", Justification = "System.Net.dll is still using pre-v4 security model and needs this demand")] [System.Security.SecurityCritical] [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.SerializationFormatter, SerializationFormatter = true)] void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) { GetObjectData(info, context); } /// /// This is made virtual so that derived types can be implemented correctly /// /// /// [SecurityPermission(SecurityAction.LinkDemand, SerializationFormatter = true)] protected virtual void GetObjectData(SerializationInfo info, StreamingContext context) { info.AddValue("_PeerName", m_PeerName); info.AddValue("_Authority", m_Authority); info.AddValue("_Classifier", m_Classifier); } } }