IPV6Address.java

  1. /*
  2.  * Copyright (C) 2020-2023 Dipl.-Inform. Kai Hofmann. All rights reserved!
  3.  */
  4. package de.powerstat.validation.values;


  5. import java.util.Locale;
  6. import java.util.Objects;
  7. import java.util.regex.Pattern;

  8. import de.powerstat.validation.interfaces.IValueObject;


  9. /**
  10.  * IP V6 address.
  11.  *
  12.  * DSGVO relevant.
  13.  *
  14.  * TODO ping ok?
  15.  */
  16. public final class IPV6Address implements Comparable<IPV6Address>, IValueObject
  17.  {
  18.   /* *
  19.    * Logger.
  20.    */
  21.   // private static final Logger LOGGER = LogManager.getLogger(IPV6Address.class);

  22.   /* *
  23.    * Cache for singletons.
  24.    */
  25.   // private static final Map<String, IPV6Address> CACHE = new WeakHashMap<>();

  26.   /**
  27.    * IP V6 regexp.
  28.    */
  29.   private static final Pattern IPV6_REGEXP = Pattern.compile("^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$"); //$NON-NLS-1$

  30.   /**
  31.    * IPV6 zero block.
  32.    */
  33.   private static final String BLOCK_ZERO = "0000"; //$NON-NLS-1$

  34.   /**
  35.    * Hex output format.
  36.    */
  37.   private static final String HEX_OUTPUT = "%02x"; //$NON-NLS-1$

  38.   /**
  39.    * IPV6 block expansion.
  40.    */
  41.   private static final String IPV6_EXP = "::"; //$NON-NLS-1$

  42.   /**
  43.    * IPV6 block separator.
  44.    */
  45.   private static final String IV6_SEP = ":"; //$NON-NLS-1$

  46.   /**
  47.    * IP V6 address.
  48.    */
  49.   private final String address;

  50.   /**
  51.    * IP V6 address parts.
  52.    */
  53.   private final String[] blocks;


  54.   /**
  55.    * Constructor.
  56.    *
  57.    * @param address IP V6 address
  58.    * @throws NullPointerException if address is null
  59.    * @throws IllegalArgumentException if address is not an ip v6 address
  60.    */
  61.   private IPV6Address(final String address)
  62.    {
  63.     super();
  64.     Objects.requireNonNull(address, "address"); //$NON-NLS-1$
  65.     if ((address.length() < 2) || (address.length() > 45)) // 39, ipv4 embedding
  66.      {
  67.       throw new IllegalArgumentException("To short or long for an IP V6 address"); //$NON-NLS-1$
  68.      }
  69.     String expandedAddress = address.toLowerCase(Locale.getDefault());
  70.     expandedAddress = expandIPV4Address(expandedAddress);
  71.     expandedAddress = expandExpansionBlock(expandedAddress);
  72.     if (!IPV6Address.IPV6_REGEXP.matcher(expandedAddress).matches())
  73.      {
  74.       throw new IllegalArgumentException("Not an IP V6 address"); //$NON-NLS-1$
  75.      }
  76.     expandedAddress = normalizeIPV6Address(expandedAddress);
  77.     this.address = expandedAddress;
  78.     this.blocks = expandedAddress.split(IPV6Address.IV6_SEP);
  79.    }


  80.   /**
  81.    * Expand a possibly embedded IP V4 address.
  82.    *
  83.    * @param address IP V6 address
  84.    * @return IP V6 address
  85.    * @throws NullPointerException if address is null
  86.    * @throws IllegalArgumentException if address is not an ip v4 address
  87.    */
  88.   private static String expandIPV4Address(final String address)
  89.    {
  90.     final int ipv4pos = address.indexOf('.');
  91.     if (ipv4pos == -1)
  92.      {
  93.       return address;
  94.      }
  95.     final int blockStart = address.lastIndexOf(':', ipv4pos);
  96.     final var ipv4 = address.substring(blockStart + 1);
  97.     /* final IPV4Address ipv4address = */ IPV4Address.of(ipv4); // TODO use IPV4Address to ip v6 conversion method
  98.     final var newAddress = address.substring(0, blockStart + 1);
  99.     final String[] parts = ipv4.split("\\."); //$NON-NLS-1$
  100.     final int block1 = Integer.parseInt(parts[0]);
  101.     final int block2 = Integer.parseInt(parts[1]);
  102.     final int block3 = Integer.parseInt(parts[2]);
  103.     final int block4 = Integer.parseInt(parts[3]);
  104.     return newAddress + Integer.toHexString(block1) + String.format(IPV6Address.HEX_OUTPUT, block2) + ':' + Integer.toHexString(block3) + String.format(IPV6Address.HEX_OUTPUT, block4);
  105.    }


  106.   /**
  107.    * Count colons.
  108.    *
  109.    * @param str String to count coolons in
  110.    * @return Numbe rof colons found
  111.    */
  112.   private static int countColons(final String str)
  113.    {
  114.     int colons = 0;
  115.     int expPos = -1;
  116.     do
  117.      {
  118.       expPos = str.indexOf(':', expPos + 1);
  119.       if (expPos > -1)
  120.        {
  121.         ++colons;
  122.        }
  123.      }
  124.     while (expPos > -1);
  125.     return colons;
  126.    }


  127.   /**
  128.    * Expand possible expansion block.
  129.    *
  130.    * @param address IP V6 address
  131.    * @return IP V6 address
  132.    */
  133.   private static String expandExpansionBlock(final String address)
  134.    {
  135.     final int expPos = address.indexOf(IPV6Address.IPV6_EXP);
  136.     if ((expPos == -1))
  137.      {
  138.       return address;
  139.      }
  140.     if (address.indexOf(IPV6Address.IPV6_EXP, expPos + 1) != -1)
  141.      {
  142.       throw new IllegalArgumentException("Not an IP V6 address (more than one expansion block)"); //$NON-NLS-1$
  143.      }
  144.     final var start = address.substring(0, expPos);
  145.     final var end = address.substring(expPos + 2);
  146.     int blocks = 8;
  147.     if (start.length() > 0)
  148.      {
  149.       blocks -= countColons(start) + 1;
  150.      }
  151.     if (end.length() > 0)
  152.      {
  153.       blocks -= countColons(end) + 1;
  154.      }
  155.     final var replace = new StringBuilder();
  156.     if (start.length() > 0)
  157.      {
  158.       replace.append(':');
  159.      }
  160.     while (blocks > 0)
  161.      {
  162.       replace.append(IPV6Address.BLOCK_ZERO);
  163.       --blocks;
  164.       if (blocks > 0)
  165.        {
  166.         replace.append(':');
  167.        }
  168.      }
  169.     if (end.length() > 0)
  170.      {
  171.       replace.append(':');
  172.      }
  173.     replace.append(end);
  174.     replace.insert(0, start);
  175.     return replace.toString();
  176.    }


  177.   /**
  178.    * Normalize IP V6 address.
  179.    *
  180.    * @param address IP V6 address
  181.    * @return Normalized IP V6 address
  182.    */
  183.   private static String normalizeIPV6Address(final String address)
  184.    {
  185.     final String[] parts = address.split(IPV6Address.IV6_SEP);
  186.     final var normalizedAddress = new StringBuilder();
  187.     for (final String part : parts)
  188.      {
  189.       normalizedAddress.append(IPV6Address.BLOCK_ZERO.substring(part.length())).append(part).append(':');
  190.      }
  191.     normalizedAddress.setLength(normalizedAddress.length() - 1);
  192.     return normalizedAddress.toString();
  193.    }


  194.   /**
  195.    * IPV6Address factory.
  196.    *
  197.    * @param address IP V6 address
  198.    * @return IPV6address object
  199.    */
  200.   public static IPV6Address of(final String address)
  201.    {
  202.     /*
  203.     synchronized (IPV6Address.class)
  204.      {
  205.       IPV6Address obj = IPV6Address.CACHE.get(address);
  206.       if (obj != null)
  207.        {
  208.         return obj;
  209.        }
  210.       obj = new IPV6Address(address);
  211.       IPV6Address.CACHE.put(address, obj);
  212.       return obj;
  213.      }
  214.     */
  215.     return new IPV6Address(address);
  216.    }


  217.   /**
  218.    * Is an IP V6 private address.
  219.    *
  220.    * fc Unique Local Unicast
  221.    * fd Unique Local Unicast
  222.    * fe:80:00:00:00:00:00:00 Link-Local
  223.    *
  224.    * @return true if private, false otherwise
  225.    */
  226.   @SuppressWarnings("java:S1313")
  227.   public boolean isPrivate()
  228.    {
  229.     return ("00fe:0080:0000:0000:0000:0000:0000:0000".equals(this.address) || // Link-Local //$NON-NLS-1$
  230.             "00fc".equals(this.blocks[0]) || "00fd".equals(this.blocks[0]) // Unique Local Unicast //$NON-NLS-1$ //$NON-NLS-2$
  231.            );
  232.    }


  233.   /**
  234.    * Is an IP V6 special address.
  235.    *
  236.    * 0:0:0:0:0:0:0:0 default route
  237.    * 0:0:0:0:0:0:0:1 loopback
  238.    * ff Multicast
  239.    *
  240.    * @return true if special, false otherwise
  241.    */
  242.   public boolean isSpecial()
  243.    {
  244.     return ("0000:0000:0000:0000:0000:0000:0000:0000".equals(this.address) || "0000:0000:0000:0000:0000:0000:0000:0001".equals(this.address) || // default route, loopback //$NON-NLS-1$ //$NON-NLS-2$
  245.             "00ff".equals(this.blocks[0]) // Multicast //$NON-NLS-1$
  246.            );
  247.    }


  248.   /**
  249.    * Is an IP V6 public address.
  250.    *
  251.    * 0:0:0:0:0:ffff::/96 IPv4 mapped (abgebildete) IPv6 Adressen
  252.    * 2000::/3 IANA vergebenen globalen Unicast
  253.    * 2001 Provider area
  254.    * 2001:0: Toredo
  255.    * 2001:0db8::/32 Documentation purposes
  256.    * 2002 6to4 tunnel
  257.    * 2003, 0240, 0260, 0261, 0262, 0280, 02a0, 02b0 und 02c0 Regional Internet Registries (RIRs)
  258.    * 0064:ff9b::/96 NAT64
  259.    *
  260.    * @return true when public address, otherwise false
  261.    */
  262.   public boolean isPublic()
  263.    {
  264.     return !isPrivate() && !isSpecial();
  265.    }


  266.   /**
  267.    * Returns the value of this IPV6Address as a string.
  268.    *
  269.    * @return The text value represented by this object after conversion to type string.
  270.    */
  271.   @Override
  272.   public String stringValue()
  273.    {
  274.     return this.address;
  275.    }


  276.   /**
  277.    * Calculate hash code.
  278.    *
  279.    * @return Hash
  280.    * @see java.lang.Object#hashCode()
  281.    */
  282.   @Override
  283.   public int hashCode()
  284.    {
  285.     return this.address.hashCode();
  286.    }


  287.   /**
  288.    * Is equal with another object.
  289.    *
  290.    * @param obj Object
  291.    * @return true when equal, false otherwise
  292.    * @see java.lang.Object#equals(java.lang.Object)
  293.    */
  294.   @Override
  295.   public boolean equals(final Object obj)
  296.    {
  297.     if (this == obj)
  298.      {
  299.       return true;
  300.      }
  301.     if (!(obj instanceof IPV6Address))
  302.      {
  303.       return false;
  304.      }
  305.     final IPV6Address other = (IPV6Address)obj;
  306.     return this.address.equals(other.address);
  307.    }


  308.   /**
  309.    * Returns the string representation of this IPV6Address.
  310.    *
  311.    * The exact details of this representation are unspecified and subject to change, but the following may be regarded as typical:
  312.    *
  313.    * "IPV6Address[address=ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff]"
  314.    *
  315.    * @return String representation of this IPV6Address
  316.    * @see java.lang.Object#toString()
  317.    */
  318.   @Override
  319.   public String toString()
  320.    {
  321.     final var builder = new StringBuilder(21);
  322.     builder.append("IPV6Address[address=").append(this.address).append(']'); //$NON-NLS-1$
  323.     return builder.toString();
  324.    }


  325.   /**
  326.    * Compare with another object.
  327.    *
  328.    * @param obj Object to compare with
  329.    * @return 0: equal; 1: greater; -1: smaller
  330.    * @see java.lang.Comparable#compareTo(java.lang.Object)
  331.    */
  332.   @Override
  333.   public int compareTo(final IPV6Address obj)
  334.    {
  335.     Objects.requireNonNull(obj, "obj"); //$NON-NLS-1$
  336.     return this.address.compareTo(obj.address);
  337.    }

  338.  }