/* 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.io.IOException;
import java.util.ArrayList;
import junit.framework.TestCase;
import freenet.clients.fcp.FCPPluginConnectionImpl;
import freenet.clients.fcp.FCPPluginClientMessage;
import freenet.clients.fcp.FCPPluginServerMessage;
import freenet.node.FSParseException;
import freenet.support.SimpleFieldSet;
/**
* Tests encoding and decoding of {@link FCPPluginMessage}s into the on-network format:
* - Encodes them via {@link FCPPluginServerMessage}, which is the encoder for sending messages from
* a FCP server plugin to a client which is attached by network.<br>
* - Decodes them via {@link FCPPluginClientMessage}, which is the decoder for decoding messages
* which we have received from a client which is attached by network.<br>
* - Compares whether the decoded version matches the original {@link FCPPluginMessage}.<br><br>
*
* Notice that this test is sort of an abuse: {@link FCPPluginClientMessage} is not meant to decode
* {@link FCPPluginServerMessage}s as they are from the server, not from the client.<br>
* It works because their format is very similar though.<br>
* And it is the only way we have for testing encoding and decoding: There is no decoder for server
* messages and no encoder for client messages because the current architecture of plugin FCP is to
* always have the server plugin run locally in the same node as the FCP code and only allow clients
* to be attached by network, not server plugins.<br>
* See {@link FCPPluginConnectionImpl} for an overview of the architecture.
*/
public final class FCPPluginMessageEncodeDecodeTest extends TestCase {
/**
* Creates different interesting types of {@link FCPPluginMessage}, whose actual encoding and
* decoding is then tested using {@link #testEncodeDecode(FCPPluginMessage)}.
*/
public final void testEncodeDecode() throws MessageInvalidException, IOException, FSParseException {
ArrayList<FCPPluginMessage> messages = new ArrayList<FCPPluginMessage>();
// Non-reply messages. Can either have a SimpleFieldSet, or a Bucket, or both. We don't use
// Buckets because the parsing code for those is higher level code, so we only have to use
// the constructor which creates a SimpleFieldSet and sets the Bucket to null.
messages.add(FCPPluginMessage.construct());
// Reply messages, with all possible allowed combinations of errorCode / errorMessage.
messages.add(FCPPluginMessage.constructErrorReply(FCPPluginMessage.construct(),
"TestErrorCode", "Test errorMessage"));
messages.add(FCPPluginMessage.constructErrorReply(FCPPluginMessage.construct(),
"TestErrorCode", null));
messages.add(FCPPluginMessage.constructErrorReply(FCPPluginMessage.construct(),
null, null));
messages.add(FCPPluginMessage.constructSuccessReply(FCPPluginMessage.construct()));
// Now the messages are created, but their SimpleFieldSet are empty. We test the
// encode-decode cycle with various amount of data in the SFS.
for(FCPPluginMessage message : messages) {
// Non-reply messages cannot have an empty SFS and a null Bucket because then they have
// no meaning at all.
// (Reply messages can be empty because they still contain the boolean success)
if(message.params != null && !message.isReplyMessage()) {
message.params.putOverwrite("key1", "value1");
}
testEncodeDecode(message);
}
for(FCPPluginMessage message : messages) {
if(message.params != null) {
message.params.putOverwrite("key2", "value2");
}
testEncodeDecode(message);
}
for(FCPPluginMessage message : messages) {
if(message.params != null) {
message.params.putOverwrite("key3", "value3 with spaces");
}
testEncodeDecode(message);
}
}
/**
* @see FCPPluginMessageEncodeDecodeTest Explained at class-level JavaDoc of this class.
*/
private final void testEncodeDecode(FCPPluginMessage message)
throws MessageInvalidException, IOException, FSParseException {
SimpleFieldSet encodedMessage
= new FCPPluginServerMessage("testPlugin", message).getFieldSet();
// The params have a different prefix in FCPPluginServerMessage and FCPPluginClientMessage.
// So we have to rename them by removing the sub-SimpleFieldSet with the old prefix and
// re-adding it with the new one.
SimpleFieldSet params = encodedMessage.subset(FCPPluginServerMessage.PARAM_PREFIX);
if(params != null) {
encodedMessage.removeSubset(FCPPluginServerMessage.PARAM_PREFIX);
encodedMessage.put(FCPPluginClientMessage.PARAM_PREFIX, params);
}
FCPPluginMessage decodedMessage
= new FCPPluginClientMessage(encodedMessage).constructFCPPluginMessage();
// Permissions are set by the FCPPluginConnectionImpl when the message is actually delivered
assertEquals(null, decodedMessage.permissions);
assertEquals(message.identifier, decodedMessage.identifier);
// SimpleFieldSet offers no equals(). But its designed to be human readable, so we can
// just encode them into Strings and compare those.
if(message.params == null) {
assertEquals(null, decodedMessage.params);
} else {
assertEquals(message.params.toOrderedString(), decodedMessage.params.toOrderedString());
}
if(message.data == null) {
assertEquals(null, decodedMessage.data);
} else {
// Not implemented yet because the parsing of the data is higher level FCP code; i.e.
// it is not implemented in FCPPluginClientMessage, but one of its parent classes.
throw new UnsupportedOperationException("TODO: Implement");
}
assertEquals(message.success, decodedMessage.success);
assertEquals(message.errorCode, decodedMessage.errorCode);
assertEquals(message.errorMessage, decodedMessage.errorMessage);
}
}