/** * Compare two records based on their duplicate scores. If the scores are equal, we break ties * based on mapping quality (added to the mate's mapping quality if paired and mapped), then * library/read name. * * <p>If true is given to assumeMateCigar, then any score that can use the mate cigar to to * compute the mate's score will return the score computed on both ends. * * <p>We allow different scoring strategies. We return <0 if rec1 has a better strategy than rec2. */ public static int compare( final SAMRecord rec1, final SAMRecord rec2, final ScoringStrategy scoringStrategy, final boolean assumeMateCigar) { int cmp; // always prefer paired over non-paired if (rec1.getReadPairedFlag() != rec2.getReadPairedFlag()) return rec1.getReadPairedFlag() ? 1 : -1; cmp = computeDuplicateScore(rec2, scoringStrategy, assumeMateCigar) - computeDuplicateScore(rec1, scoringStrategy, assumeMateCigar); /** * Finally, use library ID and read name This is important because we cannot control the order * in which reads appear for reads that are comparable up to now (i.e. cmp == 0). We want to * deterministically choose them, and so we need this. */ if (0 == cmp) cmp = SAMUtils.getCanonicalRecordName(rec1).compareTo(SAMUtils.getCanonicalRecordName(rec2)); return cmp; }
/** * Returns the base qualities for the read as a string. * * @param read read whose base qualities should be returned * @return Base qualities string as printable ASCII values (encoded as a FASTQ string). */ public static String getBaseQualityString(final GATKRead read) { Utils.nonNull(read); if (Arrays.equals(SAMRecord.NULL_QUALS, read.getBaseQualities())) { return SAMRecord.NULL_QUALS_STRING; } return SAMUtils.phredToFastq(read.getBaseQualities()); }
/** * Retrieve the original base qualities of the given read, if present, as stored in the OQ * attribute. * * @param read read to check * @return original base qualities as stored in the OQ attribute, or null if the OQ attribute is * not present */ public static byte[] getOriginalBaseQualities(final GATKRead read) { if (!read.hasAttribute(ORIGINAL_BASE_QUALITIES_TAG)) { return null; } final String oqString = read.getAttributeAsString(ORIGINAL_BASE_QUALITIES_TAG); return oqString.length() > 0 ? SAMUtils.fastqToPhred(oqString) : null; }
/** * Set the base qualities from a string of ASCII encoded values * * @param read read whose base qualities should be set * @param baseQualityString ASCII encoded (encoded as a FASTQ string) values of base qualities. */ public static void setBaseQualityString(final GATKRead read, final String baseQualityString) { Utils.nonNull(read); Utils.nonNull(baseQualityString); if (SAMRecord.NULL_QUALS_STRING.equals(baseQualityString)) { read.setBaseQualities(SAMRecord.NULL_QUALS); } else { read.setBaseQualities(SAMUtils.fastqToPhred(baseQualityString)); } }
/** Setters and Accessors for base insertion and base deletion quality scores */ public void setBaseQualities(final byte[] quals, final EventType errorModel) { switch (errorModel) { case BASE_SUBSTITUTION: setBaseQualities(quals); break; case BASE_INSERTION: setAttribute( GATKSAMRecord.BQSR_BASE_INSERTION_QUALITIES, quals == null ? null : SAMUtils.phredToFastq(quals)); break; case BASE_DELETION: setAttribute( GATKSAMRecord.BQSR_BASE_DELETION_QUALITIES, quals == null ? null : SAMUtils.phredToFastq(quals)); break; default: throw new ReviewedGATKException("Unrecognized Base Recalibration type: " + errorModel); } }
/** * Returns the duplicate score computed from the given fragment. value should be capped by * Short.MAX_VALUE/2 since the score from two reads will be added and an overflow will be * * <p>If true is given to assumeMateCigar, then any score that can use the mate cigar to compute * the mate's score will return the score computed on both ends. */ public static short computeDuplicateScore( final SAMRecord record, final ScoringStrategy scoringStrategy, final boolean assumeMateCigar) { Short storedScore = (Short) record.getTransientAttribute(Attr.DuplicateScore); if (storedScore == null) { short score = 0; switch (scoringStrategy) { case SUM_OF_BASE_QUALITIES: // two (very) long reads worth of high-quality bases can go over Short.MAX_VALUE/2 // and risk overflow. score += (short) Math.min(getSumOfBaseQualities(record), Short.MAX_VALUE / 2); break; case TOTAL_MAPPED_REFERENCE_LENGTH: if (!record.getReadUnmappedFlag()) { // no need to remember the score since this scoring mechanism is symmetric score = (short) Math.min(record.getCigar().getReferenceLength(), Short.MAX_VALUE / 2); } if (assumeMateCigar && record.getReadPairedFlag() && !record.getMateUnmappedFlag()) { score += (short) Math.min( SAMUtils.getMateCigar(record).getReferenceLength(), Short.MAX_VALUE / 2); } break; // The RANDOM score gives the same score to both reads so that they get filtered together. // it's not critical do use the readName since the scores from both ends get added, but it // seem // to be clearer this way. case RANDOM: // start with a random number between Short.MIN_VALUE/4 and Short.MAX_VALUE/4 score += (short) (hasher.hashUnencodedChars(record.getReadName()) & 0b11_1111_1111_1111); // subtract Short.MIN_VALUE/4 from it to end up with a number between // 0 and Short.MAX_VALUE/2. This number can be then discounted in case the read is // not passing filters. We need to stay far from overflow so that when we add the two // scores from the two read mates we do not overflow since that could cause us to chose a // failing read-pair instead of a passing one. score -= Short.MIN_VALUE / 4; } // make sure that filter-failing records are heavily discounted. (the discount can happen // twice, once // for each mate, so need to make sure we do not subtract more than Short.MIN_VALUE overall.) score += record.getReadFailsVendorQualityCheckFlag() ? (short) (Short.MIN_VALUE / 2) : 0; storedScore = score; record.setTransientAttribute(Attr.DuplicateScore, storedScore); } return storedScore; }
@Test public void testChainProgramRecord() { SAMFileHeader header = new SAMFileHeader(); SAMProgramRecord first = header.createProgramRecord(); SAMUtils.chainSAMProgramRecord(header, first); Assert.assertEquals(header.getProgramRecords().size(), 1); Assert.assertNull(first.getPreviousProgramGroupId()); SAMProgramRecord second = header.createProgramRecord(); SAMUtils.chainSAMProgramRecord(header, second); Assert.assertEquals(header.getProgramRecords().size(), 2); Assert.assertNull(first.getPreviousProgramGroupId()); Assert.assertEquals(second.getPreviousProgramGroupId(), first.getProgramGroupId()); SAMProgramRecord third = new SAMProgramRecord("3"); SAMUtils.chainSAMProgramRecord(header, third); header.addProgramRecord(third); Assert.assertEquals(header.getProgramRecords().size(), 3); Assert.assertNull(first.getPreviousProgramGroupId()); Assert.assertEquals(second.getPreviousProgramGroupId(), first.getProgramGroupId()); Assert.assertEquals(third.getPreviousProgramGroupId(), second.getProgramGroupId()); }
/* Parse and execute a NAMING message */ protected boolean execNamingMessage(String opcode, Properties props) { if (opcode.equals("LOOKUP")) { if (props == null) { _log.debug("No parameters specified in NAMING LOOKUP message"); return false; } String name = props.getProperty("NAME"); if (name == null) { _log.debug("Name to resolve not specified in NAMING message"); return false; } Destination dest = null; if (name.equals("ME")) { if (getRawSession() != null) { dest = getRawSession().getDestination(); } else if (getStreamSession() != null) { dest = getStreamSession().getDestination(); } else if (getDatagramSession() != null) { dest = getDatagramSession().getDestination(); } else { _log.debug("Lookup for SESSION destination, but session is null"); return false; } } else { try { dest = SAMUtils.getDest(name); } catch (DataFormatException e) { } } if (dest == null) { return writeString("NAMING REPLY RESULT=KEY_NOT_FOUND NAME=" + name + "\n"); } return writeString( "NAMING REPLY RESULT=OK NAME=" + name + " VALUE=" + dest.toBase64() + "\n"); } else { _log.debug("Unrecognized NAMING message opcode: \"" + opcode + "\""); return false; } }
/* Parse and execute a DEST message*/ protected boolean execDestMessage(String opcode, Properties props) { if (opcode.equals("GENERATE")) { String sigTypeStr = props.getProperty("SIGNATURE_TYPE"); SigType sigType; if (sigTypeStr != null) { sigType = SigType.parseSigType(sigTypeStr); if (sigType == null) { writeString( "DEST REPLY RESULT=I2P_ERROR MESSAGE=\"SIGNATURE_TYPE " + sigTypeStr + " unsupported\"\n"); return false; } } else { sigType = SigType.DSA_SHA1; } ByteArrayOutputStream priv = new ByteArrayOutputStream(663); ByteArrayOutputStream pub = new ByteArrayOutputStream(387); SAMUtils.genRandomKey(priv, pub, sigType); return writeString( "DEST REPLY" + " PUB=" + Base64.encode(pub.toByteArray()) + " PRIV=" + Base64.encode(priv.toByteArray()) + "\n"); } else { writeString("DEST REPLY RESULT=I2P_ERROR MESSAGE=\"DEST GENERATE required\""); if (_log.shouldLog(Log.DEBUG)) _log.debug("Unrecognized DEST message opcode: \"" + opcode + "\""); return false; } }
/* Parse and execute a DEST message*/ protected boolean execDestMessage(String opcode, Properties props) { if (opcode.equals("GENERATE")) { if (!props.isEmpty()) { _log.debug("Properties specified in DEST GENERATE message"); return false; } ByteArrayOutputStream priv = new ByteArrayOutputStream(); ByteArrayOutputStream pub = new ByteArrayOutputStream(); SAMUtils.genRandomKey(priv, pub); return writeString( "DEST REPLY" + " PUB=" + Base64.encode(pub.toByteArray()) + " PRIV=" + Base64.encode(priv.toByteArray()) + "\n"); } else { _log.debug("Unrecognized DEST message opcode: \"" + opcode + "\""); return false; } }
/* Parse and execute a SESSION message */ protected boolean execSessionMessage(String opcode, Properties props) { String dest = "BUG!"; try { if (opcode.equals("CREATE")) { if ((getRawSession() != null) || (getDatagramSession() != null) || (getStreamSession() != null)) { if (_log.shouldLog(Log.DEBUG)) _log.debug("Trying to create a session, but one still exists"); return writeString( "SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"Session already exists\"\n"); } if (props.isEmpty()) { if (_log.shouldLog(Log.DEBUG)) _log.debug("No parameters specified in SESSION CREATE message"); return writeString( "SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"No parameters for SESSION CREATE\"\n"); } dest = props.getProperty("DESTINATION"); if (dest == null) { if (_log.shouldLog(Log.DEBUG)) _log.debug("SESSION DESTINATION parameter not specified"); return writeString( "SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"DESTINATION not specified\"\n"); } props.remove("DESTINATION"); String destKeystream = null; if (dest.equals("TRANSIENT")) { _log.debug("TRANSIENT destination requested"); ByteArrayOutputStream priv = new ByteArrayOutputStream(640); SAMUtils.genRandomKey(priv, null); destKeystream = Base64.encode(priv.toByteArray()); } else { destKeystream = bridge.getKeystream(dest); if (destKeystream == null) { if (_log.shouldLog(Log.DEBUG)) _log.debug( "Custom destination specified [" + dest + "] but it isn't known, creating a new one"); ByteArrayOutputStream baos = new ByteArrayOutputStream(640); SAMUtils.genRandomKey(baos, null); destKeystream = Base64.encode(baos.toByteArray()); bridge.addKeystream(dest, destKeystream); } else { if (_log.shouldLog(Log.DEBUG)) _log.debug("Custom destination specified [" + dest + "] and it is already known"); } } String style = props.getProperty("STYLE"); if (style == null) { if (_log.shouldLog(Log.DEBUG)) _log.debug("SESSION STYLE parameter not specified"); return writeString( "SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"No SESSION STYLE specified\"\n"); } props.remove("STYLE"); // Unconditionally override what the client may have set // (iMule sets BestEffort) as None is more efficient // and the client has no way to access delivery notifications props.setProperty(I2PClient.PROP_RELIABILITY, I2PClient.PROP_RELIABILITY_NONE); if (style.equals("RAW")) { rawSession = new SAMRawSession(destKeystream, props, this); } else if (style.equals("DATAGRAM")) { datagramSession = new SAMDatagramSession(destKeystream, props, this); } else if (style.equals("STREAM")) { String dir = props.getProperty("DIRECTION"); if (dir == null) { if (_log.shouldLog(Log.DEBUG)) _log.debug("No DIRECTION parameter in STREAM session, defaulting to BOTH"); dir = "BOTH"; } else if (!dir.equals("CREATE") && !dir.equals("RECEIVE") && !dir.equals("BOTH")) { if (_log.shouldLog(Log.DEBUG)) _log.debug("Unknown DIRECTION parameter value: [" + dir + "]"); return writeString( "SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"Unknown DIRECTION parameter\"\n"); } else { props.remove("DIRECTION"); } streamSession = newSAMStreamSession(destKeystream, dir, props); } else { if (_log.shouldLog(Log.DEBUG)) _log.debug("Unrecognized SESSION STYLE: \"" + style + "\""); return writeString( "SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"Unrecognized SESSION STYLE\"\n"); } return writeString("SESSION STATUS RESULT=OK DESTINATION=" + dest + "\n"); } else { if (_log.shouldLog(Log.DEBUG)) _log.debug("Unrecognized SESSION message opcode: \"" + opcode + "\""); return writeString("SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"Unrecognized opcode\"\n"); } } catch (DataFormatException e) { if (_log.shouldLog(Log.DEBUG)) _log.debug("Invalid destination specified"); return writeString( "SESSION STATUS RESULT=INVALID_KEY DESTINATION=" + dest + " MESSAGE=\"" + e.getMessage() + "\"\n"); } catch (I2PSessionException e) { if (_log.shouldLog(Log.DEBUG)) _log.debug("I2P error when instantiating session", e); return writeString( "SESSION STATUS RESULT=I2P_ERROR DESTINATION=" + dest + " MESSAGE=\"" + e.getMessage() + "\"\n"); } catch (SAMException e) { _log.error("Unexpected SAM error", e); return writeString( "SESSION STATUS RESULT=I2P_ERROR DESTINATION=" + dest + " MESSAGE=\"" + e.getMessage() + "\"\n"); } catch (IOException e) { _log.error("Unexpected IOException", e); return writeString( "SESSION STATUS RESULT=I2P_ERROR DESTINATION=" + dest + " MESSAGE=\"" + e.getMessage() + "\"\n"); } }
public void handle() { String msg = null; String domain = null; String opcode = null; boolean canContinue = false; Properties props; final StringBuilder buf = new StringBuilder(128); this.thread.setName("SAMv1Handler " + _id); if (_log.shouldLog(Log.DEBUG)) _log.debug("SAM handling started"); try { boolean gotFirstLine = false; while (true) { if (shouldStop()) { if (_log.shouldLog(Log.DEBUG)) _log.debug("Stop request found"); break; } SocketChannel clientSocketChannel = getClientSocket(); if (clientSocketChannel == null) { _log.info("Connection closed by client"); break; } if (clientSocketChannel.socket() == null) { _log.info("Connection closed by client"); break; } buf.setLength(0); // first time, set a timeout try { Socket sock = clientSocketChannel.socket(); ReadLine.readLine(sock, buf, gotFirstLine ? 0 : FIRST_READ_TIMEOUT); sock.setSoTimeout(0); } catch (SocketTimeoutException ste) { writeString("SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"command timeout, bye\"\n"); break; } msg = buf.toString(); if (_log.shouldLog(Log.DEBUG)) { _log.debug("New message received: [" + msg + ']'); } props = SAMUtils.parseParams(msg); domain = (String) props.remove(SAMUtils.COMMAND); if (domain == null) { if (_log.shouldLog(Log.DEBUG)) _log.debug("Ignoring newline"); continue; } opcode = (String) props.remove(SAMUtils.OPCODE); if (opcode == null) { if (_log.shouldLog(Log.DEBUG)) _log.debug("Error in message format"); break; } if (_log.shouldLog(Log.DEBUG)) { _log.debug("Parsing (domain: \"" + domain + "\"; opcode: \"" + opcode + "\")"); } gotFirstLine = true; if (domain.equals("STREAM")) { canContinue = execStreamMessage(opcode, props); } else if (domain.equals("DATAGRAM")) { canContinue = execDatagramMessage(opcode, props); } else if (domain.equals("RAW")) { canContinue = execRawMessage(opcode, props); } else if (domain.equals("SESSION")) { if (i2cpProps != null) props.putAll(i2cpProps); // make sure we've got the i2cp settings canContinue = execSessionMessage(opcode, props); } else if (domain.equals("DEST")) { canContinue = execDestMessage(opcode, props); } else if (domain.equals("NAMING")) { canContinue = execNamingMessage(opcode, props); } else { if (_log.shouldLog(Log.DEBUG)) _log.debug("Unrecognized message domain: \"" + domain + "\""); break; } if (!canContinue) { break; } } } catch (IOException e) { if (_log.shouldLog(Log.DEBUG)) _log.debug("Caught IOException for message [" + msg + "]", e); } catch (SAMException e) { _log.error("Unexpected exception for message [" + msg + "]", e); } catch (RuntimeException e) { _log.error("Unexpected exception for message [" + msg + "]", e); } finally { if (_log.shouldLog(Log.DEBUG)) _log.debug("Stopping handler"); try { closeClientSocket(); } catch (IOException e) { if (_log.shouldWarn()) _log.warn("Error closing socket", e); } if (getRawSession() != null) { getRawSession().close(); } if (getDatagramSession() != null) { getDatagramSession().close(); } if (getStreamSession() != null) { getStreamSession().close(); } } }
/** * Return the right SAM handler depending on the protocol version required by the client. * * @param s Socket attached to SAM client * @param i2cpProps config options for our i2cp connection * @throws SAMException if the connection handshake (HELLO message) was malformed * @return A SAM protocol handler, or null if the client closed before the handshake */ public static SAMHandler createSAMHandler(SocketChannel s, Properties i2cpProps, SAMBridge parent) throws SAMException { String line; Log log = I2PAppContext.getGlobalContext().logManager().getLog(SAMHandlerFactory.class); try { Socket sock = s.socket(); sock.setKeepAlive(true); StringBuilder buf = new StringBuilder(128); ReadLine.readLine(sock, buf, HELLO_TIMEOUT); sock.setSoTimeout(0); line = buf.toString(); } catch (SocketTimeoutException e) { throw new SAMException("Timeout waiting for HELLO VERSION", e); } catch (IOException e) { throw new SAMException("Error reading from socket", e); } catch (RuntimeException e) { throw new SAMException("Unexpected error", e); } if (log.shouldDebug()) log.debug("New message received: [" + line + ']'); // Message format: HELLO VERSION [MIN=v1] [MAX=v2] Properties props = SAMUtils.parseParams(line); if (!"HELLO".equals(props.remove(SAMUtils.COMMAND)) || !"VERSION".equals(props.remove(SAMUtils.OPCODE))) { throw new SAMException("Must start with HELLO VERSION"); } String minVer = props.getProperty("MIN"); if (minVer == null) { // throw new SAMException("Missing MIN parameter in HELLO VERSION message"); // MIN optional as of 0.9.14 minVer = "1"; } String maxVer = props.getProperty("MAX"); if (maxVer == null) { // throw new SAMException("Missing MAX parameter in HELLO VERSION message"); // MAX optional as of 0.9.14 maxVer = "99.99"; } String ver = chooseBestVersion(minVer, maxVer); if (ver == null) { SAMHandler.writeString("HELLO REPLY RESULT=NOVERSION\n", s); return null; } if (Boolean.parseBoolean(i2cpProps.getProperty(SAMBridge.PROP_AUTH))) { String user = props.getProperty("USER"); String pw = props.getProperty("PASSWORD"); if (user == null || pw == null) throw new SAMException("USER and PASSWORD required"); String savedPW = i2cpProps.getProperty(SAMBridge.PROP_PW_PREFIX + user + SAMBridge.PROP_PW_SUFFIX); if (savedPW == null) throw new SAMException("Authorization failed"); PasswordManager pm = new PasswordManager(I2PAppContext.getGlobalContext()); if (!pm.checkHash(savedPW, pw)) throw new SAMException("Authorization failed"); } // Let's answer positively if (!SAMHandler.writeString("HELLO REPLY RESULT=OK VERSION=" + ver + "\n", s)) throw new SAMException("Error writing to socket"); // ...and instantiate the right SAM handler int verMajor = getMajor(ver); int verMinor = getMinor(ver); SAMHandler handler; try { switch (verMajor) { case 1: handler = new SAMv1Handler(s, verMajor, verMinor, i2cpProps, parent); break; case 2: handler = new SAMv2Handler(s, verMajor, verMinor, i2cpProps, parent); break; case 3: handler = new SAMv3Handler(s, verMajor, verMinor, i2cpProps, parent); break; default: log.error("BUG! Trying to initialize the wrong SAM version!"); throw new SAMException("BUG! (in handler instantiation)"); } } catch (IOException e) { log.error("Error creating the handler for version " + verMajor, e); throw new SAMException("IOException caught during SAM handler instantiation"); } return handler; }
/** @return the base deletion quality or null if read doesn't have one */ public static byte[] getExistingBaseDeletionQualities(final GATKRead read) { return SAMUtils.fastqToPhred(read.getAttributeAsString(BQSR_BASE_DELETION_QUALITIES)); }
public static void setDeletionBaseQualities(final GATKRead read, final byte[] quals) { read.setAttribute( BQSR_BASE_DELETION_QUALITIES, quals == null ? null : SAMUtils.phredToFastq(quals)); }
/** @return the base deletion quality or null if read doesn't have one */ public byte[] getExistingBaseDeletionQualities() { return SAMUtils.fastqToPhred(getStringAttribute(BQSR_BASE_DELETION_QUALITIES)); }
public void handle() { String msg = null; String domain = null; String opcode = null; boolean canContinue = false; StringTokenizer tok; Properties props; this.thread.setName("SAMv3Handler " + _id); _log.debug("SAM handling started"); try { InputStream in = getClientSocket().socket().getInputStream(); while (true) { if (shouldStop()) { _log.debug("Stop request found"); break; } String line = DataHelper.readLine(in); if (line == null) { _log.debug("Connection closed by client (line read : null)"); break; } msg = line.trim(); if (_log.shouldLog(Log.DEBUG)) { _log.debug("New message received: [" + msg + "]"); } if (msg.equals("")) { _log.debug("Ignoring newline"); continue; } tok = new StringTokenizer(msg, " "); if (tok.countTokens() < 2) { // This is not a correct message, for sure _log.debug("Error in message format"); break; } domain = tok.nextToken(); opcode = tok.nextToken(); if (_log.shouldLog(Log.DEBUG)) { _log.debug("Parsing (domain: \"" + domain + "\"; opcode: \"" + opcode + "\")"); } props = SAMUtils.parseParams(tok); if (domain.equals("STREAM")) { canContinue = execStreamMessage(opcode, props); } else if (domain.equals("SESSION")) { if (i2cpProps != null) props.putAll(i2cpProps); // make sure we've got the i2cp settings canContinue = execSessionMessage(opcode, props); } else if (domain.equals("DEST")) { canContinue = execDestMessage(opcode, props); } else if (domain.equals("NAMING")) { canContinue = execNamingMessage(opcode, props); } else if (domain.equals("DATAGRAM")) { canContinue = execDatagramMessage(opcode, props); } else { _log.debug("Unrecognized message domain: \"" + domain + "\""); break; } if (!canContinue) { break; } } } catch (IOException e) { _log.debug("Caught IOException (" + e.getMessage() + ") for message [" + msg + "]", e); } catch (Exception e) { _log.error("Unexpected exception for message [" + msg + "]", e); } finally { _log.debug("Stopping handler"); if (!this.stolenSocket) { try { closeClientSocket(); } catch (IOException e) { _log.error("Error closing socket: " + e.getMessage()); } } if (streamForwardingSocket) { if (this.getStreamSession() != null) { try { this.streamSession.stopForwardingIncoming(); } catch (SAMException e) { _log.error("Error while stopping forwarding connections: " + e.getMessage()); } catch (InterruptedIOException e) { _log.error("Interrupted while stopping forwarding connections: " + e.getMessage()); } } } die(); } }
/* Parse and execute a SESSION message */ @Override protected boolean execSessionMessage(String opcode, Properties props) { String dest = "BUG!"; String nick = null; boolean ok = false; try { if (opcode.equals("CREATE")) { if ((this.getRawSession() != null) || (this.getDatagramSession() != null) || (this.getStreamSession() != null)) { _log.debug("Trying to create a session, but one still exists"); return writeString( "SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"Session already exists\"\n"); } if (props == null) { _log.debug("No parameters specified in SESSION CREATE message"); return writeString( "SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"No parameters for SESSION CREATE\"\n"); } dest = props.getProperty("DESTINATION"); if (dest == null) { _log.debug("SESSION DESTINATION parameter not specified"); return writeString( "SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"DESTINATION not specified\"\n"); } props.remove("DESTINATION"); if (dest.equals("TRANSIENT")) { _log.debug("TRANSIENT destination requested"); ByteArrayOutputStream priv = new ByteArrayOutputStream(640); SAMUtils.genRandomKey(priv, null); dest = Base64.encode(priv.toByteArray()); } else { _log.debug("Custom destination specified [" + dest + "]"); } try { SAMUtils.checkPrivateDestination(dest); } catch (SAMUtils.InvalidDestination e) { return writeString("SESSION STATUS RESULT=INVALID_KEY\n"); } nick = props.getProperty("ID"); if (nick == null) { _log.debug("SESSION ID parameter not specified"); return writeString("SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"ID not specified\"\n"); } props.remove("ID"); String style = props.getProperty("STYLE"); if (style == null) { _log.debug("SESSION STYLE parameter not specified"); return writeString( "SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"No SESSION STYLE specified\"\n"); } props.remove("STYLE"); // Unconditionally override what the client may have set // (iMule sets BestEffort) as None is more efficient // and the client has no way to access delivery notifications i2cpProps.setProperty(I2PClient.PROP_RELIABILITY, I2PClient.PROP_RELIABILITY_NONE); // Record the session in the database sSessionsHash Properties allProps = new Properties(); allProps.putAll(i2cpProps); allProps.putAll(props); try { sSessionsHash.put(nick, new SessionRecord(dest, allProps, this)); } catch (SessionsDB.ExistingId e) { _log.debug("SESSION ID parameter already in use"); return writeString("SESSION STATUS RESULT=DUPLICATED_ID\n"); } catch (SessionsDB.ExistingDest e) { return writeString("SESSION STATUS RESULT=DUPLICATED_DEST\n"); } // Create the session if (style.equals("RAW")) { DatagramServer.getInstance(i2cpProps); rawSession = newSAMRawSession(nick); this.session = rawSession; } else if (style.equals("DATAGRAM")) { DatagramServer.getInstance(i2cpProps); datagramSession = newSAMDatagramSession(nick); this.session = datagramSession; } else if (style.equals("STREAM")) { streamSession = newSAMStreamSession(nick); this.session = streamSession; } else { _log.debug("Unrecognized SESSION STYLE: \"" + style + "\""); return writeString( "SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"Unrecognized SESSION STYLE\"\n"); } ok = true; return writeString("SESSION STATUS RESULT=OK DESTINATION=" + dest + "\n"); } else { _log.debug("Unrecognized SESSION message opcode: \"" + opcode + "\""); return writeString("SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"Unrecognized opcode\"\n"); } } catch (DataFormatException e) { _log.debug("Invalid destination specified"); return writeString( "SESSION STATUS RESULT=INVALID_KEY DESTINATION=" + dest + " MESSAGE=\"" + e.getMessage() + "\"\n"); } catch (I2PSessionException e) { _log.debug("I2P error when instantiating session", e); return writeString( "SESSION STATUS RESULT=I2P_ERROR DESTINATION=" + dest + " MESSAGE=\"" + e.getMessage() + "\"\n"); } catch (SAMException e) { _log.info("Funny SAM error", e); return writeString( "SESSION STATUS RESULT=I2P_ERROR DESTINATION=" + dest + " MESSAGE=\"" + e.getMessage() + "\"\n"); } catch (IOException e) { _log.error("Unexpected IOException", e); return writeString( "SESSION STATUS RESULT=I2P_ERROR DESTINATION=" + dest + " MESSAGE=\"" + e.getMessage() + "\"\n"); } finally { // unregister the session if it has not been created if (!ok && nick != null) { sSessionsHash.del(nick); session = null; } } }