SSU2: Fragmented Session Confirmed

Revert related parts of "Prep for fragmented RI",
we are now fragmenting Session Confirmed instead.
Fragment and send multiple Session Confirmed packets if required
Reassemble Session Confirmed packets

Don't process ack block identical to previous received
Log tweaks
bump -12
This commit is contained in:
zzz
2022-04-05 09:26:42 -04:00
parent 8418bda5a5
commit cc85df19aa
7 changed files with 261 additions and 87 deletions

View File

@@ -1,3 +1,6 @@
2022-04-05 zzz
* SSU2: Fragmented Session Confirmed
2022-03-27 zzz
* Crypto: Fix CertUtil loading EdDSA certs, check sigs
* Router: Validate family sig at startup

View File

@@ -18,7 +18,7 @@ public class RouterVersion {
/** deprecated */
public final static String ID = "Git";
public final static String VERSION = CoreVersion.VERSION;
public final static long BUILD = 11;
public final static long BUILD = 12;
/** for example "-test" */
public final static String EXTRA = "";

View File

@@ -1100,6 +1100,7 @@ class EstablishmentManager {
_context.statManager().addRateData("udp.outboundEstablishTime", state.getLifetime());
DatabaseStoreMessage dbsm = null;
if (version == 1) {
// version 2 sends our RI in handshake
if (!state.isFirstMessageOurDSM()) {
dbsm = getOurInfo();
}
@@ -1563,8 +1564,13 @@ class EstablishmentManager {
sendCreated(inboundState);
break;
case IB_STATE_CREATED_SENT: // fallthrough
// SSU2 only in practice, should only get here if expired
case IB_STATE_CONFIRMED_PARTIALLY:
if (expired)
processExpired(inboundState);
break;
case IB_STATE_CREATED_SENT: // fallthrough
case IB_STATE_RETRY_SENT: // SSU2
if (expired) {
sendDestroy(inboundState);

View File

@@ -52,6 +52,7 @@ class InboundEstablishState2 extends InboundEstablishState implements SSU2Payloa
private byte[] _sendHeaderEncryptKey2;
private byte[] _rcvHeaderEncryptKey2;
private byte[] _sessCrForReTX;
private byte[][] _sessConfFragments;
private long _timeReceived;
// not adjusted for RTT
private long _skew;
@@ -311,8 +312,8 @@ class InboundEstablishState2 extends InboundEstablishState implements SSU2Payloa
public void gotRIFragment(byte[] data, boolean isHandshake, boolean flood, boolean isGzipped, int frag, int totalFrags) {
if (_log.shouldDebug())
_log.debug("Got RI fragment " + frag + " of " + totalFrags);
if (isHandshake)
throw new IllegalStateException("RI in Sess Req");
// not supported, we fragment the whole message now
throw new IllegalStateException("fragmented RI");
}
public void gotAddress(byte[] ip, int port) {
@@ -521,12 +522,16 @@ class InboundEstablishState2 extends InboundEstablishState implements SSU2Payloa
}
/**
* Receive the last message in the handshake, and create the PeerState.
* Receive the last messages in the handshake, and create the PeerState.
* If the message is fragmented, store the data for reassembly and return,
* unless this was the last one.
*
* @return the new PeerState2, may also be retrieved from getPeerState()
* @return the new PeerState2 if are done, may also be retrieved from getPeerState(),
* or null if more fragments to go
*/
public synchronized PeerState2 receiveSessionConfirmed(UDPPacket packet) throws GeneralSecurityException {
if (_currentState != InboundState.IB_STATE_CREATED_SENT)
if (_currentState != InboundState.IB_STATE_CREATED_SENT &&
_currentState != InboundState.IB_STATE_CONFIRMED_PARTIALLY)
throw new GeneralSecurityException("Bad state for Session Confirmed: " + _currentState);
DatagramPacket pkt = packet.getPacket();
SocketAddress from = pkt.getSocketAddress();
@@ -538,7 +543,74 @@ class InboundEstablishState2 extends InboundEstablishState implements SSU2Payloa
long rid = DataHelper.fromLong8(data, off);
if (rid != _rcvConnID)
throw new GeneralSecurityException("Conn ID mismatch: req: " + _rcvConnID + " conf: " + rid);
_handshakeState.mixHash(data, off, 16);
byte fragbyte = data[off + SHORT_HEADER_FLAGS_OFFSET];
int frag = (fragbyte >> 4) & 0x0f;
// allow both 0/0 (development) and 0/1 to indicate sole fragment
int totalfrag = fragbyte & 0x0f;
if (totalfrag > 0 && frag > totalfrag - 1)
throw new GeneralSecurityException("Bad sess conf fragment " + frag + " of " + totalfrag);
if (totalfrag > 1) {
// Fragment processing. Save fragment.
// If we have all fragments, reassemble and continue,
// else return to await more.
if (_sessConfFragments == null) {
_sessConfFragments = new byte[totalfrag][];
// change state so we will no longer retransmit session created
_currentState = InboundState.IB_STATE_CONFIRMED_PARTIALLY;
_sessCrForReTX = null;
// force past expiration, we don't have anything to send until we have everything
_nextSend = _lastSend + 60*1000;
} else {
if (_sessConfFragments.length != totalfrag) // total frag changed
throw new GeneralSecurityException("Bad sess conf fragment " + frag + " of " + totalfrag);
if (_sessConfFragments[frag] != null) {
if (_log.shouldWarn())
_log.warn("Got dup sess conf frag " + frag + " on " + this);
// there is no facility to ack individual fragments
//packetReceived();
return null;
}
}
if (_log.shouldWarn())
_log.warn("Got sess conf frag " + frag + " len " + len + " on " + this);
byte[] fragdata;
if (frag == 0) {
// preserve header
fragdata = new byte[len];
System.arraycopy(data, off, fragdata, 0, len);
} else {
// discard header
len -= SHORT_HEADER_SIZE;
fragdata = new byte[len];
System.arraycopy(data, off + SHORT_HEADER_SIZE, fragdata, 0, len);
}
_sessConfFragments[frag] = fragdata;
int totalsize = 0;
for (int i = 0; i < totalfrag; i++) {
if (_sessConfFragments[i] == null) {
if (_log.shouldWarn())
_log.warn("Still missing at least one sess conf frag on " + this);
// there is no facility to ack individual fragments
//packetReceived();
return null;
}
totalsize += _sessConfFragments[i].length;
}
// we have all the fragments
// make a jumbo packet and process it through noise
len = totalsize;
off = 0;
data = new byte[len];
int joff = 0;
for (int i = 0; i < totalfrag; i++) {
byte[] f = _sessConfFragments[i];
System.arraycopy(f, 0, data, joff, f.length);
joff += f.length;
}
if (_log.shouldWarn())
_log.warn("Have all " + totalfrag + " sess conf frags, total length " + len + " on " + this);
}
_handshakeState.mixHash(data, off, SHORT_HEADER_SIZE);
//if (_log.shouldDebug())
// _log.debug("State after mixHash 3: " + _handshakeState);
@@ -637,7 +709,7 @@ class InboundEstablishState2 extends InboundEstablishState implements SSU2Payloa
}
/**
* @return null if not sent or already got the session created
* @return null if not sent or already got the session confirmed
*/
public synchronized UDPPacket getRetransmitSessionCreatedPacket() {
if (_sessCrForReTX == null)

View File

@@ -52,7 +52,7 @@ class OutboundEstablishState2 extends OutboundEstablishState implements SSU2Payl
private final byte[] _rcvRetryHeaderEncryptKey2;
private int _mtu;
private byte[] _sessReqForReTX;
private byte[] _sessConfForReTX;
private byte[][] _sessConfForReTX;
private long _timeReceived;
// not adjusted for RTT
private long _skew;
@@ -383,8 +383,8 @@ class OutboundEstablishState2 extends OutboundEstablishState implements SSU2Payl
throw new GeneralSecurityException("Conn ID mismatch: 1: " + _sendConnID + " 2: " + sid);
_handshakeState.mixHash(data, off, LONG_HEADER_SIZE);
if (_log.shouldDebug())
_log.debug("State after mixHash 2: " + _handshakeState);
//if (_log.shouldDebug())
// _log.debug("State after mixHash 2: " + _handshakeState);
// decrypt in-place
try {
@@ -394,8 +394,8 @@ class OutboundEstablishState2 extends OutboundEstablishState implements SSU2Payl
_log.debug("Session create error, State at failure: " + _handshakeState + '\n' + net.i2p.util.HexDump.dump(data, off, len), gse);
throw gse;
}
if (_log.shouldDebug())
_log.debug("State after sess cr: " + _handshakeState);
//if (_log.shouldDebug())
// _log.debug("State after sess cr: " + _handshakeState);
_timeReceived = 0;
processPayload(data, off + LONG_HEADER_SIZE, len - (LONG_HEADER_SIZE + KEY_LEN + MAC_LEN), true);
packetReceived();
@@ -462,20 +462,21 @@ class OutboundEstablishState2 extends OutboundEstablishState implements SSU2Payl
* note that we just sent the SessionConfirmed packets
* and save them for retransmission
*
* @param riFrags if non-null, the RI was fragmented, and these are the
* remaining fragments to be sent in the PeerState.
* @return the new PeerState2, may also be retrieved from getPeerState()
*/
public synchronized PeerState2 confirmedPacketSent(UDPPacket packet, List<SSU2Payload.RIBlock> riFrags) {
public synchronized PeerState2 confirmedPacketsSent(UDPPacket[] packets) {
if (_sessConfForReTX == null) {
// store pkt for retx
// only one supported right now
DatagramPacket pkt = packet.getPacket();
byte data[] = pkt.getData();
int off = pkt.getOffset();
int len = pkt.getLength();
_sessConfForReTX = new byte[len];
System.arraycopy(data, off, _sessConfForReTX, 0, len);
// store pkts for retx
_sessConfForReTX = new byte[packets.length][];
for (int i = 0; i < packets.length; i++) {
DatagramPacket pkt = packets[i].getPacket();
byte data[] = pkt.getData();
int off = pkt.getOffset();
int len = pkt.getLength();
byte[] save = new byte[len];
System.arraycopy(data, off, save, 0, len);
_sessConfForReTX[i] = save;
}
if (_rcvHeaderEncryptKey2 == null)
_rcvHeaderEncryptKey2 = SSU2Util.hkdf(_context, _handshakeState.getChainingKey(), "SessCreateHeader");
@@ -499,6 +500,7 @@ class OutboundEstablishState2 extends OutboundEstablishState implements SSU2Payl
sender.initializeKey(d_ab, 0);
ChaChaPolyCipherState rcvr = new ChaChaPolyCipherState();
rcvr.initializeKey(d_ba, 0);
/****
if (_log.shouldDebug())
_log.debug("split()\nGenerated Chain key: " + Base64.encode(ckd) +
"\nGenerated split key for A->B: " + Base64.encode(k_ab) +
@@ -509,6 +511,7 @@ class OutboundEstablishState2 extends OutboundEstablishState implements SSU2Payl
"\nIntro key for Bob: " + Base64.encode(_sendHeaderEncryptKey1) +
"\nGenerated header key 2 for A->B: " + Base64.encode(h_ab) +
"\nGenerated header key 2 for B->A: " + Base64.encode(h_ba));
****/
_handshakeState.destroy();
if (_requestSentCount == 1)
_rtt = (int) ( _context.clock().now() - _lastSend );
@@ -518,7 +521,7 @@ class OutboundEstablishState2 extends OutboundEstablishState implements SSU2Payl
_sendConnID, _rcvConnID,
_sendHeaderEncryptKey1, h_ab, h_ba);
_currentState = OutboundState.OB_STATE_CONFIRMED_COMPLETELY;
_pstate.confirmedPacketSent(_sessConfForReTX, riFrags);
_pstate.confirmedPacketsSent(_sessConfForReTX);
// PS2.super adds CLOCK_SKEW_FUDGE that doesn't apply here
_pstate.adjustClockSkew(_skew - (_rtt / 2) - PeerState.CLOCK_SKEW_FUDGE);
_pstate.setHisMTU(_mtu);

View File

@@ -490,40 +490,31 @@ class PacketBuilder2 {
len = info.length;
}
UDPPacket packets[] = new UDPPacket[1];
packets[0] = buildSessionConfirmedPacket(state, numFragments, info, len, gzip);
List<SSU2Payload.RIBlock> riFrags;
// one big block
SSU2Payload.RIBlock block = new SSU2Payload.RIBlock(info, 0, info.length,
false, gzip, 0, 1);
UDPPacket packets[];
if (numFragments > 1) {
riFrags = new ArrayList<SSU2Payload.RIBlock>(numFragments - 1);
int off = len;
for (int i = 1; i < numFragments; i++) {
if (i == numFragments - 1)
len = info.length - off;
SSU2Payload.RIBlock block = new SSU2Payload.RIBlock(info, off, len,
false, gzip, i, numFragments);
riFrags.add(block);
off += len;
}
packets = buildSessionConfirmedPackets(state, block);
} else {
riFrags = null;
packets = new UDPPacket[1];
packets[0] = buildSessionConfirmedPacket(state, block);
}
state.confirmedPacketSent(packets[0], riFrags);
state.confirmedPacketsSent(packets);
return packets;
}
/**
* Build a new SessionConfirmed packet for the given peer
* Build a single new SessionConfirmed packet for the given peer, unfragmented.
*
* @return ready to send packet, or null if there was a problem
*/
private UDPPacket buildSessionConfirmedPacket(OutboundEstablishState2 state, int numFragments, byte ourInfo[], int len, boolean gzip) {
private UDPPacket buildSessionConfirmedPacket(OutboundEstablishState2 state, SSU2Payload.RIBlock block) {
UDPPacket packet = buildShortPacketHeader(state.getSendConnID(), 0, SESSION_CONFIRMED_FLAG_BYTE);
DatagramPacket pkt = packet.getPacket();
pkt.setLength(SHORT_HEADER_SIZE);
SSU2Payload.RIBlock block = new SSU2Payload.RIBlock(ourInfo, 0, len,
false, gzip, 0, numFragments);
boolean isIPv6 = state.getSentIP().length == 16;
encryptSessionConfirmed(packet, state.getHandshakeState(), state.getMTU(), isIPv6,
encryptSessionConfirmed(packet, state.getHandshakeState(), state.getMTU(), 1, 0, isIPv6,
state.getSendHeaderEncryptKey1(), state.getSendHeaderEncryptKey2(), block, state.getNextToken());
pkt.setSocketAddress(state.getSentAddress());
packet.setMessageType(TYPE_CONF);
@@ -531,6 +522,103 @@ class PacketBuilder2 {
return packet;
}
/**
* Build all the fragmented SessionConfirmed packets
*
*/
private UDPPacket[] buildSessionConfirmedPackets(OutboundEstablishState2 state, SSU2Payload.RIBlock block) {
UDPPacket packet0 = buildShortPacketHeader(state.getSendConnID(), 0, SESSION_CONFIRMED_FLAG_BYTE);
DatagramPacket pkt = packet0.getPacket();
byte[] data0 = pkt.getData();
int off = pkt.getOffset();
boolean isIPv6 = state.getSentIP().length == 16;
// actually IP and UDP overhead
int ipOverhead = (isIPv6 ? IPV6_HEADER_SIZE : IP_HEADER_SIZE) + UDP_HEADER_SIZE;
// first packet, no new token block or padding
int overhead = ipOverhead +
SHORT_HEADER_SIZE + KEY_LEN + MAC_LEN + MAC_LEN;
int mtu = state.getMTU();
int blockSize = block.getTotalLength();
// how much of the ri block we can fit in the first packet
int first = mtu - overhead;
// how much of the ri block we can fit in additional packets
int maxAddl = mtu - (ipOverhead + SHORT_HEADER_SIZE);
// how much data fits in a packet
int max = mtu - ipOverhead;
int remaining = blockSize - first;
// ensure last packet isn't too small which would corrupt the header decryption
int lastPktSize = remaining % max;
int addPadding;
if (lastPktSize < 24) {
addPadding = 3 + 24 - lastPktSize;
remaining += addPadding;
} else {
addPadding = 0;
}
int count = 1 + ((remaining + maxAddl - 1) / maxAddl);
// put jumbo into the first packet, we will put data0 back below
// TODO if last packet is less than 8 bytes the header decryption will fail, add padding
byte[] jumbo = new byte[overhead + addPadding + block.getTotalLength()];
System.arraycopy(data0, off, jumbo, 0, SHORT_HEADER_SIZE);
pkt.setData(jumbo);
pkt.setLength(SHORT_HEADER_SIZE);
byte[] hdrKey1 = state.getSendHeaderEncryptKey1();
byte[] hdrKey2 = state.getSendHeaderEncryptKey2();
encryptSessionConfirmed(packet0, state.getHandshakeState(), state.getMTU(), count, addPadding, isIPv6,
hdrKey1, hdrKey2, block, state.getNextToken());
int total = pkt.getLength();
if (_log.shouldWarn())
_log.warn("Building " + count + " fragmented session confirmed packets" +
" max data: " + max +
" RI block size: " + blockSize +
" total data size: " + total);
// fix up packet0 by putting the byte array back
// and encrypting the header
System.arraycopy(jumbo, 0, data0, off, max);
pkt.setData(data0);
pkt.setLength(max);
pkt.setSocketAddress(state.getSentAddress());
SSU2Header.encryptShortHeader(packet0, hdrKey1, hdrKey2);
packet0.setMessageType(TYPE_CONF);
packet0.setPriority(PRIORITY_HIGH);
List<UDPPacket> rv = new ArrayList<UDPPacket>(4);
rv.add(packet0);
// build all the remaining packets
// set frag field in header and encrypt headers
// these headers are not bound to anything with mixHash(),
// but if anything changes the header will not decrypt correctly
int pktnum = 0;
for (int i = max; i < total; i += max - SHORT_HEADER_SIZE) {
// all packets have packet number 0
UDPPacket packet = buildShortPacketHeader(state.getSendConnID(), 0, SESSION_CONFIRMED_FLAG_BYTE);
pkt = packet.getPacket();
byte[] data = pkt.getData();
off = pkt.getOffset();
int len = Math.min(max - SHORT_HEADER_SIZE, total - i);
System.arraycopy(jumbo, i, data, off + SHORT_HEADER_SIZE, len);
data[off + SHORT_HEADER_FLAGS_OFFSET] = (byte) (((++pktnum) << 4) | count); // fragment n of numFragments
if (len < 24)
_log.error("FIXME " + len);
pkt.setLength(len + SHORT_HEADER_SIZE);
SSU2Header.encryptShortHeader(packet, hdrKey1, hdrKey2);
pkt.setSocketAddress(state.getSentAddress());
packet0.setMessageType(TYPE_CONF);
packet0.setPriority(PRIORITY_HIGH);
rv.add(packet);
}
if (_log.shouldInfo()) {
for (int i = 0; i < rv.size(); i++) {
_log.info("pkt " + i + " size " + rv.get(i).getPacket().getLength());
}
}
if (rv.size() != count)
throw new IllegalStateException("Count " + count + " != size " + rv.size());
return rv.toArray(new UDPPacket[count]);
}
/**
* Build a packet as Alice, to Bob to begin a peer test.
* In-session, message 1.
@@ -936,14 +1024,19 @@ class PacketBuilder2 {
}
/**
* If numFragments larger than 1, we do NOT encrypt the header here,
* that's caller's responsibility.
*
* @param packet containing only 16 byte header
* @param addPadding force-add exactly this size a padding block, for jumbo only
*/
private void encryptSessionConfirmed(UDPPacket packet, HandshakeState state, int mtu,
private void encryptSessionConfirmed(UDPPacket packet, HandshakeState state, int mtu, int numFragments, int addPadding,
boolean isIPv6, byte[] hdrKey1, byte[] hdrKey2,
SSU2Payload.RIBlock riblock, EstablishmentManager.Token token) {
DatagramPacket pkt = packet.getPacket();
byte data[] = pkt.getData();
int off = pkt.getOffset();
data[off + SHORT_HEADER_FLAGS_OFFSET] = (byte) numFragments; // fragment 0 of numFragments
mtu -= UDP_HEADER_SIZE;
mtu -= isIPv6 ? IPV6_HEADER_SIZE : IP_HEADER_SIZE;
try {
@@ -956,10 +1049,17 @@ class PacketBuilder2 {
len += block.getTotalLength();
blocks.add(block);
}
Block block = getPadding(len, mtu - (SHORT_HEADER_SIZE + KEY_LEN + MAC_LEN + MAC_LEN)); // 80
if (block != null) {
len += block.getTotalLength();
if (addPadding > 0) {
// forced padding so last packet isn't too small
Block block = new SSU2Payload.PaddingBlock(addPadding - 3);
len += addPadding;
blocks.add(block);
} else {
Block block = getPadding(len, mtu - (SHORT_HEADER_SIZE + KEY_LEN + MAC_LEN + MAC_LEN)); // 80
if (block != null) {
len += block.getTotalLength();
blocks.add(block);
}
}
// If we skip past where the static key and 1st MAC will be, we can
@@ -983,7 +1083,8 @@ class PacketBuilder2 {
}
if (_log.shouldDebug())
_log.debug("After msg 3: " + state);
SSU2Header.encryptShortHeader(packet, hdrKey1, hdrKey2);
if (numFragments <= 1)
SSU2Header.encryptShortHeader(packet, hdrKey1, hdrKey2);
}
/**

View File

@@ -43,6 +43,7 @@ public class PeerState2 extends PeerState implements SSU2Payload.PayloadCallback
private final long _sendConnID;
private final long _rcvConnID;
private final AtomicInteger _packetNumber = new AtomicInteger();
private final AtomicInteger _lastAckHashCode = new AtomicInteger(-1);
private final CipherState _sendCha;
private final CipherState _rcvCha;
private final byte[] _sendHeaderEncryptKey1;
@@ -60,8 +61,7 @@ public class PeerState2 extends PeerState implements SSU2Payload.PayloadCallback
private long _sentMessagesLastExpired;
// Session Confirmed retransmit
private byte[] _sessConfForReTX;
private List<SSU2Payload.RIBlock> _riFragsForReTX;
private byte[][] _sessConfForReTX;
private long _sessConfSentTime;
private int _sessConfSentCount;
@@ -525,10 +525,16 @@ public class PeerState2 extends PeerState implements SSU2Payload.PayloadCallback
}
public void gotACK(long ackThru, int acks, byte[] ranges) {
int hc = ((int) ackThru) ^ (acks << 24) ^ DataHelper.hashCode(ranges);
if (_lastAckHashCode.getAndSet(hc) == hc) {
if (_log.shouldDebug())
_log.debug("Got dup ACK block: " + SSU2Bitfield.toString(ackThru, acks, ranges, (ranges != null ? ranges.length / 2 : 0)));
return;
}
SSU2Bitfield ackbf;
ackbf = SSU2Bitfield.fromACKBlock(ackThru, acks, ranges, (ranges != null ? ranges.length / 2 : 0));
if (_log.shouldDebug())
_log.debug("Got ACK block: " + SSU2Bitfield.toString(ackThru, acks, ranges, (ranges != null ? ranges.length / 2 : 0)));
_log.debug("Got new ACK block: " + SSU2Bitfield.toString(ackThru, acks, ranges, (ranges != null ? ranges.length / 2 : 0)));
// calls bitSet() below
ackbf.forEachAndNot(_ackedMessages, this);
}
@@ -639,23 +645,13 @@ public class PeerState2 extends PeerState implements SSU2Payload.PayloadCallback
}
/**
* note that we just sent the SessionConfirmed packet
* and save it for retransmission.
* Note that we just sent the SessionConfirmed packets
* and save them for retransmission.
*
* @param riFrags if non-null, the RI was fragmented, and these are the
* remaining fragments to be sent and saved for retransmission.
*/
public synchronized void confirmedPacketSent(byte[] data, List<SSU2Payload.RIBlock> riFrags) {
synchronized void confirmedPacketsSent(byte[][] data) {
if (_sessConfForReTX == null)
_sessConfForReTX = data;
if (riFrags != null) {
if (_riFragsForReTX == null)
_riFragsForReTX = riFrags;
for (SSU2Payload.RIBlock block : riFrags) {
UDPPacket pkt = _transport.getBuilder2().buildPacket(Collections.emptyList(), Collections.singletonList(block), this);
_transport.send(pkt);
}
}
_sessConfSentTime = _context.clock().now();
_sessConfSentCount++;
}
@@ -666,26 +662,19 @@ public class PeerState2 extends PeerState implements SSU2Payload.PayloadCallback
private synchronized UDPPacket[] getRetransmitSessionConfirmedPackets() {
if (_sessConfForReTX == null)
return null;
UDPPacket packet = UDPPacket.acquire(_context, false);
int count = 1;
if (_riFragsForReTX != null)
count += _riFragsForReTX.size();
UDPPacket[] rv = new UDPPacket[count];
rv[0] = packet;
DatagramPacket pkt = packet.getPacket();
byte data[] = pkt.getData();
int off = pkt.getOffset();
System.arraycopy(_sessConfForReTX, 0, data, off, _sessConfForReTX.length);
pkt.setLength(_sessConfForReTX.length);
pkt.setAddress(_remoteIPAddress);
pkt.setPort(_remotePort);
packet.setMessageType(PacketBuilder2.TYPE_CONF);
packet.setPriority(PacketBuilder2.PRIORITY_HIGH);
if (_riFragsForReTX != null) {
int i = 1;
for (SSU2Payload.RIBlock block : _riFragsForReTX) {
rv[i++] = _transport.getBuilder2().buildPacket(Collections.emptyList(), Collections.singletonList(block), this);
}
UDPPacket[] rv = new UDPPacket[_sessConfForReTX.length];
for (int i = 0; i < rv.length; i++) {
UDPPacket packet = UDPPacket.acquire(_context, false);
rv[i] = packet;
DatagramPacket pkt = packet.getPacket();
byte data[] = pkt.getData();
int off = pkt.getOffset();
System.arraycopy(_sessConfForReTX[i], 0, data, off, _sessConfForReTX[i].length);
pkt.setLength(_sessConfForReTX.length);
pkt.setAddress(_remoteIPAddress);
pkt.setPort(_remotePort);
packet.setMessageType(PacketBuilder2.TYPE_CONF);
packet.setPriority(PacketBuilder2.PRIORITY_HIGH);
}
return rv;
}