Add support for RpcTokens, which, if set, are sent with each RPCRequest to
the server. RpcTokens can be used to implement XSRF protection for GWT RPC
calls.

Review at http://gwt-code-reviews.appspot.com/1107801


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@9289 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/rpc/server/RPC.java b/user/src/com/google/gwt/rpc/server/RPC.java
index afd7b47..dc1abc4 100644
--- a/user/src/com/google/gwt/rpc/server/RPC.java
+++ b/user/src/com/google/gwt/rpc/server/RPC.java
@@ -162,7 +162,7 @@
           parameterValues[i] = o;
         }
 
-        return new RPCRequest(method, parameterValues, null, 0);
+        return new RPCRequest(method, parameterValues, null, null, 0);
 
       } catch (NoSuchMethodException e) {
         throw new IncompatibleRemoteServiceException(
diff --git a/user/src/com/google/gwt/user/client/rpc/HasRpcToken.java b/user/src/com/google/gwt/user/client/rpc/HasRpcToken.java
new file mode 100644
index 0000000..fd44963
--- /dev/null
+++ b/user/src/com/google/gwt/user/client/rpc/HasRpcToken.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.user.client.rpc;
+
+/**
+ * An interface implemented by client-side RPC proxy objects. Cast the object
+ * returned from {@link com.google.gwt.core.client.GWT#create(Class)} on a
+ * {@link RemoteService} to this interface to set {@link RpcToken} and
+ * {@link RpcTokenExceptionHandler}.
+ */
+public interface HasRpcToken {
+
+  /**
+   * Return RPC token used with this RPC instance.
+   *
+   * @return RPC token or {@code null} if none set.
+   */
+  RpcToken getRpcToken();
+
+  /**
+   * Return RPC token exception handler used with this RPC instance.
+   *
+   * @return Exception handler or {@code null} if none set.
+   */
+  RpcTokenExceptionHandler getRpcTokenExceptionHandler();
+
+  /**
+   * Sets the {@link RpcToken} to be included with each RPC call.
+   */
+  void setRpcToken(RpcToken token);
+
+  /**
+   * Sets the handler for exceptions that occurred during RPC token processing.
+   */
+  void setRpcTokenExceptionHandler(RpcTokenExceptionHandler handler);
+}
diff --git a/user/src/com/google/gwt/user/client/rpc/RpcToken.java b/user/src/com/google/gwt/user/client/rpc/RpcToken.java
new file mode 100644
index 0000000..5e6dd87
--- /dev/null
+++ b/user/src/com/google/gwt/user/client/rpc/RpcToken.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.user.client.rpc;
+
+import java.io.Serializable;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * An interface for RPC token implementation objects included with each RPC
+ * call. RPC tokens can be used to implement XSRF protection for RPC calls.
+ */
+public interface RpcToken extends Serializable {
+  /**
+   * {@link RemoteService} interfaces specifying {@link RpcToken} implementation
+   * using this annotation will only have serializers for the specific class
+   * generated, as opposed to generating serializers for all {@link RpcToken}
+   * implementations.    
+   */
+  @Target(ElementType.TYPE)
+  @Retention(RetentionPolicy.RUNTIME)
+  public @interface RpcTokenImplementation {
+    String value();
+  }
+}
diff --git a/user/src/com/google/gwt/user/client/rpc/RpcTokenException.java b/user/src/com/google/gwt/user/client/rpc/RpcTokenException.java
new file mode 100644
index 0000000..63bff8c
--- /dev/null
+++ b/user/src/com/google/gwt/user/client/rpc/RpcTokenException.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.user.client.rpc;
+
+/**
+ * Exception that will be passed to the
+ * {@link RpcTokenExceptionHandler#onRpcTokenException(RpcTokenException)}
+ * method when RPC token processing resulted in an error.
+ */
+public class RpcTokenException extends RuntimeException
+    implements IsSerializable {
+  
+  private static final String DEFAULT_MESSAGE = "Invalid RPC token";
+  
+  /**
+   * Constructs an instance with the default message.
+   */
+  public RpcTokenException() {
+    super(DEFAULT_MESSAGE);
+  }
+  
+  /**
+   * Constructs an instance with the specified message.
+   */
+  public RpcTokenException(String msg) {
+    super(DEFAULT_MESSAGE + " (" + msg + ")");
+  }
+}
diff --git a/user/src/com/google/gwt/user/client/rpc/RpcTokenExceptionHandler.java b/user/src/com/google/gwt/user/client/rpc/RpcTokenExceptionHandler.java
new file mode 100644
index 0000000..f34aedd
--- /dev/null
+++ b/user/src/com/google/gwt/user/client/rpc/RpcTokenExceptionHandler.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.user.client.rpc;
+
+/**
+ * Handles an exception produced while processing {@link RpcToken}.
+ */
+public interface RpcTokenExceptionHandler {
+  
+  /**
+   * Process RPC token exception.
+   * 
+   * @param exception exception that occurred during RPC token processing.
+   */
+  public void onRpcTokenException(RpcTokenException exception);
+}
diff --git a/user/src/com/google/gwt/user/client/rpc/impl/AbstractSerializationStream.java b/user/src/com/google/gwt/user/client/rpc/impl/AbstractSerializationStream.java
index cc9d86b..9a8d093 100644
--- a/user/src/com/google/gwt/user/client/rpc/impl/AbstractSerializationStream.java
+++ b/user/src/com/google/gwt/user/client/rpc/impl/AbstractSerializationStream.java
@@ -41,10 +41,9 @@
 
   /**
    * The current RPC protocol version. This version differs from the previous
-   * one in that primitive long values are represented as single-quoted base-64
-   * strings with an alphabet of [A-Za-z0-9$_], rather than as pairs of doubles.
+   * one in that it supports {@links RpcToken}s.
    */
-  public static final int SERIALIZATION_STREAM_VERSION = 6;
+  public static final int SERIALIZATION_STREAM_VERSION = 7;
   
   /**
    * The oldest supported RPC protocol version.
@@ -55,6 +54,16 @@
    * Indicates that obfuscated type names should be used in the RPC payload.
    */
   public static final int FLAG_ELIDE_TYPE_NAMES = 0x1;
+  
+  /**
+   * Indicates that RPC token is included in the RPC payload.
+   */
+  public static final int FLAG_RPC_TOKEN_INCLUDED = 0x2;
+  
+  /**
+   * Bit mask representing all valid flags.
+   */
+  public static final int VALID_FLAGS_MASK = 0x3;
 
   private int flags = DEFAULT_FLAGS;
   private int version = SERIALIZATION_STREAM_VERSION;
@@ -62,6 +71,16 @@
   public final void addFlags(int flags) {
     this.flags |= flags;
   }
+  
+  /**
+   * Checks if flags are valid.
+   * 
+   * @return <code>true</code> if flags are valid and <code>false</code>
+   *         otherwise.
+   */
+  public final boolean areFlagsValid() {
+    return (((flags | VALID_FLAGS_MASK) ^ VALID_FLAGS_MASK) == 0);
+  }
 
   public final int getFlags() {
     return flags;
diff --git a/user/src/com/google/gwt/user/client/rpc/impl/ClientSerializationStreamReader.java b/user/src/com/google/gwt/user/client/rpc/impl/ClientSerializationStreamReader.java
index 83f2e61..6a9b797 100644
--- a/user/src/com/google/gwt/user/client/rpc/impl/ClientSerializationStreamReader.java
+++ b/user/src/com/google/gwt/user/client/rpc/impl/ClientSerializationStreamReader.java
@@ -57,6 +57,11 @@
           + SERIALIZATION_STREAM_VERSION + " from server, got " + getVersion()
           + ".");
     }
+    
+    if (!areFlagsValid()) {
+      throw new IncompatibleRemoteServiceException("Got an unknown flag from "
+          + "server: " + getFlags());
+    }
 
     stringTable = readJavaScriptObject();
   }
diff --git a/user/src/com/google/gwt/user/client/rpc/impl/RemoteServiceProxy.java b/user/src/com/google/gwt/user/client/rpc/impl/RemoteServiceProxy.java
index 849ffe6..f7df811 100644
--- a/user/src/com/google/gwt/user/client/rpc/impl/RemoteServiceProxy.java
+++ b/user/src/com/google/gwt/user/client/rpc/impl/RemoteServiceProxy.java
@@ -21,8 +21,11 @@
 import com.google.gwt.http.client.RequestCallback;
 import com.google.gwt.http.client.RequestException;
 import com.google.gwt.user.client.rpc.AsyncCallback;
+import com.google.gwt.user.client.rpc.HasRpcToken;
 import com.google.gwt.user.client.rpc.InvocationException;
 import com.google.gwt.user.client.rpc.RpcRequestBuilder;
+import com.google.gwt.user.client.rpc.RpcToken;
+import com.google.gwt.user.client.rpc.RpcTokenExceptionHandler;
 import com.google.gwt.user.client.rpc.SerializationException;
 import com.google.gwt.user.client.rpc.SerializationStreamFactory;
 import com.google.gwt.user.client.rpc.SerializationStreamReader;
@@ -37,7 +40,7 @@
  * For internal use only.
  */
 public abstract class RemoteServiceProxy implements SerializationStreamFactory,
-    ServiceDefTarget {
+    ServiceDefTarget, HasRpcToken {
 
   /**
    * The content type to be used in HTTP requests.
@@ -151,6 +154,10 @@
 
   private RpcRequestBuilder rpcRequestBuilder;
 
+  private RpcToken rpcToken;
+
+  private RpcTokenExceptionHandler rpcTokenExceptionHandler;
+  
   /**
    * The name of the serialization policy file specified during construction.
    */
@@ -216,6 +223,20 @@
     return clientSerializationStreamWriter;
   }
   
+  /**
+   * @see ServiceDefTarget#getRpcToken()
+   */
+  public RpcToken getRpcToken() {
+    return rpcToken;
+  }
+  
+  /**
+   * @see ServiceDefTarget#getRpcTokenExceptionHandler()
+   */
+  public RpcTokenExceptionHandler getRpcTokenExceptionHandler() {
+    return rpcTokenExceptionHandler;
+  }  
+  
   public String getSerializationPolicyName() {
     return serializationPolicyName;
   }
@@ -232,17 +253,43 @@
   }
 
   /**
+   * @see HasRpcToken#setRpcToken(RpcToken)
+   */  
+  public void setRpcToken(RpcToken token) {
+    checkRpcTokenType(token); 
+    this.rpcToken = token;
+  }
+  
+  /**
+   * @see HasRpcToken#setRpcTokenExceptionHandler(RpcTokenExceptionHandler)
+   */
+  public void setRpcTokenExceptionHandler(RpcTokenExceptionHandler handler) {
+    this.rpcTokenExceptionHandler = handler;
+  }
+  
+  /**
    * @see ServiceDefTarget#setServiceEntryPoint(String)
    */
   public void setServiceEntryPoint(String url) {
     this.remoteServiceURL = url;
   }
+  
+  /**
+   * This method is overridden by generated proxy classes to ensure that
+   * current service's {@link RpcToken} is of the type specified in {@link
+   * RpcToken.RpcTokenImplementation} annotation.
+   *
+   * @param token currently set {@link RpcToken}.
+   * @throws RpcTokenException if types mismatch.
+   */
+  protected void checkRpcTokenType(RpcToken token) {
+  }
 
   protected <T> RequestCallback doCreateRequestCallback(
       ResponseReader responseReader, String methodName, RpcStatsContext statsContext,
       AsyncCallback<T> callback) {
     return new RequestCallbackAdapter<T>(this, methodName, statsContext,
-        callback, responseReader);
+        callback, getRpcTokenExceptionHandler(), responseReader);
   }
 
   /**
diff --git a/user/src/com/google/gwt/user/client/rpc/impl/RequestCallbackAdapter.java b/user/src/com/google/gwt/user/client/rpc/impl/RequestCallbackAdapter.java
index 6e18d20..d6c3cbf 100644
--- a/user/src/com/google/gwt/user/client/rpc/impl/RequestCallbackAdapter.java
+++ b/user/src/com/google/gwt/user/client/rpc/impl/RequestCallbackAdapter.java
@@ -21,6 +21,8 @@
 import com.google.gwt.user.client.rpc.AsyncCallback;
 import com.google.gwt.user.client.rpc.IncompatibleRemoteServiceException;
 import com.google.gwt.user.client.rpc.InvocationException;
+import com.google.gwt.user.client.rpc.RpcTokenException;
+import com.google.gwt.user.client.rpc.RpcTokenExceptionHandler;
 import com.google.gwt.user.client.rpc.SerializationException;
 import com.google.gwt.user.client.rpc.SerializationStreamFactory;
 import com.google.gwt.user.client.rpc.SerializationStreamReader;
@@ -152,6 +154,11 @@
    * {@link SerializationStreamReader}.
    */
   private final ResponseReader responseReader;
+  
+  /**
+   * {@link RpcTokenExceptionHandler} to notify of token exceptions.
+   */
+  private final RpcTokenExceptionHandler tokenExceptionHandler;
 
   /**
    * {@link SerializationStreamFactory} for creating
@@ -160,7 +167,16 @@
   private final SerializationStreamFactory streamFactory;
 
   public RequestCallbackAdapter(SerializationStreamFactory streamFactory,
-      String methodName, RpcStatsContext statsContext, AsyncCallback<T> callback,
+      String methodName, RpcStatsContext statsContext,
+      AsyncCallback<T> callback, ResponseReader responseReader) {
+    this(streamFactory, methodName, statsContext, callback, null,
+        responseReader);
+  }
+
+  public RequestCallbackAdapter(SerializationStreamFactory streamFactory,
+      String methodName, RpcStatsContext statsContext,
+      AsyncCallback<T> callback,
+      RpcTokenExceptionHandler tokenExceptionHandler,
       ResponseReader responseReader) {
     assert (streamFactory != null);
     assert (callback != null);
@@ -171,6 +187,7 @@
     this.methodName = methodName;
     this.statsContext = statsContext;
     this.responseReader = responseReader;
+    this.tokenExceptionHandler = tokenExceptionHandler;
   }
 
   public void onError(Request request, Throwable exception) {
@@ -213,6 +230,9 @@
     try {
       if (caught == null) {
         callback.onSuccess(result);
+      } else if (tokenExceptionHandler != null &&
+          caught instanceof RpcTokenException) {
+        tokenExceptionHandler.onRpcTokenException((RpcTokenException) caught);
       } else {
         callback.onFailure(caught);
       }
diff --git a/user/src/com/google/gwt/user/rebind/rpc/ProxyCreator.java b/user/src/com/google/gwt/user/rebind/rpc/ProxyCreator.java
index 3da97e0..6e5ac08 100644
--- a/user/src/com/google/gwt/user/rebind/rpc/ProxyCreator.java
+++ b/user/src/com/google/gwt/user/rebind/rpc/ProxyCreator.java
@@ -1,12 +1,12 @@
 /*
  * Copyright 2008 Google Inc.
- * 
+ *
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
  * the License at
- * 
+ *
  * http://www.apache.org/licenses/LICENSE-2.0
- * 
+ *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
@@ -42,6 +42,9 @@
 import com.google.gwt.http.client.RequestBuilder;
 import com.google.gwt.user.client.rpc.IncompatibleRemoteServiceException;
 import com.google.gwt.user.client.rpc.RemoteServiceRelativePath;
+import com.google.gwt.user.client.rpc.RpcToken;
+import com.google.gwt.user.client.rpc.RpcToken.RpcTokenImplementation;
+import com.google.gwt.user.client.rpc.RpcTokenException;
 import com.google.gwt.user.client.rpc.SerializationException;
 import com.google.gwt.user.client.rpc.SerializationStreamWriter;
 import com.google.gwt.user.client.rpc.impl.ClientSerializationStreamWriter;
@@ -94,7 +97,7 @@
       TypeOracle typeOracle,
       SerializableTypeOracleBuilder typesSentFromBrowser,
       SerializableTypeOracleBuilder typesSentToBrowser, JClassType remoteService)
-      throws NotFoundException {
+      throws NotFoundException, UnableToCompleteException {
     logger = logger.branch(TreeLogger.DEBUG, "Analyzing '"
         + remoteService.getParameterizedQualifiedSourceName()
         + "' for serializable types", null);
@@ -103,6 +106,33 @@
 
     JClassType exceptionClass = typeOracle.getType(Exception.class.getName());
 
+    JClassType rteType = typeOracle.getType(RpcTokenException.class.getName());
+    JClassType rpcTokenClass = typeOracle.getType(RpcToken.class.getName());
+    RpcTokenImplementation tokenClassToUse =
+      remoteService.findAnnotationInTypeHierarchy(
+      RpcTokenImplementation.class);
+    if (tokenClassToUse != null) {
+      // only include serializer for the specified class literal
+      JClassType rpcTokenType = typeOracle.getType(tokenClassToUse.value());
+      if (rpcTokenType.isAssignableTo(rpcTokenClass)) {
+        typesSentFromBrowser.addRootType(logger, rpcTokenType);
+        typesSentToBrowser.addRootType(logger, rteType);
+      } else {
+        logger.branch(TreeLogger.ERROR,
+            "RPC token class " + tokenClassToUse.value() + " must implement " +
+            RpcToken.class.getName(), null);
+        throw new UnableToCompleteException();
+      }
+    } else {
+      JClassType[] rpcTokenSubclasses = rpcTokenClass.getSubtypes();
+      for (JClassType rpcTokenSubclass : rpcTokenSubclasses) {
+        typesSentFromBrowser.addRootType(logger, rpcTokenSubclass);
+      }
+      if (rpcTokenSubclasses.length > 0) {
+        typesSentToBrowser.addRootType(logger, rteType);
+      }
+    }
+
     TreeLogger validationLogger = logger.branch(TreeLogger.DEBUG,
         "Analyzing methods:", null);
     for (JMethod method : methods) {
@@ -213,7 +243,7 @@
 
   /**
    * Creates the client-side proxy class.
-   * 
+   *
    * @throws UnableToCompleteException
    */
   public String create(TreeLogger logger, GeneratorContext context)
@@ -314,12 +344,13 @@
 
     generateProxyContructor(srcWriter);
 
-    generateProxyMethods(srcWriter, typesSentFromBrowser,
+    generateProxyMethods(srcWriter, typesSentFromBrowser, typeOracle,
         syncMethToAsyncMethMap);
 
-    if (elideTypeNames) {
-      generateStreamWriterOverride(srcWriter);
-    }
+    generateStreamWriterOverride(srcWriter);
+
+    generateCheckRpcTokenTypeOverride(srcWriter, typeOracle,
+        typesSentFromBrowser);
 
     srcWriter.commit(logger);
 
@@ -359,6 +390,38 @@
     return typeName == null ? null : ('"' + typeName + '"');
   }
 
+  protected void generateCheckRpcTokenTypeOverride(SourceWriter srcWriter,
+      TypeOracle typeOracle, SerializableTypeOracle typesSentFromBrowser) {
+    JClassType rpcTokenType = typeOracle.findType(RpcToken.class.getName());
+    JClassType[] rpcTokenSubtypes = rpcTokenType.getSubtypes();
+    String rpcTokenImplementation = "";
+    for (JClassType rpcTokenSubtype : rpcTokenSubtypes) {
+      if (typesSentFromBrowser.isSerializable(rpcTokenSubtype)) {
+        if (!rpcTokenImplementation.isEmpty()) {
+          // >1 implematation of RpcToken, bail
+          rpcTokenImplementation = "";
+          break;
+        } else {
+          rpcTokenImplementation = rpcTokenSubtype.getQualifiedSourceName();
+        }
+      }
+    }
+    if (!rpcTokenImplementation.isEmpty()) {
+      srcWriter.println("@Override");
+      srcWriter.println("protected void checkRpcTokenType(RpcToken token) {");
+      srcWriter.indent();
+      srcWriter.println("if (!(token instanceof " + rpcTokenImplementation
+          + ")) {");
+      srcWriter.indent();
+      srcWriter.println("throw new RpcTokenException(\"Invalid RpcToken type: "
+          + "expected '" + rpcTokenImplementation + "' but got '\" + "
+          + "token.getClass() + \"'\");");
+      srcWriter.outdent();
+      srcWriter.println("}");
+      srcWriter.outdent();
+      srcWriter.println("}");
+    }
+  }
   /**
    * Generate the proxy constructor and delegate to the superclass constructor
    * using the default address for the
@@ -379,7 +442,7 @@
 
   /**
    * Generate any fields required by the proxy.
-   * 
+   *
    * @param serializableTypeOracle the type oracle
    */
   protected void generateProxyFields(SourceWriter srcWriter,
@@ -398,12 +461,12 @@
 
   /**
    * Generates the client's asynchronous proxy method.
-   * 
+   *
    * @param serializableTypeOracle the type oracle
    */
   protected void generateProxyMethod(SourceWriter w,
-      SerializableTypeOracle serializableTypeOracle, JMethod syncMethod,
-      JMethod asyncMethod) {
+      SerializableTypeOracle serializableTypeOracle, TypeOracle typeOracle,
+      JMethod syncMethod, JMethod asyncMethod) {
 
     w.println();
 
@@ -459,6 +522,12 @@
     w.println("try {");
     w.indent();
 
+    w.println("if (getRpcToken() != null) {");
+    w.indent();
+    w.println(streamWriterName + ".writeObject(getRpcToken());");
+    w.outdent();
+    w.println("}");
+
     w.println(streamWriterName + ".writeString(REMOTE_SERVICE_INTERFACE_NAME);");
 
     // Write the method name
@@ -552,7 +621,7 @@
   }
 
   protected void generateProxyMethods(SourceWriter w,
-      SerializableTypeOracle serializableTypeOracle,
+      SerializableTypeOracle serializableTypeOracle, TypeOracle typeOracle,
       Map<JMethod, JMethod> syncMethToAsyncMethMap) {
     JMethod[] syncMethods = serviceIntf.getOverridableMethods();
     for (JMethod syncMethod : syncMethods) {
@@ -575,7 +644,8 @@
         }
       }
 
-      generateProxyMethod(w, serializableTypeOracle, syncMethod, asyncMethod);
+      generateProxyMethod(w, serializableTypeOracle, typeOracle, syncMethod,
+          asyncMethod);
     }
   }
 
@@ -599,7 +669,16 @@
      */
     srcWriter.println("ClientSerializationStreamWriter toReturn =");
     srcWriter.indentln("(ClientSerializationStreamWriter) super.createStreamWriter();");
-    srcWriter.println("toReturn.addFlags(ClientSerializationStreamWriter.FLAG_ELIDE_TYPE_NAMES);");
+    if (elideTypeNames) {
+      srcWriter.println("toReturn.addFlags(ClientSerializationStreamWriter."
+          + "FLAG_ELIDE_TYPE_NAMES);");
+    }
+    srcWriter.println("if (getRpcToken() != null) {");
+    srcWriter.indent();
+    srcWriter.println("toReturn.addFlags(ClientSerializationStreamWriter."
+        + "FLAG_RPC_TOKEN_INCLUDED);");
+    srcWriter.outdent();
+    srcWriter.println("}");
     srcWriter.println("return toReturn;");
     srcWriter.outdent();
     srcWriter.println("}");
@@ -814,6 +893,8 @@
         SerializationStreamWriter.class.getCanonicalName(),
         GWT.class.getCanonicalName(), ResponseReader.class.getCanonicalName(),
         SerializationException.class.getCanonicalName(),
+        RpcToken.class.getCanonicalName(),
+        RpcTokenException.class.getCanonicalName(),
         Impl.class.getCanonicalName(),
         RpcStatsContext.class.getCanonicalName()};
     for (String imp : imports) {
diff --git a/user/src/com/google/gwt/user/server/rpc/HybridServiceServlet.java b/user/src/com/google/gwt/user/server/rpc/HybridServiceServlet.java
index 39e7b22..9d7c619 100644
--- a/user/src/com/google/gwt/user/server/rpc/HybridServiceServlet.java
+++ b/user/src/com/google/gwt/user/server/rpc/HybridServiceServlet.java
@@ -18,6 +18,7 @@
 import com.google.gwt.rpc.server.ClientOracle;
 import com.google.gwt.rpc.server.RpcServlet;
 import com.google.gwt.user.client.rpc.IncompatibleRemoteServiceException;
+import com.google.gwt.user.client.rpc.RpcTokenException;
 import com.google.gwt.user.client.rpc.SerializationException;
 
 import java.io.IOException;
@@ -132,6 +133,10 @@
           "An IncompatibleRemoteServiceException was thrown while processing this call.",
           ex);
       return RPC.encodeResponseForFailure(null, ex);
+    } catch (RpcTokenException tokenException) {
+      log("An RpcTokenException was thrown while processing this call.",
+          tokenException);
+      return RPC.encodeResponseForFailure(null, tokenException);
     }
   }
 
diff --git a/user/src/com/google/gwt/user/server/rpc/RPC.java b/user/src/com/google/gwt/user/server/rpc/RPC.java
index ae49bab..e0a292b 100644
--- a/user/src/com/google/gwt/user/server/rpc/RPC.java
+++ b/user/src/com/google/gwt/user/server/rpc/RPC.java
@@ -17,6 +17,7 @@
 
 import com.google.gwt.user.client.rpc.IncompatibleRemoteServiceException;
 import com.google.gwt.user.client.rpc.RemoteService;
+import com.google.gwt.user.client.rpc.RpcToken;
 import com.google.gwt.user.client.rpc.SerializationException;
 import com.google.gwt.user.client.rpc.impl.AbstractSerializationStream;
 import com.google.gwt.user.server.rpc.impl.LegacySerializationPolicy;
@@ -235,6 +236,12 @@
           classLoader, serializationPolicyProvider);
       streamReader.prepareToRead(encodedRequest);
 
+      RpcToken rpcToken = null;
+      if (streamReader.hasFlags(AbstractSerializationStream.FLAG_RPC_TOKEN_INCLUDED)) {
+        // Read the RPC token
+        rpcToken = (RpcToken) streamReader.deserializeValue(RpcToken.class);
+      }
+            
       // Read the name of the RemoteService interface
       String serviceIntfName = maybeDeobfuscate(streamReader,
           streamReader.readString());
@@ -296,8 +303,8 @@
           parameterValues[i] = streamReader.deserializeValue(parameterTypes[i]);
         }
 
-        return new RPCRequest(method, parameterValues, serializationPolicy,
-            streamReader.getFlags());
+        return new RPCRequest(method, parameterValues, rpcToken,
+            serializationPolicy, streamReader.getFlags());
 
       } catch (NoSuchMethodException e) {
         throw new IncompatibleRemoteServiceException(
diff --git a/user/src/com/google/gwt/user/server/rpc/RPCRequest.java b/user/src/com/google/gwt/user/server/rpc/RPCRequest.java
index 8f14fdd..d673500 100644
--- a/user/src/com/google/gwt/user/server/rpc/RPCRequest.java
+++ b/user/src/com/google/gwt/user/server/rpc/RPCRequest.java
@@ -15,6 +15,8 @@
  */
 package com.google.gwt.user.server.rpc;
 
+import com.google.gwt.user.client.rpc.RpcToken;
+
 import java.lang.reflect.Method;
 
 /**
@@ -37,6 +39,11 @@
    * The parameters for this request.
    */
   private final Object[] parameters;
+  
+  /**
+   * The RPC token for this request.
+   */
+  private final RpcToken rpcToken;
 
   /**
    * {@link SerializationPolicy} used for decoding this request and for encoding
@@ -48,9 +55,10 @@
    * Construct an RPCRequest.
    */
   public RPCRequest(Method method, Object[] parameters,
-      SerializationPolicy serializationPolicy, int flags) {
+      RpcToken rpcToken, SerializationPolicy serializationPolicy, int flags) {
     this.method = method;
     this.parameters = parameters;
+    this.rpcToken = rpcToken;
     this.serializationPolicy = serializationPolicy;
     this.flags = flags;
   }
@@ -74,6 +82,13 @@
   }
 
   /**
+   * Get the request's RPC token.
+   */
+  public RpcToken getRpcToken() {
+    return rpcToken;
+  }
+  
+  /**
    * Returns the {@link SerializationPolicy} used to decode this request. This
    * is also the <code>SerializationPolicy</code> that should be used to encode
    * responses.
diff --git a/user/src/com/google/gwt/user/server/rpc/RemoteServiceServlet.java b/user/src/com/google/gwt/user/server/rpc/RemoteServiceServlet.java
index 0605721..9bddd66 100644
--- a/user/src/com/google/gwt/user/server/rpc/RemoteServiceServlet.java
+++ b/user/src/com/google/gwt/user/server/rpc/RemoteServiceServlet.java
@@ -16,6 +16,7 @@
 package com.google.gwt.user.server.rpc;
 
 import com.google.gwt.user.client.rpc.IncompatibleRemoteServiceException;
+import com.google.gwt.user.client.rpc.RpcTokenException;
 import com.google.gwt.user.client.rpc.SerializationException;
 
 import java.io.IOException;
@@ -212,6 +213,10 @@
           "An IncompatibleRemoteServiceException was thrown while processing this call.",
           ex);
       return RPC.encodeResponseForFailure(null, ex);
+    } catch (RpcTokenException tokenException) {
+      log("An RpcTokenException was thrown while processing this call.",
+          tokenException);
+      return RPC.encodeResponseForFailure(null, tokenException);
     }
   }
 
diff --git a/user/src/com/google/gwt/user/server/rpc/impl/ServerSerializationStreamReader.java b/user/src/com/google/gwt/user/server/rpc/impl/ServerSerializationStreamReader.java
index 4b642e1..3b5da63 100644
--- a/user/src/com/google/gwt/user/server/rpc/impl/ServerSerializationStreamReader.java
+++ b/user/src/com/google/gwt/user/server/rpc/impl/ServerSerializationStreamReader.java
@@ -437,9 +437,14 @@
           + SERIALIZATION_STREAM_VERSION + " from client, got " + getVersion()
           + ".");
     }
+    
+    // Check the flags
+    if (!areFlagsValid()) {
+      throw new IncompatibleRemoteServiceException("Got an unknown flag from "
+          + "client: " + getFlags());
+    }
 
     // Read the type name table
-    //
     deserializeStringTable();
 
     // Write the serialization policy info
diff --git a/user/test/com/google/gwt/user/RPCSuite.gwt.xml b/user/test/com/google/gwt/user/RPCSuite.gwt.xml
index 2360e11..0a1658c 100644
--- a/user/test/com/google/gwt/user/RPCSuite.gwt.xml
+++ b/user/test/com/google/gwt/user/RPCSuite.gwt.xml
@@ -33,6 +33,10 @@
     class='com.google.gwt.user.server.rpc.ValueTypesTestServiceImpl' />
   <servlet path='/servlettest'
     class='com.google.gwt.user.server.rpc.RemoteServiceServletTestServiceImpl' />
+  <servlet path='/rpctokentest'
+    class='com.google.gwt.user.server.rpc.RpcTokenServiceImpl' />
+  <servlet path='/rpctokentest-annotation'
+    class='com.google.gwt.user.server.rpc.AnnotatedRpcTokenTestServiceImpl' />
   <servlet path='/unicodeEscape'
     class='com.google.gwt.user.server.rpc.UnicodeEscapingServiceImpl' />
 
diff --git a/user/test/com/google/gwt/user/RPCSuite.java b/user/test/com/google/gwt/user/RPCSuite.java
index 9821dc0..8f0182a 100644
--- a/user/test/com/google/gwt/user/RPCSuite.java
+++ b/user/test/com/google/gwt/user/RPCSuite.java
@@ -38,6 +38,7 @@
 import com.google.gwt.user.client.rpc.InheritanceTestWithTypeObfuscation;
 import com.google.gwt.user.client.rpc.ObjectGraphTest;
 import com.google.gwt.user.client.rpc.ObjectGraphTestWithTypeObfuscation;
+import com.google.gwt.user.client.rpc.RpcTokenTest;
 import com.google.gwt.user.client.rpc.RunTimeSerializationErrorsTest;
 import com.google.gwt.user.client.rpc.UnicodeEscapingTest;
 import com.google.gwt.user.client.rpc.UnicodeEscapingTestWithTypeObfuscation;
@@ -97,6 +98,7 @@
     suite.addTestSuite(CustomFieldSerializerTest.class);
     suite.addTestSuite(ObjectGraphTest.class);
     suite.addTestSuite(com.google.gwt.user.client.rpc.RemoteServiceServletTest.class);
+    suite.addTestSuite(RpcTokenTest.class);
     suite.addTestSuite(UnicodeEscapingTest.class);
     suite.addTestSuite(RunTimeSerializationErrorsTest.class);
 
diff --git a/user/test/com/google/gwt/user/client/rpc/AnnotatedRpcTokenTestService.java b/user/test/com/google/gwt/user/client/rpc/AnnotatedRpcTokenTestService.java
new file mode 100644
index 0000000..e2c8d02
--- /dev/null
+++ b/user/test/com/google/gwt/user/client/rpc/AnnotatedRpcTokenTestService.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.user.client.rpc;
+
+import com.google.gwt.user.client.rpc.RpcToken.RpcTokenImplementation;
+
+/**
+ * Version of {@link RpcTokenTestService} annotated with
+ * {@link RpcToken.RpcTokenImplementation}. 
+ */
+@RpcTokenImplementation(
+    "com.google.gwt.user.client.rpc.RpcTokenTest.AnotherTestRpcToken")
+public interface AnnotatedRpcTokenTestService extends RpcTokenTestService {
+}
diff --git a/user/test/com/google/gwt/user/client/rpc/AnnotatedRpcTokenTestServiceAsync.java b/user/test/com/google/gwt/user/client/rpc/AnnotatedRpcTokenTestServiceAsync.java
new file mode 100644
index 0000000..f5bd750
--- /dev/null
+++ b/user/test/com/google/gwt/user/client/rpc/AnnotatedRpcTokenTestServiceAsync.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.user.client.rpc;
+
+/**
+ * Async peer of {@link AnnotatedRpcTokenTestService}.
+ */
+public interface AnnotatedRpcTokenTestServiceAsync {
+
+  void test(AsyncCallback<Void> callback);
+  
+  void capitalize(String input, AsyncCallback<String> callback);
+  
+  void getRpcTokenFromRequest(AsyncCallback<RpcToken> callback);
+}
diff --git a/user/test/com/google/gwt/user/client/rpc/RpcTokenTest.java b/user/test/com/google/gwt/user/client/rpc/RpcTokenTest.java
new file mode 100644
index 0000000..60ceba2
--- /dev/null
+++ b/user/test/com/google/gwt/user/client/rpc/RpcTokenTest.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.user.client.rpc;
+
+import com.google.gwt.core.client.GWT;
+
+/**
+ * Tests RpcToken functionality.
+ */
+public class RpcTokenTest extends RpcTestBase {
+
+  /**
+   * First RpcToken implementation
+   */
+  public static class TestRpcToken implements RpcToken {
+    String tokenValue;
+  }
+
+  /**
+   * Second RpcToken implementation
+   */
+  public static class AnotherTestRpcToken implements RpcToken {
+    int token;
+  }
+
+  protected static RpcTokenTestServiceAsync getAsyncService() {
+    RpcTokenTestServiceAsync service =
+      (RpcTokenTestServiceAsync) GWT.create(RpcTokenTestService.class);
+
+    ((ServiceDefTarget) service).setServiceEntryPoint(GWT.getModuleBaseURL()
+        + "rpctokentest");
+
+    return service;
+  }
+
+  protected static AnnotatedRpcTokenTestServiceAsync getAnnotatedAsyncService() {
+    AnnotatedRpcTokenTestServiceAsync service =
+      (AnnotatedRpcTokenTestServiceAsync) GWT.create(AnnotatedRpcTokenTestService.class);
+
+    ((ServiceDefTarget) service).setServiceEntryPoint(GWT.getModuleBaseURL()
+        + "rpctokentest-annotation");
+
+    return service;
+  }
+
+  public void testRpcTokenMissing() {
+    RpcTokenTestServiceAsync service = getAsyncService();
+
+    delayTestFinishForRpc();
+
+    service.getRpcTokenFromRequest(new AsyncCallback<RpcToken>() {
+
+      public void onFailure(Throwable caught) {
+        TestSetValidator.rethrowException(caught);
+      }
+
+      public void onSuccess(RpcToken token) {
+        assertNull(token);
+        finishTest();
+      }
+    });
+  }
+
+  public void testRpcTokenPresent() {
+    RpcTokenTestServiceAsync service = getAsyncService();
+
+    final TestRpcToken token = new TestRpcToken();
+    token.tokenValue = "Drink kumys!";
+    ((HasRpcToken) service).setRpcToken(token);
+
+    delayTestFinishForRpc();
+
+    service.getRpcTokenFromRequest(new AsyncCallback<RpcToken>() {
+
+      public void onFailure(Throwable caught) {
+        TestSetValidator.rethrowException(caught);
+      }
+
+      public void onSuccess(RpcToken rpcToken) {
+        assertNotNull(rpcToken);
+        assertTrue(rpcToken instanceof TestRpcToken);
+        assertEquals(token.tokenValue, ((TestRpcToken) rpcToken).tokenValue);
+        finishTest();
+      }
+    });
+  }
+
+  public void testRpcTokenExceptionHandler() {
+    RpcTokenTestServiceAsync service = getAsyncService();
+    ((ServiceDefTarget) service).setServiceEntryPoint(GWT.getModuleBaseURL()
+        + "rpctokentest?throw=true");
+    ((HasRpcToken) service).setRpcTokenExceptionHandler(new RpcTokenExceptionHandler() {
+      public void onRpcTokenException(RpcTokenException exception) {
+        assertNotNull(exception);
+        finishTest();
+      }
+    });
+
+    delayTestFinishForRpc();
+
+    service.getRpcTokenFromRequest(new AsyncCallback<RpcToken>() {
+
+      public void onFailure(Throwable caught) {
+        TestSetValidator.rethrowException(caught);
+      }
+
+      public void onSuccess(RpcToken rpcToken) {
+        fail("Should've called RpcTokenExceptionHandler");
+      }
+    });
+  }
+
+  public void testRpcTokenAnnotationDifferentFromActualType() {
+    AnnotatedRpcTokenTestServiceAsync service = getAnnotatedAsyncService();
+
+    // service is annotated to use AnotherTestRpcToken and not TestRpcToken,
+    // generated proxy should catch this error
+    final TestRpcToken token = new TestRpcToken();
+    token.tokenValue = "Drink kumys!";
+    try {
+      ((HasRpcToken) service).setRpcToken(token);
+      fail("Should have thrown an RpcTokenException");
+    } catch (RpcTokenException e) {
+      // Expected
+    }
+  }
+
+  public void testRpcTokenAnnotation() {
+    AnnotatedRpcTokenTestServiceAsync service = getAnnotatedAsyncService();
+
+    final AnotherTestRpcToken token = new AnotherTestRpcToken();
+    token.token = 1337;
+    ((HasRpcToken) service).setRpcToken(token);
+
+    service.getRpcTokenFromRequest(new AsyncCallback<RpcToken>() {
+
+      public void onSuccess(RpcToken result) {
+        assertNotNull(result);
+        assertTrue(result instanceof AnotherTestRpcToken);
+        assertEquals(token.token, ((AnotherTestRpcToken) result).token);
+        finishTest();
+      }
+
+      public void onFailure(Throwable caught) {
+        TestSetValidator.rethrowException(caught);
+      }
+    });
+  }
+}
diff --git a/user/test/com/google/gwt/user/client/rpc/RpcTokenTestService.java b/user/test/com/google/gwt/user/client/rpc/RpcTokenTestService.java
new file mode 100644
index 0000000..dd7e017
--- /dev/null
+++ b/user/test/com/google/gwt/user/client/rpc/RpcTokenTestService.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.user.client.rpc;
+
+/**
+ * Remote service for testing RPC tokens support.
+ */
+public interface RpcTokenTestService extends RemoteService {
+  
+  void test();
+  
+  String capitalize(String input);
+  
+  RpcToken getRpcTokenFromRequest();
+}
diff --git a/user/test/com/google/gwt/user/client/rpc/RpcTokenTestServiceAsync.java b/user/test/com/google/gwt/user/client/rpc/RpcTokenTestServiceAsync.java
new file mode 100644
index 0000000..f9e7e75
--- /dev/null
+++ b/user/test/com/google/gwt/user/client/rpc/RpcTokenTestServiceAsync.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.user.client.rpc;
+
+/**
+ * Async peer of {@link RpcTokenTestService}.
+ */
+public interface RpcTokenTestServiceAsync {
+  
+  void test(AsyncCallback<Void> callback);
+  
+  void capitalize(String input, AsyncCallback<String> callback);
+  
+  void getRpcTokenFromRequest(AsyncCallback<RpcToken> callback);
+}
diff --git a/user/test/com/google/gwt/user/server/rpc/AnnotatedRpcTokenTestServiceImpl.java b/user/test/com/google/gwt/user/server/rpc/AnnotatedRpcTokenTestServiceImpl.java
new file mode 100644
index 0000000..1c18751
--- /dev/null
+++ b/user/test/com/google/gwt/user/server/rpc/AnnotatedRpcTokenTestServiceImpl.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.user.server.rpc;
+
+import com.google.gwt.user.client.rpc.AnnotatedRpcTokenTestService;
+
+/**
+ * Remote service for testing RPC tokens support annotated with
+ * {@link RpcTokenImplementation.Class}.
+ */
+public class AnnotatedRpcTokenTestServiceImpl extends RpcTokenServiceImpl
+    implements AnnotatedRpcTokenTestService {
+}
diff --git a/user/test/com/google/gwt/user/server/rpc/RPCRequestTest.java b/user/test/com/google/gwt/user/server/rpc/RPCRequestTest.java
index 5aa9aa9..b06b165 100644
--- a/user/test/com/google/gwt/user/server/rpc/RPCRequestTest.java
+++ b/user/test/com/google/gwt/user/server/rpc/RPCRequestTest.java
@@ -43,7 +43,7 @@
 
     // Test simple case
     Object params[] = new Object[] {"abcdefg", 1234};
-    RPCRequest request = new RPCRequest(method, params, null, 0);
+    RPCRequest request = new RPCRequest(method, params, null, null, 0);
     String strRequest = request.toString();
     assertEquals("com.google.gwt.user.server.rpc.RPCRequestTest$"
         + "MockRequestImplementation.doSomething(\"abcdefg\", 1234)",
@@ -51,7 +51,7 @@
 
     // Test case with a string that needs escaping
     Object params2[] = new Object[] {"ab\"cde\"fg", 1234};
-    RPCRequest request2 = new RPCRequest(method, params2, null, 0);
+    RPCRequest request2 = new RPCRequest(method, params2, null, null, 0);
     String strRequest2 = request2.toString();
     assertEquals("com.google.gwt.user.server.rpc.RPCRequestTest$"
         + "MockRequestImplementation.doSomething(\"ab\\\"cde\\\"fg\", 1234)",
diff --git a/user/test/com/google/gwt/user/server/rpc/RPCTest.java b/user/test/com/google/gwt/user/server/rpc/RPCTest.java
index bc63ab6..6bc88c2 100644
--- a/user/test/com/google/gwt/user/server/rpc/RPCTest.java
+++ b/user/test/com/google/gwt/user/server/rpc/RPCTest.java
@@ -20,6 +20,7 @@
 import com.google.gwt.user.client.rpc.IncompatibleRemoteServiceException;
 import com.google.gwt.user.client.rpc.IsSerializable;
 import com.google.gwt.user.client.rpc.RemoteService;
+import com.google.gwt.user.client.rpc.RpcToken;
 import com.google.gwt.user.client.rpc.SerializableException;
 import com.google.gwt.user.client.rpc.SerializationException;
 import com.google.gwt.user.client.rpc.impl.AbstractSerializationStream;
@@ -233,6 +234,24 @@
       "1" + RPC_SEPARATOR_CHAR + // param count
       "5" + RPC_SEPARATOR_CHAR + // 'J' == long param type
       "P7cuph2VDIQ" + RPC_SEPARATOR_CHAR; // long in base-64 encoding
+  
+  private static final String VALID_V6_ENCODED_REQUEST_WITH_INVALID_FLAGS = ""
+      + AbstractSerializationStream.SERIALIZATION_STREAM_VERSION +
+      RPC_SEPARATOR_CHAR + // version
+      "123" + RPC_SEPARATOR_CHAR + // flags
+      "5" + RPC_SEPARATOR_CHAR + // string table count
+      "moduleBaseUrl" + RPC_SEPARATOR_CHAR + // string table entry #1
+      "whitelistHashCode" + RPC_SEPARATOR_CHAR + // string table entry #2
+      D.class.getName() + RPC_SEPARATOR_CHAR + // string table entry #3
+      "echo" + RPC_SEPARATOR_CHAR + // string table entry #4
+      "J" + RPC_SEPARATOR_CHAR + // string table entry #5
+      "1" + RPC_SEPARATOR_CHAR + // moduleBaseUrl
+      "2" + RPC_SEPARATOR_CHAR + // whitelist hashcode
+      "3" + RPC_SEPARATOR_CHAR + // interface name
+      "4" + RPC_SEPARATOR_CHAR + // method name
+      "1" + RPC_SEPARATOR_CHAR + // param count
+      "5" + RPC_SEPARATOR_CHAR + // 'J' == long param type
+      "P7cuph2VDIQ" + RPC_SEPARATOR_CHAR; // long in base-64 encoding
 
   /**
    * Tests that out-of-range or other illegal integer values generated
@@ -344,6 +363,15 @@
       // Expected
     }
   }
+  
+  public void testDecodeInvalidFlags() {
+    try {
+      RPC.decodeRequest(VALID_V6_ENCODED_REQUEST_WITH_INVALID_FLAGS);
+      fail("Should have thrown an IncompatibleRemoteServiceException");
+    } catch (IncompatibleRemoteServiceException e) {
+      // Expected
+    }
+  }
 
   /**
    * Tests for method {@link RPC#decodeRequest(String)}.
@@ -441,6 +469,79 @@
     }
   }
 
+  private static class TestRpcToken implements RpcToken {
+    String tokenValue;
+    public TestRpcToken() { }
+  }
+
+  private static final String VALID_V6_ENCODED_REQUEST_WITH_RPC_TOKEN = "" +
+      AbstractSerializationStream.SERIALIZATION_STREAM_VERSION +
+      RPC_SEPARATOR_CHAR + // version
+      AbstractSerializationStream.FLAG_RPC_TOKEN_INCLUDED + // flags 
+          RPC_SEPARATOR_CHAR +
+      "7" + RPC_SEPARATOR_CHAR + // string table count
+      "moduleBaseUrl" + RPC_SEPARATOR_CHAR + // string table entry #1
+      "whitelistHashCode" + RPC_SEPARATOR_CHAR + // string table entry #2
+      TestRpcToken.class.getName() + "/3856085925" + // string table entry #3
+          RPC_SEPARATOR_CHAR +
+      "RPC_TOKEN_VALUE" + RPC_SEPARATOR_CHAR + // string table entry #4 
+      D.class.getName() + // string table entry #5
+          RPC_SEPARATOR_CHAR + 
+      "echo" + RPC_SEPARATOR_CHAR + // string table entry #6
+      "J" + RPC_SEPARATOR_CHAR + // string table entry #7
+      "1" + RPC_SEPARATOR_CHAR + // moduleBaseUrl
+      "2" + RPC_SEPARATOR_CHAR + // whitelist hashcode
+      "3" + RPC_SEPARATOR_CHAR + // RPC token class
+      "4" + RPC_SEPARATOR_CHAR + // RPC token value
+      "5" + RPC_SEPARATOR_CHAR + // interface name
+      "6" + RPC_SEPARATOR_CHAR + // method name
+      "1" + RPC_SEPARATOR_CHAR + // param count
+      "7" + RPC_SEPARATOR_CHAR + // 'J' == long param type
+      "P7cuph2VDIQ" + RPC_SEPARATOR_CHAR; // long in base-64 encoding
+  
+  public void testDecodeRpcToken() {
+    class TestPolicy extends SerializationPolicy {
+      @Override
+      public boolean shouldDeserializeFields(Class<?> clazz) {
+        return TestRpcToken.class.equals(clazz);
+      }
+
+      @Override
+      public boolean shouldSerializeFields(Class<?> clazz) {
+        return TestRpcToken.class.equals(clazz);
+      }
+
+      @Override
+      public void validateDeserialize(Class<?> clazz)
+          throws SerializationException {
+      }
+
+      @Override
+      public void validateSerialize(Class<?> clazz)
+          throws SerializationException {
+      }
+
+      @Override
+      public Set<String> getClientFieldNamesForEnhancedClass(Class<?> clazz) {
+        return null;
+      }
+    }
+    class TestPolicyProvider implements SerializationPolicyProvider {      
+      public SerializationPolicy getSerializationPolicy(
+          String moduleBaseURL, String serializationPolicyStrongName) {
+        return new TestPolicy();
+      }
+    }
+    RPCRequest requestWithoutToken = RPC.decodeRequest(
+        VALID_V6_ENCODED_REQUEST);
+    assertNull(requestWithoutToken.getRpcToken());
+    RPCRequest requestWithToken = RPC.decodeRequest(
+        VALID_V6_ENCODED_REQUEST_WITH_RPC_TOKEN, null, new TestPolicyProvider());
+    assertNotNull(requestWithToken.getRpcToken());
+    TestRpcToken token = (TestRpcToken) requestWithToken.getRpcToken();
+    assertEquals("RPC_TOKEN_VALUE", token.tokenValue);
+  }
+
   public void testDecodeV5Long() {
     try {
       RPCRequest request = RPC.decodeRequest(VALID_V5_ENCODED_REQUEST,
diff --git a/user/test/com/google/gwt/user/server/rpc/RpcTokenAwareRemoteService.java b/user/test/com/google/gwt/user/server/rpc/RpcTokenAwareRemoteService.java
new file mode 100644
index 0000000..8a53c84
--- /dev/null
+++ b/user/test/com/google/gwt/user/server/rpc/RpcTokenAwareRemoteService.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.user.server.rpc;
+
+import com.google.gwt.user.client.rpc.RpcToken;
+import com.google.gwt.user.client.rpc.RpcTokenException;
+
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * {@link RemoteServiceServlet} implementation for RPC tokens support tests.
+ */
+public class RpcTokenAwareRemoteService extends RemoteServiceServlet {
+  
+  public static final String TOKEN = "TOKEN";
+  
+  @Override
+  protected void onAfterRequestDeserialized(RPCRequest rpcRequest) {
+    HttpServletRequest req = getThreadLocalRequest();
+    
+    if (req.getParameter("throw") != null) {
+      throw new RpcTokenException("This is OK. Testing RpcTokenException handler.");
+    } else {
+      RpcToken token = rpcRequest.getRpcToken();
+      req.setAttribute(TOKEN, token);
+    }
+  }
+}
diff --git a/user/test/com/google/gwt/user/server/rpc/RpcTokenServiceImpl.java b/user/test/com/google/gwt/user/server/rpc/RpcTokenServiceImpl.java
new file mode 100644
index 0000000..d755717
--- /dev/null
+++ b/user/test/com/google/gwt/user/server/rpc/RpcTokenServiceImpl.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.user.server.rpc;
+
+import com.google.gwt.user.client.rpc.RpcToken;
+import com.google.gwt.user.client.rpc.RpcTokenTestService;
+
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * Remote service for testing RPC tokens support.
+ */
+public class RpcTokenServiceImpl extends RpcTokenAwareRemoteService 
+    implements RpcTokenTestService {
+
+  public void test() { }
+  
+  public String capitalize(String input) {
+    return input.toUpperCase();
+  }
+  
+  public RpcToken getRpcTokenFromRequest() {
+    HttpServletRequest req = getThreadLocalRequest();
+    RpcToken token = (RpcToken) req.getAttribute(RpcTokenAwareRemoteService.TOKEN);
+    return token;
+  }
+}