/*
* Copyright 2013-2016 Cel Skeggs
*
* This file is part of the CCRE, the Common Chicken Runtime Engine.
*
* The CCRE 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 CCRE 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 CCRE. If not, see <http://www.gnu.org/licenses/>.
*/
package ccre.cluck;
import ccre.log.Logger;
import ccre.verifier.FlowPhase;
import ccre.verifier.SetupPhase;
/**
* A helper class for objects shared on a CluckNode, by providing lots of basic
* implementation for links.
*
* This provides sorting of messages into direct messages, broadcast messages,
* and side-channel messages, provides easy ways to attach to a node, provides
* reasonable default handling for side-channel messages, provides helpers for
* responding to Cluck pings, and provides helpers for validating and sorting
* based on Cluck message headers.
*
* @author skeggsc
* @see CluckRMTSubscriber
*/
public abstract class CluckSubscriber implements CluckLink {
/**
* The CluckNode that this is attached to.
*/
public final CluckNode node;
/**
* The link name of this subscriber.
*/
private String linkName;
/**
* Create a new CluckSubscriber ready to be attached to the specified node.
*
* @param node The CluckNode that this should be shared over.
*/
public CluckSubscriber(CluckNode node) {
if (node == null) {
throw new NullPointerException();
}
this.node = node;
}
/**
* A default implementation of {@link #send(String, String, byte[])} that
* automatically sorts messages into direct messages (to null), broadcast
* messages (to *), and side-channel messages (to a subpath.)
*
* @param dest the destination address, usually expected to be null or the
* broadcast address.
* @param source the source address.
* @param data the data.
* @return always true - never detach.
*
* @see #receive(String, byte[])
* @see #receiveBroadcast(String, byte[])
* @see #receiveSideChannel(String, String, byte[])
*/
@Override
public final boolean send(String dest, String source, byte[] data) {
if (dest == null) {
receive(source, data);
} else if (CluckConstants.BROADCAST_DESTINATION.equals(dest)) {
receiveBroadcast(source, data);
} else {
receiveSideChannel(dest, source, data);
}
return true;
}
/**
* Attach this subscriber to the specified name on the already-attached
* node.
*
* @param name The name to attach with.
*/
@SetupPhase
public final void attach(String name) {
if (name == null) {
throw new NullPointerException();
}
if (linkName != null) {
throw new IllegalStateException("Link name already set!");
}
this.linkName = name;
node.addLink(this, name);
}
/**
* Should be overridden to handle a message sent to something other than
* this node or the broadcast address.
*
* @param dest The destination path.
* @param source The source path.
* @param data The message data.
*/
@FlowPhase
protected void receiveSideChannel(String dest, String source, byte[] data) {
Logger.warning("Unhandled side-channel message sent to " + linkName + " / " + dest + " from " + source + "!");
}
/**
* A handler for a common operation - ensure the message is not null, reply
* to the message if it's a PING message, ensure that the remote type of the
* message is correct, and then return true if all checks succeed.
*
* @param source The source address.
* @param data The message contents.
* @param rmt The remote type of this subscriber.
* @return If this message should be handled as the given remote type.
*/
@FlowPhase
protected boolean requireRMT(String source, byte[] data, byte rmt) {
return requireRMT(source, data, rmt, 1);
}
/**
* A handler for a common operation - ensure the message is not null, reply
* to the message if it's a PING message, ensure that the remote type of the
* message is correct, ensure that the length is okay, and then return true
* if all checks succeed.
*
* @param source The source address.
* @param data The message contents.
* @param rmt The remote type of this subscriber.
* @param minLength The minimum length of the remote.
* @return If this message should be handled as the given remote type.
*/
@FlowPhase
protected boolean requireRMT(String source, byte[] data, byte rmt, int minLength) {
if (data.length == 0) {
Logger.warning("Received null message from " + source);
} else if (data[0] == CluckConstants.RMT_PING && data.length == 1) {
node.transmit(source, linkName, new byte[] { CluckConstants.RMT_PING, rmt });
} else if (data[0] == CluckConstants.RMT_NEGATIVE_ACK) {
// Discard messages saying that the link is closed.
} else if (data[0] != rmt) {
Logger.warning("Received wrong RMT: " + CluckConstants.rmtToString(data[0]) + " from " + source + " (expected " + CluckConstants.rmtToString(rmt) + ") addressed to " + linkName);
} else if (data.length < minLength) {
Logger.warning("Received too-short message from " + source);
} else {
return true;
}
return false;
}
/**
* Default handler for a broadcasted message - reply to it if it's a PING
* message, otherwise ignore it
*
* @param source The source path.
* @param data The message data.
* @param rmt The remote type of this subscriber.
*/
@FlowPhase
protected void defaultBroadcastHandle(String source, byte[] data, byte rmt) {
if (data.length == 1 && data[0] == CluckConstants.RMT_PING) {
node.transmit(source, linkName, new byte[] { CluckConstants.RMT_PING, rmt });
}
}
/**
* Implement to handle messages sent to this subscriber. Usually this is
* wrapped in a conditional of requireRMT.
*
* @param source The source path.
* @param data The message data.
*/
@FlowPhase
protected abstract void receive(String source, byte[] data);
/**
* Implement to handle messages broadcasted to this subscriber. Usually this
* is implemented using defaultBroadcastHandle.
*
* @param source The source path.
* @param data The message data.
*/
@FlowPhase
protected abstract void receiveBroadcast(String source, byte[] data);
}