mirror of
https://codeberg.org/Mercury-IM/Smack
synced 2024-11-24 07:12:05 +01:00
Merge branch '4.4'
This commit is contained in:
commit
4fefa92e40
22 changed files with 288 additions and 132 deletions
|
@ -148,7 +148,7 @@ allprojects {
|
||||||
jxmppVersion = '[1.0.0, 1.0.999]'
|
jxmppVersion = '[1.0.0, 1.0.999]'
|
||||||
miniDnsVersion = '[1.0.0, 1.0.999]'
|
miniDnsVersion = '[1.0.0, 1.0.999]'
|
||||||
smackMinAndroidSdk = 19
|
smackMinAndroidSdk = 19
|
||||||
junitVersion = '5.6.2'
|
junitVersion = '5.7.1'
|
||||||
commonsIoVersion = '2.6'
|
commonsIoVersion = '2.6'
|
||||||
bouncyCastleVersion = '1.68'
|
bouncyCastleVersion = '1.68'
|
||||||
guavaVersion = '30.1-jre'
|
guavaVersion = '30.1-jre'
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Copyright 2015 Florian Schmaus
|
* Copyright 2015-2021 Florian Schmaus
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -44,11 +44,20 @@ public abstract class AbstractListFilter implements StanzaFilter {
|
||||||
* @param filters the filters to add.
|
* @param filters the filters to add.
|
||||||
*/
|
*/
|
||||||
protected AbstractListFilter(StanzaFilter... filters) {
|
protected AbstractListFilter(StanzaFilter... filters) {
|
||||||
|
this(new ArrayList<StanzaFilter>(Arrays.asList(filters)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an filter using the specified filters.
|
||||||
|
*
|
||||||
|
* @param filters the filters to add.
|
||||||
|
*/
|
||||||
|
protected AbstractListFilter(List<StanzaFilter> filters) {
|
||||||
Objects.requireNonNull(filters, "Parameter must not be null.");
|
Objects.requireNonNull(filters, "Parameter must not be null.");
|
||||||
for (StanzaFilter filter : filters) {
|
for (StanzaFilter filter : filters) {
|
||||||
Objects.requireNonNull(filter, "Parameter must not be null.");
|
Objects.requireNonNull(filter, "Parameter must not be null.");
|
||||||
}
|
}
|
||||||
this.filters = new ArrayList<StanzaFilter>(Arrays.asList(filters));
|
this.filters = filters;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Copyright 2003-2007 Jive Software.
|
* Copyright 2003-2007 Jive Software, 2021 Florian Schmaus.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -17,6 +17,8 @@
|
||||||
|
|
||||||
package org.jivesoftware.smack.filter;
|
package org.jivesoftware.smack.filter;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import org.jivesoftware.smack.packet.Stanza;
|
import org.jivesoftware.smack.packet.Stanza;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -44,6 +46,15 @@ public class AndFilter extends AbstractListFilter implements StanzaFilter {
|
||||||
super(filters);
|
super(filters);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an AND filter using the specified filters.
|
||||||
|
*
|
||||||
|
* @param filters the filters to add.
|
||||||
|
*/
|
||||||
|
public AndFilter(List<StanzaFilter> filters) {
|
||||||
|
super(filters);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean accept(Stanza packet) {
|
public boolean accept(Stanza packet) {
|
||||||
for (StanzaFilter filter : filters) {
|
for (StanzaFilter filter : filters) {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Copyright 2003-2007 Jive Software, 2016-2019 Florian Schmaus.
|
* Copyright 2003-2007 Jive Software, 2016-2021 Florian Schmaus.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -47,4 +47,8 @@ public class RandomUtil {
|
||||||
public static int nextSecureRandomInt() {
|
public static int nextSecureRandomInt() {
|
||||||
return SECURE_RANDOM.get().nextInt();
|
return SECURE_RANDOM.get().nextInt();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void fillWithSecureRandom(byte[] bytes) {
|
||||||
|
SECURE_RANDOM.get().nextBytes(bytes);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -183,6 +183,10 @@ public abstract class FileTransfer {
|
||||||
|
|
||||||
protected void setException(Exception exception) {
|
protected void setException(Exception exception) {
|
||||||
this.exception = exception;
|
this.exception = exception;
|
||||||
|
Status currentStatus = getStatus();
|
||||||
|
if (currentStatus != Status.error) {
|
||||||
|
updateStatus(currentStatus, Status.error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void setStatus(Status status) {
|
protected void setStatus(Status status) {
|
||||||
|
|
|
@ -40,6 +40,7 @@ import org.jivesoftware.smackx.bytestreams.socks5.packet.Bytestream;
|
||||||
import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
|
import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
|
||||||
import org.jivesoftware.smackx.filetransfer.FileTransferException.NoAcceptableTransferMechanisms;
|
import org.jivesoftware.smackx.filetransfer.FileTransferException.NoAcceptableTransferMechanisms;
|
||||||
import org.jivesoftware.smackx.filetransfer.FileTransferException.NoStreamMethodsOfferedException;
|
import org.jivesoftware.smackx.filetransfer.FileTransferException.NoStreamMethodsOfferedException;
|
||||||
|
import org.jivesoftware.smackx.formtypes.FormFieldRegistry;
|
||||||
import org.jivesoftware.smackx.si.packet.StreamInitiation;
|
import org.jivesoftware.smackx.si.packet.StreamInitiation;
|
||||||
import org.jivesoftware.smackx.xdata.FormField;
|
import org.jivesoftware.smackx.xdata.FormField;
|
||||||
import org.jivesoftware.smackx.xdata.ListSingleFormField;
|
import org.jivesoftware.smackx.xdata.ListSingleFormField;
|
||||||
|
@ -66,6 +67,9 @@ public final class FileTransferNegotiator extends Manager {
|
||||||
private static final String STREAM_INIT_PREFIX = "jsi_";
|
private static final String STREAM_INIT_PREFIX = "jsi_";
|
||||||
|
|
||||||
static final String STREAM_DATA_FIELD_NAME = "stream-method";
|
static final String STREAM_DATA_FIELD_NAME = "stream-method";
|
||||||
|
static {
|
||||||
|
FormFieldRegistry.addLookasideFieldRegistryEntry(STREAM_DATA_FIELD_NAME, FormField.Type.list_single);
|
||||||
|
}
|
||||||
|
|
||||||
private static final Random randomGenerator = new Random();
|
private static final Random randomGenerator = new Random();
|
||||||
|
|
||||||
|
|
|
@ -324,6 +324,10 @@ public class OutgoingFileTransfer extends FileTransfer {
|
||||||
transferThread.start();
|
transferThread.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setCallback(NegotiationProgress negotiationProcess) {
|
||||||
|
this.callback = negotiationProcess;
|
||||||
|
}
|
||||||
|
|
||||||
private void handleXMPPException(XMPPErrorException e) {
|
private void handleXMPPException(XMPPErrorException e) {
|
||||||
StanzaError error = e.getStanzaError();
|
StanzaError error = e.getStanzaError();
|
||||||
if (error != null) {
|
if (error != null) {
|
||||||
|
|
|
@ -36,6 +36,8 @@ public class FormFieldRegistry {
|
||||||
|
|
||||||
private static final Map<String, FormField.Type> CLARK_NOTATION_FIELD_REGISTRY = new ConcurrentHashMap<>();
|
private static final Map<String, FormField.Type> CLARK_NOTATION_FIELD_REGISTRY = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
private static final Map<String, FormField.Type> LOOKASIDE_FIELD_REGISTRY = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
@SuppressWarnings("ReferenceEquality")
|
@SuppressWarnings("ReferenceEquality")
|
||||||
public static void register(DataForm dataForm) {
|
public static void register(DataForm dataForm) {
|
||||||
// TODO: Also allow forms of type 'result'?
|
// TODO: Also allow forms of type 'result'?
|
||||||
|
@ -98,11 +100,11 @@ public class FormFieldRegistry {
|
||||||
|
|
||||||
public static FormField.Type lookup(String formType, String fieldName) {
|
public static FormField.Type lookup(String formType, String fieldName) {
|
||||||
if (formType == null) {
|
if (formType == null) {
|
||||||
if (!XmlUtil.isClarkNotation(fieldName)) {
|
if (XmlUtil.isClarkNotation(fieldName)) {
|
||||||
return null;
|
return CLARK_NOTATION_FIELD_REGISTRY.get(fieldName);
|
||||||
}
|
}
|
||||||
|
|
||||||
return CLARK_NOTATION_FIELD_REGISTRY.get(fieldName);
|
return LOOKASIDE_FIELD_REGISTRY.get(fieldName);
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized (REGISTRY) {
|
synchronized (REGISTRY) {
|
||||||
|
@ -122,4 +124,7 @@ public class FormFieldRegistry {
|
||||||
return lookup(null, fieldName);
|
return lookup(null, fieldName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void addLookasideFieldRegistryEntry(String fieldName, FormField.Type formFieldType) {
|
||||||
|
LOOKASIDE_FIELD_REGISTRY.put(fieldName, formFieldType);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Copyright 2003-2007 Jive Software. 2020 Florian Schmaus
|
* Copyright 2003-2007 Jive Software. 2020-2021 Florian Schmaus
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -149,6 +149,8 @@ public class MultiUserChat {
|
||||||
private EntityFullJid myRoomJid;
|
private EntityFullJid myRoomJid;
|
||||||
private StanzaCollector messageCollector;
|
private StanzaCollector messageCollector;
|
||||||
|
|
||||||
|
private DiscoverInfo mucServiceDiscoInfo;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used to signal that the reflected self-presence was received <b>and</b> processed by us.
|
* Used to signal that the reflected self-presence was received <b>and</b> processed by us.
|
||||||
*/
|
*/
|
||||||
|
@ -342,7 +344,8 @@ public class MultiUserChat {
|
||||||
private Presence enter(MucEnterConfiguration conf) throws NotConnectedException, NoResponseException,
|
private Presence enter(MucEnterConfiguration conf) throws NotConnectedException, NoResponseException,
|
||||||
XMPPErrorException, InterruptedException, NotAMucServiceException {
|
XMPPErrorException, InterruptedException, NotAMucServiceException {
|
||||||
final DomainBareJid mucService = room.asDomainBareJid();
|
final DomainBareJid mucService = room.asDomainBareJid();
|
||||||
if (!multiUserChatManager.providesMucService(mucService)) {
|
mucServiceDiscoInfo = multiUserChatManager.getMucServiceDiscoInfo(mucService);
|
||||||
|
if (mucServiceDiscoInfo == null) {
|
||||||
throw new NotAMucServiceException(this);
|
throw new NotAMucServiceException(this);
|
||||||
}
|
}
|
||||||
// We enter a room by sending a presence packet where the "to"
|
// We enter a room by sending a presence packet where the "to"
|
||||||
|
@ -757,6 +760,10 @@ public class MultiUserChat {
|
||||||
throw new MucNotJoinedException(this);
|
throw new MucNotJoinedException(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Consider adding a origin-id to the presence, once it is moved form smack-experimental into
|
||||||
|
// smack-extensions, in case the MUC service does not support stable IDs, and modify
|
||||||
|
// reflectedLeavePresenceFilters accordingly.
|
||||||
|
|
||||||
// We leave a room by sending a presence packet where the "to"
|
// We leave a room by sending a presence packet where the "to"
|
||||||
// field is in the form "roomName@service/nickname"
|
// field is in the form "roomName@service/nickname"
|
||||||
Presence leavePresence = connection.getStanzaFactory().buildPresenceStanza()
|
Presence leavePresence = connection.getStanzaFactory().buildPresenceStanza()
|
||||||
|
@ -764,14 +771,19 @@ public class MultiUserChat {
|
||||||
.to(myRoomJid)
|
.to(myRoomJid)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
StanzaFilter reflectedLeavePresenceFilter = new AndFilter(
|
List<StanzaFilter> reflectedLeavePresenceFilters = new ArrayList<>(3);
|
||||||
StanzaTypeFilter.PRESENCE,
|
reflectedLeavePresenceFilters.add(StanzaTypeFilter.PRESENCE);
|
||||||
new StanzaIdFilter(leavePresence),
|
reflectedLeavePresenceFilters.add(new OrFilter(
|
||||||
new OrFilter(
|
new AndFilter(FromMatchesFilter.createFull(myRoomJid), PresenceTypeFilter.UNAVAILABLE,
|
||||||
new AndFilter(FromMatchesFilter.createFull(myRoomJid), PresenceTypeFilter.UNAVAILABLE, MUCUserStatusCodeFilter.STATUS_110_PRESENCE_TO_SELF),
|
MUCUserStatusCodeFilter.STATUS_110_PRESENCE_TO_SELF),
|
||||||
new AndFilter(fromRoomFilter, PresenceTypeFilter.ERROR)
|
new AndFilter(fromRoomFilter, PresenceTypeFilter.ERROR)));
|
||||||
)
|
|
||||||
);
|
boolean supportsStableId = mucServiceDiscoInfo.containsFeature(MultiUserChatConstants.STABLE_ID_FEATURE);
|
||||||
|
if (supportsStableId) {
|
||||||
|
reflectedLeavePresenceFilters.add(new StanzaIdFilter(leavePresence));
|
||||||
|
}
|
||||||
|
|
||||||
|
StanzaFilter reflectedLeavePresenceFilter = new AndFilter(reflectedLeavePresenceFilters);
|
||||||
|
|
||||||
// Reset occupant information first so that we are assume that we left the room even if sendStanza() would
|
// Reset occupant information first so that we are assume that we left the room even if sendStanza() would
|
||||||
// throw.
|
// throw.
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Copyright 2020 Florian Schmaus
|
* Copyright 2020-2021 Florian Schmaus
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -20,4 +20,6 @@ public class MultiUserChatConstants {
|
||||||
|
|
||||||
public static final String NAMESPACE = "http://jabber.org/protocol/muc";
|
public static final String NAMESPACE = "http://jabber.org/protocol/muc";
|
||||||
|
|
||||||
|
public static final String STABLE_ID_FEATURE = NAMESPACE + "#stable_id";
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Copyright © 2014-2020 Florian Schmaus
|
* Copyright © 2014-2021 Florian Schmaus
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -137,7 +137,7 @@ public final class MultiUserChatManager extends Manager {
|
||||||
private static final StanzaFilter INVITATION_FILTER = new AndFilter(StanzaTypeFilter.MESSAGE, new StanzaExtensionFilter(new MUCUser()),
|
private static final StanzaFilter INVITATION_FILTER = new AndFilter(StanzaTypeFilter.MESSAGE, new StanzaExtensionFilter(new MUCUser()),
|
||||||
new NotFilter(MessageTypeFilter.ERROR));
|
new NotFilter(MessageTypeFilter.ERROR));
|
||||||
|
|
||||||
private static final ExpirationCache<DomainBareJid, Void> KNOWN_MUC_SERVICES = new ExpirationCache<>(
|
private static final ExpirationCache<DomainBareJid, DiscoverInfo> KNOWN_MUC_SERVICES = new ExpirationCache<>(
|
||||||
100, 1000 * 60 * 60 * 24);
|
100, 1000 * 60 * 60 * 24);
|
||||||
|
|
||||||
private final Set<InvitationListener> invitationsListeners = new CopyOnWriteArraySet<InvitationListener>();
|
private final Set<InvitationListener> invitationsListeners = new CopyOnWriteArraySet<InvitationListener>();
|
||||||
|
@ -396,16 +396,23 @@ public final class MultiUserChatManager extends Manager {
|
||||||
*/
|
*/
|
||||||
public boolean providesMucService(DomainBareJid domainBareJid) throws NoResponseException,
|
public boolean providesMucService(DomainBareJid domainBareJid) throws NoResponseException,
|
||||||
XMPPErrorException, NotConnectedException, InterruptedException {
|
XMPPErrorException, NotConnectedException, InterruptedException {
|
||||||
boolean contains = KNOWN_MUC_SERVICES.containsKey(domainBareJid);
|
return getMucServiceDiscoInfo(domainBareJid) != null;
|
||||||
if (!contains) {
|
}
|
||||||
if (serviceDiscoveryManager.supportsFeature(domainBareJid,
|
|
||||||
MUCInitialPresence.NAMESPACE)) {
|
DiscoverInfo getMucServiceDiscoInfo(DomainBareJid mucServiceAddress)
|
||||||
KNOWN_MUC_SERVICES.put(domainBareJid, null);
|
throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
|
||||||
return true;
|
DiscoverInfo discoInfo = KNOWN_MUC_SERVICES.get(mucServiceAddress);
|
||||||
}
|
if (discoInfo != null) {
|
||||||
|
return discoInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
return contains;
|
discoInfo = serviceDiscoveryManager.discoverInfo(mucServiceAddress);
|
||||||
|
if (!discoInfo.containsFeature(MUCInitialPresence.NAMESPACE)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
KNOWN_MUC_SERVICES.put(mucServiceAddress, discoInfo);
|
||||||
|
return discoInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
*/
|
*/
|
||||||
package org.igniterealtime.smack;
|
package org.igniterealtime.smack;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
@ -90,7 +91,13 @@ public class XmppConnectionStressTest {
|
||||||
MessageBuilder messageBuilder = fromConnection.getStanzaFactory().buildMessageStanza();
|
MessageBuilder messageBuilder = fromConnection.getStanzaFactory().buildMessageStanza();
|
||||||
messageBuilder.to(toConnection.getUser());
|
messageBuilder.to(toConnection.getUser());
|
||||||
|
|
||||||
int payloadChunkCount = random.nextInt(configuration.maxPayloadChunks) + 1;
|
final int payloadChunkCount;
|
||||||
|
if (configuration.maxPayloadChunks == 0) {
|
||||||
|
payloadChunkCount = 0;
|
||||||
|
} else {
|
||||||
|
payloadChunkCount = random.nextInt(configuration.maxPayloadChunks) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
for (int c = 0; c < payloadChunkCount; c++) {
|
for (int c = 0; c < payloadChunkCount; c++) {
|
||||||
int payloadChunkSize = random.nextInt(configuration.maxPayloadChunkSize) + 1;
|
int payloadChunkSize = random.nextInt(configuration.maxPayloadChunkSize) + 1;
|
||||||
String payloadCunk = StringUtils.randomString(payloadChunkSize, random);
|
String payloadCunk = StringUtils.randomString(payloadChunkSize, random);
|
||||||
|
@ -128,6 +135,13 @@ public class XmppConnectionStressTest {
|
||||||
Map<XMPPConnection, Map<EntityFullJid, boolean[]>> receiveMarkers = new ConcurrentHashMap<>(connections.size());
|
Map<XMPPConnection, Map<EntityFullJid, boolean[]>> receiveMarkers = new ConcurrentHashMap<>(connections.size());
|
||||||
|
|
||||||
for (XMPPConnection connection : connections) {
|
for (XMPPConnection connection : connections) {
|
||||||
|
final Map<EntityFullJid, boolean[]> myReceiveMarkers = new HashMap<>(connections.size());
|
||||||
|
receiveMarkers.put(connection, myReceiveMarkers);
|
||||||
|
for (XMPPConnection otherConnection : connections) {
|
||||||
|
boolean[] fromMarkers = new boolean[configuration.messagesPerConnection];
|
||||||
|
myReceiveMarkers.put(otherConnection.getUser(), fromMarkers);
|
||||||
|
}
|
||||||
|
|
||||||
connection.addSyncStanzaListener(new StanzaListener() {
|
connection.addSyncStanzaListener(new StanzaListener() {
|
||||||
@Override
|
@Override
|
||||||
public void processStanza(Stanza stanza) {
|
public void processStanza(Stanza stanza) {
|
||||||
|
@ -139,17 +153,7 @@ public class XmppConnectionStressTest {
|
||||||
|
|
||||||
Integer messageNumber = (Integer) extension.getProperty(MESSAGE_NUMBER_PROPERTY);
|
Integer messageNumber = (Integer) extension.getProperty(MESSAGE_NUMBER_PROPERTY);
|
||||||
|
|
||||||
Map<EntityFullJid, boolean[]> myReceiveMarkers = receiveMarkers.get(connection);
|
|
||||||
if (myReceiveMarkers == null) {
|
|
||||||
myReceiveMarkers = new HashMap<>(connections.size());
|
|
||||||
receiveMarkers.put(connection, myReceiveMarkers);
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean[] fromMarkers = myReceiveMarkers.get(from);
|
boolean[] fromMarkers = myReceiveMarkers.get(from);
|
||||||
if (fromMarkers == null) {
|
|
||||||
fromMarkers = new boolean[configuration.messagesPerConnection];
|
|
||||||
myReceiveMarkers.put(from, fromMarkers);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sanity check: All markers before must be true, all markers including the messageNumber marker must be false.
|
// Sanity check: All markers before must be true, all markers including the messageNumber marker must be false.
|
||||||
for (int i = 0; i < fromMarkers.length; i++) {
|
for (int i = 0; i < fromMarkers.length; i++) {
|
||||||
|
@ -173,7 +177,9 @@ public class XmppConnectionStressTest {
|
||||||
exceptionMessage.append(i);
|
exceptionMessage.append(i);
|
||||||
exceptionMessage.append("\nMessage with id ").append(stanza.getStanzaId())
|
exceptionMessage.append("\nMessage with id ").append(stanza.getStanzaId())
|
||||||
.append(" from ").append(from)
|
.append(" from ").append(from)
|
||||||
.append(" to ").append(stanza.getTo());
|
.append(" to ").append(stanza.getTo())
|
||||||
|
.append('\n');
|
||||||
|
exceptionMessage.append("From Markers: ").append(Arrays.toString(fromMarkers)).append('\n');
|
||||||
|
|
||||||
Exception exception = new Exception(exceptionMessage.toString());
|
Exception exception = new Exception(exceptionMessage.toString());
|
||||||
receiveExceptions.put(connection, exception);
|
receiveExceptions.put(connection, exception);
|
||||||
|
@ -188,15 +194,14 @@ public class XmppConnectionStressTest {
|
||||||
|
|
||||||
fromMarkers[messageNumber] = true;
|
fromMarkers[messageNumber] = true;
|
||||||
|
|
||||||
if (myReceiveMarkers.size() != connections.size()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (boolean[] markers : myReceiveMarkers.values()) {
|
for (boolean[] markers : myReceiveMarkers.values()) {
|
||||||
if (BooleansUtils.contains(markers, false)) {
|
if (BooleansUtils.contains(markers, false)) {
|
||||||
|
// There is at least one message we did not receive yet, therefore do not signal the
|
||||||
|
// receivedSemaphore.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// All markers set to true, this means we received all messages.
|
// All markers set to true, this means we received all messages.
|
||||||
receivedSemaphore.release();
|
receivedSemaphore.release();
|
||||||
}
|
}
|
||||||
|
@ -276,6 +281,7 @@ public class XmppConnectionStressTest {
|
||||||
XMPPConnection connection = connections.get(i);
|
XMPPConnection connection = connections.get(i);
|
||||||
EntityFullJid connectionAddress = connection.getUser();
|
EntityFullJid connectionAddress = connection.getUser();
|
||||||
connectionIds.put(connectionAddress, i);
|
connectionIds.put(connectionAddress, i);
|
||||||
|
sb.append(i).append(": ").append(connection).append('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Map.Entry<XMPPConnection, Map<EntityFullJid, boolean[]>> entry : receiveMarkers.entrySet()) {
|
for (Map.Entry<XMPPConnection, Map<EntityFullJid, boolean[]>> entry : receiveMarkers.entrySet()) {
|
||||||
|
@ -295,7 +301,7 @@ public class XmppConnectionStressTest {
|
||||||
sb.append(markerToConnectionId)
|
sb.append(markerToConnectionId)
|
||||||
.append(" is missing ").append(numberOfFalseMarkers)
|
.append(" is missing ").append(numberOfFalseMarkers)
|
||||||
.append(" messages from ").append(markerFromConnectionId)
|
.append(" messages from ").append(markerFromConnectionId)
|
||||||
.append(" :");
|
.append(": ");
|
||||||
for (int i = 0; i < marker.length; i++) {
|
for (int i = 0; i < marker.length; i++) {
|
||||||
if (marker[i]) {
|
if (marker[i]) {
|
||||||
continue;
|
continue;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Copyright 2015-2020 Florian Schmaus
|
* Copyright 2015-2021 Florian Schmaus
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -26,6 +26,7 @@ import java.util.Arrays;
|
||||||
import org.jivesoftware.smack.SmackException;
|
import org.jivesoftware.smack.SmackException;
|
||||||
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
|
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
|
||||||
import org.jivesoftware.smack.util.StringUtils;
|
import org.jivesoftware.smack.util.StringUtils;
|
||||||
|
import org.jivesoftware.smackx.filetransfer.FileTransfer.Status;
|
||||||
|
|
||||||
import org.igniterealtime.smack.inttest.AbstractSmackIntegrationTest;
|
import org.igniterealtime.smack.inttest.AbstractSmackIntegrationTest;
|
||||||
import org.igniterealtime.smack.inttest.SmackIntegrationTestEnvironment;
|
import org.igniterealtime.smack.inttest.SmackIntegrationTestEnvironment;
|
||||||
|
@ -98,9 +99,12 @@ public class FileTransferIntegrationTest extends AbstractSmackIntegrationTest {
|
||||||
oft.sendStream(new ByteArrayInputStream(dataToSend), "hello.txt", dataToSend.length, "A greeting");
|
oft.sendStream(new ByteArrayInputStream(dataToSend), "hello.txt", dataToSend.length, "A greeting");
|
||||||
int duration = 0;
|
int duration = 0;
|
||||||
while (!oft.isDone()) {
|
while (!oft.isDone()) {
|
||||||
switch (oft.getStatus()) {
|
Status status = oft.getStatus();
|
||||||
|
switch (status) {
|
||||||
case error:
|
case error:
|
||||||
throw new Exception("FileTransfer error: " + oft.getError());
|
FileTransfer.Error error = oft.getError();
|
||||||
|
Exception exception = oft.getException();
|
||||||
|
throw new Exception("FileTransfer error: " + error, exception);
|
||||||
default:
|
default:
|
||||||
LOGGER.info("FileTransfer status: " + oft.getStatus() + ". Progress: " + oft.getProgress());
|
LOGGER.info("FileTransfer status: " + oft.getStatus() + ". Progress: " + oft.getProgress());
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Copyright 2017 Paul Schaub
|
* Copyright 2017 Paul Schaub, 2021 Florian Schmaus
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -17,7 +17,9 @@
|
||||||
package org.jivesoftware.smackx.omemo;
|
package org.jivesoftware.smackx.omemo;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.security.InvalidAlgorithmParameterException;
|
||||||
|
import java.security.InvalidKeyException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
|
@ -25,6 +27,7 @@ import java.util.logging.Logger;
|
||||||
|
|
||||||
import javax.crypto.BadPaddingException;
|
import javax.crypto.BadPaddingException;
|
||||||
import javax.crypto.IllegalBlockSizeException;
|
import javax.crypto.IllegalBlockSizeException;
|
||||||
|
import javax.crypto.NoSuchPaddingException;
|
||||||
|
|
||||||
import org.jivesoftware.smackx.omemo.element.OmemoElement;
|
import org.jivesoftware.smackx.omemo.element.OmemoElement;
|
||||||
import org.jivesoftware.smackx.omemo.element.OmemoKeyElement;
|
import org.jivesoftware.smackx.omemo.element.OmemoKeyElement;
|
||||||
|
@ -172,11 +175,10 @@ public abstract class OmemoRatchet<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey,
|
||||||
byte[] encryptedBody = payloadAndAuthTag(element, cipherAndAuthTag.getAuthTag());
|
byte[] encryptedBody = payloadAndAuthTag(element, cipherAndAuthTag.getAuthTag());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
String plaintext = new String(cipherAndAuthTag.getCipher().doFinal(encryptedBody), StandardCharsets.UTF_8);
|
return cipherAndAuthTag.decrypt(encryptedBody);
|
||||||
return plaintext;
|
} catch (IllegalBlockSizeException | BadPaddingException | InvalidKeyException | NoSuchAlgorithmException
|
||||||
} catch (IllegalBlockSizeException | BadPaddingException e) {
|
| NoSuchPaddingException | InvalidAlgorithmParameterException e) {
|
||||||
throw new CryptoFailedException("decryptMessageElement could not decipher message body: "
|
throw new CryptoFailedException("decryptMessageElement could not decipher message body", e);
|
||||||
+ e.getMessage());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Copyright 2017 Paul Schaub
|
* Copyright 2017 Paul Schaub, 2021 Florian Schmaus
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -16,10 +16,6 @@
|
||||||
*/
|
*/
|
||||||
package org.jivesoftware.smackx.omemo.exceptions;
|
package org.jivesoftware.smackx.omemo.exceptions;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Exception gets thrown when some cryptographic function failed.
|
* Exception gets thrown when some cryptographic function failed.
|
||||||
*
|
*
|
||||||
|
@ -27,20 +23,18 @@ import java.util.List;
|
||||||
*/
|
*/
|
||||||
public class CryptoFailedException extends Exception {
|
public class CryptoFailedException extends Exception {
|
||||||
|
|
||||||
private static final long serialVersionUID = 3466888654338119924L;
|
private static final long serialVersionUID = 1;
|
||||||
|
|
||||||
private final ArrayList<Exception> exceptions = new ArrayList<>();
|
public CryptoFailedException(String message, Exception wrappedException) {
|
||||||
|
super(message, wrappedException);
|
||||||
|
}
|
||||||
|
|
||||||
public CryptoFailedException(String message) {
|
public CryptoFailedException(String message) {
|
||||||
super(message);
|
this(message, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public CryptoFailedException(Exception e) {
|
public CryptoFailedException(Exception e) {
|
||||||
super(e);
|
this("Crypto failed " + e.getMessage(), e);
|
||||||
exceptions.add(e);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Exception> getExceptions() {
|
|
||||||
return Collections.unmodifiableList(exceptions);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Copyright 2017 Paul Schaub, 2019 Florian Schmaus
|
* Copyright 2017 Paul Schaub, 2019-2021 Florian Schmaus
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -16,18 +16,14 @@
|
||||||
*/
|
*/
|
||||||
package org.jivesoftware.smackx.omemo.internal;
|
package org.jivesoftware.smackx.omemo.internal;
|
||||||
|
|
||||||
import static org.jivesoftware.smackx.omemo.util.OmemoConstants.Crypto.CIPHERMODE;
|
import java.nio.charset.StandardCharsets;
|
||||||
import static org.jivesoftware.smackx.omemo.util.OmemoConstants.Crypto.KEYTYPE;
|
|
||||||
|
|
||||||
import java.security.InvalidAlgorithmParameterException;
|
import java.security.InvalidAlgorithmParameterException;
|
||||||
|
import java.security.InvalidKeyException;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
|
||||||
import javax.crypto.Cipher;
|
import javax.crypto.BadPaddingException;
|
||||||
|
import javax.crypto.IllegalBlockSizeException;
|
||||||
import javax.crypto.NoSuchPaddingException;
|
import javax.crypto.NoSuchPaddingException;
|
||||||
import javax.crypto.spec.IvParameterSpec;
|
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
|
||||||
|
|
||||||
import org.jivesoftware.smackx.omemo.exceptions.CryptoFailedException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encapsulate Cipher and AuthTag.
|
* Encapsulate Cipher and AuthTag.
|
||||||
|
@ -45,21 +41,10 @@ public class CipherAndAuthTag {
|
||||||
this.wasPreKey = wasPreKey;
|
this.wasPreKey = wasPreKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Cipher getCipher() throws CryptoFailedException {
|
public String decrypt(byte[] ciphertext) throws InvalidKeyException, IllegalBlockSizeException, BadPaddingException,
|
||||||
|
NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException {
|
||||||
Cipher cipher;
|
byte[] plaintext = OmemoAesCipher.decryptAesGcmNoPadding(ciphertext, key, iv);
|
||||||
try {
|
return new String(plaintext, StandardCharsets.UTF_8);
|
||||||
cipher = Cipher.getInstance(CIPHERMODE);
|
|
||||||
SecretKeySpec keySpec = new SecretKeySpec(key, KEYTYPE);
|
|
||||||
IvParameterSpec ivSpec = new IvParameterSpec(iv);
|
|
||||||
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
|
|
||||||
} catch (NoSuchAlgorithmException | java.security.InvalidKeyException |
|
|
||||||
InvalidAlgorithmParameterException |
|
|
||||||
NoSuchPaddingException e) {
|
|
||||||
throw new CryptoFailedException(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return cipher;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] getAuthTag() {
|
public byte[] getAuthTag() {
|
||||||
|
|
|
@ -0,0 +1,99 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Copyright 2017 Paul Schaub, 2019-2021 Florian Schmaus
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.jivesoftware.smackx.omemo.internal;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.security.InvalidAlgorithmParameterException;
|
||||||
|
import java.security.InvalidKeyException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
|
||||||
|
import javax.crypto.BadPaddingException;
|
||||||
|
import javax.crypto.Cipher;
|
||||||
|
import javax.crypto.IllegalBlockSizeException;
|
||||||
|
import javax.crypto.NoSuchPaddingException;
|
||||||
|
import javax.crypto.SecretKey;
|
||||||
|
import javax.crypto.spec.IvParameterSpec;
|
||||||
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.util.RandomUtil;
|
||||||
|
import org.jivesoftware.smackx.omemo.util.OmemoConstants;
|
||||||
|
import org.jivesoftware.smackx.omemo.util.OmemoMessageBuilder;
|
||||||
|
|
||||||
|
public class OmemoAesCipher {
|
||||||
|
|
||||||
|
static {
|
||||||
|
byte[] iv = OmemoMessageBuilder.generateIv();
|
||||||
|
byte[] key = new byte[16];
|
||||||
|
RandomUtil.fillWithSecureRandom(key);
|
||||||
|
|
||||||
|
try {
|
||||||
|
encryptAesGcmNoPadding("This is just a test", key, iv);
|
||||||
|
} catch (InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException
|
||||||
|
| InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) {
|
||||||
|
String message = "Unable to perform " + OmemoConstants.Crypto.CIPHERMODE
|
||||||
|
+ " operation requires by OMEMO. Ensure that a suitable crypto provider for is available."
|
||||||
|
+ " For example Bouncycastle on Android (BouncyCastleProvider)";
|
||||||
|
throw new AssertionError(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum CipherOpmode {
|
||||||
|
encrypt(Cipher.ENCRYPT_MODE),
|
||||||
|
decrypt(Cipher.DECRYPT_MODE),
|
||||||
|
;
|
||||||
|
|
||||||
|
public final int opmodeInt;
|
||||||
|
|
||||||
|
CipherOpmode(int opmodeInt) {
|
||||||
|
this.opmodeInt = opmodeInt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] performCipherOperation(CipherOpmode opmode, byte[] input, byte[] key,
|
||||||
|
byte[] initializationVector)
|
||||||
|
throws IllegalBlockSizeException, BadPaddingException, NoSuchAlgorithmException,
|
||||||
|
NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException {
|
||||||
|
SecretKey secretKey = new SecretKeySpec(key, OmemoConstants.Crypto.KEYTYPE);
|
||||||
|
IvParameterSpec ivSpec = new IvParameterSpec(initializationVector);
|
||||||
|
|
||||||
|
Cipher cipher = Cipher.getInstance(OmemoConstants.Crypto.CIPHERMODE);
|
||||||
|
cipher.init(opmode.opmodeInt, secretKey, ivSpec);
|
||||||
|
|
||||||
|
byte[] ciphertext = cipher.doFinal(input);
|
||||||
|
|
||||||
|
return ciphertext;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] decryptAesGcmNoPadding(byte[] ciphertext, byte[] key, byte[] initializationVector)
|
||||||
|
throws InvalidKeyException, IllegalBlockSizeException, BadPaddingException,
|
||||||
|
NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException {
|
||||||
|
return performCipherOperation(CipherOpmode.decrypt, ciphertext, key, initializationVector);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] encryptAesGcmNoPadding(byte[] plaintext, byte[] key, byte[] initializationVector)
|
||||||
|
throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException,
|
||||||
|
InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
|
||||||
|
return performCipherOperation(CipherOpmode.encrypt, plaintext, key, initializationVector);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] encryptAesGcmNoPadding(String plaintext, byte[] key, byte[] initializationVector)
|
||||||
|
throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException,
|
||||||
|
InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
|
||||||
|
byte[] plaintextBytes = plaintext.getBytes(StandardCharsets.UTF_8);
|
||||||
|
return encryptAesGcmNoPadding(plaintextBytes, key, initializationVector);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Copyright 2017 Paul Schaub, 2019 Florian Schmaus
|
* Copyright 2017 Paul Schaub, 2019-2021 Florian Schmaus
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -16,26 +16,20 @@
|
||||||
*/
|
*/
|
||||||
package org.jivesoftware.smackx.omemo.util;
|
package org.jivesoftware.smackx.omemo.util;
|
||||||
|
|
||||||
import static org.jivesoftware.smackx.omemo.util.OmemoConstants.Crypto.CIPHERMODE;
|
|
||||||
import static org.jivesoftware.smackx.omemo.util.OmemoConstants.Crypto.KEYLENGTH;
|
import static org.jivesoftware.smackx.omemo.util.OmemoConstants.Crypto.KEYLENGTH;
|
||||||
import static org.jivesoftware.smackx.omemo.util.OmemoConstants.Crypto.KEYTYPE;
|
import static org.jivesoftware.smackx.omemo.util.OmemoConstants.Crypto.KEYTYPE;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.security.InvalidAlgorithmParameterException;
|
import java.security.InvalidAlgorithmParameterException;
|
||||||
import java.security.InvalidKeyException;
|
import java.security.InvalidKeyException;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.security.SecureRandom;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import javax.crypto.BadPaddingException;
|
import javax.crypto.BadPaddingException;
|
||||||
import javax.crypto.Cipher;
|
|
||||||
import javax.crypto.IllegalBlockSizeException;
|
import javax.crypto.IllegalBlockSizeException;
|
||||||
import javax.crypto.KeyGenerator;
|
import javax.crypto.KeyGenerator;
|
||||||
import javax.crypto.NoSuchPaddingException;
|
import javax.crypto.NoSuchPaddingException;
|
||||||
import javax.crypto.SecretKey;
|
|
||||||
import javax.crypto.spec.IvParameterSpec;
|
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.util.RandomUtil;
|
||||||
import org.jivesoftware.smackx.omemo.OmemoRatchet;
|
import org.jivesoftware.smackx.omemo.OmemoRatchet;
|
||||||
import org.jivesoftware.smackx.omemo.OmemoService;
|
import org.jivesoftware.smackx.omemo.OmemoService;
|
||||||
import org.jivesoftware.smackx.omemo.element.OmemoElement;
|
import org.jivesoftware.smackx.omemo.element.OmemoElement;
|
||||||
|
@ -47,6 +41,7 @@ import org.jivesoftware.smackx.omemo.exceptions.NoIdentityKeyException;
|
||||||
import org.jivesoftware.smackx.omemo.exceptions.UndecidedOmemoIdentityException;
|
import org.jivesoftware.smackx.omemo.exceptions.UndecidedOmemoIdentityException;
|
||||||
import org.jivesoftware.smackx.omemo.exceptions.UntrustedOmemoIdentityException;
|
import org.jivesoftware.smackx.omemo.exceptions.UntrustedOmemoIdentityException;
|
||||||
import org.jivesoftware.smackx.omemo.internal.CiphertextTuple;
|
import org.jivesoftware.smackx.omemo.internal.CiphertextTuple;
|
||||||
|
import org.jivesoftware.smackx.omemo.internal.OmemoAesCipher;
|
||||||
import org.jivesoftware.smackx.omemo.internal.OmemoDevice;
|
import org.jivesoftware.smackx.omemo.internal.OmemoDevice;
|
||||||
import org.jivesoftware.smackx.omemo.trust.OmemoFingerprint;
|
import org.jivesoftware.smackx.omemo.trust.OmemoFingerprint;
|
||||||
import org.jivesoftware.smackx.omemo.trust.OmemoTrustCallback;
|
import org.jivesoftware.smackx.omemo.trust.OmemoTrustCallback;
|
||||||
|
@ -159,16 +154,7 @@ public class OmemoMessageBuilder<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_
|
||||||
}
|
}
|
||||||
|
|
||||||
// Encrypt message body
|
// Encrypt message body
|
||||||
SecretKey secretKey = new SecretKeySpec(messageKey, KEYTYPE);
|
byte[] ciphertext = OmemoAesCipher.encryptAesGcmNoPadding(message, messageKey, initializationVector);
|
||||||
IvParameterSpec ivSpec = new IvParameterSpec(initializationVector);
|
|
||||||
Cipher cipher = Cipher.getInstance(CIPHERMODE);
|
|
||||||
cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivSpec);
|
|
||||||
|
|
||||||
byte[] body;
|
|
||||||
byte[] ciphertext;
|
|
||||||
|
|
||||||
body = message.getBytes(StandardCharsets.UTF_8);
|
|
||||||
ciphertext = cipher.doFinal(body);
|
|
||||||
|
|
||||||
byte[] clearKeyWithAuthTag = new byte[messageKey.length + 16];
|
byte[] clearKeyWithAuthTag = new byte[messageKey.length + 16];
|
||||||
byte[] cipherTextWithoutAuthTag = new byte[ciphertext.length - 16];
|
byte[] cipherTextWithoutAuthTag = new byte[ciphertext.length - 16];
|
||||||
|
@ -278,9 +264,8 @@ public class OmemoMessageBuilder<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_
|
||||||
* @return initialization vector
|
* @return initialization vector
|
||||||
*/
|
*/
|
||||||
public static byte[] generateIv() {
|
public static byte[] generateIv() {
|
||||||
SecureRandom random = new SecureRandom();
|
|
||||||
byte[] iv = new byte[12];
|
byte[] iv = new byte[12];
|
||||||
random.nextBytes(iv);
|
RandomUtil.fillWithSecureRandom(iv);
|
||||||
return iv;
|
return iv;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,6 @@ import static org.jivesoftware.smackx.omemo.util.OmemoConstants.Crypto.KEYLENGTH
|
||||||
import static org.jivesoftware.smackx.omemo.util.OmemoConstants.Crypto.KEYTYPE;
|
import static org.jivesoftware.smackx.omemo.util.OmemoConstants.Crypto.KEYTYPE;
|
||||||
import static org.junit.Assert.assertArrayEquals;
|
import static org.junit.Assert.assertArrayEquals;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
import static org.junit.Assert.assertNotNull;
|
|
||||||
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
|
||||||
|
@ -61,7 +60,6 @@ public class WrapperObjectsTest extends SmackTestSuite {
|
||||||
|
|
||||||
CipherAndAuthTag cat = new CipherAndAuthTag(key, iv, authTag, true);
|
CipherAndAuthTag cat = new CipherAndAuthTag(key, iv, authTag, true);
|
||||||
|
|
||||||
assertNotNull(cat.getCipher());
|
|
||||||
assertArrayEquals(key, cat.getKey());
|
assertArrayEquals(key, cat.getKey());
|
||||||
assertArrayEquals(iv, cat.getIv());
|
assertArrayEquals(iv, cat.getIv());
|
||||||
assertArrayEquals(authTag, cat.getAuthTag());
|
assertArrayEquals(authTag, cat.getAuthTag());
|
||||||
|
|
|
@ -1143,16 +1143,27 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e) {
|
catch (Exception e) {
|
||||||
// The exception can be ignored if the the connection is 'done'
|
// Set running to false since this thread will exit here and notifyConnectionError() will wait until
|
||||||
// or if the it was caused because the socket got closed.
|
// the reader and writer thread's 'running' value is false. Hence we need to set it to false before calling
|
||||||
if (!done) {
|
// notifyConnetctionError() below, even though run() also sets it to false. Therefore, do not remove this.
|
||||||
// Set running to false since this thread will exit here and notifyConnectionError() will wait until
|
running = false;
|
||||||
// the reader and writer thread's 'running' value is false.
|
|
||||||
running = false;
|
String ignoreReasonThread = null;
|
||||||
// Close the connection and notify connection listeners of the
|
|
||||||
// error.
|
boolean writerThreadWasShutDown = packetWriter.queue.isShutdown();
|
||||||
notifyConnectionError(e);
|
if (writerThreadWasShutDown) {
|
||||||
|
ignoreReasonThread = "writer";
|
||||||
|
} else if (done) {
|
||||||
|
ignoreReasonThread = "reader";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ignoreReasonThread != null) {
|
||||||
|
LOGGER.log(Level.FINER, "Ignoring " + e + " as " + ignoreReasonThread + " was already shut down");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close the connection and notify connection listeners of the error.
|
||||||
|
notifyConnectionError(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,6 +73,12 @@ public final class StaxXmlPullParser implements XmlPullParser {
|
||||||
return namespaceContext.getNamespaceURI(prefix);
|
return namespaceContext.getNamespaceURI(prefix);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getNamespace() {
|
||||||
|
String prefix = getPrefix();
|
||||||
|
return getNamespace(prefix);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getDepth() {
|
public int getDepth() {
|
||||||
return depth;
|
return depth;
|
||||||
|
@ -106,13 +112,6 @@ public final class StaxXmlPullParser implements XmlPullParser {
|
||||||
return xmlStreamReader.getText();
|
return xmlStreamReader.getText();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getNamespace() {
|
|
||||||
NamespaceContext namespaceContext = xmlStreamReader.getNamespaceContext();
|
|
||||||
String prefix = getPrefix();
|
|
||||||
return namespaceContext.getNamespaceURI(prefix);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getName() {
|
public String getName() {
|
||||||
QName qname = getQName();
|
QName qname = getQName();
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Copyright 2019 Florian Schmaus.
|
* Copyright 2019-2021 Florian Schmaus.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -28,7 +28,14 @@ public class SmackXmlParser {
|
||||||
xmlPullParserFactoryServiceLoader = ServiceLoader.load(XmlPullParserFactory.class);
|
xmlPullParserFactoryServiceLoader = ServiceLoader.load(XmlPullParserFactory.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static XmlPullParserFactory xmlPullParserFactory;
|
||||||
|
|
||||||
public static XmlPullParserFactory getXmlPullParserFactory() {
|
public static XmlPullParserFactory getXmlPullParserFactory() {
|
||||||
|
final XmlPullParserFactory xmlPullParserFactory = SmackXmlParser.xmlPullParserFactory;
|
||||||
|
if (xmlPullParserFactory != null) {
|
||||||
|
return xmlPullParserFactory;
|
||||||
|
}
|
||||||
|
|
||||||
Iterator<XmlPullParserFactory> iterator = xmlPullParserFactoryServiceLoader.iterator();
|
Iterator<XmlPullParserFactory> iterator = xmlPullParserFactoryServiceLoader.iterator();
|
||||||
if (!iterator.hasNext()) {
|
if (!iterator.hasNext()) {
|
||||||
throw new IllegalStateException(
|
throw new IllegalStateException(
|
||||||
|
@ -37,6 +44,10 @@ public class SmackXmlParser {
|
||||||
return iterator.next();
|
return iterator.next();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void setXmlPullParserFactory(XmlPullParserFactory xmlPullParserFactory) {
|
||||||
|
SmackXmlParser.xmlPullParserFactory = xmlPullParserFactory;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new XmlPullParser suitable for parsing XMPP. This means in particular that
|
* Creates a new XmlPullParser suitable for parsing XMPP. This means in particular that
|
||||||
* FEATURE_PROCESS_NAMESPACES is enabled.
|
* FEATURE_PROCESS_NAMESPACES is enabled.
|
||||||
|
|
Loading…
Reference in a new issue