/* This code is part of Freenet. It is distributed under the GNU General * Public License, version 2 (or at your option any later version). See * http://www.gnu.org/ for further details of the GPL. */ package freenet.clients.fcp; import java.util.UUID; import freenet.pluginmanager.FredPluginFCPMessageHandler; import freenet.support.SimpleFieldSet; import freenet.support.StringValidityChecker; import freenet.support.api.Bucket; /** * Container class for both incoming and outgoing FCP messages. */ public final class FCPPluginMessage { public static enum ClientPermissions { /** The client is connected by network and the owner of the node has configured * restricted access for the client's IP */ ACCESS_FCP_RESTRICTED, /** The client is connected by network and the owner of the node has configured full * access for the client's IP */ ACCESS_FCP_FULL, /** The client plugin is running within the same node as the server plugin.<br> * This probably should be interpreted as {@link #ACCESS_FCP_FULL}: If the client * plugin is running inside the node, it can probably do whatever it wants. We're * nevertheless shipping this information to you as it is available anyway. */ ACCESS_DIRECT }; /** * The permissions of the client which sent the messages. Null for server-to-client and * outgoing messages.<br> * Will be set by the {@link FCPPluginConnection} before delivery of the message. Thus, you * can pass null for this in all constructors. */ public final ClientPermissions permissions; /** * The unique identifier of the message.<br> * Can be used by server and client to track the progress of messages.<br> * This especially applies to {@link FCPPluginConnection#sendSynchronous(SendDirection, * FCPPluginMessage, long)} which will wait for a reply with the same identifier as the * original message until it returns.<br><br> * * For reply messages, this shall be the same as the identifier of the message to which this * is a reply.<br> * For non-reply message, this shall be a sufficiently random {@link String} to prevent * collisions with any previous message identifiers. The default is a random {@link UUID}, and * alternate implementations are recommended to use a random {@link UUID} as well.<br><br> * * <b>Notice:</b> Custom client implementations can chose the identifier freely when sending * messages, and thus violate these rules. This is highly discouraged though, as non-unique * identifiers make tracking messages impossible. But if a client does violate the rules and * thereby breaks its message tracking, thats not the server's problem, and thus should not * cause complexification of the server code.<br> * So server implementations <b>should</b> assume that the client chooses the identifier in * a sane manner which follows the rules.<br> * This class does follow the rules, and thus client and server implementations using it * will do so as well. */ public final String identifier; /** * Part 1 of the actual message: Human-readable parameters. Shall be small amount of data.<br> * Can be null for data-only or success-indicator messages. */ public final SimpleFieldSet params; /** * Part 2 of the actual message: Non-human readable, large size bulk data.<br> * Can be null if no large amount of data is to be transfered. */ public final Bucket data; /** * For messages which are a reply to another message, this is always non-null. It then * is true if the operation to which this is a reply succeeded, false if it failed.<br> * For non-reply messages, this is always null.<br><br> * * Notice: Whether this is null or non-null is used to determine the return value of * {@link #isReplyMessage()} - a reply message has success != null, a non-reply message has * success == null. */ public final Boolean success; /** * For reply messages with {@link #success} == false, may contain an alpha-numeric String * which identifies a reason for the failure in a standardized representation which software * can parse easily. May also be null in that case, but please try to not do that.<br> * For {@link #success} == null or true, this must be null.<br><br> * * The String shall be for programming purposes and thus <b>must</b> be alpha-numeric.<br> * For unclassified errors, such as Exceptions which you do not expect, use "InternalError". */ public final String errorCode; /** * For reply messages with {@link #errorCode} != null, may contain a String which describes * the problem in a human-readable, user-friendly manner. May also be null in that case, but * please try to not do that.<br> * For {@link #errorCode} == null, this must be null.<br><br> * * You are encouraged to provide it translated to the configured language already.<br> * The String shall not be used for identifying problems in programming.<br> * There, use {@link #errorCode}. * For Exceptions which you do not expect, {@link Exception#toString()} will return a * sufficient errorMessage (containing the name of the Exception and the localized error * message, or non-localized if there is no translation).<br><br> * * (Notice: This may only be non-null if {@link #errorCode} is non-null instead of just * if {@link #success} == false to ensure that a developer-friendly error signaling is * implemented: errorCode is designed to be easy to parse, errorMessage is designed * to be human readable and thus cannot be parsed. Therefore, the errorCode field should be * more mandatory than this field.) */ public final String errorMessage; /** * @return * True if the message is merely a reply to a previous message from your side.<br> * In that case, you <b>must not</b> send another reply message back to prevent infinite * bouncing of "success!" replies. */ public boolean isReplyMessage() { return success != null; } /** * See the JavaDoc of the member variables with the same name as the parameters for an * explanation of the parameters. */ private FCPPluginMessage(ClientPermissions permissions, String identifier, SimpleFieldSet params, Bucket data, Boolean success, String errorCode, String errorMessage) { // See JavaDoc of member variables with the same name for reasons of the requirements assert(permissions != null || permissions == null); assert(identifier != null); assert(params != null || params == null); assert(data != null || data == null); assert(success != null || success == null); assert(params != null || data != null || success != null) : "Messages should not be empty"; assert(errorCode == null || (success != null && success == false)) : "errorCode should only be provided for reply messages which indicate failure."; assert(errorCode == null || StringValidityChecker.isLatinLettersAndNumbersOnly(errorCode)) : "errorCode should be alpha-numeric"; assert(errorMessage == null || errorCode != null) : "errorCode should always be provided if there is an errorMessage"; this.permissions = permissions; this.identifier = identifier; this.params = params; this.data = data; this.success = success; this.errorCode = errorCode; this.errorMessage = errorMessage; } /** * For being used by server or client to construct outgoing messages.<br> * Those can then be passed to the send functions of {@link FCPPluginConnection} or returned in * the message handlers of {@link FredPluginFCPMessageHandler}.<br><br> * * <b>ATTENTION</b>: Messages constructed with this constructor here are <b>not</b> reply * messages.<br> * If you are replying to a message, notably when returning a message in the message handler * interface implementation, you must use {@link #constructReplyMessage(FCPPluginMessage, * SimpleFieldSet, Bucket, boolean, String, String)} (or one of its shortcuts) instead.<br> * <br> * * See the JavaDoc of the member variables with the same name as the parameters for an * explanation of the parameters.<br><br> * * There is a shortcut to this constructor for typical choice of parameters:<br> * {@link #construct()}. */ public static FCPPluginMessage construct(SimpleFieldSet params, Bucket data) { // Notice: While the specification of FCP formally allows the client to freely chose the // ID, we hereby restrict it to be a random UUID instead of allowing the client // (or server) to chose it. This is to prevent accidental collisions with the IDs of // other messages. I cannot think of any usecase of free-choice identifiers. And // collisions are *bad*: They can break the "ACK" mechanism of the "success" variable. // This would in turn break things such as the sendSynchronous() functions of // FCPPluginConnection. return new FCPPluginMessage(null, UUID.randomUUID().toString(), params, data, // success, errorCode, errorMessage are null since non-reply messages must not // indicate errors null, null, null); } /** * Same as {@link #construct(SimpleFieldSet, Bucket)} with the missing parameters being:<br> * <code>SimpleFieldSet params = new SimpleFieldSet(shortLived = true);<br> * Bucket data = null;</code><br><br> * * <b>ATTENTION</b>: Messages constructed with this constructor here are <b>not</b> reply * messages.<br> * If you are replying to a message, notably when returning a message in the message handler * interface implementation, you must use {@link #constructReplyMessage(FCPPluginMessage, * SimpleFieldSet, Bucket, boolean, String, String)} (or one of its shortcuts) instead.<br> */ public static FCPPluginMessage construct() { return construct(new SimpleFieldSet(true), null); } /** * For being used by server or client to construct outgoing messages which are a reply to an * original message.<br> * Those then can be returned from the message handler * {@link FredPluginFCPMessageHandler#handlePluginFCPMessage(FCPPluginConnection, * FCPPluginMessage)}.<br><br> * * See the JavaDoc of the member variables with the same name as the parameters for an * explanation of the parameters.<br><br> * * There are shortcuts to this constructor for typical choice of parameters:<br> * {@link #constructSuccessReply(FCPPluginMessage)}.<br> * {@link #constructErrorReply(FCPPluginMessage, String, String)}.<br> * * @throws IllegalStateException * If the original message was a reply message already.<br> * Replies often shall only indicate success / failure instead of triggering actual * operations, so it could cause infinite bouncing if you reply to them again.<br> * Consider the whole of this as a remote procedure call process: A non-reply message is the * procedure call, a reply message is the procedure result. When receiving the result, the * procedure call is finished, and thus shouldn't cause further replies to be sent.<br> * <b>Notice</b>: The JavaDoc of the aforementioned message handling function explains how * you can nevertheless send a reply to reply messages. */ public static FCPPluginMessage constructReplyMessage(FCPPluginMessage originalMessage, SimpleFieldSet params, Bucket data, boolean success, String errorCode, String errorMessage) { if(originalMessage.isReplyMessage()) { throw new IllegalStateException("Constructing a reply message for a message which " + "was a reply message already not allowed."); } return new FCPPluginMessage(null, originalMessage.identifier, params, data, success, errorCode, errorMessage); } /** * Same as {@link #constructReplyMessage(FCPPluginMessage, SimpleFieldSet, Bucket, boolean, * String, String)} with the missing parameters being:<br> * <code> * SimpleFieldSet params = new SimpleFieldSet(shortLived = true);<br> * Bucket data = null;<br> * boolean success = true;<br> * errorCode = null;<br> * errorMessage = null;<br> * </code> */ public static FCPPluginMessage constructSuccessReply(FCPPluginMessage originalMessage) { return constructReplyMessage( originalMessage, new SimpleFieldSet(true), null, true, null, null); } /** * Same as {@link #constructReplyMessage(FCPPluginMessage, SimpleFieldSet, Bucket, boolean, * String, String)} with the missing parameters being:<br> * <code> * SimpleFieldSet params = new SimpleFieldSet(shortLived = true);<br> * Bucket data = null;<br> * boolean success = false;<br> * </code> */ public static FCPPluginMessage constructErrorReply(FCPPluginMessage originalMessage, String errorCode, String errorMessage) { return constructReplyMessage( originalMessage, new SimpleFieldSet(true), null, false, errorCode, errorMessage); } /** * ATTENTION: Only for being used by internal network code.<br><br> * * You <b>must not</b> use this for constructing outgoing messages in server or client * implementations.<br> * * This function is typically to construct incoming messages for passing them to the message * handling function * {@link FredPluginFCPMessageHandler#handlePluginFCPMessage(FCPPluginConnection, * FCPPluginMessage)}.<br><br> * * See the JavaDoc of the member variables with the same name as the parameters for an * explanation of the parameters.<br> */ static FCPPluginMessage constructRawMessage(ClientPermissions permissions, String identifier, SimpleFieldSet params, Bucket data, Boolean success, String errorCode, String errorMessage) { return new FCPPluginMessage(permissions, identifier, params, data, success, errorCode, errorMessage); } @Override public String toString() { return super.toString() + " (permissions: " + permissions + "; identifier: " + identifier + "; data: " + data + "; success: " + success + "; errorCode: " + errorCode + "; errorMessage: " + errorMessage + // At the end because a SimpleFieldSet usually contains multiple line breaks. "; params: " + '\n' + (params != null ? params.toOrderedString() : null); } }