Last active
February 16, 2019 00:28
-
-
Save wkgcass/0bec457a6737a54f2f1025ed779118aa to your computer and use it in GitHub Desktop.
ipv4 and ipv6 literal parser
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public static String[] split(String str, String e) { | |
List<String> ls = new LinkedList<>(); | |
int idx = -e.length(); | |
int lastIdx = 0; | |
while (true) { | |
idx = str.indexOf(e, idx + e.length()); | |
if (idx == -1) { | |
ls.add(str.substring(lastIdx)); | |
break; | |
} | |
ls.add(str.substring(lastIdx, idx)); | |
lastIdx = idx + e.length(); | |
} | |
//noinspection ToArrayCallWithZeroLengthArrayArgument | |
return ls.toArray(new String[ls.size()]); | |
} | |
// return null if not ip literal | |
public static byte[] parseIpString(String ip) { | |
if (ip.contains(":")) | |
return parseIpv6String(ip); | |
else | |
return parseIpv4String(ip); | |
} | |
// return null if not ipv4 | |
public static byte[] parseIpv4String(String ipv4) { | |
byte[] ipBytes = new byte[4]; | |
if (parseIpv4String(ipv4, ipBytes, 0) == -1) { | |
return null; // wrong len or parse fail | |
} else { | |
return ipBytes; | |
} | |
} | |
private static int parseIpv4String(String ipv4, byte[] ipBytes, int fromIdx) { | |
String[] split = split(ipv4, "."); | |
if (split.length != 4) | |
return -1; // wrong len | |
for (int i = 0; i < split.length; ++i) { | |
int idx = fromIdx + i; | |
if (idx >= ipBytes.length) { | |
return -1; // too long | |
} | |
String s = split[i]; | |
char[] digits = s.toCharArray(); | |
if (digits.length > 3 || digits.length == 0) { | |
return -1; // invalid for a byte | |
} | |
for (char c : digits) { | |
if (c < '0' || c > '9') | |
return -1; // should be decimal digits | |
} | |
if (s.startsWith("0") && s.length() > 1) | |
return -1; // 0n is invalid | |
int num = Integer.parseInt(s); | |
if (num > 255) | |
return -1; // invalid byte | |
ipBytes[idx] = (byte) num; | |
} | |
return split.length; | |
} | |
// return null if not ipv6 | |
public static byte[] parseIpv6String(String ipv6) { | |
if (ipv6.startsWith("[") && ipv6.endsWith("]")) { | |
ipv6 = ipv6.substring(1, ipv6.length() - 1); | |
} | |
{ // check how many :: | |
int count = split(ipv6, "::").length - 1; | |
if (count > 1) | |
return null; // at most 1 | |
} | |
boolean hasDblColon; | |
String colonOnly; | |
String colonAndDot; | |
{ | |
int idx = ipv6.indexOf("::"); | |
if (idx == -1) { | |
hasDblColon = false; | |
colonOnly = null; | |
colonAndDot = ipv6; | |
} else { | |
hasDblColon = true; | |
colonOnly = ipv6.substring(0, idx); | |
colonAndDot = ipv6.substring(idx + "::".length()); | |
} | |
} | |
byte[] ipBytes = new byte[16]; | |
int consumed = parseIpv6ColonPart(colonOnly, ipBytes, 0); | |
if (consumed == -1) | |
return null; // parse failed | |
int consumed2 = parseIpv6LastBits(colonAndDot, ipBytes); | |
if (consumed2 == -1) | |
return null; // parse failed | |
if (hasDblColon) { | |
if (consumed + consumed2 >= 16) | |
return null; // wrong len | |
} else { | |
if (consumed + consumed2 != 16) | |
return null; // wrong len | |
} | |
return ipBytes; | |
} | |
// -1 for fail | |
private static int parseIpv6ColonPart(String s, byte[] ipBytes, int fromIdx) { | |
if (s == null || s.isEmpty()) | |
return 0; | |
if (fromIdx < 0) | |
return -1; | |
// only hex number splited by `:` | |
String[] split = split(s, ":"); | |
for (int i = 0; i < split.length; ++i) { | |
int baseIndex = fromIdx + 2 * i; // every field occupy 2 bytes | |
if (baseIndex >= ipBytes.length) { | |
return -1; // too long | |
} | |
char[] field = split[i].toCharArray(); | |
if (field.length > 4) { | |
// four hexadecimal digits | |
return -1; | |
} | |
if (field.length == 0) { | |
// there must be at least one numeral in every field | |
return -1; | |
} | |
for (char c : field) { | |
if ((c < 'A' || c > 'F') && (c < 'a' || c > 'f') && (c < '0' || c > '9')) { | |
// hexadecimal | |
return -1; | |
} | |
} | |
switch (field.length) { | |
case 1: | |
ipBytes[baseIndex + 1] = (byte) Integer.parseInt(field[0] + "", 16); | |
break; | |
case 2: | |
ipBytes[baseIndex + 1] = (byte) Integer.parseInt(field[0] + "" + field[1], 16); | |
break; | |
case 3: | |
ipBytes[baseIndex] = (byte) Integer.parseInt(field[0] + "", 16); | |
ipBytes[baseIndex + 1] = (byte) Integer.parseInt(field[1] + "" + field[2], 16); | |
break; | |
case 4: | |
ipBytes[baseIndex] = (byte) Integer.parseInt(field[0] + "" + field[1], 16); | |
ipBytes[baseIndex + 1] = (byte) Integer.parseInt(field[2] + "" + field[3], 16); | |
break; | |
default: | |
// should not happen | |
return -1; | |
} | |
} | |
return split.length * 2; | |
} | |
// -1 for fail | |
private static int parseIpv6LastBits(String s, byte[] ipBytes) { | |
if (s.contains(".")) { | |
int idx = s.indexOf('.'); | |
idx = s.lastIndexOf(':', idx); | |
if (idx == -1) { | |
return parseIpv4String(s, ipBytes, ipBytes.length - 4); | |
} else { | |
String colonPart = s.substring(0, idx); | |
String dotPart = s.substring(idx + 1); | |
int r = parseIpv4String(dotPart, ipBytes, ipBytes.length - 4); | |
if (r == -1) { | |
return -1; // wrong len or parse failed | |
} | |
return 4 + parseIpv6ColonPart(colonPart, ipBytes, ipBytes.length - 4 - split(colonPart, ":").length * 2); | |
} | |
} else { | |
return parseIpv6ColonPart(s, ipBytes, ipBytes.length - split(s, ":").length * 2); | |
} | |
} | |
public static byte[] parseIpv4StringConsiderV6Compatible(String s) { | |
byte[] v6 = parseIpv6String(s); | |
if (v6 != null) { | |
// maybe v4-compatible ipv6 | |
for (int i = 0; i < 10; ++i) { | |
if (0 != v6[i]) | |
return null; | |
} | |
for (int i = 11; i < 12; ++i) { | |
if ((byte) 0xFF != v6[i]) | |
return null; | |
} | |
byte[] v4 = new byte[4]; | |
System.arraycopy(v6, 12, v4, 0, 4); | |
return v4; | |
} | |
return parseIpv4String(s); | |
} | |
public static boolean isIpv4(String s) { | |
return parseIpv4StringConsiderV6Compatible(s) != null; | |
} | |
public static boolean isIpv6(String s) { | |
return parseIpv6String(s) != null; | |
} | |
public static boolean isIpLiteral(String s) { | |
return isIpv4(s) || isIpv6(s); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment