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

chat.dim.CommonMessenger Maven / Gradle / Ivy

There is a newer version: 0.3.0
Show newest version
/* license: https://mit-license.org
 *
 *  DIM-SDK : Decentralized Instant Messaging Software Development Kit
 *
 *                                Written in 2022 by Moky 
 *
 * ==============================================================================
 * The MIT License (MIT)
 *
 * Copyright (c) 2022 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 chat.dim.compat.Compatible;
import chat.dim.crypto.SymmetricKey;
import chat.dim.mkm.Entity;
import chat.dim.mkm.User;
import chat.dim.protocol.Command;
import chat.dim.protocol.Content;
import chat.dim.protocol.Envelope;
import chat.dim.protocol.ID;
import chat.dim.protocol.InstantMessage;
import chat.dim.protocol.ReliableMessage;
import chat.dim.protocol.SecureMessage;
import chat.dim.type.Converter;
import chat.dim.type.Pair;
import chat.dim.utils.Log;

/**
 *  Common Messenger with Session & Database
 */
public class CommonMessenger extends Messenger implements Transmitter {

    private final Session session;
    private final CommonFacebook facebook;
    private final CipherKeyDelegate database;
    private Packer packer;
    private Processor processor;

    public CommonMessenger(Session session, CommonFacebook facebook, CipherKeyDelegate database) {
        super();
        this.session = session;
        this.facebook = facebook;
        this.database = database;
        this.packer = null;
        this.processor = null;
    }

    public Session getSession() {
        return session;
    }

    @Override
    protected Entity.Delegate getEntityDelegate() {
        return facebook;
    }

    public CommonFacebook getFacebook() {
        return facebook;
    }

    @Override
    protected CipherKeyDelegate getCipherKeyDelegate() {
        return database;
    }

    @Override
    protected Packer getPacker() {
        return packer;
    }
    public void setPacker(Packer packer) {
        this.packer = packer;
    }

    @Override
    protected Processor getProcessor() {
        return processor;
    }
    public void setProcessor(Processor processor) {
        this.processor = processor;
    }

    @Override
    public byte[] encryptKey(byte[] data, ID receiver, InstantMessage iMsg) {
        try {
            return super.encryptKey(data, receiver, iMsg);
        } catch (Exception e) {
            // FIXME:
            e.printStackTrace();
            return null;
        }
    }

    @Override
    public byte[] serializeKey(SymmetricKey password, InstantMessage iMsg) {
        // TODO: reuse message key

        // 0. check message key
        Object reused = password.get("reused");
        Object digest = password.get("digest");
        if (reused == null && digest == null) {
            // flags not exist, serialize it directly
            return super.serializeKey(password, iMsg);
        }
        // 1. remove before serializing key
        password.remove("reused");
        password.remove("digest");
        // 2. serialize key without flags
        byte[] data = super.serializeKey(password, iMsg);
        // 3. put it back after serialized
        if (Converter.getBoolean(reused, false)) {
            password.put("reused", true);
        }
        if (digest != null) {
            password.put("digest", digest);
        }
        return data;
    }

    @Override
    public byte[] serializeContent(Content content, SymmetricKey password, InstantMessage iMsg) {
        if (content instanceof Command) {
            content = Compatible.fixCommand((Command) content);
        }
        return super.serializeContent(content, password, iMsg);
    }

    @Override
    public Content deserializeContent(byte[] data, SymmetricKey password, SecureMessage sMsg) {
        Content content = super.deserializeContent(data, password, sMsg);
        if (content instanceof Command) {
            content = Compatible.fixCommand((Command) content);
        }
        return content;
    }

    //
    //  Interfaces for Transmitting Message
    //

    @Override
    public Pair sendContent(ID sender, ID receiver, Content content, int priority) {
        if (sender == null) {
            User current = getFacebook().getCurrentUser();
            assert current != null : "current user not set";
            sender = current.getIdentifier();
        }
        Envelope env = Envelope.create(sender, receiver, null);
        InstantMessage iMsg = InstantMessage.create(env, content);
        ReliableMessage rMsg = sendInstantMessage(iMsg, priority);
        return new Pair<>(iMsg, rMsg);
    }

    @Override
    public ReliableMessage sendInstantMessage(InstantMessage iMsg, int priority) {
        // 0. check cycled message
        if (iMsg.getSender().equals(iMsg.getReceiver())) {
            Log.warning("drop cycled message: " + iMsg.getContent() + " "
                    + iMsg.getSender() + " => " + iMsg.getReceiver() + ", " + iMsg.getGroup());
            return null;
        } else {
            Log.debug("send instant message (type=" + iMsg.getContent().getType() + "): "
                    + iMsg.getSender() + " => " + iMsg.getReceiver() + ", " + iMsg.getGroup());
        }
        // 1. encrypt message
        SecureMessage sMsg = encryptMessage(iMsg);
        if (sMsg == null) {
            // assert false : "public key not found?";
            return null;
        }
        // 2. sign message
        ReliableMessage rMsg = signMessage(sMsg);
        if (rMsg == null) {
            // TODO: set msg.state = error
            throw new NullPointerException("failed to sign message: " + sMsg);
        }
        // 3. send message
        if (sendReliableMessage(rMsg, priority)) {
            return rMsg;
        }
        // failed
        return null;
    }

    @Override
    public boolean sendReliableMessage(ReliableMessage rMsg, int priority) {
        // 0. check cycled message
        if (rMsg.getSender().equals(rMsg.getReceiver())) {
            Log.warning("drop cycled message: "
                    + rMsg.getSender() + " => " + rMsg.getReceiver() + ", " + rMsg.getGroup());
            return false;
        }
        // 1. serialize message
        byte[] data = serializeMessage(rMsg);
        if (data == null) {
            assert false : "failed to serialize message: " + rMsg;
            return false;
        }
        // 2. call gate keeper to send the message data package
        //    put message package into the waiting queue of current session
        return getSession().queueMessagePackage(rMsg, data, priority);
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy