All Downloads are FREE. Search and download functionalities are using the official Maven repository.

chat.dim.MessagePacker Maven / Gradle / Ivy

The newest version!
/* license: https://mit-license.org
 *
 *  DIM-SDK : Decentralized Instant Messaging Software Development Kit
 *
 *                                Written in 2020 by Moky 
 *
 * ==============================================================================
 * The MIT License (MIT)
 *
 * Copyright (c) 2020 Albert Moky
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 * ==============================================================================
 */
package chat.dim;

import java.util.List;

import chat.dim.core.TwinsHelper;
import chat.dim.crypto.SymmetricKey;
import chat.dim.format.JSON;
import chat.dim.format.UTF8;
import chat.dim.mkm.User;
import chat.dim.msg.InstantMessagePacker;
import chat.dim.msg.MessageHelper;
import chat.dim.msg.ReliableMessagePacker;
import chat.dim.msg.SecureMessagePacker;
import chat.dim.protocol.ID;
import chat.dim.protocol.InstantMessage;
import chat.dim.protocol.Meta;
import chat.dim.protocol.ReliableMessage;
import chat.dim.protocol.SecureMessage;
import chat.dim.protocol.Visa;

public class MessagePacker extends TwinsHelper implements Packer {

    protected final InstantMessagePacker instantPacker;
    protected final SecureMessagePacker securePacker;
    protected final ReliableMessagePacker reliablePacker;

    public MessagePacker(Facebook facebook, Messenger messenger) {
        super(facebook, messenger);
        instantPacker = new InstantMessagePacker(messenger);
        securePacker = new SecureMessagePacker(messenger);
        reliablePacker = new ReliableMessagePacker(messenger);
    }

    @Override
    protected Facebook getFacebook() {
        return (Facebook) super.getFacebook();
    }

    @Override
    protected Messenger getMessenger() {
        return (Messenger) super.getMessenger();
    }

    //
    //  InstantMessage -> SecureMessage -> ReliableMessage -> Data
    //

    @Override
    public SecureMessage encryptMessage(InstantMessage iMsg) {
        // TODO: check receiver before calling this, make sure the visa.key exists;
        //       otherwise, suspend this message for waiting receiver's visa/meta;
        //       if receiver is a group, query all members' visa too!

        SecureMessage sMsg;
        // NOTICE: before sending group message, you can decide whether expose the group ID
        //      (A) if you don't want to expose the group ID,
        //          you can split it to multi-messages before encrypting,
        //          replace the 'receiver' to each member and keep the group hidden in the content;
        //          in this situation, the packer will use the personal message key (user to user);
        //      (B) if the group ID is overt, no need to worry about the exposing,
        //          you can keep the 'receiver' being the group ID, or set the group ID as 'group'
        //          when splitting to multi-messages to let the remote packer knows it;
        //          in these situations, the local packer will use the group msg key (user to group)
        //          to encrypt the message, and the remote packer can get the overt group ID before
        //          decrypting to take the right message key.
        ID receiver = iMsg.getReceiver();

        //
        //  1. get message key with direction (sender -> receiver) or (sender -> group)
        //
        SymmetricKey password = getMessenger().getEncryptKey(iMsg);
        assert password != null : "failed to get msg key: "
                + iMsg.getSender() + " => " + receiver + ", " + iMsg.get("group");

        //
        //  2. encrypt 'content' to 'data' for receiver/group members
        //
        if (receiver.isGroup()) {
            // group message
            List members = getFacebook().getMembers(receiver);
            assert !members.isEmpty() : "group not ready: " + receiver;
            // a station will never send group message, so here must be a client;
            // the client messenger should check the group's meta & members before encrypting,
            // so we can trust that the group members MUST exist here.
            sMsg = instantPacker.encryptMessage(iMsg, password, members);
        } else {
            // personal message (or split group message)
            sMsg = instantPacker.encryptMessage(iMsg, password, null);
        }
        if (sMsg == null) {
            // public key for encryption not found
            // TODO: suspend this message for waiting receiver's meta
            return null;
        }

        // NOTICE: copy content type to envelope
        //         this help the intermediate nodes to recognize message type
        sMsg.getEnvelope().setType(iMsg.getContent().getType());

        // OK
        return sMsg;
    }

    @Override
    public ReliableMessage signMessage(SecureMessage sMsg) {
        assert sMsg.getData() != null : "message data cannot be empty: " + sMsg;
        // sign 'data' by sender
        return securePacker.signMessage(sMsg);
    }

    @Override
    public byte[] serializeMessage(ReliableMessage rMsg) {
        return UTF8.encode(JSON.encode(rMsg.toMap()));
    }

    //
    //  Data -> ReliableMessage -> SecureMessage -> InstantMessage
    //

    @Override
    public ReliableMessage deserializeMessage(byte[] data) {
        String json = UTF8.decode(data);
        if (json == null) {
            assert false : "message data error: " + data.length;
            return null;
        }
        Object dict = JSON.decode(json);
        // TODO: translate short keys
        //       'S' -> 'sender'
        //       'R' -> 'receiver'
        //       'W' -> 'time'
        //       'T' -> 'type'
        //       'G' -> 'group'
        //       ------------------
        //       'D' -> 'data'
        //       'V' -> 'signature'
        //       'K' -> 'key', 'keys'
        //       ------------------
        //       'M' -> 'meta'
        //       'P' -> 'visa'
        return ReliableMessage.parse(dict);
    }

    /**
     *  Check meta & visa
     *
     * @param rMsg - received message
     * @return false on error
     */
    protected boolean checkAttachments(ReliableMessage rMsg) {
        Facebook facebook = getFacebook();
        ID sender = rMsg.getSender();
        // [Meta Protocol]
        Meta meta = MessageHelper.getMeta(rMsg);
        if (meta != null) {
            facebook.saveMeta(meta, sender);
        }
        // [Visa Protocol]
        Visa visa = MessageHelper.getVisa(rMsg);
        if (visa != null) {
            facebook.saveDocument(visa);
        }
        //
        //  TODO: check [Visa Protocol] before calling this
        //        make sure the sender's meta(visa) exists
        //        (do in by application)
        //
        return true;
    }

    @Override
    public SecureMessage verifyMessage(ReliableMessage rMsg) {
        // make sure meta exists before verifying message
        if (!checkAttachments(rMsg)) {
            return null;
        }

        assert rMsg.getSignature() != null : "message signature cannot be empty: " + rMsg;
        // verify 'data' with 'signature'
        return reliablePacker.verifyMessage(rMsg);
    }

    @Override
    public InstantMessage decryptMessage(SecureMessage sMsg) {
        // TODO: check receiver before calling this, make sure you are the receiver,
        //       or you are a member of the group when this is a group message,
        //       so that you will have a private key (decrypt key) to decrypt it.
        ID receiver = sMsg.getReceiver();
        User user = getFacebook().selectLocalUser(receiver);
        if (user == null) {
            // not for you?
            throw new NullPointerException("receiver error: " + sMsg.getReceiver()
                    + ", from " + sMsg.getSender() + ", " + sMsg.getGroup());
        }
        assert sMsg.getData() != null : "message data empty: "
                + sMsg.getSender() + " => " + sMsg.getReceiver() + ", " + sMsg.getGroup();
        // decrypt 'data' to 'content'
        return securePacker.decryptMessage(sMsg, user.getIdentifier());

        // TODO: check top-secret message
        //       (do it by application)
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy