private void clearStates() { if (_sendingStates == null) // only null if overridden by I2PSimpleSession return; synchronized (_sendingStates) { for (Iterator iter = _sendingStates.iterator(); iter.hasNext(); ) { MessageState state = (MessageState) iter.next(); state.cancel(); } if (_log.shouldLog(Log.INFO)) _log.info(getPrefix() + "Disconnecting " + _sendingStates.size() + " states"); _sendingStates.clear(); } }
@Override public void write(char[] buffer, int offset, int length) { if (!_state.isActive()) throw new IllegalStateException(String.valueOf(_state)); byte[] wsBuffer = _buffer; int wsOffset = _offset; for (; length > 0; length--) { if (wsBuffer.length <= wsOffset + 2) { _offset = wsOffset; complete(false); wsOffset = _offset; } char ch = buffer[offset++]; if (ch < 0x80) wsBuffer[wsOffset++] = (byte) ch; else if (ch < 0x800) { wsBuffer[wsOffset++] = (byte) (0xc0 + (ch >> 6)); wsBuffer[wsOffset++] = (byte) (0x80 + (ch & 0x3f)); } else if (0xd800 <= ch && ch <= 0xdbff) { _savedPair = ch; } else if (0xdc00 <= ch && ch <= 0xdfff) { int cp = ((_savedPair & 0x3ff) << 10) + (ch & 0x3ff); _savedPair = 0; if (buffer.length <= _offset + 4) { complete(false); } cp += 0x10000; wsBuffer[wsOffset++] = (byte) (0xf0 + (cp >> 18)); wsBuffer[wsOffset++] = (byte) (0x80 + ((cp >> 12) & 0x3f)); wsBuffer[wsOffset++] = (byte) (0x80 + ((cp >> 6) & 0x3f)); wsBuffer[wsOffset++] = (byte) (0x80 + (cp & 0x3f)); } else { wsBuffer[wsOffset++] = (byte) (0xe0 + (ch >> 12)); wsBuffer[wsOffset++] = (byte) (0x80 + ((ch >> 6) & 0x3f)); wsBuffer[wsOffset++] = (byte) (0x80 + (ch & 0x3f)); } } _offset = wsOffset; }
@Override public void write(int ch) { if (!_state.isActive()) throw new IllegalStateException(String.valueOf(_state)); byte[] buffer = _buffer; if (_offset == buffer.length) { complete(false); } if (ch < 0x80) buffer[_offset++] = (byte) ch; else if (ch < 0x800) { if (buffer.length <= _offset + 1) { complete(false); } buffer[_offset++] = (byte) (0xc0 + (ch >> 6)); buffer[_offset++] = (byte) (0x80 + (ch & 0x3f)); } else if (0xd800 <= ch && ch <= 0xdbff) { _savedPair = (char) ch; } else if (0xdc00 <= ch && ch <= 0xdfff) { int cp = ((_savedPair & 0x3ff) << 10) + (ch & 0x3ff); _savedPair = 0; if (buffer.length <= _offset + 4) { complete(false); } cp += 0x10000; buffer[_offset++] = (byte) (0xf0 + (cp >> 18)); buffer[_offset++] = (byte) (0x80 + ((cp >> 12) & 0x3f)); buffer[_offset++] = (byte) (0x80 + ((cp >> 6) & 0x3f)); buffer[_offset++] = (byte) (0x80 + (cp & 0x3f)); } else { if (buffer.length <= _offset + 2) { complete(false); } buffer[_offset++] = (byte) (0xe0 + (ch >> 12)); buffer[_offset++] = (byte) (0x80 + ((ch >> 6) & 0x3f)); buffer[_offset++] = (byte) (0x80 + (ch & 0x3f)); } }
/** * Only call this with nonzero status, i.e. for outbound messages whose MessageState may be queued * on _sendingStates. * * <p>Even when using sendBestEffort(), this is a waste, because the MessageState is removed from * _sendingStates immediately and so the lookup here fails. And iterating through the HashSet * instead of having a map is bad too. * * <p>This is now pretty much avoided since streaming now sets i2cp.messageReliability = none, * which forces sendNoEffort() instead of sendBestEffort(), so the router won't send us any MSM's * for outbound traffic. * * @param status != 0 */ @Override public void receiveStatus(int msgId, long nonce, int status) { if (_log.shouldLog(Log.DEBUG)) _log.debug(getPrefix() + "Received status " + status + " for msgId " + msgId + " / " + nonce); MessageState state = null; long beforeSync = _context.clock().now(); long inSync = 0; synchronized (_sendingStates) { inSync = _context.clock().now(); for (Iterator iter = _sendingStates.iterator(); iter.hasNext(); ) { state = (MessageState) iter.next(); if (_log.shouldLog(Log.DEBUG)) _log.debug(getPrefix() + "State " + state.getMessageId() + " / " + state.getNonce()); if (state.getNonce() == nonce) { if (_log.shouldLog(Log.DEBUG)) _log.debug(getPrefix() + "Found a matching state"); break; } else if ((state.getMessageId() != null) && (state.getMessageId().getMessageId() == msgId)) { if (_log.shouldLog(Log.DEBUG)) _log.debug(getPrefix() + "Found a matching state by msgId"); break; } else { if (_log.shouldLog(Log.DEBUG)) _log.debug(getPrefix() + "State does not match"); state = null; } } } long afterSync = _context.clock().now(); if (_log.shouldLog(Log.DEBUG)) _log.debug( "receiveStatus(" + msgId + ", " + nonce + ", " + status + "): sync: " + (inSync - beforeSync) + "ms, check: " + (afterSync - inSync)); if (state != null) { if (state.getMessageId() == null) { MessageId id = new MessageId(); id.setMessageId(msgId); state.setMessageId(id); } state.receive(status); long lifetime = state.getElapsed(); switch (status) { case 1: _context.statManager().addRateData("i2cp.receiveStatusTime.1", lifetime, 0); break; // best effort codes unused // case 2: // _context.statManager().addRateData("i2cp.receiveStatusTime.2", lifetime, 0); // break; // case 3: // _context.statManager().addRateData("i2cp.receiveStatusTime.3", lifetime, 0); // break; case 4: _context.statManager().addRateData("i2cp.receiveStatusTime.4", lifetime, 0); break; case 5: _context.statManager().addRateData("i2cp.receiveStatusTime.5", lifetime, 0); break; } } else { if (_log.shouldLog(Log.INFO)) _log.info( getPrefix() + "No matching state for messageId " + msgId + " / " + nonce + " w/ status = " + status); } _context .statManager() .addRateData("i2cp.receiveStatusTime", _context.clock().now() - beforeSync, 0); }
/** * TODO - Don't need to save MessageState since actuallyWait is false... But for now just use * sendNoEffort() instead. * * @param flags to be passed to the router * @since 0.8.4 */ protected boolean sendBestEffort(Destination dest, byte payload[], long expires, int flags) throws I2PSessionException { // SessionKey key = null; // SessionKey newKey = null; // SessionTag tag = null; // Set sentTags = null; // int oldTags = 0; long begin = _context.clock().now(); /** * ********* if (I2CPMessageProducer.END_TO_END_CRYPTO) { if (_log.shouldLog(Log.DEBUG)) * _log.debug("begin sendBestEffort"); key = * _context.sessionKeyManager().getCurrentKey(dest.getPublicKey()); if * (_log.shouldLog(Log.DEBUG)) _log.debug("key fetched"); if (key == null) key = * _context.sessionKeyManager().createSession(dest.getPublicKey()); tag = * _context.sessionKeyManager().consumeNextAvailableTag(dest.getPublicKey(), key); if * (_log.shouldLog(Log.DEBUG)) _log.debug("tag consumed"); sentTags = null; oldTags = * _context.sessionKeyManager().getAvailableTags(dest.getPublicKey(), key); long availTimeLeft = * _context.sessionKeyManager().getAvailableTimeLeft(dest.getPublicKey(), key); * * <p>if ( (tagsSent == null) || (tagsSent.isEmpty()) ) { if (oldTags < NUM_TAGS) { sentTags = * createNewTags(NUM_TAGS); if (_log.shouldLog(Log.DEBUG)) _log.debug("** sendBestEffort only * had " + oldTags + " with " + availTimeLeft + ", adding " + NUM_TAGS + ": " + sentTags); } * else if (availTimeLeft < 2 * 60 * 1000) { // if we have > 50 tags, but they expire in under 2 * minutes, we want more sentTags = createNewTags(NUM_TAGS); if (_log.shouldLog(Log.DEBUG)) * _log.debug(getPrefix() + "Tags expiring in " + availTimeLeft + ", adding " + NUM_TAGS + " new * ones: " + sentTags); //_log.error("** sendBestEffort available time left " + availTimeLeft); * } else { if (_log.shouldLog(Log.DEBUG)) _log.debug("sendBestEffort old tags: " + oldTags + " * available time left: " + availTimeLeft); } } else { if (_log.shouldLog(Log.DEBUG)) * _log.debug("sendBestEffort is sending " + tagsSent.size() + " with " + availTimeLeft + "ms * left, " + oldTags + " tags known and " + (tag == null ? "no tag" : " a valid tag")); } * * <p>if (false) // rekey newKey = _context.keyGenerator().generateSessionKey(); * * <p>if ( (tagsSent != null) && (!tagsSent.isEmpty()) ) { if (sentTags == null) sentTags = new * HashSet(); sentTags.addAll(tagsSent); } } else { // not using end to end crypto, so don't * ever bundle any tags } ******** */ // if (_log.shouldLog(Log.DEBUG)) _log.debug("before creating nonce"); long nonce = _context.random().nextInt(Integer.MAX_VALUE - 1) + 1; // if (_log.shouldLog(Log.DEBUG)) _log.debug("before sync state"); MessageState state = new MessageState(_context, nonce, getPrefix()); // state.setKey(key); // state.setTags(sentTags); // state.setNewKey(newKey); state.setTo(dest); // if (_log.shouldLog(Log.DEBUG)) _log.debug(getPrefix() + "Setting key = " + key); // if (keyUsed != null) { // if (I2CPMessageProducer.END_TO_END_CRYPTO) { // if (newKey != null) // keyUsed.setData(newKey.getData()); // else // keyUsed.setData(key.getData()); // } else { // keyUsed.setData(SessionKey.INVALID_KEY.getData()); // } // } // if (tagsSent != null) { // if (sentTags != null) { // tagsSent.addAll(sentTags); // } // } // if (_log.shouldLog(Log.DEBUG)) _log.debug("before sync state"); long beforeSendingSync = _context.clock().now(); long inSendingSync = 0; synchronized (_sendingStates) { inSendingSync = _context.clock().now(); _sendingStates.add(state); } long afterSendingSync = _context.clock().now(); if (_log.shouldLog(Log.DEBUG)) _log.debug( getPrefix() + "Adding sending state " + state.getMessageId() + " / " + state.getNonce() + " for best effort " + " sync took " + (inSendingSync - beforeSendingSync) + " add took " + (afterSendingSync - inSendingSync)); // _producer.sendMessage(this, dest, nonce, payload, tag, key, sentTags, newKey, expires); _producer.sendMessage(this, dest, nonce, payload, expires, flags); // since this is 'best effort', all we're waiting for is a status update // saying that the router received it - in theory, that should come back // immediately, but in practice can take up to a second (though usually // much quicker). setting this to false will short-circuit that delay boolean actuallyWait = false; // true; long beforeWaitFor = _context.clock().now(); if (actuallyWait) state.waitFor( MessageStatusMessage.STATUS_SEND_ACCEPTED, _context.clock().now() + getTimeout()); // long afterWaitFor = _context.clock().now(); // long inRemovingSync = 0; synchronized (_sendingStates) { // inRemovingSync = _context.clock().now(); _sendingStates.remove(state); } long afterRemovingSync = _context.clock().now(); boolean found = !actuallyWait || state.received(MessageStatusMessage.STATUS_SEND_ACCEPTED); if (_log.shouldLog(Log.DEBUG)) _log.debug( getPrefix() + "After waitFor sending state " + state.getMessageId() + " / " + state.getNonce() + " found = " + found); long timeToSend = afterRemovingSync - beforeSendingSync; if ((timeToSend > 10 * 1000) && (_log.shouldLog(Log.WARN))) { _log.warn("wtf, took " + timeToSend + "ms to send the message?!", new Exception("baz")); } if ((afterRemovingSync - begin > 500) && (_log.shouldLog(Log.WARN))) { _log.warn( "Took " + (afterRemovingSync - begin) + "ms to sendBestEffort, " + (afterSendingSync - begin) + "ms to prepare, " + (beforeWaitFor - afterSendingSync) + "ms to send, " + (afterRemovingSync - beforeWaitFor) + "ms waiting for reply"); } _context .statManager() .addRateData("i2cp.sendBestEffortTotalTime", afterRemovingSync - begin, 0); // _context.statManager().addRateData("i2cp.sendBestEffortStage0", beforeSendingSync- begin, 0); // _context.statManager().addRateData("i2cp.sendBestEffortStage1", afterSendingSync- // beforeSendingSync, 0); // _context.statManager().addRateData("i2cp.sendBestEffortStage2", beforeWaitFor- // afterSendingSync, 0); // _context.statManager().addRateData("i2cp.sendBestEffortStage3", afterWaitFor- beforeWaitFor, // 0); // _context.statManager().addRateData("i2cp.sendBestEffortStage4", afterRemovingSync- // afterWaitFor, 0); if (found) { if (_log.shouldLog(Log.INFO)) _log.info( getPrefix() + "Message sent after " + state.getElapsed() + "ms with " + payload.length + " bytes"); } else { if (_log.shouldLog(Log.INFO)) _log.info( getPrefix() + "Message send failed after " + state.getElapsed() + "ms with " + payload.length + " bytes"); if (_log.shouldLog(Log.ERROR)) _log.error( getPrefix() + "Never received *accepted* from the router! dropping and reconnecting"); disconnect(); return false; } return found; }
/** * Get the current state of the message * * @return {@link MessageState} */ public MessageState getMsgState() { // HACK: The "toUpperCase()" call is to make the change backward compatible return MessageState.valueOf( _record.getSimpleField(Attributes.MSG_STATE.toString()).toUpperCase()); }
/** * Set the current state of the message * * @param msgState {@link MessageState} */ public void setMsgState(MessageState msgState) { // HACK: The "tolowerCase()" call is to make the // change backward compatible _record.setSimpleField(Attributes.MSG_STATE.toString(), msgState.toString().toLowerCase()); }