View Javadoc
1   /*
2    * Copyright (C) 2020-2023 Dipl.-Inform. Kai Hofmann. All rights reserved!
3    */
4   package de.powerstat.validation.values;
5   
6   
7   import java.util.Arrays;
8   import java.util.Locale;
9   import java.util.Objects;
10  import java.util.regex.Pattern;
11  
12  import de.powerstat.validation.interfaces.IValueObject;
13  
14  
15  /**
16   * Canonical Media-Access-Control-Adresse (MAC).
17   *
18   * TODO getManufacturer name
19   * TODO Exists in network
20   * http://standards-oui.ieee.org/oui/oui.csv
21   */
22  public final class MACAddress implements Comparable<MACAddress>, IValueObject
23   {
24    /* *
25     * Cache for singletons.
26     */
27    // private static final Map<String, MACAddress> CACHE = new WeakHashMap<>();
28  
29    /**
30     * Hex 00.
31     */
32    private static final String H00 = "00"; //$NON-NLS-1$
33  
34    /**
35     * Hex 01.
36     */
37    private static final String H01 = "01"; //$NON-NLS-1$
38  
39    /**
40     * Hex 33.
41     */
42    private static final String H33 = "33"; //$NON-NLS-1$
43  
44    /**
45     * Hex 5e.
46     */
47    private static final String H5E = "5e"; //$NON-NLS-1$
48  
49    /**
50     * Hex ff.
51     */
52    private static final String HFF = "ff"; //$NON-NLS-1$
53  
54    /**
55     * Byte separator.
56     */
57    private static final String SEPARATOR = ":"; //$NON-NLS-1$
58  
59    /**
60     * Delimiter.
61     */
62    private static final String DELIMITER = "-"; //$NON-NLS-1$
63  
64    /**
65     * Delimiter constant.
66     */
67    private static final String DELIMITER_TXT = "delimiter"; //$NON-NLS-1$
68  
69    /**
70     * Illegal delimiter length constant.
71     */
72    private static final String ILLEGAL_DELIMITER_LENGTH = "Illegal delimiter length"; //$NON-NLS-1$
73  
74    /**
75     * Illegal delimiter character constant.
76     */
77    private static final String ILLEGAL_DELIMITER_CHARACTER = "Illegal delimiter character"; //$NON-NLS-1$
78  
79    /**
80     * IP V6 regexp.
81     */
82    private static final Pattern IPV6_REGEXP = Pattern.compile("^[0-9a-f]{2}([:-]?[0-9a-f]{2}){5}$"); //$NON-NLS-1$
83  
84    /**
85     * IP V6 separator regexp.
86     */
87    private static final Pattern IPV6_SEPARATOR_REGEXP = Pattern.compile("[:-]"); //$NON-NLS-1$
88  
89    /**
90     * MAC address parts.
91     */
92    private final String[] parts;
93  
94  
95    /**
96     * Constructor.
97     *
98     * @param address MAC address
99     * @throws NullPointerException if address is null
100    * @throws IllegalArgumentException if address is not a mac address
101    */
102   private MACAddress(final String address)
103    {
104     super();
105     Objects.requireNonNull(address, "address"); //$NON-NLS-1$
106     if ((address.length() != 12) && (address.length() != 17))
107      {
108       throw new IllegalArgumentException("To short or long for a mac address"); //$NON-NLS-1$
109      }
110     if (!MACAddress.IPV6_REGEXP.matcher(address.toLowerCase(Locale.getDefault())).matches())
111      {
112       throw new IllegalArgumentException("Not a mac address"); //$NON-NLS-1$
113      }
114     this.parts = MACAddress.IPV6_SEPARATOR_REGEXP.split(address.toLowerCase(Locale.getDefault()));
115    }
116 
117 
118   /**
119    * MACAddress factory.
120    *
121    * @param address MAC address
122    * @return MACAddress object
123    */
124   public static MACAddress of(final String address)
125    {
126     /*
127     synchronized (MACAddress.class)
128      {
129       MACAddress obj = MACAddress.CACHE.get(address);
130       if (obj != null)
131        {
132         return obj;
133        }
134       obj = new MACAddress(address);
135       MACAddress.CACHE.put(address, obj);
136       return obj;
137      }
138     */
139     return new MACAddress(address);
140    }
141 
142 
143   /**
144    * Returns the value of this MACAddress as a string.
145    *
146    * @param delimiter Delimiter could be empty, : or -
147    * @return The text value represented by this object after conversion to type string.
148    */
149   public String stringValue(final String delimiter)
150    {
151     Objects.requireNonNull(delimiter, MACAddress.DELIMITER_TXT);
152     if (delimiter.length() > 1)
153      {
154       throw new IllegalArgumentException(MACAddress.ILLEGAL_DELIMITER_LENGTH);
155      }
156     if (!delimiter.isEmpty() && !MACAddress.SEPARATOR.equals(delimiter) && !MACAddress.DELIMITER.equals(delimiter))
157      {
158       throw new IllegalArgumentException(MACAddress.ILLEGAL_DELIMITER_CHARACTER);
159      }
160     return String.join(delimiter, this.parts);
161    }
162 
163 
164   /**
165    * Returns the value of this MACADdress as a string with delimiter ':'.
166    *
167    * @return The text value represented by this object after conversion to type string.
168    */
169   @Override
170   public String stringValue()
171    {
172     return stringValue(MACAddress.SEPARATOR);
173    }
174 
175 
176   /**
177    * Is broadcast address.
178    *
179    * @return true if broadcast address, false otherwise
180    */
181   public boolean isBroadcast()
182    {
183     return MACAddress.HFF.equals(this.parts[0]) && MACAddress.HFF.equals(this.parts[1]) && MACAddress.HFF.equals(this.parts[2]) && MACAddress.HFF.equals(this.parts[3]) && MACAddress.HFF.equals(this.parts[4]) && MACAddress.HFF.equals(this.parts[5]);
184    }
185 
186 
187   /**
188    * Is group.
189    *
190    * @return true: group, false: individual
191    */
192   public boolean isGroup()
193    {
194     return (Integer.parseInt(this.parts[0], 16) & 0x01) != 0;
195    }
196 
197 
198   /**
199    * Is local.
200    *
201    * @return true: local, false: universal
202    */
203   public boolean isLocal()
204    {
205     return (Integer.parseInt(this.parts[0], 16) & 0x02) != 0;
206    }
207 
208 
209   /**
210    * Is IP v4 multicast mac.
211    *
212    * @return true if mac is an ip v4 multicast address, false otherwise
213    */
214   public boolean isIPV4Multicast()
215    {
216     return MACAddress.H01.equals(this.parts[0]) && MACAddress.H00.equals(this.parts[1]) && MACAddress.H5E.equals(this.parts[2]) && ((Integer.parseInt(this.parts[3], 16) & 0x80) == 0);
217    }
218 
219 
220   /**
221    * Is IP v6 multicast mac.
222    *
223    * @return true if mac is an ip v6 multicast address, false otherwise
224    */
225   public boolean isIPV6Multicast()
226    {
227     return MACAddress.H33.equals(this.parts[0]) && MACAddress.H33.equals(this.parts[1]);
228    }
229 
230 
231   /**
232    * Is VRRP mac (Virtual Router Redundancy Protocol).
233    *
234    * @return true if mac is a vrrp address, false otherwise
235    */
236   public boolean isVRRP()
237    {
238     return MACAddress.H00.equals(this.parts[0]) && MACAddress.H00.equals(this.parts[1]) && MACAddress.H5E.equals(this.parts[2]) && MACAddress.H00.equals(this.parts[3]) && MACAddress.H01.equals(this.parts[4]);
239    }
240 
241 
242   /**
243    * Get OUI (Organizationally Unique Identifier).
244    *
245    * @return OUI (MA-L) string 000000
246    *
247    * TODO OUI-28 (MA-M), OUI-36 (MA-S)
248    */
249   public String getOUI()
250    {
251     return String.format("%1$02X", Integer.parseInt(this.parts[0], 16) & 0xfc) + this.parts[1].toUpperCase(Locale.getDefault()) + this.parts[2].toUpperCase(Locale.getDefault()); //$NON-NLS-1$
252    }
253 
254 
255   /**
256    * Calculate hash code.
257    *
258    * @return Hash
259    * @see java.lang.Object#hashCode()
260    */
261   @Override
262   public int hashCode()
263    {
264     return Arrays.hashCode(this.parts);
265    }
266 
267 
268   /**
269    * Is equal with another object.
270    *
271    * @param obj Object
272    * @return true when equal, false otherwise
273    * @see java.lang.Object#equals(java.lang.Object)
274    */
275   @Override
276   public boolean equals(final Object obj)
277    {
278     if (this == obj)
279      {
280       return true;
281      }
282     if (!(obj instanceof MACAddress))
283      {
284       return false;
285      }
286     final MACAddress other = (MACAddress)obj;
287     return Arrays.equals(this.parts, other.parts);
288    }
289 
290 
291   /**
292    * Returns the string representation of this MACAddress.
293    *
294    * The exact details of this representation are unspecified and subject to change, but the following may be regarded as typical:
295    *
296    * "MACAddress[address=00:00:00:00:00:00]"
297    *
298    * @return String representation of this MACAddress
299    * @see java.lang.Object#toString()
300    */
301   @Override
302   public String toString()
303    {
304     final var builder = new StringBuilder(21);
305     builder.append("MACAddress[address=").append(String.join(MACAddress.SEPARATOR, this.parts)).append(']'); //$NON-NLS-1$
306     return builder.toString();
307    }
308 
309 
310   /**
311    * Compare with another object.
312    *
313    * @param obj Object to compare with
314    * @return 0: equal; 1: greater; -1: smaller
315    * @see java.lang.Comparable#compareTo(java.lang.Object)
316    */
317   @Override
318   public int compareTo(final MACAddress obj)
319    {
320     Objects.requireNonNull(obj, "obj"); //$NON-NLS-1$
321     return Arrays.compare(this.parts, obj.parts);
322    }
323 
324  }