package mireka.address.parser;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import mireka.address.parser.Ipv6Token.Kind;
import mireka.address.parser.base.AST;
import mireka.address.parser.base.CharScanner;
import mireka.address.parser.base.Spelling;
import mireka.address.parser.base.Terminal;
/**
* Ipv6Parser parses an IPv6 address literal and convert it to an
* {@link Inet6Address}. For example:
* <ul>
* <li>2001:db8:0:0:0:0:0:0
* <li>2001:db8::
* <li>2001:db8::1
* <li>::1
* <li>2001:db8::192.0.2.0
* </ul>
* <p>
* Grammar:
*
* <pre>
* IPv6 := [NUM_SEQENCE] ENDING
* NUM_SEQENCE := NUM *(: NUM)
* NUM := 1*4HEXDIG
* ENDING := :: [NUM_SEQENCE [. IPv4Rest]]
* | . IPv4Rest
* | E
* IPv4Rest := NUM . NUM . NUM
* </pre>
*/
public class Ipv6Parser {
private Ipv6Scanner scanner;
private Ipv6Token currentToken;
private Spelling spelling = new Spelling();
public Ipv6Parser(String address) throws ParseException {
this(new CharScanner(address));
}
public Ipv6Parser(CharScanner charScanner) throws ParseException {
this.scanner = new Ipv6Scanner(charScanner);
currentToken = scanner.scan();
}
public Ipv6 parseLeft() throws ParseException {
Ipv6 ipv6AST = parseIpv6();
new AddressContextualAnalyzer(ipv6AST).decorate();
scanner.finish(currentToken);
return ipv6AST;
}
public Ipv6 parse() throws ParseException {
Ipv6 ipv6AST = parseIpv6();
new AddressContextualAnalyzer(ipv6AST).decorate();
if (currentToken.kind != Kind.EOF)
throw currentToken.otherSyntaxException("Superfluous characters "
+ "after IPv6 address: {0}.");
return ipv6AST;
}
private Ipv6 parseIpv6() throws ParseException {
int position = currentToken.position;
spelling.start();
NumSequence numSequenceAST = null;
if (currentToken.kind == Kind.NUM) {
numSequenceAST = parseNumSequence();
}
Ending ending = parseEnding();
return new Ipv6(position, spelling.finish(), numSequenceAST, ending);
}
private NumSequence parseNumSequence() throws ParseException {
int position = currentToken.position;
List<Num> numbers = new ArrayList<Num>();
numbers.add(parseNum());
while (currentToken.kind == Kind.COLON) {
acceptIt();
numbers.add(parseNum());
}
return new NumSequence(position, numbers);
}
private Num parseNum() throws ParseException {
if (currentToken.kind == Kind.NUM) {
Num numAST = new Num(currentToken);
acceptIt();
return numAST;
} else {
throw currentToken.syntaxException("hex digits");
}
}
private Ending parseEnding() throws ParseException {
int position = currentToken.position;
RestOfIpv4 restOfIpv4AST = null;
switch (currentToken.kind) {
case DOUBLE_COLON:
acceptIt();
NumSequence numSequenceAST = null;
if (currentToken.kind == Kind.NUM) {
numSequenceAST = parseNumSequence();
if (currentToken.kind == Kind.DOT) {
acceptIt();
restOfIpv4AST = parseRestOfIpv4();
}
}
return new DoubleColonEnding(position, numSequenceAST,
restOfIpv4AST);
case DOT:
acceptIt();
restOfIpv4AST = parseRestOfIpv4();
return new Ipv4Ending(position, restOfIpv4AST);
case OTHER:
case EOF:
return new EmptyEnding(position);
default:
throw currentToken
.syntaxException("'.', '::' or end of IPv6 literal");
}
}
private RestOfIpv4 parseRestOfIpv4() throws ParseException {
int position = currentToken.position;
Num dec2AST = parseNum();
accept(Kind.DOT);
Num dec3AST = parseNum();
accept(Kind.DOT);
Num dec4AST = parseNum();
return new RestOfIpv4(position, dec2AST, dec3AST, dec4AST);
}
private void accept(Kind kind) throws ParseException {
if (currentToken.kind == kind)
acceptIt();
else
throw currentToken.syntaxException(kind);
}
private void acceptIt() throws ParseException {
spelling.append(currentToken.spelling);
currentToken = scanner.scan();
}
/**
* AddressContextualAnalyzer does additional checks on the address which are
* not included in the grammar and extracts the IPv6 address bytes from the
* abstract syntax tree.
*/
private static class AddressContextualAnalyzer {
final Ipv6 ipv6AST;
LinkedList<Num> leftNumbers = new LinkedList<Num>();
LinkedList<Num> rightNumbers = new LinkedList<Num>();
LinkedList<Num> ipv4Numbers = new LinkedList<Num>();
boolean hasDoubleColon = false;
public AddressContextualAnalyzer(Ipv6 ipv6ast) {
super();
ipv6AST = ipv6ast;
}
/**
* Traverses the abstract tree and decorates the Ipv6 node with the
* evaluates address in the form of a byte array and an
* {@link Inet6Address} object.
*/
public void decorate() throws ParseException {
collectNumbers(ipv6AST);
checkLength();
byte[] addressBytes = convertToBytes();
ipv6AST.addressBytes = addressBytes;
try {
ipv6AST.address = InetAddress.getByAddress(addressBytes);
} catch (UnknownHostException e) {
// this could only happen if the length of the byte array were
// invalid (not 16), which is impossible.
throw new RuntimeException("Assertion failed", e);
}
}
private void collectNumbers(Ipv6 ipv6AST) throws ParseException {
if (ipv6AST.leftNumSequence != null)
leftNumbers.addAll(ipv6AST.leftNumSequence.numbers);
if (ipv6AST.ending instanceof DoubleColonEnding) {
DoubleColonEnding ending = (DoubleColonEnding) ipv6AST.ending;
hasDoubleColon = true;
if (ending.rightNumSequence != null) {
rightNumbers.addAll(ending.rightNumSequence.numbers);
if (ending.restOfIpv4 != null) {
ipv4Numbers.add(rightNumbers.removeLast());
ipv4Numbers.add(ending.restOfIpv4.dec2);
ipv4Numbers.add(ending.restOfIpv4.dec3);
ipv4Numbers.add(ending.restOfIpv4.dec4);
}
}
} else if (ipv6AST.ending instanceof Ipv4Ending) {
Ipv4Ending ending = (Ipv4Ending) ipv6AST.ending;
if (ipv6AST.leftNumSequence == null)
throw ending.syntaxException("Dot must follow a decimal "
+ "number in the IPv4 part of an IPv6 address.");
ipv4Numbers.add(leftNumbers.removeLast());
ipv4Numbers.add(ending.restOfIpv4.dec2);
ipv4Numbers.add(ending.restOfIpv4.dec3);
ipv4Numbers.add(ending.restOfIpv4.dec4);
} else if (ipv6AST.ending instanceof EmptyEnding) {
// nothing to do
}
}
private void checkLength() throws ParseException {
// bytes without the :: placeholder
int countOfExplicitlySpecifiedBytes =
leftNumbers.size() * 2 + rightNumbers.size() * 2
+ ipv4Numbers.size();
int maxExplicitlySpecifiedBytes = hasDoubleColon ? 14 : 16;
if (countOfExplicitlySpecifiedBytes > maxExplicitlySpecifiedBytes)
throw ipv6AST.syntaxException("IPv6 address literal specifies "
+ "more than 16 bytes.");
if (!hasDoubleColon && countOfExplicitlySpecifiedBytes < 16)
throw ipv6AST.syntaxException("IPv6 address literal specifies "
+ "less than 16 bytes.");
}
private byte[] convertToBytes() throws ParseException {
byte[] result = new byte[16];
int pos = 0;
for (Num hex : leftNumbers) {
int doubleByte = evaluateHexDoubleByte(hex);
result[pos++] = (byte) (doubleByte >> 8);
result[pos++] = (byte) (doubleByte & 0xFF);
}
pos = 15;
for (Iterator<Num> it = ipv4Numbers.descendingIterator(); it
.hasNext();) {
Num dec = it.next();
result[pos--] = evaluateDecByte(dec);
}
for (Iterator<Num> it = rightNumbers.descendingIterator(); it
.hasNext();) {
Num hex = it.next();
int doubleByte = evaluateHexDoubleByte(hex);
result[pos--] = (byte) (doubleByte & 0xFF);
result[pos--] = (byte) (doubleByte >> 8);
}
return result;
}
private int evaluateHexDoubleByte(Num hex) {
return Integer.parseInt(hex.spelling, 16);
}
private byte evaluateDecByte(Num dec) throws ParseException {
try {
int result = Integer.parseInt(dec.spelling);
if (result > 255)
throw dec.syntaxException("Byte value must be lower "
+ "than or equal with 255 in the IPv4 "
+ "compatible part of an IPv6 address.");
return (byte) result;
} catch (NumberFormatException e) {
throw dec.syntaxException("The IPv4 compatible part of "
+ "an IPv6 address must consists of decimal "
+ "and not hex digits.");
}
}
}
public static class Ipv6 extends AST {
public String spelling;
private NumSequence leftNumSequence;
private Ending ending;
public byte[] addressBytes;
public InetAddress address;
private Ipv6(int position, String spelling,
NumSequence leftNumSequence, Ending ending) {
super(position);
this.spelling = spelling;
this.leftNumSequence = leftNumSequence;
this.ending = ending;
}
}
private static class NumSequence extends AST {
public List<Num> numbers;
public NumSequence(int position, List<Num> numbers) {
super(position);
this.numbers = numbers;
}
}
private static class Num extends Terminal {
public Num(Ipv6Token token) {
super(token.position, token.spelling);
}
}
private static class Ending extends AST {
public Ending(int position) {
super(position);
}
}
private static class DoubleColonEnding extends Ending {
public NumSequence rightNumSequence;
public RestOfIpv4 restOfIpv4;
public DoubleColonEnding(int position, NumSequence rightNumSequence,
RestOfIpv4 restOfIpv4) {
super(position);
this.rightNumSequence = rightNumSequence;
this.restOfIpv4 = restOfIpv4;
}
}
private static class RestOfIpv4 extends AST {
public Num dec2;
public Num dec3;
public Num dec4;
public RestOfIpv4(int position, Num dec2, Num dec3, Num dec4) {
super(position);
this.dec2 = dec2;
this.dec3 = dec3;
this.dec4 = dec4;
}
}
public static class Ipv4Ending extends Ending {
public RestOfIpv4 restOfIpv4;
public Ipv4Ending(int position, RestOfIpv4 restOfIpv4) {
super(position);
this.restOfIpv4 = restOfIpv4;
}
}
public static class EmptyEnding extends Ending {
public EmptyEnding(int position) {
super(position);
}
}
}