/* * Copyright 2009 Perforce Software Inc., All Rights Reserved. */ package com.perforce.p4java.impl.mapbased.rpc.packet; import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.util.HashMap; import java.util.Map; import java.util.concurrent.atomic.AtomicBoolean; import com.perforce.p4java.Log; import com.perforce.p4java.exception.P4JavaError; import com.perforce.p4java.exception.NullPointerError; import com.perforce.p4java.exception.ProtocolError; import com.perforce.p4java.impl.mapbased.rpc.ExternalEnv; import com.perforce.p4java.impl.mapbased.rpc.func.RpcFunctionMapKey; import com.perforce.p4java.impl.mapbased.rpc.func.RpcFunctionSpec; import com.perforce.p4java.impl.mapbased.rpc.packet.helper.RpcPacketFieldRule; import com.perforce.p4java.server.callback.IFilterCallback; /** * Describes the format of, and implements a bunch of methods for, * Perforce RPC packets as pushed across the wire between * Perforce clients and servers.<p> * * Each RPC packet consists of the following elements (as cribbed from * the C++ API code and byte-level TCP/IP packet analysis done on p4 * server network traffic): * <ul> * <li>a preamble that specifies the length of the rest of the packet * (the payload) plus a simple sanity check checksum; the format and * semantics of the preamble are given in the class RpcPacketPreamble * below. * <li>an optional argument argument or results element containing zero * or more packet fields (described in the RpcPacketField class * Javadoc) used to provide arguments for server-side functions * or to provide results from the server. These fields can be interpreted * as either text fields or byte fields, according to the RPC function * and context. * <li>an optional client environment specifier that spells out client * state, etc.; this is only sent by a client, and only with the * initial user command. * <li>a function field, specifying the function this packet is requesting. * Functions in this usage can be user functions (initial requests * that the Perforce server do something for the end user, e.g. "info"), * or client functions (something the Perforce server is asking the * client to do, e.g. "client message", a request to send the associated * text to the end user), or one of an obscure menagerie of other * functions spelled out elsewhere. * </ul> * All elements except the preamble are actually serialized arrays of text or byte * fields in the format described in the RpcPacketField class. This makes the * overall format fairly uniform and relatively straightforward to pick off * the wire in most cases.<p> * * Note that the order of individual fields within the overall scheme * is theoretically unimportant (with the obvious exception of the preamble, * which has to be first), but there may be unknown server dependencies on * element order, so we try to closely follow the C++ API's ordering in our * implementation.<p> * * Note that RPC packets are not in any way synonymous with TCP * packets or any underlying transport layer packet mechanism -- text * packets can span TCP packets or several RPC packets can be packed into a * single TCP packet, for example.<p> * * */ public class RpcPacket { public static final int DEFAULT_RPC_PACKET_BUFFER_SIZE = 2048; // and why not? /** * Length in bytes of the RPC packet length fields. This is a <i>very</i> * fundamental constant; changing this will probably cause catastrophic * P4Java misbehavior. */ public static final int RPC_LENGTH_FIELD_LENGTH = 4; public static final String TRACE_PREFIX = "RpcPacket"; @SuppressWarnings("unused") private RpcFunctionSpec funcName = null; // NOTE: do not use this field // unless you know what you're doing... private String funcNameString = null; private ExternalEnv env = null; private String[] strArgs = null; private Map<String, Object> mapArgs = null; // for named args going to the wire private Map<String, Object> resultsMap = null; // for results off the wire private int packetLength = 0; /** * Return a four byte array ready for sending across the wire that * represents in Perforce standard wire form the integer passed in.<p> * * Used extensively in the Perforce RPC protocol to encode ints before * sending them across the wire. */ public static byte[] encodeInt4(int i) { byte[] bytes = new byte[RPC_LENGTH_FIELD_LENGTH]; bytes[0] = (byte) ((i / 0x1 ) % 0x100); bytes[1] = (byte) ((i / 0x100 ) % 0x100); bytes[2] = (byte) ((i / 0x10000 ) % 0x100); bytes[3] = (byte) ((i / 0x1000000 ) % 0x100); return bytes; } /** * Decode a Java int from the passed-in 4 byte Perforce encoded * integer value.<p> * * Used extensively in the Perforce RPC protocol to decode ints * coming in off the wire. Note that we have to go to some length * to convince Java that we want unsigned byte (it's amazing that * Java still doesn't have unsigned integral types...); this * helps explain the seemingly-redundant "& 0xFF"'s in the code * below. */ public static int decodeInt4(byte[] bytes) { if (bytes == null) { throw new NullPointerError( "Null bytes passed to RpcPacket.decodeInt"); } if (bytes.length != RPC_LENGTH_FIELD_LENGTH) { throw new ProtocolError( "Bad byte array size in RpcPacket.decodeInt: " + bytes.length); } return ((bytes[0] & 0xFF) * 0x1) + ((bytes[1] & 0xFF) * 0x100) + ((bytes[2] & 0xFF) * 0x10000) + ((bytes[3] & 0xFF) * 0x1000000); } /** * Construct an RPC packet for a user command. * * @param funcName non-null function name * @param args potentially-null function arguments as a string array * @param env potentially-null command environment * @return non-null text packet ready for marshaling (or whatever) */ public static RpcPacket constructRpcPacket( RpcFunctionSpec funcName, String realName, String[] args, ExternalEnv env) { return new RpcPacket(funcName, realName, args, env); } /** * Construct an RPC packet for a user command. * * @param funcName non-null function name * @param args potentially-null function arguments in map form * @param env potentially-null command environment * @return non-null text packet ready for marshaling (or whatever) */ public static RpcPacket constructRpcPacket( RpcFunctionSpec funcName, Map<String, Object> args, ExternalEnv env) { return new RpcPacket(funcName, args, env); } /** * Construct an RPC packet for a user command. * * @param funcName non-null function name * @param args potentially-null function arguments in map form * @param env potentially-null command environment * @return non-null text packet ready for marshaling (or whatever) */ public static RpcPacket constructRpcPacket( String funcName, Map<String, Object> args, ExternalEnv env) { return new RpcPacket(funcName, args, env); } /** * Construct an RPC packet from the passed-in preamble, bytes, and charset. */ public static RpcPacket constructRpcPacket(RpcPacketPreamble preamble, byte[] bytes, boolean isUnicodeServer, Charset charset) { return constructRpcPacket(preamble, bytes, isUnicodeServer, charset, null, null); } /** * Construct an RPC packet from the passed-in preamble, bytes, charset and fieldRule. */ public static RpcPacket constructRpcPacket(RpcPacketPreamble preamble, byte[] bytes, boolean isUnicodeServer, Charset charset, RpcPacketFieldRule fieldRule, IFilterCallback filterCallback) { return new RpcPacket(preamble, bytes, isUnicodeServer, charset, fieldRule, filterCallback); } private RpcPacket(RpcFunctionSpec funcName, String realName, String[] args, ExternalEnv env) { if (funcName == null) { throw new NullPointerError( "Null function name passed to RpcPacket constructor"); } this.funcName = funcName; if (funcName == RpcFunctionSpec.USER_SPECIFIED) { // Special case for relaxed user command checking... this.funcNameString = "user-" + realName; } else { this.funcNameString = funcName.getEncoding(); } this.strArgs = args; this.env = env; } private RpcPacket(RpcFunctionSpec funcName, Map<String, Object> args, ExternalEnv env) { if (funcName == null) { throw new NullPointerError( "Null function name passed to RpcPacket constructor"); } this.funcName = funcName; this.funcNameString = funcName.getEncoding(); this.mapArgs = args; this.env = env; } private RpcPacket(String funcName, Map<String, Object> args, ExternalEnv env) { if (funcName == null) { throw new NullPointerError( "Null function name passed to RpcPacket constructor"); } this.funcNameString = funcName; this.mapArgs = args; this.env = env; } private RpcPacket(Map<String, Object> resultsMap, int packetLength) { this.resultsMap = resultsMap; this.packetLength = packetLength; if (resultsMap != null) { this.funcNameString = (String) resultsMap.get(RpcFunctionMapKey.FUNCTION); } } private RpcPacket(RpcPacketPreamble preamble, byte[] payloadBytes, boolean isUnicodeServer, Charset charset, RpcPacketFieldRule fieldRule, IFilterCallback filterCallback) { if (preamble == null) { throw new NullPointerError("null RPC preamble passed to RpcPacket constructor"); } if (!preamble.isValidChecksum()) { throw new ProtocolError( "Bad checksum in RPC preamble passed to RpcPacket constructor"); } if (payloadBytes == null) { throw new NullPointerError("null payload byte array passed to RpcPacket constructor"); } int payloadLength = preamble.getPayloadSize(); if (payloadLength != payloadBytes.length) { throw new P4JavaError("bad byte array size in RpcPacket constructor; byte array length: " + payloadBytes.length + "; from preamble: " + payloadLength); } try { resultsMap = new HashMap<String, Object>(); ByteBuffer packetBuf = ByteBuffer.wrap(payloadBytes); final Map<String, String> doNotSkipKeysMap = filterCallback != null ? filterCallback.getDoNotSkipKeysMap() : null; final AtomicBoolean skipSubsequent = new AtomicBoolean(false); while (packetBuf.position() < packetBuf.limit()) { Object[] fields = RpcPacketField.retrievePacketField(packetBuf, isUnicodeServer, charset, fieldRule); // Filter callback if (filterCallback != null) { // Check for RPC protocol related keys which cannot be skipped. // Also, check for do-not-skip keys passed-in by the callback. if (!RpcFunctionMapKey.RPC_KEYS_MAP.containsKey((String)fields[RpcPacketField.NAME_FIELD]) && !(doNotSkipKeysMap != null && doNotSkipKeysMap.containsKey((String)fields[RpcPacketField.NAME_FIELD]))) { if (skipSubsequent != null && skipSubsequent.get()) { // Skip continue; } else { if (filterCallback.skip((String)fields[RpcPacketField.NAME_FIELD], fields[RpcPacketField.VALUE_FIELD], skipSubsequent)) { // Skip continue; } } } } if (fields[RpcPacketField.NAME_FIELD] == null) { resultsMap.put(null, fields[RpcPacketField.VALUE_FIELD]); } else { // Assumes zeroth field is always a string; this assumption has yet // to be broken, but keep a good look out in any case... -- HR. String fieldName = (String) fields[RpcPacketField.NAME_FIELD]; // func2 detection below is related to job037970 -- the server is incorrectly // sending *two* func2 fields in certain proxy-related circumstances; only the // first value is correct. This should be fixed in the server, but this quick // fix should help out in the field and with older servers -- HR. if (fieldName.equalsIgnoreCase(RpcFunctionMapKey.FUNC2)) { if (!resultsMap.containsKey(fieldName)) { resultsMap.put(fieldName, fields[RpcPacketField.VALUE_FIELD]); } } else { // Handle multiple fields with the same field name by // appending an incremental number to the end. This // happens in the case of the "istat" command with the // "-a -s" flags. The repeating field names: "change", // "parentChange", "copyParent", "mergeParent", // "mergeHighVal", "branchHash" and "status". if (resultsMap.containsKey(fieldName)) { int suffixCounter = 0; while (true) { if (!resultsMap.containsKey(fieldName + suffixCounter)) { break; } suffixCounter++; } resultsMap.put(fieldName + suffixCounter, fields[RpcPacketField.VALUE_FIELD]); } else { resultsMap.put(fieldName, fields[RpcPacketField.VALUE_FIELD]); } } } } this.packetLength = payloadLength; if (resultsMap != null) { this.funcNameString = (String) resultsMap.get(RpcFunctionMapKey.FUNCTION); } // Callback reset() if (filterCallback != null) { filterCallback.reset(); } } catch (ProtocolError pe) { throw pe; } catch (Throwable thr) { Log.error("Unexpected exception: " + thr.getLocalizedMessage()); Log.exception(thr); throw new ProtocolError(thr.getLocalizedMessage(), thr); } } public Map<String, Object> getResultsMap() { return this.resultsMap; } public void setResultsMap(Map<String, Object> resultsMap) { this.resultsMap = resultsMap; } public String getFuncNameString() { return this.funcNameString; } public void setFuncNameString(String funcNameString) { this.funcNameString = funcNameString; } public int getPacketLength() { return this.packetLength; } public void setPacketLength(int packetLength) { this.packetLength = packetLength; } public String[] getStrArgs() { return this.strArgs; } public void setStrArgs(String[] strArgs) { this.strArgs = strArgs; } public Map<String, Object> getMapArgs() { return this.mapArgs; } public void setMapArgs(Map<String, Object> mapArgs) { this.mapArgs = mapArgs; } public ExternalEnv getEnv() { return this.env; } public void setEnv(ExternalEnv env) { this.env = env; } }
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#1 | 19903 | stuartrowe |
Branching //guest/perforce_software/p4java/... to //guest/stuartrowe/p4java/... |
||
//guest/perforce_software/p4java/r14.1/src/main/java/com/perforce/p4java/impl/mapbased/rpc/packet/RpcPacket.java | |||||
#1 | 12541 | Matt Attaway | Initial add of the 14.1 p4java source code |