/* * Copyright (c) [2016] [ <ether.camp> ] * This file is part of the ethereumJ library. * * The ethereumJ library is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * The ethereumJ library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with the ethereumJ library. If not, see <http://www.gnu.org/licenses/>. */ package org.ethereum.net.shh; import org.apache.commons.collections4.map.LRUMap; import org.ethereum.config.SystemProperties; import org.ethereum.crypto.ECKey; import org.ethereum.util.ByteUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.spongycastle.util.encoders.Hex; import org.springframework.stereotype.Component; import java.nio.charset.StandardCharsets; import java.util.*; @Component public class WhisperImpl extends Whisper { private final static Logger logger = LoggerFactory.getLogger("net.shh"); private Set<MessageWatcher> filters = new HashSet<>(); private List<Topic> knownTopics = new ArrayList<>(); private Map<WhisperMessage, ?> known = new LRUMap<>(1024); // essentially Set private Map<String, ECKey> identities = new HashMap<>(); private List<ShhHandler> activePeers = new ArrayList<>(); BloomFilter hostBloomFilter = BloomFilter.createAll(); public WhisperImpl() { } @Override public void send(String from, String to, byte[] payload, Topic[] topicList, int ttl, int workToProve) { ECKey fromKey = null; if (from != null && !from.isEmpty()) { fromKey = getIdentity(from); if (fromKey == null) { throw new Error(String.format("Unknown identity to send from %s", from)); } } WhisperMessage m = new WhisperMessage() .setFrom(fromKey) .setTo(to) .setPayload(payload) .setTopics(topicList) .setTtl(ttl) .setWorkToProve(workToProve); logger.info("Sending Whisper message: " + m); addMessage(m, null); } public void processEnvelope(ShhEnvelopeMessage e, ShhHandler shhHandler) { for (WhisperMessage message : e.getMessages()) { message.decrypt(identities.values(), knownTopics); logger.info("New Whisper message: " + message); addMessage(message, shhHandler); } } void addPeer(ShhHandler peer) { activePeers.add(peer); } void removePeer(ShhHandler peer) { activePeers.remove(peer); } public void watch(MessageWatcher f) { filters.add(f); for (Topic topic : f.getTopics()) { hostBloomFilter.addTopic(topic); knownTopics.add(topic); } notifyBloomFilterChanged(); } public void unwatch(MessageWatcher f) { filters.remove(f); for (Topic topic : f.getTopics()) { hostBloomFilter.removeTopic(topic); } notifyBloomFilterChanged(); } private void notifyBloomFilterChanged() { for (ShhHandler peer : activePeers) { peer.sendHostBloom(); } } // Processing both messages: // own outgoing messages (shhHandler == null) // and inbound messages from peers private void addMessage(WhisperMessage m, ShhHandler inboundPeer) { if (!known.containsKey(m)) { known.put(m, null); if (inboundPeer != null) { matchMessage(m); } for (ShhHandler peer : activePeers) { if (peer != inboundPeer) { peer.sendEnvelope(new ShhEnvelopeMessage(m)); } } } } private void matchMessage(WhisperMessage m) { for (MessageWatcher f : filters) { if (f.match(m.getTo(), m.getFrom(), m.getTopics())) { f.newMessage(m); } } } public static String toIdentity(ECKey key) { return Hex.toHexString(key.getNodeId()); } public static ECKey fromIdentityToPub(String identity) { try { return identity == null ? null : ECKey.fromPublicOnly(ByteUtil.merge(new byte[] {0x04}, Hex.decode(identity))); } catch (Exception e) { throw new RuntimeException("Converting identity '" + identity + "'", e); } } @Override public String addIdentity(ECKey key) { String identity = toIdentity(key); identities.put(identity, key); return identity; } @Override public String newIdentity() { return addIdentity(new ECKey()); } public ECKey getIdentity(String identity) { if (identities.containsKey(identity)) { return identities.get(identity); } return null; } }