/**
   * 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;
  }
Exemple #2
0
 /**
  * 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());
 }
Exemple #3
0
 /**
  * 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;
 }
Exemple #4
0
  /**
   * 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());
  }
Exemple #8
0
  /* 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;
    }
  }
Exemple #10
0
  /* 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;
    }
  }
Exemple #11
0
  /* 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");
    }
  }
Exemple #12
0
  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;
  }
Exemple #14
0
 /** @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));
 }
Exemple #15
0
 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;
      }
    }
  }