diff --git a/src/main/java/org/qortal/api/resource/ChatResource.java b/src/main/java/org/qortal/api/resource/ChatResource.java index 150b6f63..986bb03d 100644 --- a/src/main/java/org/qortal/api/resource/ChatResource.java +++ b/src/main/java/org/qortal/api/resource/ChatResource.java @@ -40,6 +40,8 @@ import org.qortal.utils.Base58; import com.google.common.primitives.Bytes; +import static org.qortal.data.chat.ChatMessage.Encoding; + @Path("/chat") @Tag(name = "Chat") public class ChatResource { @@ -73,6 +75,7 @@ public class ChatResource { @QueryParam("chatreference") String chatReference, @QueryParam("haschatreference") Boolean hasChatReference, @QueryParam("sender") String sender, + @QueryParam("encoding") Encoding encoding, @Parameter(ref = "limit") @QueryParam("limit") Integer limit, @Parameter(ref = "offset") @QueryParam("offset") Integer offset, @Parameter(ref = "reverse") @QueryParam("reverse") Boolean reverse) { @@ -109,6 +112,7 @@ public class ChatResource { hasChatReference, involvingAddresses, sender, + encoding, limit, offset, reverse); } catch (DataException e) { throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e); @@ -131,7 +135,7 @@ public class ChatResource { } ) @ApiErrors({ApiError.INVALID_CRITERIA, ApiError.INVALID_ADDRESS, ApiError.REPOSITORY_ISSUE}) - public ChatMessage getMessageBySignature(@PathParam("signature") String signature58) { + public ChatMessage getMessageBySignature(@PathParam("signature") String signature58, @QueryParam("encoding") Encoding encoding) { byte[] signature = Base58.decode(signature58); try (final Repository repository = RepositoryManager.getRepository()) { @@ -141,7 +145,7 @@ public class ChatResource { throw ApiExceptionFactory.INSTANCE.createCustomException(request, ApiError.INVALID_CRITERIA, "Message not found"); } - return repository.getChatRepository().toChatMessage(chatTransactionData); + return repository.getChatRepository().toChatMessage(chatTransactionData, encoding); } catch (DataException e) { throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e); } @@ -164,12 +168,12 @@ public class ChatResource { } ) @ApiErrors({ApiError.INVALID_CRITERIA, ApiError.INVALID_ADDRESS, ApiError.REPOSITORY_ISSUE}) - public ActiveChats getActiveChats(@PathParam("address") String address) { + public ActiveChats getActiveChats(@PathParam("address") String address, @QueryParam("encoding") Encoding encoding) { if (address == null || !Crypto.isValidAddress(address)) throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS); try (final Repository repository = RepositoryManager.getRepository()) { - return repository.getChatRepository().getActiveChats(address); + return repository.getChatRepository().getActiveChats(address, encoding); } catch (DataException e) { throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e); } diff --git a/src/main/java/org/qortal/api/websocket/ActiveChatsWebSocket.java b/src/main/java/org/qortal/api/websocket/ActiveChatsWebSocket.java index 405fe7e5..d683f519 100644 --- a/src/main/java/org/qortal/api/websocket/ActiveChatsWebSocket.java +++ b/src/main/java/org/qortal/api/websocket/ActiveChatsWebSocket.java @@ -2,6 +2,7 @@ package org.qortal.api.websocket; import java.io.IOException; import java.io.StringWriter; +import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicReference; @@ -21,6 +22,8 @@ import org.qortal.repository.DataException; import org.qortal.repository.Repository; import org.qortal.repository.RepositoryManager; +import static org.qortal.data.chat.ChatMessage.Encoding; + @WebSocket @SuppressWarnings("serial") public class ActiveChatsWebSocket extends ApiWebSocket { @@ -75,7 +78,7 @@ public class ActiveChatsWebSocket extends ApiWebSocket { } try (final Repository repository = RepositoryManager.getRepository()) { - ActiveChats activeChats = repository.getChatRepository().getActiveChats(ourAddress); + ActiveChats activeChats = repository.getChatRepository().getActiveChats(ourAddress, getTargetEncoding(session)); StringWriter stringWriter = new StringWriter(); @@ -93,4 +96,11 @@ public class ActiveChatsWebSocket extends ApiWebSocket { } } + private Encoding getTargetEncoding(Session session) { + // Default to Base58 if not specified, for backwards support + Map> queryParams = session.getUpgradeRequest().getParameterMap(); + String encoding = (queryParams.get("encoding") != null && !queryParams.get("encoding").isEmpty()) ? queryParams.get("encoding").get(0) : "BASE58"; + return Encoding.valueOf(encoding); + } + } diff --git a/src/main/java/org/qortal/api/websocket/ChatMessagesWebSocket.java b/src/main/java/org/qortal/api/websocket/ChatMessagesWebSocket.java index c6d7aaed..aeb1b10b 100644 --- a/src/main/java/org/qortal/api/websocket/ChatMessagesWebSocket.java +++ b/src/main/java/org/qortal/api/websocket/ChatMessagesWebSocket.java @@ -22,6 +22,8 @@ import org.qortal.repository.DataException; import org.qortal.repository.Repository; import org.qortal.repository.RepositoryManager; +import static org.qortal.data.chat.ChatMessage.Encoding; + @WebSocket @SuppressWarnings("serial") public class ChatMessagesWebSocket extends ApiWebSocket { @@ -35,6 +37,7 @@ public class ChatMessagesWebSocket extends ApiWebSocket { @Override public void onWebSocketConnect(Session session) { Map> queryParams = session.getUpgradeRequest().getParameterMap(); + Encoding encoding = getTargetEncoding(session); List txGroupIds = queryParams.get("txGroupId"); if (txGroupIds != null && txGroupIds.size() == 1) { @@ -50,6 +53,7 @@ public class ChatMessagesWebSocket extends ApiWebSocket { null, null, null, + encoding, null, null, null); sendMessages(session, chatMessages); @@ -81,6 +85,7 @@ public class ChatMessagesWebSocket extends ApiWebSocket { null, involvingAddresses, null, + encoding, null, null, null); sendMessages(session, chatMessages); @@ -155,7 +160,7 @@ public class ChatMessagesWebSocket extends ApiWebSocket { // Convert ChatTransactionData to ChatMessage ChatMessage chatMessage; try (final Repository repository = RepositoryManager.getRepository()) { - chatMessage = repository.getChatRepository().toChatMessage(chatTransactionData); + chatMessage = repository.getChatRepository().toChatMessage(chatTransactionData, getTargetEncoding(session)); } catch (DataException e) { // No output this time? return; @@ -164,4 +169,11 @@ public class ChatMessagesWebSocket extends ApiWebSocket { sendMessages(session, Collections.singletonList(chatMessage)); } + private Encoding getTargetEncoding(Session session) { + // Default to Base58 if not specified, for backwards support + Map> queryParams = session.getUpgradeRequest().getParameterMap(); + String encoding = (queryParams.get("encoding") != null && !queryParams.get("encoding").isEmpty()) ? queryParams.get("encoding").get(0) : "BASE58"; + return Encoding.valueOf(encoding); + } + } diff --git a/src/main/java/org/qortal/data/chat/ActiveChats.java b/src/main/java/org/qortal/data/chat/ActiveChats.java index d5ebcf3f..248af82e 100644 --- a/src/main/java/org/qortal/data/chat/ActiveChats.java +++ b/src/main/java/org/qortal/data/chat/ActiveChats.java @@ -1,10 +1,15 @@ package org.qortal.data.chat; +import org.bouncycastle.util.encoders.Base64; +import org.qortal.utils.Base58; + import java.util.List; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; +import static org.qortal.data.chat.ChatMessage.Encoding; + @XmlAccessorType(XmlAccessType.FIELD) public class ActiveChats { @@ -18,20 +23,38 @@ public class ActiveChats { private String sender; private String senderName; private byte[] signature; - private byte[] data; + private Encoding encoding; + private String data; protected GroupChat() { /* JAXB */ } - public GroupChat(int groupId, String groupName, Long timestamp, String sender, String senderName, byte[] signature, byte[] data) { + public GroupChat(int groupId, String groupName, Long timestamp, String sender, String senderName, + byte[] signature, Encoding encoding, byte[] data) { this.groupId = groupId; this.groupName = groupName; this.timestamp = timestamp; this.sender = sender; this.senderName = senderName; this.signature = signature; - this.data = data; + this.encoding = encoding != null ? encoding : Encoding.BASE58; + + if (data != null) { + switch (this.encoding) { + case BASE64: + this.data = Base64.toBase64String(data); + break; + + case BASE58: + default: + this.data = Base58.encode(data); + break; + } + } + else { + this.data = null; + } } public int getGroupId() { @@ -58,7 +81,7 @@ public class ActiveChats { return this.signature; } - public byte[] getData() { + public String getData() { return this.data; } } diff --git a/src/main/java/org/qortal/data/chat/ChatMessage.java b/src/main/java/org/qortal/data/chat/ChatMessage.java index 5d16bb7c..5d9ecb4e 100644 --- a/src/main/java/org/qortal/data/chat/ChatMessage.java +++ b/src/main/java/org/qortal/data/chat/ChatMessage.java @@ -1,11 +1,19 @@ package org.qortal.data.chat; +import org.bouncycastle.util.encoders.Base64; +import org.qortal.utils.Base58; + import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; @XmlAccessorType(XmlAccessType.FIELD) public class ChatMessage { + public enum Encoding { + BASE58, + BASE64 + } + // Properties private long timestamp; @@ -29,7 +37,9 @@ public class ChatMessage { private byte[] chatReference; - private byte[] data; + private Encoding encoding; + + private String data; private boolean isText; private boolean isEncrypted; @@ -44,8 +54,8 @@ public class ChatMessage { // For repository use public ChatMessage(long timestamp, int txGroupId, byte[] reference, byte[] senderPublicKey, String sender, - String senderName, String recipient, String recipientName, byte[] chatReference, byte[] data, - boolean isText, boolean isEncrypted, byte[] signature) { + String senderName, String recipient, String recipientName, byte[] chatReference, + Encoding encoding, byte[] data, boolean isText, boolean isEncrypted, byte[] signature) { this.timestamp = timestamp; this.txGroupId = txGroupId; this.reference = reference; @@ -55,7 +65,24 @@ public class ChatMessage { this.recipient = recipient; this.recipientName = recipientName; this.chatReference = chatReference; - this.data = data; + this.encoding = encoding != null ? encoding : Encoding.BASE58; + + if (data != null) { + switch (this.encoding) { + case BASE64: + this.data = Base64.toBase64String(data); + break; + + case BASE58: + default: + this.data = Base58.encode(data); + break; + } + } + else { + this.data = null; + } + this.isText = isText; this.isEncrypted = isEncrypted; this.signature = signature; @@ -97,7 +124,7 @@ public class ChatMessage { return this.chatReference; } - public byte[] getData() { + public String getData() { return this.data; } diff --git a/src/main/java/org/qortal/repository/ChatRepository.java b/src/main/java/org/qortal/repository/ChatRepository.java index 34ad77dd..7443fb51 100644 --- a/src/main/java/org/qortal/repository/ChatRepository.java +++ b/src/main/java/org/qortal/repository/ChatRepository.java @@ -6,6 +6,8 @@ import org.qortal.data.chat.ActiveChats; import org.qortal.data.chat.ChatMessage; import org.qortal.data.transaction.ChatTransactionData; +import static org.qortal.data.chat.ChatMessage.Encoding; + public interface ChatRepository { /** @@ -15,10 +17,11 @@ public interface ChatRepository { */ public List getMessagesMatchingCriteria(Long before, Long after, Integer txGroupId, byte[] reference, byte[] chatReferenceBytes, Boolean hasChatReference, - List involving, String senderAddress, Integer limit, Integer offset, Boolean reverse) throws DataException; + List involving, String senderAddress, Encoding encoding, + Integer limit, Integer offset, Boolean reverse) throws DataException; - public ChatMessage toChatMessage(ChatTransactionData chatTransactionData) throws DataException; + public ChatMessage toChatMessage(ChatTransactionData chatTransactionData, Encoding encoding) throws DataException; - public ActiveChats getActiveChats(String address) throws DataException; + public ActiveChats getActiveChats(String address, Encoding encoding) throws DataException; } diff --git a/src/main/java/org/qortal/repository/hsqldb/HSQLDBChatRepository.java b/src/main/java/org/qortal/repository/hsqldb/HSQLDBChatRepository.java index 55467d87..9e310e78 100644 --- a/src/main/java/org/qortal/repository/hsqldb/HSQLDBChatRepository.java +++ b/src/main/java/org/qortal/repository/hsqldb/HSQLDBChatRepository.java @@ -14,6 +14,8 @@ import org.qortal.repository.ChatRepository; import org.qortal.repository.DataException; import org.qortal.transaction.Transaction.TransactionType; +import static org.qortal.data.chat.ChatMessage.Encoding; + public class HSQLDBChatRepository implements ChatRepository { protected HSQLDBRepository repository; @@ -24,8 +26,8 @@ public class HSQLDBChatRepository implements ChatRepository { @Override public List getMessagesMatchingCriteria(Long before, Long after, Integer txGroupId, byte[] referenceBytes, - byte[] chatReferenceBytes, Boolean hasChatReference, List involving, String senderAddress, - Integer limit, Integer offset, Boolean reverse) throws DataException { + byte[] chatReferenceBytes, Boolean hasChatReference, List involving, String senderAddress, + Encoding encoding, Integer limit, Integer offset, Boolean reverse) throws DataException { // Check args meet expectations if ((txGroupId != null && involving != null && !involving.isEmpty()) || (txGroupId == null && (involving == null || involving.size() != 2))) @@ -127,7 +129,7 @@ public class HSQLDBChatRepository implements ChatRepository { byte[] signature = resultSet.getBytes(13); ChatMessage chatMessage = new ChatMessage(timestamp, groupId, reference, senderPublicKey, sender, - senderName, recipient, recipientName, chatReference, data, isText, isEncrypted, signature); + senderName, recipient, recipientName, chatReference, encoding, data, isText, isEncrypted, signature); chatMessages.add(chatMessage); } while (resultSet.next()); @@ -139,7 +141,7 @@ public class HSQLDBChatRepository implements ChatRepository { } @Override - public ChatMessage toChatMessage(ChatTransactionData chatTransactionData) throws DataException { + public ChatMessage toChatMessage(ChatTransactionData chatTransactionData, Encoding encoding) throws DataException { String sql = "SELECT SenderNames.name, RecipientNames.name " + "FROM ChatTransactions " + "LEFT OUTER JOIN Names AS SenderNames ON SenderNames.owner = sender " @@ -166,21 +168,22 @@ public class HSQLDBChatRepository implements ChatRepository { byte[] signature = chatTransactionData.getSignature(); return new ChatMessage(timestamp, groupId, reference, senderPublicKey, sender, - senderName, recipient, recipientName, chatReference, data, isText, isEncrypted, signature); + senderName, recipient, recipientName, chatReference, encoding, data, + isText, isEncrypted, signature); } catch (SQLException e) { throw new DataException("Unable to fetch convert chat transaction from repository", e); } } @Override - public ActiveChats getActiveChats(String address) throws DataException { - List groupChats = getActiveGroupChats(address); + public ActiveChats getActiveChats(String address, Encoding encoding) throws DataException { + List groupChats = getActiveGroupChats(address, encoding); List directChats = getActiveDirectChats(address); return new ActiveChats(groupChats, directChats); } - private List getActiveGroupChats(String address) throws DataException { + private List getActiveGroupChats(String address, Encoding encoding) throws DataException { // Find groups where address is a member and potential latest message details String groupsSql = "SELECT group_id, group_name, latest_timestamp, sender, sender_name, signature, data " + "FROM GroupMembers " @@ -213,7 +216,7 @@ public class HSQLDBChatRepository implements ChatRepository { byte[] signature = resultSet.getBytes(6); byte[] data = resultSet.getBytes(7); - GroupChat groupChat = new GroupChat(groupId, groupName, timestamp, sender, senderName, signature, data); + GroupChat groupChat = new GroupChat(groupId, groupName, timestamp, sender, senderName, signature, encoding, data); groupChats.add(groupChat); } while (resultSet.next()); } @@ -247,7 +250,7 @@ public class HSQLDBChatRepository implements ChatRepository { data = resultSet.getBytes(5); } - GroupChat groupChat = new GroupChat(0, null, timestamp, sender, senderName, signature, data); + GroupChat groupChat = new GroupChat(0, null, timestamp, sender, senderName, signature, encoding, data); groupChats.add(groupChat); } catch (SQLException e) { throw new DataException("Unable to fetch active group chats from repository", e); diff --git a/src/test/java/org/qortal/test/RepositoryTests.java b/src/test/java/org/qortal/test/RepositoryTests.java index bb6510d5..30cbaea5 100644 --- a/src/test/java/org/qortal/test/RepositoryTests.java +++ b/src/test/java/org/qortal/test/RepositoryTests.java @@ -9,6 +9,7 @@ import org.qortal.crosschain.BitcoinACCTv1; import org.qortal.crypto.Crypto; import org.qortal.data.account.AccountBalanceData; import org.qortal.data.account.AccountData; +import org.qortal.data.chat.ChatMessage; import org.qortal.repository.DataException; import org.qortal.repository.Repository; import org.qortal.repository.RepositoryManager; @@ -417,7 +418,7 @@ public class RepositoryTests extends Common { try (final HSQLDBRepository hsqldb = (HSQLDBRepository) RepositoryManager.getRepository()) { String address = Crypto.toAddress(new byte[32]); - hsqldb.getChatRepository().getActiveChats(address); + hsqldb.getChatRepository().getActiveChats(address, ChatMessage.Encoding.BASE58); } catch (DataException e) { fail("HSQLDB bug #1580"); }