package com.subgraph.orchid.directory.consensus; import com.subgraph.orchid.ConsensusDocument.ConsensusFlavor; import com.subgraph.orchid.TorParsingException; import com.subgraph.orchid.crypto.TorMessageDigest; import com.subgraph.orchid.data.HexDigest; import com.subgraph.orchid.directory.consensus.ConsensusDocumentParser.DocumentSection; import com.subgraph.orchid.directory.parsing.DocumentFieldParser; public class RouterStatusSectionParser extends ConsensusDocumentSectionParser { private RouterStatusImpl currentEntry = null; RouterStatusSectionParser(DocumentFieldParser parser, ConsensusDocumentImpl document) { super(parser, document); } @Override void parseLine(DocumentKeyword keyword) { if(!keyword.equals(DocumentKeyword.R)) assertCurrentEntry(); switch(keyword) { case R: parseFirstLine(); break; case S: parseFlags(); break; case V: parseVersion(); break; case W: parseBandwidth(); break; case P: parsePortList(); break; case M: parseMicrodescriptorHash(); break; default: break; } } private void assertCurrentEntry() { if(currentEntry == null) throw new TorParsingException("Router status entry must begin with an 'r' line"); } private void addCurrentEntry() { assertCurrentEntry(); document.addRouterStatusEntry(currentEntry); currentEntry = null; } private void parseFirstLine() { if(currentEntry != null) throw new TorParsingException("Unterminated router status entry."); currentEntry = new RouterStatusImpl(); currentEntry.setNickname(fieldParser.parseNickname()); currentEntry.setIdentity(parseBase64Digest()); if(document.getFlavor() != ConsensusFlavor.MICRODESC) { currentEntry.setDigest(parseBase64Digest()); } currentEntry.setPublicationTime(fieldParser.parseTimestamp()); currentEntry.setAddress(fieldParser.parseAddress()); currentEntry.setRouterPort(fieldParser.parsePort()); currentEntry.setDirectoryPort(fieldParser.parsePort()); } private HexDigest parseBase64Digest() { return HexDigest.createFromDigestBytes(fieldParser.parseBase64Data()); } private void parseFlags() { while(fieldParser.argumentsRemaining() > 0) currentEntry.addFlag(fieldParser.parseString()); } private void parseVersion() { currentEntry.setVersion(fieldParser.parseConcatenatedString()); } private void parseBandwidth() { while(fieldParser.argumentsRemaining() > 0) { final String[] parts = fieldParser.parseString().split("="); if(parts.length == 2) parseBandwidthItem(parts[0], fieldParser.parseInteger(parts[1])); } if(document.getFlavor() == ConsensusFlavor.MICRODESC) { addCurrentEntry(); } } private void parseBandwidthItem(String key, int value) { if(key.equals("Bandwidth")) currentEntry.setEstimatedBandwidth(value); else if(key.equals("Measured")) currentEntry.setMeasuredBandwidth(value); } private void parsePortList() { if(document.getFlavor() == ConsensusFlavor.MICRODESC) { throw new TorParsingException("'p' line does not appear in consensus flavor 'microdesc'"); } final String arg = fieldParser.parseString(); if(arg.equals("accept")) { currentEntry.setAcceptedPorts(fieldParser.parseString()); } else if(arg.equals("reject")) { currentEntry.setRejectedPorts(fieldParser.parseString()); } addCurrentEntry(); } private void parseMicrodescriptorHash() { if(document.getFlavor() != ConsensusFlavor.MICRODESC) { throw new TorParsingException("'m' line is invalid unless consensus flavor is microdesc"); } final byte[] hashBytes = fieldParser.parseBase64Data(); if(hashBytes.length != TorMessageDigest.TOR_DIGEST256_SIZE) { throw new TorParsingException("'m' line has incorrect digest size "+ hashBytes.length +" != "+ TorMessageDigest.TOR_DIGEST256_SIZE); } currentEntry.setMicrodescriptorDigest(HexDigest.createFromDigestBytes(hashBytes)); } @Override String getNextStateKeyword() { return "directory-footer"; } @Override DocumentSection getSection() { return DocumentSection.ROUTER_STATUS; } DocumentSection nextSection() { return DocumentSection.FOOTER; } }