public boolean accept(Stanza packet) { // sender equals remote peer if (!packet.getFrom().equalsIgnoreCase(remoteJID)) { return false; } DataPacketExtension data; if (packet instanceof Data) { data = ((Data) packet).getDataPacketExtension(); } else { // stanza contains data packet extension data = packet.getExtension( DataPacketExtension.ELEMENT, DataPacketExtension.NAMESPACE); if (data == null) { return false; } } // session ID equals this session ID if (!data.getSessionID().equals(byteStreamRequest.getSessionID())) { return false; } return true; }
@Override protected synchronized void writeToXML(DataPacketExtension data) throws IOException { // create IQ stanza containing data packet IQ iq = new Data(data); iq.setTo(remoteJID); try { connection.createPacketCollectorAndSend(iq).nextResultOrThrow(); } catch (Exception e) { // close session unless it is already closed if (!this.isClosed) { InBandBytestreamSession.this.close(); // Sadly we are unable to use the IOException(Throwable) constructor because this // constructor is only supported from Android API 9 on. IOException ioException = new IOException(); ioException.initCause(e); throw ioException; } } }
/** * Enable the Jabber services related to file transfer on the particular * connection. * * @param connection The connection on which to enable or disable the services. * @param isEnabled True to enable, false to disable. */ private static void setServiceEnabled(final XMPPConnection connection, final boolean isEnabled) { ServiceDiscoveryManager manager = ServiceDiscoveryManager .getInstanceFor(connection); List<String> namespaces = new ArrayList<String>(); namespaces.addAll(Arrays.asList(NAMESPACE)); namespaces.add(DataPacketExtension.NAMESPACE); if (!IBB_ONLY) { namespaces.add(Bytestream.NAMESPACE); } for (String namespace : namespaces) { if (isEnabled) { manager.addFeature(namespace); } else { manager.removeFeature(namespace); } } }
/** * Checks to see if all file transfer related services are enabled on the * connection. * * @param connection The connection to check * @return True if all related services are enabled, false if they are not. */ public static boolean isServiceEnabled(final XMPPConnection connection) { ServiceDiscoveryManager manager = ServiceDiscoveryManager .getInstanceFor(connection); List<String> namespaces = new ArrayList<String>(); namespaces.addAll(Arrays.asList(NAMESPACE)); namespaces.add(DataPacketExtension.NAMESPACE); if (!IBB_ONLY) { namespaces.add(Bytestream.NAMESPACE); } for (String namespace : namespaces) { if (!manager.includesFeature(namespace)) { return false; } } return true; }
/** * Valid data packets should be confirmed. * * @throws Exception should not happen */ @Test public void shouldConfirmReceivedDataPacket() throws Exception { // verify data packet confirmation is of type RESULT protocol.addResponse(null, Verification.requestTypeRESULT); InBandBytestreamSession session = new InBandBytestreamSession(connection, initBytestream, initiatorJID); InputStream inputStream = session.getInputStream(); StanzaListener listener = Whitebox.getInternalState(inputStream, StanzaListener.class); String base64Data = Base64.encode("Data"); DataPacketExtension dpe = new DataPacketExtension(sessionID, 0, base64Data); Data data = new Data(dpe); listener.processPacket(data); protocol.verifyAll(); }
public boolean accept(Packet packet) { // sender equals remote peer if (!packet.getFrom().equalsIgnoreCase(remoteJID)) { return false; } // stanza contains data packet extension PacketExtension packetExtension = packet.getExtension(DataPacketExtension.ELEMENT_NAME, InBandBytestreamManager.NAMESPACE); if (packetExtension == null || !(packetExtension instanceof DataPacketExtension)) { return false; } // session ID equals this session ID DataPacketExtension data = (DataPacketExtension) packetExtension; if (!data.getSessionID().equals(byteStreamRequest.getSessionID())) { return false; } return true; }
private synchronized void flushBuffer() throws IOException { // do nothing if no data to send available if (bufferPointer == 0) { return; } // create data packet String enc = StringUtils.encodeBase64(buffer, 0, bufferPointer, false); DataPacketExtension data = new DataPacketExtension(byteStreamRequest.getSessionID(), this.seq, enc); // write to XMPP stream writeToXML(data); // reset buffer pointer bufferPointer = 0; // increment sequence, considering sequence overflow this.seq = (this.seq + 1 == 65535 ? 0 : this.seq + 1); }
@Override protected synchronized void writeToXML(DataPacketExtension data) throws IOException { // create IQ stanza containing data packet IQ iq = new Data(data); iq.setTo(remoteJID); try { SyncPacketSend.getReply(connection, iq); } catch (XMPPException e) { // close session unless it is already closed if (!this.isClosed) { InBandBytestreamSession.this.close(); throw new IOException("Error while sending Data: " + e.getMessage()); } } }
public boolean accept(Packet packet) { // sender equals remote peer if (!packet.getFrom().equalsIgnoreCase(remoteJID)) { return false; } // stanza contains data packet extension PacketExtension packetExtension = packet.getExtension( DataPacketExtension.ELEMENT_NAME, InBandBytestreamManager.NAMESPACE); if (packetExtension == null || !(packetExtension instanceof DataPacketExtension)) { return false; } // session ID equals this session ID DataPacketExtension data = (DataPacketExtension) packetExtension; if (!data.getSessionID().equals(byteStreamRequest.getSessionID())) { return false; } return true; }
private synchronized void flushBuffer() throws IOException { // do nothing if no data to send available if (bufferPointer == 0) { return; } // create data packet String enc = StringUtils.encodeBase64(buffer, 0, bufferPointer, false); DataPacketExtension data = new DataPacketExtension( byteStreamRequest.getSessionID(), this.seq, enc); // write to XMPP stream writeToXML(data); // reset buffer pointer bufferPointer = 0; // increment sequence, considering sequence overflow this.seq = (this.seq + 1 == 65535 ? 0 : this.seq + 1); }
@Override public Data parse(XmlPullParser parser, int initialDepth) throws XmlPullParserException, IOException, SmackException { DataPacketExtension data = packetExtensionProvider.parse(parser); Data iq = new Data(data); return iq; }
@Override public DataPacketExtension parse(XmlPullParser parser, int initialDepth) throws XmlPullParserException, IOException { String sessionID = parser.getAttributeValue("", "sid"); long seq = Long.parseLong(parser.getAttributeValue("", "seq")); String data = parser.nextText(); return new DataPacketExtension(sessionID, seq, data); }
protected StanzaListener getDataPacketListener() { return new StanzaListener() { public void processPacket(Stanza packet) { // get data packet extension DataPacketExtension data = (DataPacketExtension) packet.getExtension( DataPacketExtension.ELEMENT, DataPacketExtension.NAMESPACE); // check if encoded data is valid if (data.getDecodedData() == null) { /* * TODO once a majority of XMPP server implementation support XEP-0079 * Advanced Message Processing the invalid message could be answered with an * appropriate error. For now we just ignore the packet. Subsequent packets * with an increased sequence will cause the input stream to close the * stream/session. */ return; } // data is valid; add to data queue dataQueue.offer(data); // TODO confirm packet once XMPP servers support XEP-0079 } }; }
private synchronized void flushBuffer() throws IOException { // do nothing if no data to send available if (bufferPointer == 0) { return; } // create data packet String enc = Base64.encodeToString(buffer, 0, bufferPointer); DataPacketExtension data = new DataPacketExtension(byteStreamRequest.getSessionID(), this.seq, enc); // write to XMPP stream try { writeToXML(data); } catch (NotConnectedException e) { IOException ioException = new IOException(); ioException.initCause(e); throw ioException; } // reset buffer pointer bufferPointer = 0; // increment sequence, considering sequence overflow this.seq = (this.seq + 1 == 65535 ? 0 : this.seq + 1); }
@Override protected synchronized void writeToXML(DataPacketExtension data) throws NotConnectedException { // create message stanza containing data packet Message message = new Message(remoteJID); message.addExtension(data); connection.sendStanza(message); }
/** * Returns a collection of the supported transfer protocols. * * @return Returns a collection of the supported transfer protocols. */ public static Collection<String> getSupportedProtocols() { List<String> protocols = new ArrayList<String>(); protocols.add(DataPacketExtension.NAMESPACE); if (!IBB_ONLY) { protocols.add(Bytestream.NAMESPACE); } return Collections.unmodifiableList(protocols); }
private StreamNegotiator getNegotiator(final FormField field) throws NoAcceptableTransferMechanisms { String variable; boolean isByteStream = false; boolean isIBB = false; for (FormField.Option option : field.getOptions()) { variable = option.getValue(); if (variable.equals(Bytestream.NAMESPACE) && !IBB_ONLY) { isByteStream = true; } else if (variable.equals(DataPacketExtension.NAMESPACE)) { isIBB = true; } } if (!isByteStream && !isIBB) { throw new FileTransferException.NoAcceptableTransferMechanisms(); } if (isByteStream && isIBB) { return new FaultTolerantNegotiator(connection(), byteStreamTransferManager, inbandTransferManager); } else if (isByteStream) { return byteStreamTransferManager; } else { return inbandTransferManager; } }
private StreamNegotiator getOutgoingNegotiator(final FormField field) throws NoAcceptableTransferMechanisms { boolean isByteStream = false; boolean isIBB = false; for (String variable : field.getValues()) { if (variable.equals(Bytestream.NAMESPACE) && !IBB_ONLY) { isByteStream = true; } else if (variable.equals(DataPacketExtension.NAMESPACE)) { isIBB = true; } } if (!isByteStream && !isIBB) { throw new FileTransferException.NoAcceptableTransferMechanisms(); } if (isByteStream && isIBB) { return new FaultTolerantNegotiator(connection(), byteStreamTransferManager, inbandTransferManager); } else if (isByteStream) { return byteStreamTransferManager; } else { return inbandTransferManager; } }
private DataForm createDefaultInitiationForm() { DataForm form = new DataForm(DataForm.Type.form); FormField field = new FormField(STREAM_DATA_FIELD_NAME); field.setType(FormField.Type.list_single); if (!IBB_ONLY) { field.addOption(new FormField.Option(Bytestream.NAMESPACE)); } field.addOption(new FormField.Option(DataPacketExtension.NAMESPACE)); form.addField(field); return form; }
/** * If a data stanza(/packet) of an unknown session is received it should be replied * with an <item-not-found/> error. * * @throws Exception should not happen */ @Test public void shouldReplyErrorIfSessionIsUnknown() throws Exception { // mock connection XMPPConnection connection = mock(XMPPConnection.class); // initialize InBandBytestreamManager to get the DataListener InBandBytestreamManager byteStreamManager = InBandBytestreamManager.getByteStreamManager(connection); // get the DataListener from InBandByteStreamManager DataListener dataListener = Whitebox.getInternalState(byteStreamManager, DataListener.class); DataPacketExtension dpe = new DataPacketExtension("unknownSessionID", 0, "Data"); Data data = new Data(dpe); data.setFrom(initiatorJID); data.setTo(targetJID); dataListener.handleIQRequest(data); // wait because packet is processed in an extra thread Thread.sleep(200); // capture reply to the In-Band Bytestream close request ArgumentCaptor<IQ> argument = ArgumentCaptor.forClass(IQ.class); verify(connection).sendStanza(argument.capture()); // assert that reply is the correct error packet assertEquals(initiatorJID, argument.getValue().getTo()); assertEquals(IQ.Type.error, argument.getValue().getType()); assertEquals(XMPPError.Condition.item_not_found, argument.getValue().getError().getCondition()); }
/** * Initialize fields used in the tests. * @throws XMPPException * @throws SmackException */ @Before public void setup() throws XMPPException, SmackException { // build protocol verifier protocol = new Protocol(); // create mocked XMPP connection connection = ConnectionUtils.createMockedConnection(protocol, initiatorJID, xmppServer); // initialize InBandBytestreamManager to get the InitiationListener byteStreamManager = InBandBytestreamManager.getByteStreamManager(connection); // create a In-Band Bytestream open packet with message stanza initBytestream = new Open(sessionID, blockSize, StanzaType.MESSAGE); initBytestream.setFrom(initiatorJID); initBytestream.setTo(targetJID); incrementingSequence = new Verification<Message, IQ>() { long lastSeq = 0; public void verify(Message request, IQ response) { DataPacketExtension dpe = (DataPacketExtension) request.getExtension( DataPacketExtension.ELEMENT, DataPacketExtension.NAMESPACE); assertEquals(lastSeq++, dpe.getSeq()); } }; }
/** * If a data stanza(/packet) is received out of order the session should be closed. See XEP-0047 Section * 2.2. * * @throws Exception should not happen */ @Test public void shouldSendCloseRequestIfInvalidSequenceReceived() throws Exception { // confirm close request IQ resultIQ = IBBPacketUtils.createResultIQ(initiatorJID, targetJID); protocol.addResponse(resultIQ, Verification.requestTypeSET, Verification.correspondingSenderReceiver); // get IBB sessions data packet listener InBandBytestreamSession session = new InBandBytestreamSession(connection, initBytestream, initiatorJID); InputStream inputStream = session.getInputStream(); StanzaListener listener = Whitebox.getInternalState(inputStream, StanzaListener.class); // build invalid packet with out of order sequence String base64Data = Base64.encode("Data"); DataPacketExtension dpe = new DataPacketExtension(sessionID, 123, base64Data); Message dataMessage = new Message(); dataMessage.addExtension(dpe); // add data packets listener.processPacket(dataMessage); // read until exception is thrown try { inputStream.read(); fail("exception should be thrown"); } catch (IOException e) { assertTrue(e.getMessage().contains("Packets out of sequence")); } protocol.verifyAll(); }
/** * Test the input stream read(byte[], int, int) method. * * @throws Exception should not happen */ @Test public void shouldReadAllReceivedData1() throws Exception { // create random data Random rand = new Random(); byte[] controlData = new byte[3 * blockSize]; rand.nextBytes(controlData); // get IBB sessions data packet listener InBandBytestreamSession session = new InBandBytestreamSession(connection, initBytestream, initiatorJID); InputStream inputStream = session.getInputStream(); StanzaListener listener = Whitebox.getInternalState(inputStream, StanzaListener.class); // verify data packet and notify listener for (int i = 0; i < controlData.length / blockSize; i++) { String base64Data = Base64.encodeToString(controlData, i * blockSize, blockSize); DataPacketExtension dpe = new DataPacketExtension(sessionID, i, base64Data); Message dataMessage = new Message(); dataMessage.addExtension(dpe); listener.processPacket(dataMessage); } byte[] bytes = new byte[3 * blockSize]; int read = 0; read = inputStream.read(bytes, 0, blockSize); assertEquals(blockSize, read); read = inputStream.read(bytes, 10, blockSize); assertEquals(blockSize, read); read = inputStream.read(bytes, 20, blockSize); assertEquals(blockSize, read); // verify data for (int i = 0; i < bytes.length; i++) { assertEquals(controlData[i], bytes[i]); } protocol.verifyAll(); }
/** * Test the input stream read() method. * * @throws Exception should not happen */ @Test public void shouldReadAllReceivedData2() throws Exception { // create random data Random rand = new Random(); byte[] controlData = new byte[3 * blockSize]; rand.nextBytes(controlData); // get IBB sessions data packet listener InBandBytestreamSession session = new InBandBytestreamSession(connection, initBytestream, initiatorJID); InputStream inputStream = session.getInputStream(); StanzaListener listener = Whitebox.getInternalState(inputStream, StanzaListener.class); // verify data packet and notify listener for (int i = 0; i < controlData.length / blockSize; i++) { String base64Data = Base64.encodeToString(controlData, i * blockSize, blockSize); DataPacketExtension dpe = new DataPacketExtension(sessionID, i, base64Data); Message dataMessage = new Message(); dataMessage.addExtension(dpe); listener.processPacket(dataMessage); } // read data byte[] bytes = new byte[3 * blockSize]; for (int i = 0; i < bytes.length; i++) { bytes[i] = (byte) inputStream.read(); } // verify data for (int i = 0; i < bytes.length; i++) { assertEquals(controlData[i], bytes[i]); } protocol.verifyAll(); }
/** * If the input stream is closed the output stream should not be closed as well. * * @throws Exception should not happen */ @Test public void shouldNotCloseBothStreamsIfOutputStreamIsClosed() throws Exception { InBandBytestreamSession session = new InBandBytestreamSession(connection, initBytestream, initiatorJID); OutputStream outputStream = session.getOutputStream(); outputStream.close(); // verify data packet confirmation is of type RESULT protocol.addResponse(null, Verification.requestTypeRESULT); // insert data to read InputStream inputStream = session.getInputStream(); StanzaListener listener = Whitebox.getInternalState(inputStream, StanzaListener.class); String base64Data = Base64.encode("Data"); DataPacketExtension dpe = new DataPacketExtension(sessionID, 0, base64Data); Data data = new Data(dpe); listener.processPacket(data); // verify no packet send protocol.verifyAll(); try { outputStream.flush(); fail("should throw an exception"); } catch (IOException e) { assertTrue(e.getMessage().contains("closed")); } assertTrue(inputStream.read() != 0); }
/** * If the data stanza(/packet) has a sequence that is already used an 'unexpected-request' error should * be returned. See XEP-0047 Section 2.2. * * @throws Exception should not happen */ @Test public void shouldReplyWithErrorIfAlreadyUsedSequenceIsReceived() throws Exception { // verify reply to first valid data packet is of type RESULT protocol.addResponse(null, Verification.requestTypeRESULT); // verify reply to invalid data packet is an error protocol.addResponse(null, Verification.requestTypeERROR, new Verification<IQ, IQ>() { public void verify(IQ request, IQ response) { assertEquals(XMPPError.Condition.unexpected_request, request.getError().getCondition()); } }); // get IBB sessions data packet listener InBandBytestreamSession session = new InBandBytestreamSession(connection, initBytestream, initiatorJID); InputStream inputStream = session.getInputStream(); StanzaListener listener = Whitebox.getInternalState(inputStream, StanzaListener.class); // build data packets String base64Data = Base64.encode("Data"); DataPacketExtension dpe = new DataPacketExtension(sessionID, 0, base64Data); Data data1 = new Data(dpe); Data data2 = new Data(dpe); // notify listener listener.processPacket(data1); listener.processPacket(data2); protocol.verifyAll(); }
/** * If the data stanza(/packet) contains invalid Base64 encoding an 'bad-request' error should be * returned. See XEP-0047 Section 2.2. * * @throws Exception should not happen */ @Test public void shouldReplyWithErrorIfDataIsInvalid() throws Exception { // verify reply to invalid data packet is an error protocol.addResponse(null, Verification.requestTypeERROR, new Verification<IQ, IQ>() { public void verify(IQ request, IQ response) { assertEquals(XMPPError.Condition.bad_request, request.getError().getCondition()); } }); // get IBB sessions data packet listener InBandBytestreamSession session = new InBandBytestreamSession(connection, initBytestream, initiatorJID); InputStream inputStream = session.getInputStream(); StanzaListener listener = Whitebox.getInternalState(inputStream, StanzaListener.class); // build data packets DataPacketExtension dpe = new DataPacketExtension(sessionID, 0, "AA=BB"); Data data = new Data(dpe); // notify listener listener.processPacket(data); protocol.verifyAll(); }
/** * If a data stanza(/packet) is received out of order the session should be closed. See XEP-0047 Section * 2.2. * * @throws Exception should not happen */ @Test public void shouldSendCloseRequestIfInvalidSequenceReceived() throws Exception { IQ resultIQ = IBBPacketUtils.createResultIQ(initiatorJID, targetJID); // confirm data packet with invalid sequence protocol.addResponse(resultIQ); // confirm close request protocol.addResponse(resultIQ, Verification.requestTypeSET, Verification.correspondingSenderReceiver); // get IBB sessions data packet listener InBandBytestreamSession session = new InBandBytestreamSession(connection, initBytestream, initiatorJID); InputStream inputStream = session.getInputStream(); StanzaListener listener = Whitebox.getInternalState(inputStream, StanzaListener.class); // build invalid packet with out of order sequence String base64Data = Base64.encode("Data"); DataPacketExtension dpe = new DataPacketExtension(sessionID, 123, base64Data); Data data = new Data(dpe); // add data packets listener.processPacket(data); // read until exception is thrown try { inputStream.read(); fail("exception should be thrown"); } catch (IOException e) { assertTrue(e.getMessage().contains("Packets out of sequence")); } protocol.verifyAll(); }
/** * Test the input stream read() method. * * @throws Exception should not happen */ @Test public void shouldReadAllReceivedData2() throws Exception { // create random data Random rand = new Random(); byte[] controlData = new byte[3 * blockSize]; rand.nextBytes(controlData); IQ resultIQ = IBBPacketUtils.createResultIQ(initiatorJID, targetJID); // get IBB sessions data packet listener InBandBytestreamSession session = new InBandBytestreamSession(connection, initBytestream, initiatorJID); InputStream inputStream = session.getInputStream(); StanzaListener listener = Whitebox.getInternalState(inputStream, StanzaListener.class); // set data packet acknowledgment and notify listener for (int i = 0; i < controlData.length / blockSize; i++) { protocol.addResponse(resultIQ); String base64Data = Base64.encodeToString(controlData, i * blockSize, blockSize); DataPacketExtension dpe = new DataPacketExtension(sessionID, i, base64Data); Data data = new Data(dpe); listener.processPacket(data); } // read data byte[] bytes = new byte[3 * blockSize]; for (int i = 0; i < bytes.length; i++) { bytes[i] = (byte) inputStream.read(); } // verify data for (int i = 0; i < bytes.length; i++) { assertEquals(controlData[i], bytes[i]); } protocol.verifyAll(); }
/** * If the output stream is closed the input stream should not be closed as well. * * @throws Exception should not happen */ @Test public void shouldNotCloseBothStreamsIfInputStreamIsClosed() throws Exception { // acknowledgment for data packet IQ resultIQ = IBBPacketUtils.createResultIQ(initiatorJID, targetJID); protocol.addResponse(resultIQ); // get IBB sessions data packet listener InBandBytestreamSession session = new InBandBytestreamSession(connection, initBytestream, initiatorJID); InputStream inputStream = session.getInputStream(); StanzaListener listener = Whitebox.getInternalState(inputStream, StanzaListener.class); // build data packet String base64Data = Base64.encode("Data"); DataPacketExtension dpe = new DataPacketExtension(sessionID, 0, base64Data); Data data = new Data(dpe); // add data packets listener.processPacket(data); inputStream.close(); protocol.verifyAll(); try { while (inputStream.read() != -1) { } inputStream.read(); fail("should throw an exception"); } catch (IOException e) { assertTrue(e.getMessage().contains("closed")); } session.getOutputStream().flush(); }