/* * Part of the CCNx Java Library. * * Copyright (C) 2008-2012 Palo Alto Research Center, Inc. * * This library is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License version 2.1 * as published by the Free Software Foundation. * This 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 this library; * if not, write to the Free Software Foundation, Inc., 51 Franklin Street, * Fifth Floor, Boston, MA 02110-1301 USA. */ package org.ccnx.ccn.profiles; import org.ccnx.ccn.impl.support.DataUtils; import org.ccnx.ccn.protocol.Component; import org.ccnx.ccn.protocol.ContentName; /** * The command marker prefix allows profiles to register a namespace * for commands, queries, or other special identifiers, beginning with * the special prefix marker byte C1 (COMMAND_MARKER_BYTE). This class * contains operations for representing and storing command marker prefixes * (without arguments or application data). The CommandComponent class * handles full component markers containing arguments and data. * * Commands are separated into namespaces, which are UTF-8 strings, * followed by an operation, which is a UTF-8 string component following * the last "." in the namespace designation. (The "operation" can also * just be considered the last component of the namespace.) The remainder of the * command name component is interpreted by the namespace owner in whatever * manner they choose, with an optional convention to separate (optional) * text arguments from the namespace and operation (and each other) with * the tilde character (~), and a single binary argument * (which must come last) which must be prefixed with %00 for a raw binary * argument, or %C1 for an argument which is CCNB-encoded data. (Multiple * binary arguments can be provided using CCNB-encoding.) * * The namespace designation can contain "." (and so can be broken down by * reverse DNS name, as with Java), " ", and other legal UTF-8 characters. It * ends either with the last ., or at the end of the name component, whichever * comes last. * Namespaces containing only capital letters are reserved for CCN itself, * and are listed here. * * We consider the initial marker byte/namespace/operation component as the "command * marker" and the remainder as arguments (the former refers to a fixed operation, * while the latter may vary across calls to that operation). Methods to parse a name * component encountered in processing either hand back a CommandMarker representing the * namespace/operation, or the arguments; we don't put the arguments into a CommandMarker * object. * * Examples: * * The repository uses a command namespace of "R", and commands like: * start_write: * %C1.R.sw * (where R is the namespace marker, sw is the specific command, and it takes no arguments) * * %C1.org.ccnx.frobnicate~1~37 * would be a command in the namespace "org.ccnx", where the command is "frobnicate", * which takes two arguments, in this case 1 and 37 * * The nonce protocol has only one operation, generating a nonce, with a * random binary argument. * %C1.N%00<binary argument> * * A namespace org.ccnx.foo could have an operation bar, that took a single ccnb-encoded argument: * %C1.org.ccnx.foo.bar%C1<argument> * * or 2 UTF-8 arguments and a binary argument * %C1.org.ccnx.foo.bar~arg1~arg1%00<binary argument> * * For now put the built-in commands here as well, though as we get ones that take * arguments we should start to break them out to profile-specific locations. But * start simple. * * @author rasmusse, smetters * */ public class CommandMarker implements ContentName.ComponentProvider { /** * Reserved bytes. */ public static byte[] CCN_reserved_markers = { (byte)0xC0, (byte)0xC1, (byte)0xF5, (byte)0xF6, (byte)0xF7, (byte)0xF8, (byte)0xF9, (byte)0xFA, (byte)0xFB, (byte)0xFC, (byte)0xFD, (byte)0xFE}; public static final byte COMMAND_PREFIX_BYTE = (byte)0xC1; /** * %C1. */ public static final byte [] COMMAND_PREFIX = {COMMAND_PREFIX_BYTE, (byte)0x2E}; public static final String COMMAND_SEPARATOR = "."; public static final byte COMMAND_SEPARATOR_BYTE = COMMAND_SEPARATOR.getBytes()[0]; public static final String UTF8_ARGUMENT_SEPARATOR = "~"; public static final byte UTF8_ARGUMENT_SEPARATOR_BYTE = UTF8_ARGUMENT_SEPARATOR.getBytes()[0]; public static final byte BINARY_ARGUMENT_SEPARATOR = 0x00; public static final byte CCNB_ARGUMENT_SEPARATOR = (byte)0xC1; /** * (Name) enumeration "marker" */ public static final String ENUMERATION_NAMESPACE = "E"; /** * Basic enumeration command, no arguments, */ public static final CommandMarker COMMAND_MARKER_BASIC_ENUMERATION = commandMarker(ENUMERATION_NAMESPACE, "be"); /** * Repository "marker" */ public static final String REPOSITORY_NAMESPACE = "R"; /** * Sync marker */ public static final String SYNC_NAMESPACE = "S"; /** * Start write command. */ public static final CommandMarker COMMAND_MARKER_REPO_START_WRITE = commandMarker(REPOSITORY_NAMESPACE, "sw"); /** * Checked Start Write: request storage of particular stream if not already held * The name prefix before the command marker component is the base name * The component after the command marker is a nonce * The next component is the starting segment component * The next and final component is the starting segment digest component explicitly * The command marker is in the middle so that the response from repo is not a data * object having another data object full name (digest included) as prefix of its name */ public static final CommandMarker COMMAND_MARKER_REPO_CHECKED_START_WRITE = commandMarker(REPOSITORY_NAMESPACE, "sw-c"); public static final CommandMarker COMMAND_MARKER_REPO_ADD_FILE = commandMarker(REPOSITORY_NAMESPACE, "af"); /** * Some very simple markers that need no other support. See KeyProfile and * MetadataProfile for related core markers that use the Marker namespace. */ /** * Nonce marker */ public static final String NONCE_NAMESPACE = "N"; public static final CommandMarker COMMAND_MARKER_NONCE = commandMarker(NONCE_NAMESPACE, null); /** * Marker for typed binary name components. These aren't general binary name components, but name * components with defined semantics. Specific examples are defined in their own profiles, see * KeyProfile and GuidProfile, as well as markers for access controls. The interpretation of these * should be a) you shouldn't show them to a user unless you really have to, and b) the content of the * marker tells you the type and interpretation of the value. * * Save "B" for general binary name components if we need to go there; * use M for marker. Start by trying to define them in their own profiles; might * have to centralize here for reference. */ public static final String MARKER_NAMESPACE = "M"; /** * GUID marker */ public static final CommandMarker COMMAND_MARKER_GUID = commandMarker(CommandMarker.MARKER_NAMESPACE, "G"); /** * Marker for a name component that is supposed to indicate a scope */ public static final CommandMarker COMMAND_MARKER_SCOPE = CommandMarker.commandMarker(CommandMarker.MARKER_NAMESPACE, "S"); /** * This in practice might be only the prefix, with additional variable arguments added * on the fly. */ protected byte [] _byteCommandMarker; public static final CommandMarker commandMarker(String namespace, String command) { return new CommandMarker(namespace, command); } public static final CommandMarker commandMarker(CommandMarker namespace, String command) { return new CommandMarker(namespace, command); } protected CommandMarker(CommandMarker parent, String operation) { if (null == operation) { _byteCommandMarker = parent.getBytes(); } else { byte [] prefix = parent.getBytes(); StringBuffer sb = new StringBuffer(COMMAND_SEPARATOR); sb.append(operation); byte [] csb = Component.parseNative(sb.toString()); byte [] bc = new byte[csb.length + prefix.length]; System.arraycopy(prefix, 0, bc, 0, prefix.length); System.arraycopy(csb, 0, bc, prefix.length, csb.length); _byteCommandMarker = bc; } } protected CommandMarker(String namespace, String command) { StringBuffer sb = new StringBuffer(namespace); if ((null != command) && (command.length() > 0)) { if ((null != namespace) && (namespace.length() > 0)) { // otherwise use leading . and empty namespace sb.append(COMMAND_SEPARATOR); } sb.append(command); } byte [] csb = Component.parseNative(sb.toString()); byte [] bc = new byte[csb.length + COMMAND_PREFIX.length]; System.arraycopy(COMMAND_PREFIX, 0, bc, 0, COMMAND_PREFIX.length); System.arraycopy(csb, 0, bc, COMMAND_PREFIX.length, csb.length); _byteCommandMarker = bc; } protected CommandMarker(byte [] nameComponent) { if (!isCommandComponent(nameComponent)) { throw new IllegalArgumentException("Not a command marker!"); } _byteCommandMarker = nameComponent; } protected CommandMarker() {} /** * Return binary representation of command marker. * @return */ public byte [] getBytes() { return _byteCommandMarker; } public int length() { return _byteCommandMarker.length; } /** * Returns the initial name components after the first marker of this command marker, * up to the operation (if any). Terminating "." is stripped. If there is only one initial * string component, the operation is interpreted as empty, and that component is returned * as the namespace. We assume we don't have any arguments in our byte array. * @return */ public String getNamespace() { int occurcount = DataUtils.occurcount(_byteCommandMarker, COMMAND_PREFIX.length, COMMAND_SEPARATOR_BYTE); if (occurcount == 0) { // only one component return new String(_byteCommandMarker, COMMAND_PREFIX.length, _byteCommandMarker.length-COMMAND_PREFIX.length); } int lastDot = DataUtils.byterindex(_byteCommandMarker, _byteCommandMarker.length-1, COMMAND_SEPARATOR_BYTE); return new String(_byteCommandMarker, COMMAND_PREFIX.length, lastDot-1-COMMAND_PREFIX.length); } /** * Returns the final string component operation of the prefix of this command marker, * if any (see getNamespace). */ public String getOperation() { int occurcount = DataUtils.occurcount(_byteCommandMarker, COMMAND_PREFIX.length, COMMAND_SEPARATOR_BYTE); if (occurcount == 0) { // only one component return null; } int lastDot = DataUtils.byterindex(_byteCommandMarker, COMMAND_PREFIX.length, COMMAND_SEPARATOR_BYTE); return new String(_byteCommandMarker, lastDot+1, _byteCommandMarker.length - lastDot - 1); } /** * Generate a name component that adds arguments and data to this command marker. * See addArguments and addData if you only need to add one and not the other. * @param arguments * @param applicationData * @return the name component to use containing the command marker and arguments */ protected byte [] addArgumentsAndData(String [] arguments, byte [] applicationData, byte dataMarker) { byte [] csb = null; if ((null != arguments) && (arguments.length > 0)) { StringBuffer sb = new StringBuffer(); for (int i=0; i < arguments.length; ++i) { sb.append(CommandMarker.UTF8_ARGUMENT_SEPARATOR); sb.append(arguments[i]); } csb = Component.parseNative(sb.toString()); } int csblen = ((null != csb) ? csb.length : 0); int addlComponentLength = csblen + ((applicationData != null) ? applicationData.length + 1 : 0); int offset = 0; byte [] component = new byte[length() + addlComponentLength]; System.arraycopy(getBytes(), 0, component, offset, length()); offset += length(); if (csblen > 0) { System.arraycopy(csb, 0, component, offset, csblen); offset += csblen; } if ((null != applicationData) && (applicationData.length > 0)) { component[length()] = dataMarker; offset += 1; System.arraycopy(applicationData, 0, component, offset, applicationData.length); } return component; } /** * Helper method if you just need to add arguments. * @param arguments * @return */ public byte [] addArguments(String [] arguments) { return addArgumentsAndData(arguments, null, (byte)0x00); } /** * Helper method if you just need to add one argument */ public byte [] addArgument(String argument) { return addArguments(new String[]{argument}); } /** * Helper method if you just need to add data. * @param applicationData -- raw binary * @return */ public byte [] addBinaryData(byte [] applicationData) { return addArgumentsAndData(null, applicationData, BINARY_ARGUMENT_SEPARATOR); } /** * Helper method if you just need to add data. * @param applicationData -- ccnb encoded * @return */ public byte [] addCCNBEncodedData(byte [] applicationData) { return addArgumentsAndData(null, applicationData, CCNB_ARGUMENT_SEPARATOR); } public static boolean isCommandComponent(byte [] commandComponent) { return DataUtils.isBinaryPrefix(COMMAND_PREFIX, commandComponent); } /** * Does the prefix of this component match the command bytes of this marker? * @return */ public boolean isMarker(byte [] nameComponent) { return (0 == DataUtils.bytencmp(getBytes(), nameComponent, length())); } /** * Find component that contains the marker * @param name * @return */ public int findMarker(ContentName name) { int nameCount = name.count(); for (int i = 0; i < nameCount; i++) { if (isMarker(name.component(i))) return i; } return -1; } public static CommandMarker getMarker(byte [] nameComponent) { int argumentStart = argumentStart(nameComponent); if (argumentStart < 0) { return new CommandMarker(nameComponent); } return new CommandMarker(DataUtils.subarray(nameComponent, 0, argumentStart)); } public static int argumentStart(byte [] nameComponent) { // Find the point where arguments (text or binary) start after the namespace/op. // Start searching after the command component prefix. int idx = textArgumentStart(nameComponent); if (idx < 0) { idx = binaryArgumentStart(nameComponent); } return idx; } public static int textArgumentStart(byte [] nameComponent) { int idx = DataUtils.byteindex(nameComponent, COMMAND_PREFIX.length, UTF8_ARGUMENT_SEPARATOR_BYTE); return idx; } public static int binaryArgumentStart(byte [] nameComponent) { int idx = DataUtils.byteindex(nameComponent, COMMAND_PREFIX.length, BINARY_ARGUMENT_SEPARATOR); return idx; } /** * Processing application data coming in over the wire according to generic * conventions. Particular markers can instantiate subclasses of CommandMarker * to do more specific processing, or can put that processing in their profiles. * * Here we put generic argument processing and command marker parsing capabilities for * CMs that follow conventions. */ /** * Extract any arguments associated with this prefix. * @param nameComponent * @return null if no arguments, otherwise String [] of text arguments. */ public static String [] getArguments(byte [] nameComponent) { if (!isCommandComponent(nameComponent)) { throw new IllegalArgumentException("Not a command marker!"); } int argumentStart = textArgumentStart(nameComponent); if (argumentStart < 0) { return null; } int argumentEnd = binaryArgumentStart(nameComponent); if (argumentEnd >= 0) { if (argumentEnd <= argumentStart+1) return null; // no Argument or other malformed data } else argumentEnd = nameComponent.length; String argString = new String(nameComponent, argumentStart+1, argumentEnd-argumentStart-1); return argString.split(UTF8_ARGUMENT_SEPARATOR); } public static boolean isCCNBApplicationData(byte [] nameComponent) { if (!isCommandComponent(nameComponent)) { throw new IllegalArgumentException("Not a command marker!"); } int argumentStart = binaryArgumentStart(nameComponent); if (argumentStart < 0) { return false; } return nameComponent[argumentStart] == CCNB_ARGUMENT_SEPARATOR; } public static byte [] extractApplicationData(byte [] nameComponent) { if (!isCommandComponent(nameComponent)) { throw new IllegalArgumentException("Not a command marker!"); } int argumentStart = binaryArgumentStart(nameComponent); if (argumentStart < 0) { return null; } return DataUtils.subarray(nameComponent, argumentStart+1, nameComponent.length - argumentStart - 1); } public byte[] getComponent() { return _byteCommandMarker; } }