View Javadoc
1   /*
2    * Copyright (C) 2013 4th Line GmbH, Switzerland
3    *
4    * The contents of this file are subject to the terms of either the GNU
5    * Lesser General Public License Version 2 or later ("LGPL") or the
6    * Common Development and Distribution License Version 1 or later
7    * ("CDDL") (collectively, the "License"). You may not use this file
8    * except in compliance with the License. See LICENSE.txt for more
9    * information.
10   *
11   * This program is distributed in the hope that it will be useful,
12   * but WITHOUT ANY WARRANTY; without even the implied warranty of
13   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
14   */
15  
16  package org.fourthline.cling.model.types;
17  
18  import org.fourthline.cling.model.ModelUtil;
19  
20  
21  import java.io.UnsupportedEncodingException;
22  import java.util.UUID;
23  import java.security.MessageDigest;
24  import java.math.BigInteger;
25  import java.util.logging.Logger;
26  
27  /**
28   * A unique device name.
29   * <p>
30   * UDA 1.0 does not specify a UUID format, however, UDA 1.1 specifies a format that is compatible
31   * with <tt>java.util.UUID</tt> variant 4. You can use any identifier string you like.
32   * </p>
33   * <p>
34   * You'll most likely need the {@link #uniqueSystemIdentifier(String)} method sooner or later.
35   * </p>
36   *
37   * @author Christian Bauer
38   */
39  public class UDN {
40  
41      final private static Logger log = Logger.getLogger(UDN.class.getName());
42  
43      public static final String PREFIX = "uuid:";
44  
45      private String identifierString;
46  
47      /**
48       * @param identifierString The identifier string without the "uuid:" prefix.
49       */
50      public UDN(String identifierString) {
51          this.identifierString = identifierString;
52      }
53  
54      public UDN(UUID uuid) {
55          this.identifierString = uuid.toString();
56      }
57  
58      public boolean isUDA11Compliant() {
59          try {
60              UUID.fromString(identifierString);
61              return true;
62          } catch (IllegalArgumentException ex) {
63              return false;
64          }
65      }
66  
67      public String getIdentifierString() {
68          return identifierString;
69      }
70  
71      public static UDN valueOf(String udnString) {
72          return new UDN(udnString.startsWith(PREFIX) ? udnString.substring(PREFIX.length()) : udnString);
73      }
74  
75      /**
76       * Generates a global unique identifier that is the same every time this method is invoked on the same machine with
77       * the same argument.
78       * <p>
79       * This method combines the first non-loopback network interface's MAC address with given salt to generate a
80       * globally unique identifier. In other words, every time you call this method with the same salt on the same
81       * machine, you get the same identifier. If you use the same salt on a different machine, a different identifier
82       * will be generated.
83       * </p>
84       * <p>
85       * Note for Android users: This method does not generate unique identifiers on Android devices and will
86       * throw an exception. We can't get details such as the hostname or MAC address on Android. Instead,
87       * construct a UDN with <code>new UDN(UUID)</code>. When your application is first started, generate all
88       * UUIDs needed for your UPnP devices and store them in your Android preferences. Then, use the stored
89       * UUID to create a UDN every time your application starts.
90       * </p>
91       * <p>
92       * Control points can remember your device's identifier, it will and should be the same every time
93       * your device is powered up.
94       * </p>
95       *
96       * @param salt An arbitrary string that uniquely identifies the device on the current system, e.g. "MyMediaServer".
97       * @return A global unique identifier, stable for the current system and salt.
98       */
99      public static UDN uniqueSystemIdentifier(String salt) {
100         StringBuilder systemSalt = new StringBuilder();
101 
102         // Bug: On Android, NetworkInterface.isLoopback() isn't implemented
103         if (!ModelUtil.ANDROID_RUNTIME) {
104             try {
105                 systemSalt.append(new String(ModelUtil.getFirstNetworkInterfaceHardwareAddress(), "UTF-8"));
106             } catch (UnsupportedEncodingException ex) {
107                 // If your JVM doesn't support utf-8, you have bigger problems
108                 throw new RuntimeException(ex);
109             }
110         } else {
111             throw new RuntimeException(
112                 "This method does not create a unique identifier on Android, see the Javadoc and " +
113                     "use new UDN(UUID) instead!"
114             );
115         }
116 
117         try {
118             byte[] hash = MessageDigest.getInstance("MD5").digest(systemSalt.toString().getBytes("UTF-8"));
119             return new UDN(
120                     new UUID(
121                             new BigInteger(-1, hash).longValue(),
122                             salt.hashCode()
123                     )
124             );
125         } catch (Exception ex) {
126             throw new RuntimeException(ex);
127         }
128     }
129 
130     @Override
131     public String toString() {
132         return PREFIX + getIdentifierString();
133     }
134 
135     @Override
136     public boolean equals(Object o) {
137         if (this == o) return true;
138         if (o == null || !(o instanceof UDN)) return false;
139         UDN udn = (UDN) o;
140         return identifierString.equals(udn.identifierString);
141     }
142 
143     @Override
144     public int hashCode() {
145         return identifierString.hashCode();
146     }
147 
148 }